changeset 2317:e836cf11783b

Make deadzones configurable and bump up the default value
author Michael Pavone <pavone@retrodev.com>
date Sun, 02 Apr 2023 23:21:39 -0700
parents 523ab225815b
children 1c7329ac7f3f
files bindings.c bindings.h controller_info.c controller_info.h nuklear_ui/blastem_nuklear.c
diffstat 5 files changed, 186 insertions(+), 29 deletions(-) [+]
line wrap: on
line diff
--- 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);
 	}
 }
--- 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 <stdint.h>
+#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);
--- 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);
--- 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_
--- 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);
 }