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.
This commit is contained in:
Andrei Alexeyev 2024-05-27 19:12:28 +02:00
parent d288f2bd27
commit 7548e4892d
No known key found for this signature in database
GPG key ID: 72D26128040B9690
8 changed files with 317 additions and 72 deletions

View file

@ -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;
}

View file

@ -0,0 +1,2 @@
objects = standard.vert gamepad_circle.frag

View file

@ -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',

View file

@ -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 \

View file

@ -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;

View file

@ -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

View file

@ -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);

View file

@ -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 <stdio.h>
#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;
}