# HG changeset patch # User Michael Pavone # Date 1680502899 25200 # Node ID e836cf11783b480e87bfc0983563f2ce13e0c7d0 # Parent 523ab225815bf02c3e64525c97b5551dd505e699 Make deadzones configurable and bump up the default value diff -r 523ab225815b -r e836cf11783b bindings.c --- a/bindings.c Sun Apr 02 23:21:04 2023 -0700 +++ b/bindings.c Sun Apr 02 23:21:39 2023 -0700 @@ -64,6 +64,7 @@ keybinding positive; keybinding negative; int16_t value; + int16_t deadzone; } joyaxis; typedef struct { @@ -144,7 +145,7 @@ } } -void bind_axis(int joystick, int axis, int positive, uint8_t bind_type, uint8_t subtype_a, uint8_t subtype_b) +void bind_axis(int joystick, int axis, int positive, int deadzone, uint8_t bind_type, uint8_t subtype_a, uint8_t subtype_b) { if (joystick >= MAX_JOYSTICKS) { return; @@ -159,6 +160,7 @@ joysticks[joystick].axes = realloc(joysticks[joystick].axes, sizeof(joyaxis) * joysticks[joystick].num_axes); memset(joysticks[joystick].axes + old_capacity, 0, (joysticks[joystick].num_axes - old_capacity) * sizeof(joyaxis)); } + joysticks[joystick].axes[axis].deadzone = deadzone; if (positive) { do_bind(&joysticks[joystick].axes[axis].positive, bind_type, subtype_a, subtype_b); } else { @@ -535,16 +537,14 @@ } } -#define JOY_AXIS_THRESHOLD 2000 - void handle_joy_axis(int joystick, int axis, int16_t value) { if (joystick >= MAX_JOYSTICKS || axis >= joysticks[joystick].num_axes) { return; } joyaxis *jaxis = joysticks[joystick].axes + axis; - int old_active = abs(jaxis->value) > JOY_AXIS_THRESHOLD; - int new_active = abs(value) > JOY_AXIS_THRESHOLD; + int old_active = abs(jaxis->value) > jaxis->deadzone; + int new_active = abs(value) > jaxis->deadzone; int old_pos = jaxis->value > 0; int new_pos = value > 0; jaxis->value = value; @@ -873,6 +873,8 @@ int padnum; tern_node *padbuttons; tern_node *mousebuttons; + int stick_deadzone; + int trigger_deadzone; } pad_button_state; @@ -905,7 +907,7 @@ bind_dpad(hostpadnum, render_dpad_part(hostbutton), render_direction_part(hostbutton), bindtype, subtype_a, subtype_b); return; } else if (hostbutton & RENDER_AXIS_BIT) { - bind_axis(hostpadnum, render_axis_part(hostbutton), hostbutton & RENDER_AXIS_POS, bindtype, subtype_a, subtype_b); + bind_axis(hostpadnum, render_axis_part(hostbutton), hostbutton & RENDER_AXIS_POS, state->trigger_deadzone, bindtype, subtype_a, subtype_b); return; } } @@ -936,6 +938,7 @@ } char *end; long axis = strtol(key, &end, 10); + int deadzone = state->stick_deadzone; if (*end) { //key is not a valid base 10 integer axis = render_translate_input_name(hostpadnum, key, 1); @@ -948,6 +951,9 @@ } goto done; } + if (!strcmp("lefttrigger", key) || !strcmp("righttrigger", key) || !strcmp("l2", key) || !strcmp("r2", key)) { + deadzone = state->trigger_deadzone; + } if (axis & RENDER_DPAD_BIT) { bind_dpad(hostpadnum, render_dpad_part(axis), render_direction_part(axis), bindtype, subtype_a, subtype_b); goto done; @@ -958,7 +964,7 @@ goto done; } } - bind_axis(hostpadnum, axis, positive, bindtype, subtype_a, subtype_b); + bind_axis(hostpadnum, axis, positive, deadzone, bindtype, subtype_a, subtype_b); done: free(key); return; @@ -1020,7 +1026,7 @@ } -tern_node *get_binding_node_for_pad(int padnum) +tern_node *get_binding_node_for_pad(int padnum, controller_info *info) { if (padnum > MAX_JOYSTICKS) { return NULL; @@ -1038,8 +1044,7 @@ free(type_id); } if (!pad) { - controller_info info = get_controller_info(padnum); - char *key = make_controller_type_key(&info); + char *key = make_controller_type_key(info); pad = tern_find_node(pads, key); free(key); } @@ -1051,7 +1056,8 @@ void handle_joy_added(int joystick) { - tern_node *pad = get_binding_node_for_pad(joystick); + controller_info info = get_controller_info(joystick); + tern_node *pad = get_binding_node_for_pad(joystick, &info); if (!pad) { return; } @@ -1078,7 +1084,7 @@ } else if (hostbutton & RENDER_AXIS_BIT) { //SDL2 knows internally whether this should be a positive or negative binding, but doesn't expose that externally //for now I'll just assume that any controller with axes for a d-pad has these mapped the "sane" way - bind_axis(joystick, render_axis_part(hostbutton), dir == 1 || dir == 3 ? 1 : 0, bindtype, subtype_a, subtype_b); + bind_axis(joystick, render_axis_part(hostbutton), dir == 1 || dir == 3 ? 1 : 0, info.stick_deadzone, bindtype, subtype_a, subtype_b); } else { bind_button(joystick, hostbutton, bindtype, subtype_a, subtype_b); } @@ -1086,22 +1092,19 @@ } } } + pad_button_state state = { + .padnum = joystick, + .padbuttons = get_pad_buttons(), + .mousebuttons = get_mouse_buttons(), + .stick_deadzone = info.stick_deadzone, + .trigger_deadzone = info.trigger_deadzone + }; tern_node *button_node = tern_find_node(pad, "buttons"); if (button_node) { - pad_button_state state = { - .padnum = joystick, - .padbuttons = get_pad_buttons(), - .mousebuttons = get_mouse_buttons() - }; tern_foreach(button_node, process_pad_button, &state); } tern_node *axes_node = tern_find_node(pad, "axes"); if (axes_node) { - pad_button_state state = { - .padnum = joystick, - .padbuttons = get_pad_buttons(), - .mousebuttons = get_mouse_buttons() - }; tern_foreach(axes_node, process_pad_axis, &state); } } diff -r 523ab225815b -r e836cf11783b bindings.h --- a/bindings.h Sun Apr 02 23:21:04 2023 -0700 +++ b/bindings.h Sun Apr 02 23:21:39 2023 -0700 @@ -1,6 +1,7 @@ #ifndef BINDINGS_H_ #define BINDINGS_H_ #include +#include "controller_info.h" typedef enum { MOUSE_NONE, //mouse is ignored @@ -12,7 +13,7 @@ void set_bindings(void); void update_pad_bindings(void); void bindings_set_mouse_mode(uint8_t mode); -tern_node *get_binding_node_for_pad(int padnum); +tern_node *get_binding_node_for_pad(int padnum, controller_info *info); void handle_keydown(int keycode, uint8_t scancode); void handle_keyup(int keycode, uint8_t scancode); void handle_joydown(int joystick, int button); diff -r 523ab225815b -r e836cf11783b controller_info.c --- a/controller_info.c Sun Apr 02 23:21:04 2023 -0700 +++ b/controller_info.c Sun Apr 02 23:21:39 2023 -0700 @@ -82,6 +82,9 @@ } } +#define DEFAULT_DEADZONE 4000 +#define DEFAULT_DEADZONE_STR "4000" + controller_info get_controller_info(int joystick) { #ifndef USE_FBDEV @@ -141,6 +144,8 @@ } } res.name = control ? SDL_GameControllerName(control) : SDL_JoystickName(stick); + res.stick_deadzone = atoi(tern_find_ptr_default(info, "stick_deadzone", DEFAULT_DEADZONE_STR)); + res.trigger_deadzone = atoi(tern_find_ptr_default(info, "trigger_deadzone", DEFAULT_DEADZONE_STR)); if (control) { SDL_GameControllerClose(control); } @@ -151,7 +156,9 @@ .type = TYPE_UNKNOWN, .subtype = SUBTYPE_UNKNOWN, .variant = VARIANT_NORMAL, - .name = SDL_JoystickName(stick) + .name = SDL_JoystickName(stick), + .stick_deadzone = DEFAULT_DEADZONE, + .trigger_deadzone = DEFAULT_DEADZONE }; } const char *name = SDL_GameControllerName(control); @@ -161,6 +168,8 @@ if (strstr(name, heuristics[i].name)) { controller_info res = heuristics[i].info; res.name = name; + res.stick_deadzone = DEFAULT_DEADZONE; + res.trigger_deadzone = DEFAULT_DEADZONE; return res; } } @@ -172,7 +181,9 @@ .type = TYPE_GENERIC_MAPPING, .subtype = SUBTYPE_UNKNOWN, .variant = VARIANT_NORMAL, - .name = name + .name = name, + .stick_deadzone = DEFAULT_DEADZONE, + .trigger_deadzone = DEFAULT_DEADZONE }; } @@ -208,6 +219,13 @@ tern_node *existing = tern_find_node(info_config, guid_string); existing = tern_insert_ptr(existing, "subtype", strdup(subtype_names[info->subtype])); existing = tern_insert_ptr(existing, "variant", strdup(variant_names[info->variant])); + char buffer[32]; + snprintf(buffer, sizeof(buffer), "%d", info->stick_deadzone); + buffer[31] = 0; + existing = tern_insert_ptr(existing, "stick_deadzone", strdup(buffer)); + snprintf(buffer, sizeof(buffer), "%d", info->trigger_deadzone); + buffer[31] = 0; + existing = tern_insert_ptr(existing, "trigger_deadzone", strdup(buffer)); info_config = tern_insert_node(info_config, guid_string, existing); persist_config_at(config, info_config, "controller_types.cfg"); handle_joy_added(joystick); diff -r 523ab225815b -r e836cf11783b controller_info.h --- a/controller_info.h Sun Apr 02 23:21:04 2023 -0700 +++ b/controller_info.h Sun Apr 02 23:21:39 2023 -0700 @@ -39,6 +39,8 @@ typedef struct { char const *name; + int16_t stick_deadzone; + int16_t trigger_deadzone; uint8_t type; uint8_t subtype; uint8_t variant; @@ -54,4 +56,4 @@ char *make_controller_type_key(controller_info *info); char *make_human_readable_type_name(controller_info *info); -#endif //CONTROLLER_INFO_H_ \ No newline at end of file +#endif //CONTROLLER_INFO_H_ diff -r 523ab225815b -r e836cf11783b nuklear_ui/blastem_nuklear.c --- a/nuklear_ui/blastem_nuklear.c Sun Apr 02 23:21:04 2023 -0700 +++ b/nuklear_ui/blastem_nuklear.c Sun Apr 02 23:21:39 2023 -0700 @@ -1044,7 +1044,7 @@ if (nk_begin(context, "Controller Bindings", nk_rect(0, 0, render_width(), render_height()), NK_WINDOW_NO_SCROLLBAR)) { if (!bindings) { bindings = calloc(1, sizeof(*bindings)); - tern_node *pad = get_binding_node_for_pad(selected_controller); + tern_node *pad = get_binding_node_for_pad(selected_controller, &selected_controller_info); if (pad) { tern_foreach(tern_find_node(pad, "buttons"), button_iter, bindings); tern_foreach(tern_find_node(pad, "axes"), axis_iter, bindings); @@ -1488,6 +1488,123 @@ nk_end(context); } } +static uint8_t stick_nav_disabled; +static SDL_GameController *current_controller; +static uint8_t deadzones_dirty; +void stick_deadzone_widget(float left, float top, float size, SDL_GameControllerAxis x_axis) +{ + float crosshair_size = context->style.font->height; + nk_stroke_rect(&context->current->buffer, nk_rect(left, top, size, size), context->style.window.rounding, context->style.window.border, nk_rgb(255, 255, 255)); + float deadzone_size = selected_controller_info.stick_deadzone * size / 65535.0f; + int16_t raw_x = SDL_GameControllerGetAxis(current_controller, x_axis); + int16_t raw_y = SDL_GameControllerGetAxis(current_controller, x_axis + 1); + if (raw_x > selected_controller_info.stick_deadzone) { + float points[] = { + left + size * 0.5f + deadzone_size, top + size * 0.5f - deadzone_size, + left + size, top, + left + size, top + size, + left + size * 0.5f + deadzone_size, top + size * 0.5f + deadzone_size, + }; + nk_fill_polygon(&context->current->buffer, points, sizeof(points)/(2 * sizeof(float)), context->style.checkbox.cursor_normal.data.color); + } else if (raw_x < -selected_controller_info.stick_deadzone) { + float points[] = { + left, top, + left + size * 0.5f - deadzone_size, top + size * 0.5f - deadzone_size, + left + size * 0.5f - deadzone_size, top + size * 0.5f + deadzone_size, + left, top + size, + }; + nk_fill_polygon(&context->current->buffer, points, sizeof(points)/(2 * sizeof(float)), context->style.checkbox.cursor_normal.data.color); + } + if (raw_y > selected_controller_info.stick_deadzone) { + float points[] = { + left, top + size, + left + size, top + size, + left + size * 0.5f + deadzone_size, top + size * 0.5f + deadzone_size, + left + size * 0.5f - deadzone_size, top + size * 0.5f + deadzone_size, + }; + nk_fill_polygon(&context->current->buffer, points, sizeof(points)/(2 * sizeof(float)), context->style.checkbox.cursor_normal.data.color); + } else if (raw_y < -selected_controller_info.stick_deadzone) { + float points[] = { + left, top, + left + size, top, + left + size * 0.5f + deadzone_size, top + size * 0.5f - deadzone_size, + left + size * 0.5f - deadzone_size, top + size * 0.5f - deadzone_size, + }; + nk_fill_polygon(&context->current->buffer, points, sizeof(points)/(2 * sizeof(float)), context->style.checkbox.cursor_normal.data.color); + } + nk_stroke_rect(&context->current->buffer, nk_rect(left + 0.5f * size - deadzone_size, top + 0.5f * size - deadzone_size, 2 * deadzone_size, 2 * deadzone_size), context->style.window.rounding, 0.5f * context->style.window.border, nk_rgb(200, 200, 200)); + //nk_layout_space_push(context, nk_rect(left, top, size, size)); + float x = raw_x * size / 65535.0f + size / 2.0f - crosshair_size / 2.0f; + float y = raw_y * size / 65535.0f + size / 2.0f - crosshair_size / 2.0f; + nk_draw_symbol(&context->current->buffer, NK_SYMBOL_X, nk_rect(left + x, top + y, crosshair_size, crosshair_size), nk_rgb(0, 0, 0), nk_rgb(255, 255, 255), 1, context->style.font); +} + +void trigger_deadzone_widget(float left, float top, float size, SDL_GameControllerAxis axis) +{ + float crosshair_size = context->style.font->height; + nk_stroke_rect(&context->current->buffer, nk_rect(left, top, size, crosshair_size * 1.5f), context->style.window.rounding, context->style.window.border, nk_rgb(255, 255, 255)); + float deadzone_size = selected_controller_info.trigger_deadzone * size / 32767.0f; + int16_t raw = SDL_GameControllerGetAxis(current_controller, axis); + if (raw < 0) { + raw = 0; + } + if (raw > selected_controller_info.trigger_deadzone) { + nk_fill_rect(&context->current->buffer, nk_rect(left + deadzone_size, top, size - deadzone_size, 1.5f * crosshair_size), context->style.window.rounding, context->style.checkbox.cursor_normal.data.color); + } + nk_stroke_line(&context->current->buffer, left + deadzone_size, top, left + deadzone_size, top + 1.5f * crosshair_size, 0.5f * context->style.window.border, nk_rgb(200, 200, 200)); + float x = raw * size / 32767.0f - crosshair_size / 2.0f; + nk_draw_symbol(&context->current->buffer, NK_SYMBOL_X, nk_rect(left + x, top + 0.25f * crosshair_size, crosshair_size, crosshair_size), nk_rgb(0, 0, 0), nk_rgb(255, 255, 255), 1, context->style.font); +} + +void view_deadzones(struct nk_context *context) +{ + if (nk_begin(context, "Deadzones", nk_rect(0, 0, render_width(), render_height()), NK_WINDOW_NO_SCROLLBAR)) { + nk_layout_space_begin(context, NK_STATIC, render_height() - 3 * context->style.font->height, 4); + + float left = render_width() / 8.0f, top = render_height() / 8.0f; + float size = render_height() / 3.0f; + stick_deadzone_widget(left, top, size, SDL_CONTROLLER_AXIS_LEFTX); + stick_deadzone_widget(left + 1.25f * size, top, size, SDL_CONTROLLER_AXIS_RIGHTX); + + top += size + context->style.font->height; + nk_layout_space_push(context, nk_rect(left, top, size * 2, context->style.font->height)); + int val = selected_controller_info.stick_deadzone; + nk_property_int(context, "Stick Deadzone", 250, &val, 32000, 250, 1.0f); + if (val != selected_controller_info.stick_deadzone) { + selected_controller_info.stick_deadzone = val; + deadzones_dirty = 1; + } + + top += 2.0f * context->style.font->height; + trigger_deadzone_widget(left, top, size, SDL_CONTROLLER_AXIS_TRIGGERLEFT); + trigger_deadzone_widget(left + 1.25f * size, top, size, SDL_CONTROLLER_AXIS_TRIGGERRIGHT); + + top += context->style.font->height * 2.5f; + nk_layout_space_push(context, nk_rect(left, top, size * 2, context->style.font->height)); + val = selected_controller_info.trigger_deadzone; + nk_property_int(context, "Trigger Deadzone", 250, &val, 32000, 250, 1.0f); + if (val != selected_controller_info.trigger_deadzone) { + selected_controller_info.trigger_deadzone = val; + deadzones_dirty = 1; + } + + nk_layout_space_end(context); + + nk_layout_row_static(context, context->style.font->height, (render_width() - 2 * context->style.font->height) / 2, 2); + if (nk_button_label(context, "Back")) { + stick_nav_disabled = 0; + if (current_controller) { + SDL_GameControllerClose(current_controller); + current_controller = NULL; + } + if (deadzones_dirty) { + save_controller_info(selected_controller, &selected_controller_info); + } + pop_view(); + } + nk_end(context); + } +} void view_controllers(struct nk_context *context) { @@ -1498,10 +1615,12 @@ int bindings_width = font->width(font->userdata, font->height, "Bindings", strlen("Bindings")) + context->style.button.padding.x * 2; int remap_width = font->width(font->userdata, font->height, "Remap", strlen("Remap")) + context->style.button.padding.x * 2; int change_type_width = font->width(font->userdata, font->height, "Change Type", strlen("Change Type")) + context->style.button.padding.x * 2; - int total = bindings_width + remap_width + change_type_width; + int deadzones_width = font->width(font->userdata, font->height, "Deadzones", strlen("Deadzones")) + context->style.button.padding.x * 2; + int total = bindings_width + remap_width + change_type_width + deadzones_width; float bindings_ratio = (float)bindings_width / total; float remap_ratio = (float)remap_width / total; float change_type_ratio = (float)change_type_width / total; + float deadzones_ratio = (float)deadzones_width / total; uint8_t found_controller = 0; @@ -1563,8 +1682,19 @@ initial_controller_config = 0; push_view(view_controller_type); } + button_start += change_type_width + context->style.window.spacing.x; + deadzones_width = deadzones_ratio * button_area_width; + nk_layout_space_push(context, nk_rect(button_start, height/2, deadzones_width, inner_height/2)); + if (nk_button_label(context, "Deadzones")) { + selected_controller = i; + selected_controller_info = info; + current_controller = render_get_controller(i); + stick_nav_disabled = 1; + deadzones_dirty = 0; + push_view(view_deadzones); + } } - //nk_layout_row_end(context); + nk_layout_space_end(context); } } if (!found_controller) { @@ -2322,6 +2452,9 @@ } else if (event->type == SDL_MOUSEBUTTONUP && event->button.button == 0) { click = 0; } + if (stick_nav_disabled && event->type == SDL_CONTROLLERAXISMOTION) { + return; + } nk_sdl_handle_event(event); }