From 7548e4892d79d6a79c7002e22662372b2614a25d Mon Sep 17 00:00:00 2001 From: Andrei Alexeyev Date: Mon, 27 May 2024 19:12:28 +0200 Subject: [PATCH] gamepad: direction snapping; more flexible configuration options - The free/restricted axis distinction is gone; the joystick always operates in "free" mode. - Added direction snapping functionality to help aid exact movement in cardinal and/or diagonal directions. The snapping angle can be adjusted from 0% (disabled) to 100% (similar to the old "restricted" mode). The snapping angle can also be biased towards cardinals or diagonals. - When the maximum zone is less than or equals dead zone, moving the character will always move at maximum speed (as in the old "restricted" mode). - Most of these settings are now visualized in the options menu and can be tested there. --- .../shader/gamepad_circle.frag.glsl | 90 +++++++++ .../shader/gamepad_circle.prog | 2 + resources/00-taisei.pkgdir/shader/meson.build | 1 + src/config.h | 3 +- src/gamepad.c | 107 +++++----- src/gamepad.h | 3 + src/menu/mainmenu.c | 1 + src/menu/options.c | 182 +++++++++++++++--- 8 files changed, 317 insertions(+), 72 deletions(-) create mode 100644 resources/00-taisei.pkgdir/shader/gamepad_circle.frag.glsl create mode 100644 resources/00-taisei.pkgdir/shader/gamepad_circle.prog diff --git a/resources/00-taisei.pkgdir/shader/gamepad_circle.frag.glsl b/resources/00-taisei.pkgdir/shader/gamepad_circle.frag.glsl new file mode 100644 index 00000000..a09c5ebd --- /dev/null +++ b/resources/00-taisei.pkgdir/shader/gamepad_circle.frag.glsl @@ -0,0 +1,90 @@ +#version 330 core + +#include "lib/render_context.glslh" +#include "lib/util.glslh" +#include "interface/standard.glslh" + +UNIFORM(1) vec4 snap_angles_sincos; +UNIFORM(2) vec4 joy_pointers; +UNIFORM(3) vec2 deadzones; + +float sdCircle(vec2 p, float r) { + return length(p) - r; +} + +float sdRoundedX(vec2 p, float w, float r) { + p = abs(p); + return length(p - min(p.x + p.y, w) * 0.5) - r; +} + +float sdPieDual(vec2 p, vec2 c, float r) { + p = abs(p); + float l = length(p) - r; + float m = length(p - c * clamp(dot(p, c), 0.0, r)); + return max(l, m * sign(c.y * p.x - c.x * p.y)); +} + +vec2 sincos(float a) { + return vec2(sin(a), cos(a)); +} + +float zones(vec2 uv, vec2 sc) { + return 0.5 - min( + sdPieDual(uv, sc, 1.0), + sdPieDual(uv.yx, sc, 1.0)); +} + +vec4 joystickPointer(vec2 p, float d, vec4 color) { + float base = 1.0 - sdCircle(p, -0.4); + + float outer = smoothstep(0.48, 0.52, base) - smoothstep(0.513-d, 0.513+d, base); + vec4 c = mix(vec4(color.a), color, outer) * outer; + + float inner = smoothstep(0.51, 0.53, base); + float innerGrad = smoothstep(0.52, 0.55, base); + c = alphaCompose(c, vec4(color.rgb * mix(1.0, 0.75, innerGrad), color.a) * inner); + + return c * c.a; +} + +void main(void) { + vec2 uv = 2.22 * (flip_native_to_topleft(texCoordRaw) - 0.5); + vec2 snapCardinalsSinCos = snap_angles_sincos.xy; + vec2 snapDiagonalsSinCos = snap_angles_sincos.zw; + + vec4 c = vec4(0); + + float d = fwidth(uv.x); + float circleBase = 1.0 - sdCircle(uv, 0.5); + float circle = smoothstep(0.5 - d, 0.5 + d, circleBase); + c = alphaCompose(c, vec4(0.25 * circle)); + + float deadzone = smoothstep(0.5 - 0.01, 0.5 + 0.01, circleBase - (1 - deadzones.x)); + float snapMask = circle * (1 - deadzone); + deadzone *= 1.0 - smoothstep(0.25, 0.7, circleBase - (1 - deadzones.x + 0.2)); + + float maxzone_val = 1 - max(deadzones.x, deadzones.y); + float maxzone = smoothstep(0.5 - maxzone_val, 0.5 + d, circleBase - maxzone_val); + maxzone -= smoothstep(0.52 - d, 0.52 + d, circleBase - maxzone_val); + + float cross1 = sdRoundedX(uv * rot(pi/4), 2.0, -0.49); + float cross2 = sdRoundedX(uv, 2.0, -0.49); + float delimiters = smoothstep(0.48, 0.6, circle * (1.0 - min(cross1, cross2))); + + float cardinals = zones(uv, snapCardinalsSinCos); + float diagonals = zones(uv * rot(pi/4), snapDiagonalsSinCos); + + c = alphaCompose(c, vec4(0.5 * maxzone)); + c = alphaCompose(c, vec4(1, 0.3, 0.3, 1) * 0.5 * snapMask * smoothstep(0.5-d, 0.5+d, cardinals)); + c = alphaCompose(c, vec4(0.3, 0.3, 1, 1) * 0.5 * snapMask * smoothstep(0.5-d, 0.5+d, diagonals)); + c = alphaCompose(c, vec4(vec3(0), 0.7 * deadzone)); + c = alphaCompose(c, joystickPointer(uv - joy_pointers.xy, d, vec4(1, 0.9, 0.2, 1))); + c = alphaCompose(c, joystickPointer(uv - joy_pointers.zw, d, vec4(0.6))); + c = alphaCompose(c, vec4(0, 0, 0, delimiters)); + + if(all(lessThan(c, vec4(1.0 / 255.0)))) { + discard; + } + + fragColor = c * r_color; +} diff --git a/resources/00-taisei.pkgdir/shader/gamepad_circle.prog b/resources/00-taisei.pkgdir/shader/gamepad_circle.prog new file mode 100644 index 00000000..60c25e1d --- /dev/null +++ b/resources/00-taisei.pkgdir/shader/gamepad_circle.prog @@ -0,0 +1,2 @@ + +objects = standard.vert gamepad_circle.frag diff --git a/resources/00-taisei.pkgdir/shader/meson.build b/resources/00-taisei.pkgdir/shader/meson.build index dd7b1ced..346239c2 100644 --- a/resources/00-taisei.pkgdir/shader/meson.build +++ b/resources/00-taisei.pkgdir/shader/meson.build @@ -31,6 +31,7 @@ glsl_files = [ 'fireparticles.vert.glsl', 'fxaa.frag.glsl', 'fxaa.vert.glsl', + 'gamepad_circle.frag.glsl', 'glitch.frag.glsl', 'graph.frag.glsl', 'healthbar.vert.glsl', diff --git a/src/config.h b/src/config.h index bea36083..fc6edb57 100644 --- a/src/config.h +++ b/src/config.h @@ -123,13 +123,14 @@ CONFIGDEF_STRING (GAMEPAD_DEVICE, "gamepad_device", "any") \ CONFIGDEF_INT (GAMEPAD_AXIS_UD, "gamepad_axis_ud", 1) \ CONFIGDEF_INT (GAMEPAD_AXIS_LR, "gamepad_axis_lr", 0) \ - CONFIGDEF_INT (GAMEPAD_AXIS_FREE, "gamepad_axis_free", 1) \ CONFIGDEF_INT (GAMEPAD_AXIS_SQUARECIRCLE, "gamepad_axis_square_to_circle", 0) \ CONFIGDEF_FLOAT (GAMEPAD_AXIS_SENS, "gamepad_axis_sensitivity", 0.0) \ CONFIGDEF_FLOAT (GAMEPAD_AXIS_UD_SENS, "gamepad_axis_ud_sensitivity", 0.0) \ CONFIGDEF_FLOAT (GAMEPAD_AXIS_LR_SENS, "gamepad_axis_lr_sensitivity", 0.0) \ CONFIGDEF_FLOAT (GAMEPAD_AXIS_DEADZONE, "gamepad_axis_deadzone", 0.1) \ CONFIGDEF_FLOAT (GAMEPAD_AXIS_MAXZONE, "gamepad_axis_maxzone", 0.9) \ + CONFIGDEF_FLOAT (GAMEPAD_AXIS_SNAP, "gamepad_axis_snap", 0.5) \ + CONFIGDEF_FLOAT (GAMEPAD_AXIS_SNAP_DIAG_BIAS, "gamepad_axis_snap_diagonal_bias", 0.0) \ CONFIGDEF_FLOAT (GAMEPAD_BTNREPEAT_DELAY, "gamepad_button_repeat_delay", CONFIG_GAMEPAD_BTNREPEAT_DELAY_DEFAULT) \ CONFIGDEF_FLOAT (GAMEPAD_BTNREPEAT_INTERVAL,"gamepad_button_repeat_interval", CONFIG_GAMEPAD_BTNREPEAT_INTERVAL_DEFAULT) \ GPKEYDEFS \ diff --git a/src/gamepad.c b/src/gamepad.c index 35ad4105..1fe79b22 100644 --- a/src/gamepad.c +++ b/src/gamepad.c @@ -14,6 +14,7 @@ #include "hirestime.h" #include "log.h" #include "transition.h" +#include "util.h" #include "util/miscmath.h" #include "util/stringops.h" #include "vfs/public.h" @@ -357,10 +358,14 @@ static int gamepad_axis2gameevt(GamepadAxis id) { return -1; } -static inline double gamepad_get_deadzone(void) { +double gamepad_get_normalized_deadzone(void) { return clamp(config_get_float(CONFIG_GAMEPAD_AXIS_DEADZONE), MIN_DEADZONE, MAX_DEADZONE); } +double gamepad_get_normalized_maxzone(void) { + return clamp(config_get_float(CONFIG_GAMEPAD_AXIS_MAXZONE), 0, 1); +} + int gamepad_axis_value(GamepadAxis id) { assert(id > GAMEPAD_AXIS_INVALID); assert(id < GAMEPAD_AXIS_MAX); @@ -419,40 +424,6 @@ static void gamepad_update_game_axis(GamepadAxis axis, int oldval) { } } -static cmplx gamepad_restrict_analog_input(cmplx z) { - typedef enum { - UP = (1 << 0), - DOWN = (1 << 1), - RIGHT = (1 << 3), - LEFT = (1 << 2), - } MoveDir; - - double x = re(z); - double y = im(z); - - MoveDir move = 0; - - if(x || y) { - int d = (int)rint(atan2(-y, x) / (M_PI/4)); - - switch(d) { - case 0: move = 0 | RIGHT; break; - case -1: move = UP | RIGHT; break; - case -2: move = UP | 0; break; - case -3: move = UP | LEFT; break; - case -4: case 4: move = 0 | LEFT; break; - case 3: move = DOWN | LEFT; break; - case 2: move = DOWN | 0; break; - case 1: move = DOWN | RIGHT; break; - } - } - - x = (bool)(move & RIGHT) - (bool)(move & LEFT); - y = (bool)(move & UP) - (bool)(move & DOWN); - - return CMPLX(x, y); -} - static double gamepad_apply_sensitivity(double p, double sens) { if(sens == 0) { return p; @@ -475,6 +446,47 @@ static cmplx square_to_circle(cmplx z) { return CMPLX(u, v); } +static cmplx gamepad_snap_analog_direction(cmplx z) { + double snap = clamp(config_get_float(CONFIG_GAMEPAD_AXIS_SNAP), 0, 1); + + if(snap == 0) { + return z; + } + + double diagonal_bias = clamp(config_get_float(CONFIG_GAMEPAD_AXIS_SNAP_DIAG_BIAS), -1, 1); + double w_cardinal = 1 - diagonal_bias; + double w_diagonal = 1 + diagonal_bias; + + #define D 0.7071067811865475 + struct { cmplx v; double weight; } directions[] = { + { CMPLX( 1, 0), w_cardinal }, + { CMPLX( 0, 1), w_cardinal }, + { CMPLX(-1, 0), w_cardinal }, + { CMPLX( 0, -1), w_cardinal }, + { CMPLX( D, D), w_diagonal }, + { CMPLX( D, -D), w_diagonal }, + { CMPLX(-D, -D), w_diagonal }, + { CMPLX(-D, D), w_diagonal }, + }; + #undef D + + double m = cabs(z); + double thres = snap * M_PI/ARRAY_SIZE(directions); + + for(int i = 0; i < ARRAY_SIZE(directions); ++i) { + cmplx q = directions[i].v; + double delta_angle = acos(cdot(q, z) / m); + + if(delta_angle < thres * directions[i].weight) { + z = q * m; + break; + } + + } + + return z; +} + void gamepad_get_player_analog_input(int *xaxis, int *yaxis) { int xraw = gamepad_player_axis_value(PLRAXIS_LR); int yraw = gamepad_player_axis_value(PLRAXIS_UD); @@ -482,8 +494,8 @@ void gamepad_get_player_analog_input(int *xaxis, int *yaxis) { *xaxis = 0; *yaxis = 0; - double deadzone = gamepad_get_deadzone(); - double maxzone = config_get_float(CONFIG_GAMEPAD_AXIS_MAXZONE); + double deadzone = gamepad_get_normalized_deadzone(); + double maxzone = gamepad_get_normalized_maxzone(); cmplx z = CMPLX( gamepad_normalize_axis_value(xraw), @@ -503,25 +515,32 @@ void gamepad_get_player_analog_input(int *xaxis, int *yaxis) { double raw_abs = sqrt(raw_abs2); assert(raw_abs > 0); - double new_abs = (raw_abs - deadzone) / (maxzone - deadzone); - new_abs = gamepad_apply_sensitivity(new_abs, config_get_float(CONFIG_GAMEPAD_AXIS_SENS)); - z *= new_abs / raw_abs; + if(deadzone < maxzone) { + double new_abs; + if(raw_abs >= maxzone) { + new_abs = max(raw_abs, 1); + } else { + new_abs = (min(raw_abs, maxzone) - deadzone) / (maxzone - deadzone); + new_abs = gamepad_apply_sensitivity(new_abs, config_get_float(CONFIG_GAMEPAD_AXIS_SENS)); + } + z *= new_abs / raw_abs; + } else { + z /= raw_abs; + } z = CMPLX( gamepad_apply_sensitivity(re(z), config_get_float(CONFIG_GAMEPAD_AXIS_LR_SENS)), gamepad_apply_sensitivity(im(z), config_get_float(CONFIG_GAMEPAD_AXIS_UD_SENS)) ); - if(!config_get_int(CONFIG_GAMEPAD_AXIS_FREE)) { - z = gamepad_restrict_analog_input(z); - } + z = gamepad_snap_analog_direction(z); *xaxis = gamepad_denormalize_axis_value(re(z)); *yaxis = gamepad_denormalize_axis_value(im(z)); } static int gamepad_axis_get_digital_value(int raw) { - double deadzone = gamepad_get_deadzone(); + double deadzone = gamepad_get_normalized_deadzone(); deadzone = max(deadzone, 0.5); int threshold = GAMEPAD_AXIS_MAX_VALUE * deadzone; diff --git a/src/gamepad.h b/src/gamepad.h index 99180da8..0ac2b4a1 100644 --- a/src/gamepad.h +++ b/src/gamepad.h @@ -136,5 +136,8 @@ void gamepad_get_player_analog_input(int *xaxis, int *yaxis); double gamepad_normalize_axis_value(int val); int gamepad_denormalize_axis_value(double val); +double gamepad_get_normalized_deadzone(void); +double gamepad_get_normalized_maxzone(void); + #define GAMEPAD_AXIS_MAX_VALUE 32767 #define GAMEPAD_AXIS_MIN_VALUE -32768 diff --git a/src/menu/mainmenu.c b/src/menu/mainmenu.c index b6956e4b..38b58a40 100644 --- a/src/menu/mainmenu.c +++ b/src/menu/mainmenu.c @@ -288,6 +288,7 @@ void menu_preload(ResourceGroup *rg) { NULL); res_group_preload(rg, RES_SHADER_PROGRAM, RESF_DEFAULT, + "gamepad_circle", "mainmenubg", "sprite_circleclipped_indicator", NULL); diff --git a/src/menu/options.c b/src/menu/options.c index 3df46e9e..10aafb9a 100644 --- a/src/menu/options.c +++ b/src/menu/options.c @@ -9,15 +9,22 @@ #include "options.h" #include "common.h" +#include "config.h" +#include "events.h" #include "mainmenu.h" #include "menu.h" #include "audio/audio.h" #include "gamepad.h" +#include "renderer/api.h" +#include "resource/font.h" #include "util/graphics.h" #include "video.h" -#include +#define OPTIONS_ACTIVE_X_OFFSET 20 /* FIXME hardcoded in draw_menu_list */ +#define OPTIONS_ITEM_HEIGHT 20 /* FIXME hardcoded in draw_menu_list */ +#define OPTIONS_X_MARGIN 100 +#define OPTIONS_Y_MARGIN 100 typedef struct OptionBinding OptionBinding; @@ -500,6 +507,11 @@ typedef struct OptionsMenuContext { MenuData *submenu_fading; ConfirmDialog *confirm_dialog; float submenu_alpha; + void (*draw_overlay)(MenuData *m, struct OptionsMenuContext *ctx); + struct { + bool allowed; + bool active; + } gamepad_testmode; } OptionsMenuContext; static void destroy_options_menu(MenuData *m) { @@ -879,12 +891,83 @@ static void destroy_options_menu_gamepad(MenuData *m) { destroy_options_menu(m); } +static inline GamepadButton options_gamepad_testing_button(void) { + return config_get_int(CONFIG_GAMEPAD_KEY_FOCUS); +} + +static void draw_gamepad_options_overlay(MenuData *m, OptionsMenuContext *ctx) { + float csize = 86; + + int x, y; + gamepad_get_player_analog_input(&x, &y); + + cmplx in_processed = CMPLX( + gamepad_normalize_axis_value(x), + gamepad_normalize_axis_value(y)); + + cmplx in_raw = CMPLX( + gamepad_normalize_axis_value(gamepad_player_axis_value(PLRAXIS_LR)), + gamepad_normalize_axis_value(gamepad_player_axis_value(PLRAXIS_UD))); + + double deadzone = gamepad_get_normalized_deadzone(); + double maxzone = gamepad_get_normalized_maxzone(); + + r_mat_mv_push(); + r_mat_mv_translate( + SCREEN_W - OPTIONS_X_MARGIN, + OPTIONS_Y_MARGIN + 6.5f*OPTIONS_ITEM_HEIGHT, + 0); + r_mat_mv_scale(csize, csize, 0); + r_mat_mv_translate(0.5f, 0.5f, 0); + float snap = clamp(config_get_float(CONFIG_GAMEPAD_AXIS_SNAP), 0, 1); + float diagonal_bias = clamp(config_get_float(CONFIG_GAMEPAD_AXIS_SNAP_DIAG_BIAS), -1, 1); + float w_cardinal = 1.0f - diagonal_bias; + float w_diagonal = 1.0f + diagonal_bias; + float thres_card = (snap * (float)M_PI/8.0f) * w_cardinal; + float thres_diag = (snap * (float)M_PI/8.0f) * w_diagonal; + r_shader("gamepad_circle"); + r_uniform_vec4("snap_angles_sincos", sinf(thres_card), cosf(thres_card), sinf(thres_diag), cosf(thres_diag)); + r_uniform_vec2("deadzones", deadzone, maxzone); + + if(ctx->gamepad_testmode.active) { + r_uniform_vec4("joy_pointers", re(in_processed), im(in_processed), re(in_raw), im(in_raw)); + r_color4(1, 1, 1, 1); + } else { + r_uniform_vec4("joy_pointers", 69, 69, 69, 69); + r_color4(0.7, 0.7, 0.7, 0.7); + } + + r_draw_quad(); + r_mat_mv_pop(); + + char buf[128]; + GamepadButton test_btn = options_gamepad_testing_button(); + + if(ctx->gamepad_testmode.active) { + snprintf(buf, sizeof(buf), + "Press any button on your gamepad to exit joystick testing mode"); + } else { + snprintf(buf, sizeof(buf), + "Press %s on your gamepad to enter joystick testing mode", + gamepad_button_name(test_btn)); + } + + text_draw(buf, &(TextParams) { + .pos = { SCREEN_W/2.0f, SCREEN_H - OPTIONS_Y_MARGIN }, + .align = ALIGN_CENTER, + .shader = "text_default", + .color = RGBA(0.7, 0.7, 0.7, 0.7), + }); +} + static MenuData* create_options_menu_gamepad(MenuData *parent) { MenuData *m = create_options_menu_base("Gamepad Options"); m->end = destroy_options_menu_gamepad; OptionsMenuContext *ctx = m->context; ctx->data = strdup(config_get_str(CONFIG_GAMEPAD_DEVICE)); + ctx->draw_overlay = draw_gamepad_options_overlay; + ctx->gamepad_testmode.allowed = true; OptionBinding *b; @@ -903,15 +986,21 @@ static MenuData* create_options_menu_gamepad(MenuData *parent) { add_menu_separator(m); - add_menu_entry(m, "Axes mode", do_nothing, - b = bind_option(CONFIG_GAMEPAD_AXIS_FREE, bind_common_onoff_get, bind_common_onoff_set) - ); bind_addvalue(b, "free"); - bind_addvalue(b, "restricted"); - add_menu_entry(m, "Remap square input into circular", do_nothing, b = bind_option(CONFIG_GAMEPAD_AXIS_SQUARECIRCLE, bind_common_onoff_get, bind_gamepad_set) ); bind_onoff(b); + add_menu_separator(m); + + add_menu_entry(m, "Direction snap factor", do_nothing, + b = bind_scale(CONFIG_GAMEPAD_AXIS_SNAP, 0, 1, 0.05) + ); + + add_menu_entry(m, "Diagonal bias", do_nothing, + b = bind_scale(CONFIG_GAMEPAD_AXIS_SNAP_DIAG_BIAS, -1, 1, 0.05) + ); b->pad++; + + add_menu_separator(m); add_menu_entry(m, "Dead zone", do_nothing, @@ -1140,7 +1229,7 @@ void draw_options_menu_bg(MenuData* menu) { static void update_options_menu(MenuData *menu) { menu->drawdata[0] += ((SCREEN_W/2 - 100) - menu->drawdata[0])/10.0; menu->drawdata[1] += ((SCREEN_W - 200) * 1.75 - menu->drawdata[1])/10.0; - menu->drawdata[2] += (20*menu->cursor - menu->drawdata[2])/10.0; + menu->drawdata[2] += (OPTIONS_ITEM_HEIGHT*menu->cursor - menu->drawdata[2])/10.0; animate_menu_list_entries(menu); @@ -1185,12 +1274,13 @@ static void options_draw_item(MenuEntry *e, int i, int cnt, void *ctx) { } text_draw(e->name, &(TextParams) { - .pos = { (1 + (bind ? bind->pad : 0)) * 20 - e->drawdata, 20*i }, + .pos = { (1 + (bind ? bind->pad : 0)) * OPTIONS_ACTIVE_X_OFFSET - e->drawdata, OPTIONS_ITEM_HEIGHT*i }, .color = &clr, }); if(bind) { - int j, origin = SCREEN_W - 220; + float margin = OPTIONS_X_MARGIN * 2 + OPTIONS_ACTIVE_X_OFFSET; + float origin = SCREEN_W - margin; switch(bind->type) { case BT_IntValue: { @@ -1204,7 +1294,7 @@ static void options_draw_item(MenuEntry *e, int i, int cnt, void *ctx) { val, ALIGN_RIGHT, origin, - 20*i, + OPTIONS_ITEM_HEIGHT*i, fnt_int, fnt_fract, &clr, @@ -1212,7 +1302,7 @@ static void options_draw_item(MenuEntry *e, int i, int cnt, void *ctx) { false ); } else if(bind->values) { - for(j = bind->displaysingle? val : bind->valrange_max; (j+1) && (!bind->displaysingle || j == val); --j) { + for(int j = bind->displaysingle? val : bind->valrange_max; (j+1) && (!bind->displaysingle || j == val); --j) { if(j != bind->valrange_max && !bind->displaysingle) { origin -= text_width(res_font("standard"), bind->values[j+1], 0) + 5; } @@ -1224,7 +1314,7 @@ static void options_draw_item(MenuEntry *e, int i, int cnt, void *ctx) { } text_draw(bind->values[j], &(TextParams) { - .pos = { origin, 20*i }, + .pos = { origin, OPTIONS_ITEM_HEIGHT*i }, .align = ALIGN_RIGHT, .color = &clr, }); @@ -1245,7 +1335,7 @@ static void options_draw_item(MenuEntry *e, int i, int cnt, void *ctx) { case BT_KeyBinding: { if(bind->blockinput) { text_draw("Press a key to assign, ESC to cancel", &(TextParams) { - .pos = { origin, 20*i }, + .pos = { origin, OPTIONS_ITEM_HEIGHT*i }, .align = ALIGN_RIGHT, .color = RGBA(0.5, 1, 0.5, 1), }); @@ -1257,7 +1347,7 @@ static void options_draw_item(MenuEntry *e, int i, int cnt, void *ctx) { } text_draw(txt, &(TextParams) { - .pos = { origin, 20*i }, + .pos = { origin, OPTIONS_ITEM_HEIGHT*i }, .align = ALIGN_RIGHT, .color = &clr, }); @@ -1294,10 +1384,10 @@ static void options_draw_item(MenuEntry *e, int i, int cnt, void *ctx) { } text_draw(txt, &(TextParams) { - .pos = { origin, 20*i }, + .pos = { origin, OPTIONS_ITEM_HEIGHT*i }, .align = ALIGN_RIGHT, .color = &clr, - .max_width = (SCREEN_W - 220) / 2, + .max_width = (SCREEN_W - margin) / 2, }); } @@ -1319,10 +1409,10 @@ static void options_draw_item(MenuEntry *e, int i, int cnt, void *ctx) { char buf[64]; snprintf(buf, sizeof(buf), "#%i: %s", bind->selected + 1, video_display_name(bind->selected)); text_draw(buf, &(TextParams) { - .pos = { origin, 20*i }, + .pos = { origin, OPTIONS_ITEM_HEIGHT*i }, .align = ALIGN_RIGHT, .color = &clr, - .max_width = (SCREEN_W - 220) / 2, + .max_width = (SCREEN_W - margin) / 2, }); break; } @@ -1336,7 +1426,7 @@ static void options_draw_item(MenuEntry *e, int i, int cnt, void *ctx) { : "Press a button to assign, Back to cancel"; text_draw(text, &(TextParams) { - .pos = { origin, 20*i }, + .pos = { origin, OPTIONS_ITEM_HEIGHT*i }, .align = ALIGN_RIGHT, .color = RGBA(0.5, 1, 0.5, 1), }); @@ -1344,13 +1434,13 @@ static void options_draw_item(MenuEntry *e, int i, int cnt, void *ctx) { int id = config_get_int(bind->configentry); const char *name = (is_axis ? gamepad_axis_name(id) : gamepad_button_name(id)); text_draw(name, &(TextParams) { - .pos = { origin, 20*i }, + .pos = { origin, OPTIONS_ITEM_HEIGHT*i }, .align = ALIGN_RIGHT, .color = &clr, }); } else { text_draw("Unbound", &(TextParams) { - .pos = { origin, 20*i }, + .pos = { origin, OPTIONS_ITEM_HEIGHT*i }, .align = ALIGN_RIGHT, .color = &clr, }); @@ -1362,14 +1452,14 @@ static void options_draw_item(MenuEntry *e, int i, int cnt, void *ctx) { if(bind->blockinput) { if(*bind->strvalue) { text_draw(bind->strvalue, &(TextParams) { - .pos = { origin, 20*i }, + .pos = { origin, OPTIONS_ITEM_HEIGHT*i }, .align = ALIGN_RIGHT, .color = RGBA(0.5, 1, 0.5, 1.0), }); } } else { text_draw(config_get_str(bind->configentry), &(TextParams) { - .pos = { origin, 20*i }, + .pos = { origin, OPTIONS_ITEM_HEIGHT*i }, .align = ALIGN_RIGHT, .color = &clr, }); @@ -1391,7 +1481,7 @@ static void options_draw_item(MenuEntry *e, int i, int cnt, void *ctx) { snprintf(tmp, sizeof(tmp), "%dx%d", w, h); text_draw(tmp, &(TextParams) { - .pos = { origin, 20*i }, + .pos = { origin, OPTIONS_ITEM_HEIGHT*i }, .align = ALIGN_RIGHT, .color = &clr, }); @@ -1403,7 +1493,7 @@ static void options_draw_item(MenuEntry *e, int i, int cnt, void *ctx) { char tmp[32]; snprintf(tmp, sizeof(tmp), "%gx %dx%d", video_get_scaling_factor(), fbsize.w, fbsize.h); text_draw(tmp, &(TextParams) { - .pos = { origin, 20*i }, + .pos = { origin, OPTIONS_ITEM_HEIGHT*i }, .align = ALIGN_RIGHT, .color = &clr, }); @@ -1427,7 +1517,7 @@ static void options_draw_item(MenuEntry *e, int i, int cnt, void *ctx) { strcpy(tmp, "0%"); r_mat_mv_push(); - r_mat_mv_translate(origin - (w+cw) * 0.5, 20 * i, 0); + r_mat_mv_translate(origin - (w+cw) * 0.5, OPTIONS_ITEM_HEIGHT * i, 0); text_draw(tmp, &(TextParams) { .pos = { -((w+cw) * 0.5 + 10), 0 }, .align = ALIGN_RIGHT, @@ -1457,9 +1547,15 @@ static void draw_options_menu(MenuData *menu) { r_state_push(); draw_options_menu_bg(menu); draw_menu_title(menu, ctx->title); - draw_menu_list(menu, 100, 100, options_draw_item, SCREEN_H * 1.1, menu); + draw_menu_list(menu, OPTIONS_X_MARGIN, OPTIONS_Y_MARGIN, options_draw_item, SCREEN_H * 1.1, menu); r_state_pop(); + if(ctx->draw_overlay) { + r_state_push(); + ctx->draw_overlay(menu, ctx); + r_state_pop(); + } + if(ctx->submenu_fading) { r_state_push(); ctx->submenu_fading->draw(ctx->submenu_fading); @@ -1707,9 +1803,21 @@ static bool options_text_input_handler(SDL_Event *event, void *arg) { static bool options_input_handler(SDL_Event *event, void *arg) { MenuData *menu = arg; + OptionsMenuContext *ctx = menu->context; OptionBinding *bind = bind_get(menu, menu->cursor); TaiseiEvent type = TAISEI_EVENT(event->type); + switch(type) { + case TE_MENU_CURSOR_UP: + case TE_MENU_CURSOR_DOWN: + case TE_MENU_CURSOR_LEFT: + case TE_MENU_CURSOR_RIGHT: + if((intptr_t)event->user.data1 == INDEV_GAMEPAD && ctx->gamepad_testmode.active) { + return false; + } + default: break; + } + switch(type) { case TE_MENU_CURSOR_UP: case TE_MENU_CURSOR_DOWN: @@ -1784,6 +1892,26 @@ static bool options_input_handler(SDL_Event *event, void *arg) { close_menu(menu); break; + case TE_GAMEPAD_BUTTON_DOWN: + if(ctx->gamepad_testmode.allowed) { + if(ctx->gamepad_testmode.active) { + switch(event->user.code) { + case GAMEPAD_BUTTON_ANALOG_STICK_DOWN: + case GAMEPAD_BUTTON_ANALOG_STICK_LEFT: + case GAMEPAD_BUTTON_ANALOG_STICK_RIGHT: + case GAMEPAD_BUTTON_ANALOG_STICK_UP: + break; + default: + ctx->gamepad_testmode.active = false; + } + } else { + if(event->user.code == options_gamepad_testing_button()) { + ctx->gamepad_testmode.active = true; + } + } + } + break; + default: break; }