diff --git a/test/gamepadutils.c b/test/gamepadutils.c index aecd2a6c52..c678d3618f 100644 --- a/test/gamepadutils.c +++ b/test/gamepadutils.c @@ -31,7 +31,8 @@ #include "gamepad_axis_arrow.h" #include "gamepad_button_background.h" -/* This is indexed by SDL_GamepadButton. */ + +/* This is indexed by gamepad element */ static const struct { int x; @@ -44,9 +45,9 @@ static const struct { 199, 157 }, /* SDL_GAMEPAD_BUTTON_BACK */ { 257, 153 }, /* SDL_GAMEPAD_BUTTON_GUIDE */ { 314, 157 }, /* SDL_GAMEPAD_BUTTON_START */ - { 100, 179 }, /* SDL_GAMEPAD_BUTTON_LEFT_STICK */ - { 330, 255 }, /* SDL_GAMEPAD_BUTTON_RIGHT_STICK */ - { 102, 65 }, /* SDL_GAMEPAD_BUTTON_LEFT_SHOULDER */ + { 98, 177 }, /* SDL_GAMEPAD_BUTTON_LEFT_STICK */ + { 331, 254 }, /* SDL_GAMEPAD_BUTTON_RIGHT_STICK */ + { 102, 65 }, /* SDL_GAMEPAD_BUTTON_LEFT_SHOULDER */ { 421, 61 }, /* SDL_GAMEPAD_BUTTON_RIGHT_SHOULDER */ { 179, 213 }, /* SDL_GAMEPAD_BUTTON_DPAD_UP */ { 179, 274 }, /* SDL_GAMEPAD_BUTTON_DPAD_DOWN */ @@ -59,19 +60,23 @@ static const struct { 355, 200 }, /* SDL_GAMEPAD_BUTTON_PADDLE4 */ }; -/* This is indexed by SDL_GamepadAxis. */ +/* This is indexed by gamepad element */ static const struct { int x; int y; double angle; } axis_positions[] = { - { 99, 178, 270.0 }, /* LEFTX */ - { 99, 178, 0.0 }, /* LEFTY */ - { 331, 256, 270.0 }, /* RIGHTX */ - { 331, 256, 0.0 }, /* RIGHTY */ - { 116, 5, 0.0 }, /* TRIGGERLEFT */ - { 400, 5, 0.0 }, /* TRIGGERRIGHT */ + { 99, 178, 270.0 }, /* SDL_GAMEPAD_ELEMENT_AXIS_LEFTX_NEGATIVE */ + { 99, 178, 90.0 }, /* SDL_GAMEPAD_ELEMENT_AXIS_LEFTX_POSITIVE */ + { 99, 178, 0.0 }, /* SDL_GAMEPAD_ELEMENT_AXIS_LEFTY_NEGATIVE */ + { 99, 178, 180.0 }, /* SDL_GAMEPAD_ELEMENT_AXIS_LEFTY_POSITIVE */ + { 331, 256, 270.0 }, /* SDL_GAMEPAD_ELEMENT_AXIS_RIGHTX_NEGATIVE */ + { 331, 256, 90.0 }, /* SDL_GAMEPAD_ELEMENT_AXIS_RIGHTX_POSITIVE */ + { 331, 256, 0.0 }, /* SDL_GAMEPAD_ELEMENT_AXIS_RIGHTY_NEGATIVE */ + { 331, 256, 180.0 }, /* SDL_GAMEPAD_ELEMENT_AXIS_RIGHTY_POSITIVE */ + { 116, 5, 180.0 }, /* SDL_GAMEPAD_ELEMENT_AXIS_LEFT_TRIGGER */ + { 400, 5, 180.0 }, /* SDL_GAMEPAD_ELEMENT_AXIS_RIGHT_TRIGGER */ }; static SDL_Rect touchpad_area = { @@ -118,8 +123,7 @@ struct GamepadImage GamepadImageFaceStyle face_style; ControllerDisplayMode display_mode; - SDL_bool buttons[SDL_GAMEPAD_BUTTON_MAX]; - int axes[SDL_GAMEPAD_AXIS_MAX]; + SDL_bool elements[SDL_GAMEPAD_ELEMENT_MAX]; SDL_JoystickPowerLevel battery_level; @@ -213,6 +217,21 @@ void SetGamepadImagePosition(GamepadImage *ctx, int x, int y) ctx->y = y; } +void GetGamepadImageArea(GamepadImage *ctx, SDL_Rect *area) +{ + if (!ctx) { + SDL_zerop(area); + } + + area->x = ctx->x; + area->y = ctx->y; + area->w = ctx->gamepad_width; + area->h = ctx->gamepad_height; + if (ctx->showing_touchpad) { + area->h += ctx->touchpad_height; + } +} + void SetGamepadImageShowingFront(GamepadImage *ctx, SDL_bool showing_front) { if (!ctx) { @@ -231,6 +250,15 @@ void SetGamepadImageFaceStyle(GamepadImage *ctx, GamepadImageFaceStyle face_styl ctx->face_style = face_style; } +GamepadImageFaceStyle GetGamepadImageFaceStyle(GamepadImage *ctx) +{ + if (!ctx) { + return GAMEPAD_IMAGE_FACE_BLANK; + } + + return ctx->face_style; +} + void SetGamepadImageDisplayMode(GamepadImage *ctx, ControllerDisplayMode display_mode) { if (!ctx) { @@ -276,17 +304,96 @@ int GetGamepadImageAxisHeight(GamepadImage *ctx) return ctx->axis_height; } -SDL_GamepadButton GetGamepadImageButtonAt(GamepadImage *ctx, float x, float y) +int GetGamepadImageElementAt(GamepadImage *ctx, float x, float y) { SDL_FPoint point; int i; if (!ctx) { - return SDL_GAMEPAD_BUTTON_INVALID; + return SDL_GAMEPAD_ELEMENT_INVALID; } point.x = x; point.y = y; + + if (ctx->showing_front) { + for (i = 0; i < SDL_arraysize(axis_positions); ++i) { + const int element = SDL_GAMEPAD_BUTTON_MAX + i; + SDL_FRect rect; + + if (element == SDL_GAMEPAD_ELEMENT_AXIS_LEFT_TRIGGER || + element == SDL_GAMEPAD_ELEMENT_AXIS_RIGHT_TRIGGER) { + rect.w = (float)ctx->axis_width; + rect.h = (float)ctx->axis_height; + rect.x = (float)ctx->x + axis_positions[i].x - rect.w / 2; + rect.y = (float)ctx->y + axis_positions[i].y - rect.h / 2; + if (SDL_PointInRectFloat(&point, &rect)) { + if (element == SDL_GAMEPAD_ELEMENT_AXIS_LEFT_TRIGGER) { + return SDL_GAMEPAD_ELEMENT_AXIS_LEFT_TRIGGER; + } else { + return SDL_GAMEPAD_ELEMENT_AXIS_RIGHT_TRIGGER; + } + } + } else if (element == SDL_GAMEPAD_ELEMENT_AXIS_LEFTX_POSITIVE) { + rect.w = (float)ctx->button_width * 2.0f; + rect.h = (float)ctx->button_height * 2.0f; + rect.x = (float)ctx->x + button_positions[SDL_GAMEPAD_BUTTON_LEFT_STICK].x - rect.w / 2; + rect.y = (float)ctx->y + button_positions[SDL_GAMEPAD_BUTTON_LEFT_STICK].y - rect.h / 2; + if (SDL_PointInRectFloat(&point, &rect)) { + float delta_x, delta_y; + float delta_squared; + float thumbstick_radius = (float)ctx->button_width * 0.1f; + + delta_x = (x - (ctx->x + button_positions[SDL_GAMEPAD_BUTTON_LEFT_STICK].x)); + delta_y = (y - (ctx->y + button_positions[SDL_GAMEPAD_BUTTON_LEFT_STICK].y)); + delta_squared = (delta_x * delta_x) + (delta_y * delta_y); + if (delta_squared > (thumbstick_radius * thumbstick_radius)) { + float angle = SDL_atan2f(delta_y, delta_x) + SDL_PI_F; + if (angle < SDL_PI_F * 0.25f) { + return SDL_GAMEPAD_ELEMENT_AXIS_LEFTX_NEGATIVE; + } else if (angle < SDL_PI_F * 0.75f) { + return SDL_GAMEPAD_ELEMENT_AXIS_LEFTY_NEGATIVE; + } else if (angle < SDL_PI_F * 1.25f) { + return SDL_GAMEPAD_ELEMENT_AXIS_LEFTX_POSITIVE; + } else if (angle < SDL_PI_F * 1.75f) { + return SDL_GAMEPAD_ELEMENT_AXIS_LEFTY_POSITIVE; + } else { + return SDL_GAMEPAD_ELEMENT_AXIS_LEFTX_NEGATIVE; + } + } + } + } else if (element == SDL_GAMEPAD_ELEMENT_AXIS_RIGHTX_POSITIVE) { + rect.w = (float)ctx->button_width * 2.0f; + rect.h = (float)ctx->button_height * 2.0f; + rect.x = (float)ctx->x + button_positions[SDL_GAMEPAD_BUTTON_RIGHT_STICK].x - rect.w / 2; + rect.y = (float)ctx->y + button_positions[SDL_GAMEPAD_BUTTON_RIGHT_STICK].y - rect.h / 2; + if (SDL_PointInRectFloat(&point, &rect)) { + float delta_x, delta_y; + float delta_squared; + float thumbstick_radius = (float)ctx->button_width * 0.1f; + + delta_x = (x - (ctx->x + button_positions[SDL_GAMEPAD_BUTTON_RIGHT_STICK].x)); + delta_y = (y - (ctx->y + button_positions[SDL_GAMEPAD_BUTTON_RIGHT_STICK].y)); + delta_squared = (delta_x * delta_x) + (delta_y * delta_y); + if (delta_squared > (thumbstick_radius * thumbstick_radius)) { + float angle = SDL_atan2f(delta_y, delta_x) + SDL_PI_F; + if (angle < SDL_PI_F * 0.25f) { + return SDL_GAMEPAD_ELEMENT_AXIS_RIGHTX_NEGATIVE; + } else if (angle < SDL_PI_F * 0.75f) { + return SDL_GAMEPAD_ELEMENT_AXIS_RIGHTY_NEGATIVE; + } else if (angle < SDL_PI_F * 1.25f) { + return SDL_GAMEPAD_ELEMENT_AXIS_RIGHTX_POSITIVE; + } else if (angle < SDL_PI_F * 1.75f) { + return SDL_GAMEPAD_ELEMENT_AXIS_RIGHTY_POSITIVE; + } else { + return SDL_GAMEPAD_ELEMENT_AXIS_RIGHTX_NEGATIVE; + } + } + } + } + } + } + for (i = 0; i < SDL_arraysize(button_positions); ++i) { SDL_bool on_front = SDL_TRUE; @@ -304,33 +411,7 @@ SDL_GamepadButton GetGamepadImageButtonAt(GamepadImage *ctx, float x, float y) } } } - return SDL_GAMEPAD_BUTTON_INVALID; -} - -SDL_GamepadAxis GetGamepadImageAxisAt(GamepadImage *ctx, float x, float y) -{ - SDL_FPoint point; - int i; - - if (!ctx) { - return SDL_GAMEPAD_AXIS_INVALID; - } - - point.x = x; - point.y = y; - if (ctx->showing_front) { - for (i = 0; i < SDL_arraysize(axis_positions); ++i) { - SDL_FRect rect; - rect.x = (float)ctx->x + axis_positions[i].x - ctx->axis_width / 2; - rect.y = (float)ctx->y + axis_positions[i].y - ctx->axis_height / 2; - rect.w = (float)ctx->axis_width; - rect.h = (float)ctx->axis_height; - if (SDL_PointInRectFloat(&point, &rect)) { - return (SDL_GamepadAxis)i; - } - } - } - return SDL_GAMEPAD_AXIS_INVALID; + return SDL_GAMEPAD_ELEMENT_INVALID; } void ClearGamepadImage(GamepadImage *ctx) @@ -339,26 +420,16 @@ void ClearGamepadImage(GamepadImage *ctx) return; } - SDL_zeroa(ctx->buttons); - SDL_zeroa(ctx->axes); + SDL_zeroa(ctx->elements); } -void SetGamepadImageButton(GamepadImage *ctx, SDL_GamepadButton button, SDL_bool active) +void SetGamepadImageElement(GamepadImage *ctx, int element, SDL_bool active) { if (!ctx) { return; } - ctx->buttons[button] = active; -} - -void SetGamepadImageAxis(GamepadImage *ctx, SDL_GamepadAxis axis, int direction) -{ - if (!ctx) { - return; - } - - ctx->axes[axis] = direction; + ctx->elements[element] = active; } void UpdateGamepadImageFromGamepad(GamepadImage *ctx, SDL_Gamepad *gamepad) @@ -369,41 +440,37 @@ void UpdateGamepadImageFromGamepad(GamepadImage *ctx, SDL_Gamepad *gamepad) return; } - if (gamepad) { - char *mapping = SDL_GetGamepadMapping(gamepad); - if (mapping) { - SDL_GamepadType gamepad_type = SDL_GetGamepadType(gamepad); - switch (gamepad_type) { - case SDL_GAMEPAD_TYPE_PS3: - case SDL_GAMEPAD_TYPE_PS4: - case SDL_GAMEPAD_TYPE_PS5: - ctx->face_style = GAMEPAD_IMAGE_FACE_SONY; - break; - case SDL_GAMEPAD_TYPE_NINTENDO_SWITCH_PRO: - case SDL_GAMEPAD_TYPE_NINTENDO_SWITCH_JOYCON_LEFT: - case SDL_GAMEPAD_TYPE_NINTENDO_SWITCH_JOYCON_RIGHT: - case SDL_GAMEPAD_TYPE_NINTENDO_SWITCH_JOYCON_PAIR: - ctx->face_style = GAMEPAD_IMAGE_FACE_BAYX; - break; - default: - if (SDL_strstr(mapping, "SDL_GAMECONTROLLER_USE_BUTTON_LABELS")) { - ctx->face_style = GAMEPAD_IMAGE_FACE_BAYX; - } else { - ctx->face_style = GAMEPAD_IMAGE_FACE_ABXY; - } - break; - } - SDL_free(mapping); + char *mapping = SDL_GetGamepadMapping(gamepad); + SDL_GamepadType gamepad_type = SDL_GetGamepadType(gamepad); + switch (gamepad_type) { + case SDL_GAMEPAD_TYPE_PS3: + case SDL_GAMEPAD_TYPE_PS4: + case SDL_GAMEPAD_TYPE_PS5: + ctx->face_style = GAMEPAD_IMAGE_FACE_SONY; + break; + case SDL_GAMEPAD_TYPE_NINTENDO_SWITCH_PRO: + case SDL_GAMEPAD_TYPE_NINTENDO_SWITCH_JOYCON_LEFT: + case SDL_GAMEPAD_TYPE_NINTENDO_SWITCH_JOYCON_RIGHT: + case SDL_GAMEPAD_TYPE_NINTENDO_SWITCH_JOYCON_PAIR: + ctx->face_style = GAMEPAD_IMAGE_FACE_BAYX; + break; + default: + if (mapping && SDL_strstr(mapping, "SDL_GAMECONTROLLER_USE_BUTTON_LABELS")) { + ctx->face_style = GAMEPAD_IMAGE_FACE_BAYX; + } else { + ctx->face_style = GAMEPAD_IMAGE_FACE_ABXY; } + break; } + SDL_free(mapping); for (i = 0; i < SDL_GAMEPAD_BUTTON_TOUCHPAD; ++i) { const SDL_GamepadButton button = (SDL_GamepadButton)i; if (SDL_GetGamepadButton(gamepad, button) == SDL_PRESSED) { - SetGamepadImageButton(ctx, button, SDL_TRUE); + SetGamepadImageElement(ctx, button, SDL_TRUE); } else { - SetGamepadImageButton(ctx, button, SDL_FALSE); + SetGamepadImageElement(ctx, button, SDL_FALSE); } } @@ -411,12 +478,31 @@ void UpdateGamepadImageFromGamepad(GamepadImage *ctx, SDL_Gamepad *gamepad) const SDL_GamepadAxis axis = (SDL_GamepadAxis)i; const Sint16 deadzone = 8000; /* !!! FIXME: real deadzone */ const Sint16 value = SDL_GetGamepadAxis(gamepad, axis); - if (value < -deadzone) { - SetGamepadImageAxis(ctx, axis, -1); - } else if (value > deadzone) { - SetGamepadImageAxis(ctx, axis, 1); - } else { - SetGamepadImageAxis(ctx, axis, 0); + switch (i) { + case SDL_GAMEPAD_AXIS_LEFTX: + SetGamepadImageElement(ctx, SDL_GAMEPAD_ELEMENT_AXIS_LEFTX_NEGATIVE, (value < -deadzone)); + SetGamepadImageElement(ctx, SDL_GAMEPAD_ELEMENT_AXIS_LEFTX_POSITIVE, (value > deadzone)); + break; + case SDL_GAMEPAD_AXIS_RIGHTX: + SetGamepadImageElement(ctx, SDL_GAMEPAD_ELEMENT_AXIS_RIGHTX_NEGATIVE, (value < -deadzone)); + SetGamepadImageElement(ctx, SDL_GAMEPAD_ELEMENT_AXIS_RIGHTX_POSITIVE, (value > deadzone)); + break; + case SDL_GAMEPAD_AXIS_LEFTY: + SetGamepadImageElement(ctx, SDL_GAMEPAD_ELEMENT_AXIS_LEFTY_NEGATIVE, (value < -deadzone)); + SetGamepadImageElement(ctx, SDL_GAMEPAD_ELEMENT_AXIS_LEFTY_POSITIVE, (value > deadzone)); + break; + case SDL_GAMEPAD_AXIS_RIGHTY: + SetGamepadImageElement(ctx, SDL_GAMEPAD_ELEMENT_AXIS_RIGHTY_NEGATIVE, (value < -deadzone)); + SetGamepadImageElement(ctx, SDL_GAMEPAD_ELEMENT_AXIS_RIGHTY_POSITIVE, (value > deadzone)); + break; + case SDL_GAMEPAD_AXIS_LEFT_TRIGGER: + SetGamepadImageElement(ctx, SDL_GAMEPAD_ELEMENT_AXIS_LEFT_TRIGGER, (value > deadzone)); + break; + case SDL_GAMEPAD_AXIS_RIGHT_TRIGGER: + SetGamepadImageElement(ctx, SDL_GAMEPAD_ELEMENT_AXIS_RIGHT_TRIGGER, (value > deadzone)); + break; + default: + break; } } @@ -489,7 +575,7 @@ void RenderGamepadImage(GamepadImage *ctx) } for (i = 0; i < SDL_arraysize(button_positions); ++i) { - if (ctx->buttons[i]) { + if (ctx->elements[i]) { SDL_GamepadButton button_position = GetRemappedButton(ctx->face_style, (SDL_GamepadButton)i); SDL_bool on_front = SDL_TRUE; @@ -497,10 +583,10 @@ void RenderGamepadImage(GamepadImage *ctx) on_front = SDL_FALSE; } if (on_front == ctx->showing_front) { - dst.x = (float)ctx->x + button_positions[button_position].x - ctx->button_width / 2; - dst.y = (float)ctx->y + button_positions[button_position].y - ctx->button_height / 2; dst.w = (float)ctx->button_width; dst.h = (float)ctx->button_height; + dst.x = (float)ctx->x + button_positions[button_position].x - dst.w / 2; + dst.y = (float)ctx->y + button_positions[button_position].y - dst.h / 2; SDL_RenderTexture(ctx->renderer, ctx->button_texture, NULL, &dst); } } @@ -508,19 +594,13 @@ void RenderGamepadImage(GamepadImage *ctx) if (ctx->showing_front) { for (i = 0; i < SDL_arraysize(axis_positions); ++i) { - if (ctx->axes[i] < 0) { + const int element = SDL_GAMEPAD_BUTTON_MAX + i; + if (ctx->elements[element]) { const double angle = axis_positions[i].angle; - dst.x = (float)ctx->x + axis_positions[i].x - ctx->axis_width / 2; - dst.y = (float)ctx->y + axis_positions[i].y - ctx->axis_height / 2; - dst.w = (float)ctx->axis_width; - dst.h = (float)ctx->axis_height; - SDL_RenderTextureRotated(ctx->renderer, ctx->axis_texture, NULL, &dst, angle, NULL, SDL_FLIP_NONE); - } else if (ctx->axes[i] > 0) { - const double angle = axis_positions[i].angle + 180.0; - dst.x = (float)ctx->x + axis_positions[i].x - ctx->axis_width / 2; - dst.y = (float)ctx->y + axis_positions[i].y - ctx->axis_height / 2; dst.w = (float)ctx->axis_width; dst.h = (float)ctx->axis_height; + dst.x = (float)ctx->x + axis_positions[i].x - dst.w / 2; + dst.y = (float)ctx->y + axis_positions[i].y - dst.h / 2; SDL_RenderTextureRotated(ctx->renderer, ctx->axis_texture, NULL, &dst, angle, NULL, SDL_FLIP_NONE); } } @@ -609,7 +689,7 @@ SDL_COMPILE_TIME_ASSERT(gamepad_button_names, SDL_arraysize(gamepad_button_names static const char *gamepad_axis_names[] = { "LeftX", - "RightX", + "LeftY", "RightX", "RightY", "Left Trigger", @@ -632,6 +712,9 @@ struct GamepadDisplay Uint64 last_sensor_update; ControllerDisplayMode display_mode; + int element_highlighted; + SDL_bool element_pressed; + int element_selected; SDL_Rect area; }; @@ -647,6 +730,9 @@ GamepadDisplay *CreateGamepadDisplay(SDL_Renderer *renderer) ctx->arrow_texture = CreateTexture(renderer, gamepad_axis_arrow_bmp, gamepad_axis_arrow_bmp_len); SDL_QueryTexture(ctx->arrow_texture, NULL, NULL, &ctx->arrow_width, &ctx->arrow_height); + + ctx->element_highlighted = SDL_GAMEPAD_ELEMENT_INVALID; + ctx->element_selected = SDL_GAMEPAD_ELEMENT_INVALID; } return ctx; } @@ -674,6 +760,7 @@ static SDL_bool GetBindingString(const char *label, char *mapping, char *text, s char *key; char *value, *end; size_t length; + SDL_bool found = SDL_FALSE; *text = '\0'; @@ -682,7 +769,14 @@ static SDL_bool GetBindingString(const char *label, char *mapping, char *text, s } key = SDL_strstr(mapping, label); - if (key) { + while (key && size > 1) { + if (found) { + *text++ = ','; + *text = '\0'; + --size; + } else { + found = SDL_TRUE; + } value = key + SDL_strlen(label); end = SDL_strchr(value, ','); if (end) { @@ -695,15 +789,18 @@ static SDL_bool GetBindingString(const char *label, char *mapping, char *text, s } SDL_memcpy(text, value, length); text[length] = '\0'; + text += length; + size -= length; + key = SDL_strstr(value, label); } - return *text ? SDL_TRUE : SDL_FALSE; + return found; } static SDL_bool GetButtonBindingString(SDL_GamepadButton button, char *mapping, char *text, size_t size) { char label[32]; - SDL_snprintf(label, sizeof(label), "%s:", SDL_GetGamepadStringForButton(button)); + SDL_snprintf(label, sizeof(label), ",%s:", SDL_GetGamepadStringForButton(button)); return GetBindingString(label, mapping, text, size); } @@ -713,16 +810,16 @@ static SDL_bool GetAxisBindingString(SDL_GamepadAxis axis, int direction, char * /* Check for explicit half-axis */ if (direction < 0) { - SDL_snprintf(label, sizeof(label), "-%s:", SDL_GetGamepadStringForAxis(axis)); + SDL_snprintf(label, sizeof(label), ",-%s:", SDL_GetGamepadStringForAxis(axis)); } else { - SDL_snprintf(label, sizeof(label), "+%s:", SDL_GetGamepadStringForAxis(axis)); + SDL_snprintf(label, sizeof(label), ",+%s:", SDL_GetGamepadStringForAxis(axis)); } if (GetBindingString(label, mapping, text, size)) { return SDL_TRUE; } /* Get the binding for the whole axis and split it if necessary */ - SDL_snprintf(label, sizeof(label), "%s:", SDL_GetGamepadStringForAxis(axis)); + SDL_snprintf(label, sizeof(label), ",%s:", SDL_GetGamepadStringForAxis(axis)); if (!GetBindingString(label, mapping, text, size)) { return SDL_FALSE; } @@ -732,24 +829,154 @@ static SDL_bool GetAxisBindingString(SDL_GamepadAxis axis, int direction, char * size_t length = SDL_strlen(text) + 1; if ((length + 1) <= size) { SDL_memmove(text + 1, text, length); + if (text[SDL_strlen(text) - 1] == '~') { + direction *= -1; + text[SDL_strlen(text) - 1] = '\0'; + } if (direction > 0) { *text = '+'; } else { *text = '-'; } } - } else if (*text == '~') { - /* Invert directions and split the axis */ - if (direction > 0) { - *text = '-'; - } else { - *text = '+'; - } } } return SDL_TRUE; } +void SetGamepadDisplayHighlight(GamepadDisplay *ctx, int element, SDL_bool pressed) +{ + if (!ctx) { + return; + } + + ctx->element_highlighted = element; + ctx->element_pressed = pressed; +} + +void SetGamepadDisplaySelected(GamepadDisplay *ctx, int element) +{ + if (!ctx) { + return; + } + + ctx->element_selected = element; +} + +int GetGamepadDisplayElementAt(GamepadDisplay *ctx, SDL_Gamepad *gamepad, float x, float y) +{ + int i; + const float margin = 8.0f; + const float center = ctx->area.w / 2.0f; + const float arrow_extent = 48.0f; + SDL_FPoint point; + SDL_FRect rect; + + if (!ctx) { + return SDL_GAMEPAD_ELEMENT_INVALID; + } + + point.x = x; + point.y = y; + + rect.x = ctx->area.x + margin; + rect.y = ctx->area.y + margin + FONT_CHARACTER_SIZE / 2 - ctx->button_height / 2; + rect.w = (float)ctx->area.w - (margin * 2); + rect.h = (float)ctx->button_height; + + for (i = 0; i < SDL_GAMEPAD_BUTTON_MAX; ++i) { + SDL_GamepadButton button = (SDL_GamepadButton)i; + + if (ctx->display_mode == CONTROLLER_MODE_TESTING && + !SDL_GamepadHasButton(gamepad, button)) { + continue; + } + + + if (SDL_PointInRectFloat(&point, &rect)) { + return i; + } + + rect.y += (float)ctx->button_height + 2.0f; + } + + for (i = 0; i < SDL_GAMEPAD_AXIS_MAX; ++i) { + SDL_GamepadAxis axis = (SDL_GamepadAxis)i; + SDL_FRect area; + + if (ctx->display_mode == CONTROLLER_MODE_TESTING && + !SDL_GamepadHasAxis(gamepad, axis)) { + continue; + } + + area.x = rect.x + center + 2.0f; + area.y = rect.y + FONT_CHARACTER_SIZE / 2 - ctx->button_height / 2; + area.w = (float)ctx->arrow_width + arrow_extent; + area.h = (float)ctx->button_height; + + if (SDL_PointInRectFloat(&point, &area)) { + switch (axis) { + case SDL_GAMEPAD_AXIS_LEFTX: + return SDL_GAMEPAD_ELEMENT_AXIS_LEFTX_NEGATIVE; + case SDL_GAMEPAD_AXIS_LEFTY: + return SDL_GAMEPAD_ELEMENT_AXIS_LEFTY_NEGATIVE; + case SDL_GAMEPAD_AXIS_RIGHTX: + return SDL_GAMEPAD_ELEMENT_AXIS_RIGHTX_NEGATIVE; + case SDL_GAMEPAD_AXIS_RIGHTY: + return SDL_GAMEPAD_ELEMENT_AXIS_RIGHTY_NEGATIVE; + default: + break; + } + } + + area.x += area.w; + + if (SDL_PointInRectFloat(&point, &area)) { + switch (axis) { + case SDL_GAMEPAD_AXIS_LEFTX: + return SDL_GAMEPAD_ELEMENT_AXIS_LEFTX_POSITIVE; + case SDL_GAMEPAD_AXIS_LEFTY: + return SDL_GAMEPAD_ELEMENT_AXIS_LEFTY_POSITIVE; + case SDL_GAMEPAD_AXIS_RIGHTX: + return SDL_GAMEPAD_ELEMENT_AXIS_RIGHTX_POSITIVE; + case SDL_GAMEPAD_AXIS_RIGHTY: + return SDL_GAMEPAD_ELEMENT_AXIS_RIGHTY_POSITIVE; + case SDL_GAMEPAD_AXIS_LEFT_TRIGGER: + return SDL_GAMEPAD_ELEMENT_AXIS_LEFT_TRIGGER; + case SDL_GAMEPAD_AXIS_RIGHT_TRIGGER: + return SDL_GAMEPAD_ELEMENT_AXIS_RIGHT_TRIGGER; + default: + break; + } + } + + rect.y += (float)ctx->button_height + 2.0f; + } + return SDL_GAMEPAD_ELEMENT_INVALID; +} + +static void RenderGamepadElementHighlight(GamepadDisplay *ctx, int element, const SDL_FRect *area) +{ + if (element == ctx->element_highlighted || element == ctx->element_selected) { + Uint8 r, g, b, a; + + SDL_GetRenderDrawColor(ctx->renderer, &r, &g, &b, &a); + + if (element == ctx->element_highlighted) { + if (ctx->element_pressed) { + SDL_SetRenderDrawColor(ctx->renderer, PRESSED_COLOR); + } else { + SDL_SetRenderDrawColor(ctx->renderer, HIGHLIGHT_COLOR); + } + } else { + SDL_SetRenderDrawColor(ctx->renderer, SELECTED_COLOR); + } + SDL_RenderFillRect(ctx->renderer, area); + + SDL_SetRenderDrawColor(ctx->renderer, r, g, b, a); + } +} + void RenderGamepadDisplay(GamepadDisplay *ctx, SDL_Gamepad *gamepad) { float x, y; @@ -758,7 +985,7 @@ void RenderGamepadDisplay(GamepadDisplay *ctx, SDL_Gamepad *gamepad) const float margin = 8.0f; const float center = ctx->area.w / 2.0f; const float arrow_extent = 48.0f; - SDL_FRect dst, rect; + SDL_FRect dst, rect, highlight; Uint8 r, g, b, a; char *mapping = NULL; SDL_bool has_accel; @@ -783,6 +1010,12 @@ void RenderGamepadDisplay(GamepadDisplay *ctx, SDL_Gamepad *gamepad) continue; } + highlight.x = x; + highlight.y = y + FONT_CHARACTER_SIZE / 2 - ctx->button_height / 2; + highlight.w = (float)ctx->area.w - (margin * 2); + highlight.h = (float)ctx->button_height; + RenderGamepadElementHighlight(ctx, i, &highlight); + SDL_snprintf(text, sizeof(text), "%s:", gamepad_button_names[i]); SDLTest_DrawString(ctx->renderer, x + center - SDL_strlen(text) * FONT_CHARACTER_SIZE, y, text); @@ -822,6 +1055,54 @@ void RenderGamepadDisplay(GamepadDisplay *ctx, SDL_Gamepad *gamepad) SDL_snprintf(text, sizeof(text), "%s:", gamepad_axis_names[i]); SDLTest_DrawString(ctx->renderer, x + center - SDL_strlen(text) * FONT_CHARACTER_SIZE, y, text); + + highlight.x = x + center + 2.0f; + highlight.y = y + FONT_CHARACTER_SIZE / 2 - ctx->button_height / 2; + highlight.w = (float)ctx->arrow_width + arrow_extent; + highlight.h = (float)ctx->button_height; + + switch (axis) { + case SDL_GAMEPAD_AXIS_LEFTX: + RenderGamepadElementHighlight(ctx, SDL_GAMEPAD_ELEMENT_AXIS_LEFTX_NEGATIVE, &highlight); + break; + case SDL_GAMEPAD_AXIS_LEFTY: + RenderGamepadElementHighlight(ctx, SDL_GAMEPAD_ELEMENT_AXIS_LEFTY_NEGATIVE, &highlight); + break; + case SDL_GAMEPAD_AXIS_RIGHTX: + RenderGamepadElementHighlight(ctx, SDL_GAMEPAD_ELEMENT_AXIS_RIGHTX_NEGATIVE, &highlight); + break; + case SDL_GAMEPAD_AXIS_RIGHTY: + RenderGamepadElementHighlight(ctx, SDL_GAMEPAD_ELEMENT_AXIS_RIGHTY_NEGATIVE, &highlight); + break; + default: + break; + } + + highlight.x += highlight.w; + + switch (axis) { + case SDL_GAMEPAD_AXIS_LEFTX: + RenderGamepadElementHighlight(ctx, SDL_GAMEPAD_ELEMENT_AXIS_LEFTX_POSITIVE, &highlight); + break; + case SDL_GAMEPAD_AXIS_LEFTY: + RenderGamepadElementHighlight(ctx, SDL_GAMEPAD_ELEMENT_AXIS_LEFTY_POSITIVE, &highlight); + break; + case SDL_GAMEPAD_AXIS_RIGHTX: + RenderGamepadElementHighlight(ctx, SDL_GAMEPAD_ELEMENT_AXIS_RIGHTX_POSITIVE, &highlight); + break; + case SDL_GAMEPAD_AXIS_RIGHTY: + RenderGamepadElementHighlight(ctx, SDL_GAMEPAD_ELEMENT_AXIS_RIGHTY_POSITIVE, &highlight); + break; + case SDL_GAMEPAD_AXIS_LEFT_TRIGGER: + RenderGamepadElementHighlight(ctx, SDL_GAMEPAD_ELEMENT_AXIS_LEFT_TRIGGER, &highlight); + break; + case SDL_GAMEPAD_AXIS_RIGHT_TRIGGER: + RenderGamepadElementHighlight(ctx, SDL_GAMEPAD_ELEMENT_AXIS_RIGHT_TRIGGER, &highlight); + break; + default: + break; + } + dst.x = x + center + 2.0f; dst.y = y + FONT_CHARACTER_SIZE / 2 - ctx->arrow_height / 2; dst.w = (float)ctx->arrow_width; @@ -897,7 +1178,7 @@ void RenderGamepadDisplay(GamepadDisplay *ctx, SDL_Gamepad *gamepad) SDL_SetRenderDrawColor(ctx->renderer, r, g, b, a); - y += ctx->button_height + 2; + y += ctx->button_height + 2.0f; } if (ctx->display_mode == CONTROLLER_MODE_TESTING) { @@ -997,6 +1278,9 @@ struct JoystickDisplay int arrow_height; SDL_Rect area; + + char *element_highlighted; + SDL_bool element_pressed; }; JoystickDisplay *CreateJoystickDisplay(SDL_Renderer *renderer) @@ -1023,6 +1307,199 @@ void SetJoystickDisplayArea(JoystickDisplay *ctx, const SDL_Rect *area) SDL_copyp(&ctx->area, area); } +char *GetJoystickDisplayElementAt(JoystickDisplay *ctx, SDL_Joystick *joystick, float x, float y) +{ + SDL_FPoint point; + int i; + int nbuttons = SDL_GetNumJoystickButtons(joystick); + int naxes = SDL_GetNumJoystickAxes(joystick); + int nhats = SDL_GetNumJoystickHats(joystick); + char text[32]; + const float margin = 8.0f; + const float center = 80.0f; + const float arrow_extent = 48.0f; + SDL_FRect dst, highlight; + char *element = NULL; + + if (!ctx) { + return NULL; + } + + point.x = x; + point.y = y; + + x = (float)ctx->area.x + margin; + y = (float)ctx->area.y + margin; + + if (nbuttons > 0) { + y += FONT_LINE_HEIGHT + 2; + + for (i = 0; i < nbuttons; ++i) { + highlight.x = x; + highlight.y = y + FONT_CHARACTER_SIZE / 2 - ctx->button_height / 2; + highlight.w = center - (margin * 2); + highlight.h = (float)ctx->button_height; + if (SDL_PointInRectFloat(&point, &highlight)) { + SDL_asprintf(&element, "b%d", i); + return element; + } + + y += ctx->button_height + 2; + } + } + + x = (float)ctx->area.x + margin + center + margin; + y = (float)ctx->area.y + margin; + + if (naxes > 0) { + y += FONT_LINE_HEIGHT + 2; + + for (i = 0; i < naxes; ++i) { + SDL_snprintf(text, sizeof(text), "%d:", i); + + highlight.x = x + FONT_CHARACTER_SIZE * SDL_strlen(text) + 2.0f; + highlight.y = y + FONT_CHARACTER_SIZE / 2 - ctx->button_height / 2; + highlight.w = (float)ctx->arrow_width + arrow_extent; + highlight.h = (float)ctx->button_height; + if (SDL_PointInRectFloat(&point, &highlight)) { + SDL_asprintf(&element, "-a%d", i); + return element; + } + + highlight.x += highlight.w; + if (SDL_PointInRectFloat(&point, &highlight)) { + SDL_asprintf(&element, "+a%d", i); + return element; + } + + y += ctx->button_height + 2; + } + } + + y += FONT_LINE_HEIGHT + 2; + + if (nhats > 0) { + y += FONT_LINE_HEIGHT + 2 + 1.5f * ctx->button_height - FONT_CHARACTER_SIZE / 2; + + for (i = 0; i < nhats; ++i) { + SDL_snprintf(text, sizeof(text), "%d:", i); + + dst.x = x + FONT_CHARACTER_SIZE * SDL_strlen(text) + 2; + dst.y = y + FONT_CHARACTER_SIZE / 2 - ctx->button_height / 2; + dst.w = (float)ctx->button_width; + dst.h = (float)ctx->button_height; + if (SDL_PointInRectFloat(&point, &dst)) { + SDL_asprintf(&element, "h%d.%d", i, SDL_HAT_LEFT); + return element; + } + + dst.x += (float)ctx->button_width; + dst.y -= (float)ctx->button_height; + if (SDL_PointInRectFloat(&point, &dst)) { + SDL_asprintf(&element, "h%d.%d", i, SDL_HAT_UP); + return element; + } + + dst.y += (float)ctx->button_height * 2; + if (SDL_PointInRectFloat(&point, &dst)) { + SDL_asprintf(&element, "h%d.%d", i, SDL_HAT_DOWN); + return element; + } + + dst.x += (float)ctx->button_width; + dst.y = y + FONT_CHARACTER_SIZE / 2 - ctx->button_height / 2; + if (SDL_PointInRectFloat(&point, &dst)) { + SDL_asprintf(&element, "h%d.%d", i, SDL_HAT_RIGHT); + return element; + } + + y += 3 * ctx->button_height + 2; + } + } + return NULL; +} + +void SetJoystickDisplayHighlight(JoystickDisplay *ctx, const char *element, SDL_bool pressed) +{ + if (ctx->element_highlighted) { + SDL_free(ctx->element_highlighted); + ctx->element_highlighted = NULL; + ctx->element_pressed = SDL_FALSE; + } + + if (element) { + ctx->element_highlighted = SDL_strdup(element); + ctx->element_pressed = pressed; + } +} + +static void RenderJoystickButtonHighlight(JoystickDisplay *ctx, int button, const SDL_FRect *area) +{ + if (!ctx->element_highlighted || *ctx->element_highlighted != 'b') { + return; + } + + if (SDL_atoi(ctx->element_highlighted + 1) == button) { + Uint8 r, g, b, a; + + SDL_GetRenderDrawColor(ctx->renderer, &r, &g, &b, &a); + + if (ctx->element_pressed) { + SDL_SetRenderDrawColor(ctx->renderer, PRESSED_COLOR); + } else { + SDL_SetRenderDrawColor(ctx->renderer, HIGHLIGHT_COLOR); + } + SDL_RenderFillRect(ctx->renderer, area); + + SDL_SetRenderDrawColor(ctx->renderer, r, g, b, a); + } +} + +static void RenderJoystickAxisHighlight(JoystickDisplay *ctx, int axis, int direction, const SDL_FRect *area) +{ + char prefix = (direction < 0 ? '-' : '+'); + + if (!ctx->element_highlighted || + ctx->element_highlighted[0] != prefix || + ctx->element_highlighted[1] != 'a') { + return; + } + + if (SDL_atoi(ctx->element_highlighted + 2) == axis) { + Uint8 r, g, b, a; + + SDL_GetRenderDrawColor(ctx->renderer, &r, &g, &b, &a); + + if (ctx->element_pressed) { + SDL_SetRenderDrawColor(ctx->renderer, PRESSED_COLOR); + } else { + SDL_SetRenderDrawColor(ctx->renderer, HIGHLIGHT_COLOR); + } + SDL_RenderFillRect(ctx->renderer, area); + + SDL_SetRenderDrawColor(ctx->renderer, r, g, b, a); + } +} + +static SDL_bool SetupJoystickHatHighlight(JoystickDisplay *ctx, int hat, int direction) +{ + if (!ctx->element_highlighted || *ctx->element_highlighted != 'h') { + return SDL_FALSE; + } + + if (SDL_atoi(ctx->element_highlighted + 1) == hat && + ctx->element_highlighted[2] == '.' && + SDL_atoi(ctx->element_highlighted + 3) == direction) { + if (ctx->element_pressed) { + SDL_SetTextureColorMod(ctx->button_texture, PRESSED_TEXTURE_MOD); + } else { + SDL_SetTextureColorMod(ctx->button_texture, HIGHLIGHT_TEXTURE_MOD); + } + return SDL_TRUE; + } + return SDL_FALSE; +} + void RenderJoystickDisplay(JoystickDisplay *ctx, SDL_Joystick *joystick) { float x, y; @@ -1034,7 +1511,7 @@ void RenderJoystickDisplay(JoystickDisplay *ctx, SDL_Joystick *joystick) const float margin = 8.0f; const float center = 80.0f; const float arrow_extent = 48.0f; - SDL_FRect dst, rect; + SDL_FRect dst, rect, highlight; Uint8 r, g, b, a; if (!ctx) { @@ -1051,6 +1528,12 @@ void RenderJoystickDisplay(JoystickDisplay *ctx, SDL_Joystick *joystick) y += FONT_LINE_HEIGHT + 2; for (i = 0; i < nbuttons; ++i) { + highlight.x = x; + highlight.y = y + FONT_CHARACTER_SIZE / 2 - ctx->button_height / 2; + highlight.w = center - (margin * 2); + highlight.h = (float)ctx->button_height; + RenderJoystickButtonHighlight(ctx, i, &highlight); + SDL_snprintf(text, sizeof(text), "%2.d:", i); SDLTest_DrawString(ctx->renderer, x, y, text); @@ -1083,6 +1566,15 @@ void RenderJoystickDisplay(JoystickDisplay *ctx, SDL_Joystick *joystick) SDL_snprintf(text, sizeof(text), "%d:", i); SDLTest_DrawString(ctx->renderer, x, y, text); + highlight.x = x + FONT_CHARACTER_SIZE * SDL_strlen(text) + 2.0f; + highlight.y = y + FONT_CHARACTER_SIZE / 2 - ctx->button_height / 2; + highlight.w = (float)ctx->arrow_width + arrow_extent; + highlight.h = (float)ctx->button_height; + RenderJoystickAxisHighlight(ctx, i, -1, &highlight); + + highlight.x += highlight.w; + RenderJoystickAxisHighlight(ctx, i, 1, &highlight); + dst.x = x + FONT_CHARACTER_SIZE * SDL_strlen(text) + 2.0f; dst.y = y + FONT_CHARACTER_SIZE / 2 - ctx->arrow_height / 2; dst.w = (float)ctx->arrow_width; @@ -1154,7 +1646,7 @@ void RenderJoystickDisplay(JoystickDisplay *ctx, SDL_Joystick *joystick) if (value & SDL_HAT_LEFT) { SDL_SetTextureColorMod(ctx->button_texture, 10, 255, 21); - } else { + } else if (!SetupJoystickHatHighlight(ctx, i, SDL_HAT_LEFT)) { SDL_SetTextureColorMod(ctx->button_texture, 255, 255, 255); } @@ -1166,7 +1658,7 @@ void RenderJoystickDisplay(JoystickDisplay *ctx, SDL_Joystick *joystick) if (value & SDL_HAT_UP) { SDL_SetTextureColorMod(ctx->button_texture, 10, 255, 21); - } else { + } else if (!SetupJoystickHatHighlight(ctx, i, SDL_HAT_UP)) { SDL_SetTextureColorMod(ctx->button_texture, 255, 255, 255); } @@ -1176,7 +1668,7 @@ void RenderJoystickDisplay(JoystickDisplay *ctx, SDL_Joystick *joystick) if (value & SDL_HAT_DOWN) { SDL_SetTextureColorMod(ctx->button_texture, 10, 255, 21); - } else { + } else if (!SetupJoystickHatHighlight(ctx, i, SDL_HAT_DOWN)) { SDL_SetTextureColorMod(ctx->button_texture, 255, 255, 255); } @@ -1185,7 +1677,7 @@ void RenderJoystickDisplay(JoystickDisplay *ctx, SDL_Joystick *joystick) if (value & SDL_HAT_RIGHT) { SDL_SetTextureColorMod(ctx->button_texture, 10, 255, 21); - } else { + } else if (!SetupJoystickHatHighlight(ctx, i, SDL_HAT_RIGHT)) { SDL_SetTextureColorMod(ctx->button_texture, 255, 255, 255); } @@ -1224,6 +1716,7 @@ struct GamepadButton int label_height; SDL_bool highlight; + SDL_bool pressed; }; GamepadButton *CreateGamepadButton(SDL_Renderer *renderer, const char *label) @@ -1254,13 +1747,30 @@ void SetGamepadButtonArea(GamepadButton *ctx, const SDL_Rect *area) ctx->area.h = (float)area->h; } -void SetGamepadButtonHighlight(GamepadButton *ctx, SDL_bool highlight) +void GetGamepadButtonArea(GamepadButton *ctx, SDL_Rect *area) +{ + if (!ctx) { + SDL_zerop(area); + } + + area->x = (int)ctx->area.x; + area->y = (int)ctx->area.y; + area->w = (int)ctx->area.w; + area->h = (int)ctx->area.h; +} + +void SetGamepadButtonHighlight(GamepadButton *ctx, SDL_bool highlight, SDL_bool pressed) { if (!ctx) { return; } ctx->highlight = highlight; + if (highlight) { + ctx->pressed = pressed; + } else { + ctx->pressed = SDL_FALSE; + } } int GetGamepadButtonLabelWidth(GamepadButton *ctx) @@ -1307,8 +1817,10 @@ void RenderGamepadButton(GamepadButton *ctx) one_third_src_width = (float)ctx->background_width / 3; one_third_src_height = (float)ctx->background_height / 3; - if (ctx->highlight) { - SDL_SetTextureColorMod(ctx->background, 10, 255, 21); + if (ctx->pressed) { + SDL_SetTextureColorMod(ctx->background, PRESSED_TEXTURE_MOD); + } else if (ctx->highlight) { + SDL_SetTextureColorMod(ctx->background, HIGHLIGHT_TEXTURE_MOD); } else { SDL_SetTextureColorMod(ctx->background, 255, 255, 255); } @@ -1392,3 +1904,548 @@ void DestroyGamepadButton(GamepadButton *ctx) SDL_free(ctx->label); SDL_free(ctx); } + + +typedef struct +{ + char *guid; + char *name; + int num_elements; + char **keys; + char **values; +} MappingParts; + +static SDL_bool AddMappingKeyValue(MappingParts *parts, char *key, char *value); + +static SDL_bool AddMappingHalfAxisValue(MappingParts *parts, const char *key, const char *value, char sign) +{ + char *new_key, *new_value; + + if (SDL_asprintf(&new_key, "%c%s", sign, key) < 0) { + return SDL_FALSE; + } + + if (*value && value[SDL_strlen(value) - 1] == '~') { + /* Invert the sign of the bound axis */ + if (sign == '+') { + sign = '-'; + } else { + sign = '+'; + } + } + + if (SDL_asprintf(&new_value, "%c%s", sign, value) < 0) { + SDL_free(new_key); + return SDL_FALSE; + } + if (new_value[SDL_strlen(new_value) - 1] == '~') { + new_value[SDL_strlen(new_value) - 1] = '\0'; + } + + return AddMappingKeyValue(parts, new_key, new_value); +} + +static SDL_bool AddMappingKeyValue(MappingParts *parts, char *key, char *value) +{ + int axis; + char **new_keys, **new_values; + + if (!key || !value) { + SDL_free(key); + SDL_free(value); + return SDL_FALSE; + } + + /* Split axis values for easy binding purposes */ + for (axis = 0; axis < SDL_GAMEPAD_AXIS_LEFT_TRIGGER; ++axis) { + if (SDL_strcmp(key, SDL_GetGamepadStringForAxis((SDL_GamepadAxis)axis)) == 0) { + SDL_bool result; + + result = AddMappingHalfAxisValue(parts, key, value, '-') && + AddMappingHalfAxisValue(parts, key, value, '+'); + SDL_free(key); + SDL_free(value); + return result; + } + } + + new_keys = (char **)SDL_realloc(parts->keys, (parts->num_elements + 1) * sizeof(*new_keys)); + if (!new_keys) { + return SDL_FALSE; + } + parts->keys = new_keys; + + new_values = (char **)SDL_realloc(parts->values, (parts->num_elements + 1) * sizeof(*new_values)); + if (!new_values) { + return SDL_FALSE; + } + parts->values = new_values; + + new_keys[parts->num_elements] = key; + new_values[parts->num_elements] = value; + ++parts->num_elements; + return SDL_TRUE; +} + +static void SplitMapping(const char *mapping, MappingParts *parts) +{ + const char *current, *comma, *colon, *key, *value; + char *new_key, *new_value; + + SDL_zerop(parts); + + if (!mapping || !*mapping) { + return; + } + + /* Get the guid */ + current = mapping; + comma = SDL_strchr(current, ','); + if (!comma) { + parts->guid = SDL_strdup(current); + return; + } + parts->guid = SDL_strndup(current, (comma - current)); + current = comma + 1; + + /* Get the name */ + comma = SDL_strchr(current, ','); + if (!comma) { + parts->name = SDL_strdup(current); + return; + } + if (*current != '*') { + parts->name = SDL_strndup(current, (comma - current)); + } + current = comma + 1; + + for (;;) { + colon = SDL_strchr(current, ':'); + if (!colon) { + break; + } + + key = current; + value = colon + 1; + comma = SDL_strchr(value, ','); + + new_key = SDL_strndup(key, (colon - key)); + if (comma) { + new_value = SDL_strndup(value, (comma - value)); + } else { + new_value = SDL_strdup(value); + } + if (!AddMappingKeyValue(parts, new_key, new_value)) { + break; + } + + if (comma) { + current = comma + 1; + } else { + break; + } + } +} + +static int FindMappingKey(const MappingParts *parts, const char *key) +{ + int i; + + for (i = 0; i < parts->num_elements; ++i) { + if (SDL_strcmp(key, parts->keys[i]) == 0) { + return i; + } + } + return -1; +} + +static void RemoveMappingValueAt(MappingParts *parts, int index) +{ + SDL_free(parts->keys[index]); + SDL_free(parts->values[index]); + --parts->num_elements; + if (index < parts->num_elements) { + SDL_memcpy(&parts->keys[index], &parts->keys[index] + 1, (parts->num_elements - index) * sizeof(parts->keys[index])); + SDL_memcpy(&parts->values[index], &parts->values[index] + 1, (parts->num_elements - index) * sizeof(parts->values[index])); + } +} + +static SDL_bool CombineMappingAxes(MappingParts *parts) +{ + int i, matching, axis; + + for (i = 0; i < parts->num_elements; ++i) { + char *key = parts->keys[i]; + char *current_value; + char *matching_key; + char *matching_value; + + if (*key != '-' && *key != '+') { + continue; + } + + for (axis = 0; axis < SDL_GAMEPAD_AXIS_LEFT_TRIGGER; ++axis) { + if (SDL_strcmp(key + 1, SDL_GetGamepadStringForAxis((SDL_GamepadAxis)axis)) == 0) { + /* Look for a matching axis with the opposite sign */ + if (SDL_asprintf(&matching_key, "%c%s", (*key == '-' ? '+' : '-'), key + 1) < 0) { + return SDL_FALSE; + } + matching = FindMappingKey(parts, matching_key); + if (matching >= 0) { + /* Check to see if they're bound to the same axis */ + current_value = parts->values[i]; + matching_value = parts->values[matching]; + if (((*current_value == '-' && *matching_value == '+') || + (*current_value == '+' && *matching_value == '-')) && + SDL_strcmp(current_value + 1, matching_value + 1) == 0) { + /* Combine these axes */ + if (*key == *current_value) { + SDL_memmove(current_value, current_value + 1, SDL_strlen(current_value)); + } else { + /* Invert the bound axis */ + SDL_memmove(current_value, current_value + 1, SDL_strlen(current_value)-1); + current_value[SDL_strlen(current_value) - 1] = '~'; + } + SDL_memmove(key, key + 1, SDL_strlen(key)); + RemoveMappingValueAt(parts, matching); + } + } + SDL_free(matching_key); + break; + } + } + } + return SDL_TRUE; +} + +static char *JoinMapping(MappingParts *parts) +{ + int i; + size_t length; + char *mapping; + const char *guid; + const char *name; + + CombineMappingAxes(parts); + + guid = parts->guid; + if (!guid || !*guid) { + guid = "*"; + } + + name = parts->name; + if (!name || !*name) { + name = "*"; + } + + length = SDL_strlen(guid) + 1 + SDL_strlen(name) + 1; + for (i = 0; i < parts->num_elements; ++i) { + length += SDL_strlen(parts->keys[i]) + 1; + length += SDL_strlen(parts->values[i]) + 1; + } + length += 1; + + mapping = (char *)SDL_malloc(length); + if (mapping) { + *mapping = '\0'; + SDL_strlcat(mapping, guid, length); + SDL_strlcat(mapping, ",", length); + SDL_strlcat(mapping, name, length); + SDL_strlcat(mapping, ",", length); + for (i = 0; i < parts->num_elements; ++i) { + SDL_strlcat(mapping, parts->keys[i], length); + SDL_strlcat(mapping, ":", length); + SDL_strlcat(mapping, parts->values[i], length); + SDL_strlcat(mapping, ",", length); + } + } + return mapping; +} + +static void FreeMappingParts(MappingParts *parts) +{ + int i; + + SDL_free(parts->guid); + SDL_free(parts->name); + for (i = 0; i < parts->num_elements; ++i) { + SDL_free(parts->keys[i]); + SDL_free(parts->values[i]); + } + SDL_free(parts->keys); + SDL_free(parts->values); + SDL_zerop(parts); +} + +static char *GetMappingValue(const char *mapping, const char *key) +{ + int i; + MappingParts parts; + char *value = NULL; + + SplitMapping(mapping, &parts); + i = FindMappingKey(&parts, key); + if (i >= 0) { + value = parts.values[i]; + parts.values[i] = NULL; /* So we don't free it */ + } + FreeMappingParts(&parts); + + return value; +} + +static char *SetMappingValue(char *mapping, const char *key, const char *value) +{ + MappingParts parts; + int i; + char *new_mapping; + char *new_key = NULL; + char *new_value = NULL; + char **new_keys = NULL; + char **new_values = NULL; + SDL_bool result = SDL_FALSE; + + SplitMapping(mapping, &parts); + i = FindMappingKey(&parts, key); + if (i >= 0) { + new_value = SDL_strdup(value); + if (new_value) { + SDL_free(parts.values[i]); + parts.values[i] = new_value; + result = SDL_TRUE; + } + } else { + int count = parts.num_elements; + + new_key = SDL_strdup(key); + if (new_key) { + new_value = SDL_strdup(value); + if (new_value) { + new_keys = (char **)SDL_realloc(parts.keys, (count + 1) * sizeof(*new_keys)); + if (new_keys) { + new_values = (char **)SDL_realloc(parts.values, (count + 1) * sizeof(*new_values)); + if (new_values) { + new_keys[count] = new_key; + new_values[count] = new_value; + parts.num_elements = (count + 1); + parts.keys = new_keys; + parts.values = new_values; + result = SDL_TRUE; + } + } + } + } + } + + if (result) { + new_mapping = JoinMapping(&parts); + if (new_mapping) { + SDL_free(mapping); + mapping = new_mapping; + } + } else { + SDL_free(new_key); + SDL_free(new_value); + SDL_free(new_keys); + SDL_free(new_values); + } + return mapping; +} + +static char *RemoveMappingKey(char *mapping, const char *key) +{ + MappingParts parts; + int i; + + SplitMapping(mapping, &parts); + i = FindMappingKey(&parts, key); + if (i >= 0) { + RemoveMappingValueAt(&parts, i); + } + return JoinMapping(&parts); +} + +SDL_bool MappingHasBindings(const char *mapping) +{ + MappingParts parts; + int i; + SDL_bool result = SDL_FALSE; + + if (!mapping || !*mapping) { + return SDL_FALSE; + } + + SplitMapping(mapping, &parts); + for (i = 0; i < SDL_GAMEPAD_BUTTON_MAX; ++i) { + if (FindMappingKey(&parts, SDL_GetGamepadStringForButton((SDL_GamepadButton)i)) >= 0) { + result = SDL_TRUE; + break; + } + } + if (!result) { + for (i = 0; i < SDL_GAMEPAD_AXIS_MAX; ++i) { + if (FindMappingKey(&parts, SDL_GetGamepadStringForAxis((SDL_GamepadAxis)i)) >= 0) { + result = SDL_TRUE; + break; + } + } + } + FreeMappingParts(&parts); + + return result; +} + +SDL_bool MappingHasName(const char *mapping) +{ + MappingParts parts; + SDL_bool retval; + + SplitMapping(mapping, &parts); + retval = parts.name ? SDL_TRUE : SDL_FALSE; + FreeMappingParts(&parts); + return retval; +} + +char *GetMappingName(const char *mapping) +{ + MappingParts parts; + char *name; + + SplitMapping(mapping, &parts); + name = parts.name; + parts.name = NULL; /* Don't free the name we're about to return */ + FreeMappingParts(&parts); + return name; +} + +char *SetMappingName(char *mapping, const char *name) +{ + MappingParts parts; + char *new_name; + char *new_mapping; + + new_name = SDL_strdup(name); + if (!new_name) { + return mapping; + } + + SplitMapping(mapping, &parts); + SDL_free(parts.name); + parts.name = new_name; + new_mapping = JoinMapping(&parts); + FreeMappingParts(&parts); + return new_mapping; +} + +char *GetMappingType(const char *mapping) +{ + return GetMappingValue(mapping, "type"); +} + +char *SetMappingType(char *mapping, const char *type) +{ + return SetMappingValue(mapping, "type", type); +} + +static const char *GetElementKey(int element) +{ + if (element < SDL_GAMEPAD_BUTTON_MAX) { + return SDL_GetGamepadStringForButton((SDL_GamepadButton)element); + } else { + static char key[16]; + + switch (element) { + case SDL_GAMEPAD_ELEMENT_AXIS_LEFTX_NEGATIVE: + SDL_snprintf(key, sizeof(key), "-%s", SDL_GetGamepadStringForAxis(SDL_GAMEPAD_AXIS_LEFTX)); + break; + case SDL_GAMEPAD_ELEMENT_AXIS_LEFTX_POSITIVE: + SDL_snprintf(key, sizeof(key), "+%s", SDL_GetGamepadStringForAxis(SDL_GAMEPAD_AXIS_LEFTX)); + break; + case SDL_GAMEPAD_ELEMENT_AXIS_LEFTY_NEGATIVE: + SDL_snprintf(key, sizeof(key), "-%s", SDL_GetGamepadStringForAxis(SDL_GAMEPAD_AXIS_LEFTY)); + break; + case SDL_GAMEPAD_ELEMENT_AXIS_LEFTY_POSITIVE: + SDL_snprintf(key, sizeof(key), "+%s", SDL_GetGamepadStringForAxis(SDL_GAMEPAD_AXIS_LEFTY)); + break; + case SDL_GAMEPAD_ELEMENT_AXIS_RIGHTX_NEGATIVE: + SDL_snprintf(key, sizeof(key), "-%s", SDL_GetGamepadStringForAxis(SDL_GAMEPAD_AXIS_RIGHTX)); + break; + case SDL_GAMEPAD_ELEMENT_AXIS_RIGHTX_POSITIVE: + SDL_snprintf(key, sizeof(key), "+%s", SDL_GetGamepadStringForAxis(SDL_GAMEPAD_AXIS_RIGHTX)); + break; + case SDL_GAMEPAD_ELEMENT_AXIS_RIGHTY_NEGATIVE: + SDL_snprintf(key, sizeof(key), "-%s", SDL_GetGamepadStringForAxis(SDL_GAMEPAD_AXIS_RIGHTY)); + break; + case SDL_GAMEPAD_ELEMENT_AXIS_RIGHTY_POSITIVE: + SDL_snprintf(key, sizeof(key), "+%s", SDL_GetGamepadStringForAxis(SDL_GAMEPAD_AXIS_RIGHTY)); + break; + case SDL_GAMEPAD_ELEMENT_AXIS_LEFT_TRIGGER: + return SDL_GetGamepadStringForAxis(SDL_GAMEPAD_AXIS_LEFT_TRIGGER); + case SDL_GAMEPAD_ELEMENT_AXIS_RIGHT_TRIGGER: + return SDL_GetGamepadStringForAxis(SDL_GAMEPAD_AXIS_RIGHT_TRIGGER); + default: + return NULL; + } + return key; + } +} + +char *GetElementBinding(const char *mapping, int element) +{ + const char *key; + + key = GetElementKey(element); + if (!key) { + return NULL; + } + return GetMappingValue(mapping, key); +} + +char *SetElementBinding(char *mapping, int element, const char *binding) +{ + if (binding) { + return SetMappingValue(mapping, GetElementKey(element), binding); + } else { + return RemoveMappingKey(mapping, GetElementKey(element)); + } +} + +SDL_bool MappingHasBinding(const char *mapping, const char *binding) +{ + MappingParts parts; + int i; + SDL_bool result = SDL_FALSE; + + if (!binding) { + return SDL_FALSE; + } + + SplitMapping(mapping, &parts); + for (i = parts.num_elements - 1; i >= 0; --i) { + if (SDL_strcmp(binding, parts.values[i]) == 0) { + result = SDL_TRUE; + break; + } + } + FreeMappingParts(&parts); + + return result; +} + +char *ClearMappingBinding(char *mapping, const char *binding) +{ + MappingParts parts; + int i; + + if (!binding) { + return mapping; + } + + SplitMapping(mapping, &parts); + for (i = parts.num_elements - 1; i >= 0; --i) { + if (SDL_strcmp(binding, parts.values[i]) == 0) { + RemoveMappingValueAt(&parts, i); + } + } + return JoinMapping(&parts); +} diff --git a/test/gamepadutils.h b/test/gamepadutils.h index 523e0b28cb..7dbe3f0d79 100644 --- a/test/gamepadutils.h +++ b/test/gamepadutils.h @@ -28,22 +28,52 @@ typedef enum GAMEPAD_IMAGE_FACE_SONY, } GamepadImageFaceStyle; +enum +{ + SDL_GAMEPAD_ELEMENT_INVALID = -1, + + /* ... SDL_GamepadButton ... */ + + SDL_GAMEPAD_ELEMENT_AXIS_LEFTX_NEGATIVE = SDL_GAMEPAD_BUTTON_MAX, + SDL_GAMEPAD_ELEMENT_AXIS_LEFTX_POSITIVE, + SDL_GAMEPAD_ELEMENT_AXIS_LEFTY_NEGATIVE, + SDL_GAMEPAD_ELEMENT_AXIS_LEFTY_POSITIVE, + SDL_GAMEPAD_ELEMENT_AXIS_RIGHTX_NEGATIVE, + SDL_GAMEPAD_ELEMENT_AXIS_RIGHTX_POSITIVE, + SDL_GAMEPAD_ELEMENT_AXIS_RIGHTY_NEGATIVE, + SDL_GAMEPAD_ELEMENT_AXIS_RIGHTY_POSITIVE, + SDL_GAMEPAD_ELEMENT_AXIS_LEFT_TRIGGER, + SDL_GAMEPAD_ELEMENT_AXIS_RIGHT_TRIGGER, + SDL_GAMEPAD_ELEMENT_AXIS_MAX, + + SDL_GAMEPAD_ELEMENT_NAME = SDL_GAMEPAD_ELEMENT_AXIS_MAX, + SDL_GAMEPAD_ELEMENT_TYPE, + SDL_GAMEPAD_ELEMENT_MAX, +}; + +#define HIGHLIGHT_COLOR 224, 255, 255, SDL_ALPHA_OPAQUE +#define HIGHLIGHT_TEXTURE_MOD 224, 255, 255 +#define PRESSED_COLOR 175, 238, 238, SDL_ALPHA_OPAQUE +#define PRESSED_TEXTURE_MOD 175, 238, 238 +#define SELECTED_COLOR 224, 255, 224, SDL_ALPHA_OPAQUE + +/* Gamepad image display */ + extern GamepadImage *CreateGamepadImage(SDL_Renderer *renderer); extern void SetGamepadImagePosition(GamepadImage *ctx, int x, int y); +extern void GetGamepadImageArea(GamepadImage *ctx, SDL_Rect *area); extern void SetGamepadImageShowingFront(GamepadImage *ctx, SDL_bool showing_front); extern void SetGamepadImageFaceStyle(GamepadImage *ctx, GamepadImageFaceStyle face_style); +extern GamepadImageFaceStyle GetGamepadImageFaceStyle(GamepadImage *ctx); extern void SetGamepadImageDisplayMode(GamepadImage *ctx, ControllerDisplayMode display_mode); extern int GetGamepadImageButtonWidth(GamepadImage *ctx); extern int GetGamepadImageButtonHeight(GamepadImage *ctx); extern int GetGamepadImageAxisWidth(GamepadImage *ctx); extern int GetGamepadImageAxisHeight(GamepadImage *ctx); - -extern SDL_GamepadButton GetGamepadImageButtonAt(GamepadImage *ctx, float x, float y); -extern SDL_GamepadAxis GetGamepadImageAxisAt(GamepadImage *ctx, float x, float y); +extern int GetGamepadImageElementAt(GamepadImage *ctx, float x, float y); extern void ClearGamepadImage(GamepadImage *ctx); -extern void SetGamepadImageButton(GamepadImage *ctx, SDL_GamepadButton button, SDL_bool active); -extern void SetGamepadImageAxis(GamepadImage *ctx, SDL_GamepadAxis axis, int direction); +extern void SetGamepadImageElement(GamepadImage *ctx, int element, SDL_bool active); extern void UpdateGamepadImageFromGamepad(GamepadImage *ctx, SDL_Gamepad *gamepad); extern void RenderGamepadImage(GamepadImage *ctx); @@ -56,6 +86,9 @@ typedef struct GamepadDisplay GamepadDisplay; extern GamepadDisplay *CreateGamepadDisplay(SDL_Renderer *renderer); extern void SetGamepadDisplayDisplayMode(GamepadDisplay *ctx, ControllerDisplayMode display_mode); extern void SetGamepadDisplayArea(GamepadDisplay *ctx, const SDL_Rect *area); +extern int GetGamepadDisplayElementAt(GamepadDisplay *ctx, SDL_Gamepad *gamepad, float x, float y); +extern void SetGamepadDisplayHighlight(GamepadDisplay *ctx, int element, SDL_bool pressed); +extern void SetGamepadDisplaySelected(GamepadDisplay *ctx, int element); extern void RenderGamepadDisplay(GamepadDisplay *ctx, SDL_Gamepad *gamepad); extern void DestroyGamepadDisplay(GamepadDisplay *ctx); @@ -65,6 +98,8 @@ typedef struct JoystickDisplay JoystickDisplay; extern JoystickDisplay *CreateJoystickDisplay(SDL_Renderer *renderer); extern void SetJoystickDisplayArea(JoystickDisplay *ctx, const SDL_Rect *area); +extern char *GetJoystickDisplayElementAt(JoystickDisplay *ctx, SDL_Joystick *joystick, float x, float y); +extern void SetJoystickDisplayHighlight(JoystickDisplay *ctx, const char *element, SDL_bool pressed); extern void RenderJoystickDisplay(JoystickDisplay *ctx, SDL_Joystick *joystick); extern void DestroyJoystickDisplay(JoystickDisplay *ctx); @@ -74,9 +109,42 @@ typedef struct GamepadButton GamepadButton; extern GamepadButton *CreateGamepadButton(SDL_Renderer *renderer, const char *label); extern void SetGamepadButtonArea(GamepadButton *ctx, const SDL_Rect *area); -extern void SetGamepadButtonHighlight(GamepadButton *ctx, SDL_bool highlight); +extern void GetGamepadButtonArea(GamepadButton *ctx, SDL_Rect *area); +extern void SetGamepadButtonHighlight(GamepadButton *ctx, SDL_bool highlight, SDL_bool pressed); extern int GetGamepadButtonLabelWidth(GamepadButton *ctx); extern int GetGamepadButtonLabelHeight(GamepadButton *ctx); extern SDL_bool GamepadButtonContains(GamepadButton *ctx, float x, float y); extern void RenderGamepadButton(GamepadButton *ctx); extern void DestroyGamepadButton(GamepadButton *ctx); + +/* Working with mappings and bindings */ + +/* Return whether a mapping has any bindings */ +extern SDL_bool MappingHasBindings(const char *mapping); + +/* Return true if the mapping has a controller name */ +extern SDL_bool MappingHasName(const char *mapping); + +/* Return the name from a mapping, which should be freed using SDL_free(), or NULL if there is no name specified */ +extern char *GetMappingName(const char *mapping); + +/* Set the name in a mapping, freeing the mapping passed in and returning a new mapping */ +extern char *SetMappingName(char *mapping, const char *name); + +/* Return the type from a mapping, which should be freed using SDL_free(), or NULL if there is no type specified */ +extern char *GetMappingType(const char *mapping); + +/* Set the name in a mapping, freeing the mapping passed in and returning a new mapping */ +extern char *SetMappingType(char *mapping, const char *type); + +/* Get the binding for an element, which should be freed using SDL_free(), or NULL if the element isn't bound */ +extern char *GetElementBinding(const char *mapping, int element); + +/* Set the binding for an element, or NULL to clear it */ +extern char *SetElementBinding(char *mapping, int element, const char *binding); + +/* Return true if a mapping contains this binding */ +extern SDL_bool MappingHasBinding(const char *mapping, const char *binding); + +/* Clear any previous binding */ +extern char *ClearMappingBinding(char *mapping, const char *binding); diff --git a/test/testcontroller.c b/test/testcontroller.c index 0d8944d973..5a82ce079a 100644 --- a/test/testcontroller.c +++ b/test/testcontroller.c @@ -24,6 +24,10 @@ #include #endif +#if 0 +#define DEBUG_AXIS_MAPPING +#endif + #define TITLE_HEIGHT 48 #define PANEL_SPACING 25 #define PANEL_WIDTH 250 @@ -47,11 +51,26 @@ static const char *power_level_strings[] = { }; SDL_COMPILE_TIME_ASSERT(power_level_strings, SDL_arraysize(power_level_strings) == SDL_JOYSTICK_POWER_MAX + 1); +typedef struct +{ + SDL_bool m_bMoving; + int m_nLastValue; + int m_nStartingValue; + int m_nFarthestValue; +} AxisState; + typedef struct { SDL_JoystickID id; + SDL_Joystick *joystick; + int num_axes; + AxisState *axis_state; + SDL_Gamepad *gamepad; + char *mapping; + SDL_bool has_bindings; + int trigger_effect; } Controller; @@ -62,18 +81,20 @@ static GamepadImage *image = NULL; static GamepadDisplay *gamepad_elements = NULL; static JoystickDisplay *joystick_elements = NULL; static GamepadButton *setup_mapping_button = NULL; -static GamepadButton *test_mapping_button = NULL; +static GamepadButton *done_mapping_button = NULL; static GamepadButton *cancel_button = NULL; static GamepadButton *clear_button = NULL; static GamepadButton *copy_button = NULL; static GamepadButton *paste_button = NULL; static char *backup_mapping = NULL; -static SDL_bool retval = SDL_FALSE; static SDL_bool done = SDL_FALSE; static SDL_bool set_LED = SDL_FALSE; static int num_controllers = 0; static Controller *controllers; static Controller *controller; +static SDL_JoystickID mapping_controller = 0; +static int binding_element = SDL_GAMEPAD_ELEMENT_INVALID; +static Uint64 binding_advance_time = 0; static SDL_Joystick *virtual_joystick = NULL; static SDL_GamepadAxis virtual_axis_active = SDL_GAMEPAD_AXIS_INVALID; static float virtual_axis_start_x; @@ -81,63 +102,6 @@ static float virtual_axis_start_y; static SDL_GamepadButton virtual_button_active = SDL_GAMEPAD_BUTTON_INVALID; -static void PrintJoystickInfo(SDL_JoystickID instance_id) -{ - char guid[64]; - const char *name; - const char *path; - const char *description; - const char *mapping = NULL; - - SDL_GetJoystickGUIDString(SDL_GetJoystickInstanceGUID(instance_id), guid, sizeof(guid)); - - if (SDL_IsGamepad(instance_id)) { - name = SDL_GetGamepadInstanceName(instance_id); - path = SDL_GetGamepadInstancePath(instance_id); - switch (SDL_GetGamepadInstanceType(instance_id)) { - case SDL_GAMEPAD_TYPE_XBOX360: - description = "XBox 360 Controller"; - break; - case SDL_GAMEPAD_TYPE_XBOXONE: - description = "XBox One Controller"; - break; - case SDL_GAMEPAD_TYPE_PS3: - description = "PS3 Controller"; - break; - case SDL_GAMEPAD_TYPE_PS4: - description = "PS4 Controller"; - break; - case SDL_GAMEPAD_TYPE_PS5: - description = "DualSense Wireless Controller"; - break; - case SDL_GAMEPAD_TYPE_NINTENDO_SWITCH_PRO: - description = "Nintendo Switch Pro Controller"; - break; - case SDL_GAMEPAD_TYPE_NINTENDO_SWITCH_JOYCON_LEFT: - case SDL_GAMEPAD_TYPE_NINTENDO_SWITCH_JOYCON_RIGHT: - case SDL_GAMEPAD_TYPE_NINTENDO_SWITCH_JOYCON_PAIR: - description = "Nintendo Switch Joy-Con"; - break; - default: - description = "Gamepad"; - break; - } - mapping = SDL_GetGamepadInstanceMapping(instance_id); - } else { - name = SDL_GetJoystickInstanceName(instance_id); - path = SDL_GetJoystickInstancePath(instance_id); - description = "Joystick"; - } - SDL_Log("%s: %s%s%s (guid %s, VID 0x%.4x, PID 0x%.4x, player index = %d)\n", - description, name ? name : "Unknown", path ? ", " : "", path ? path : "", guid, - SDL_GetJoystickInstanceVendor(instance_id), - SDL_GetJoystickInstanceProduct(instance_id), - SDL_GetJoystickInstancePlayerIndex(instance_id)); - if (mapping) { - SDL_Log("Mapping: %s\n", mapping); - } -} - static const char *GetSensorName(SDL_SensorType sensor) { switch (sensor) { @@ -210,52 +174,256 @@ static void CyclePS5TriggerEffect(Controller *device) static void ClearButtonHighlights() { - SetGamepadButtonHighlight(setup_mapping_button, SDL_FALSE); - SetGamepadButtonHighlight(test_mapping_button, SDL_FALSE); - SetGamepadButtonHighlight(cancel_button, SDL_FALSE); - SetGamepadButtonHighlight(clear_button, SDL_FALSE); - SetGamepadButtonHighlight(copy_button, SDL_FALSE); - SetGamepadButtonHighlight(paste_button, SDL_FALSE); + ClearGamepadImage(image); + SetGamepadDisplayHighlight(gamepad_elements, SDL_GAMEPAD_ELEMENT_INVALID, SDL_FALSE); + SetGamepadButtonHighlight(setup_mapping_button, SDL_FALSE, SDL_FALSE); + SetGamepadButtonHighlight(done_mapping_button, SDL_FALSE, SDL_FALSE); + SetGamepadButtonHighlight(cancel_button, SDL_FALSE, SDL_FALSE); + SetGamepadButtonHighlight(clear_button, SDL_FALSE, SDL_FALSE); + SetGamepadButtonHighlight(copy_button, SDL_FALSE, SDL_FALSE); + SetGamepadButtonHighlight(paste_button, SDL_FALSE, SDL_FALSE); } -static void UpdateButtonHighlights(float x, float y) +static void UpdateButtonHighlights(float x, float y, SDL_bool button_down) { ClearButtonHighlights(); if (display_mode == CONTROLLER_MODE_TESTING) { - SetGamepadButtonHighlight(setup_mapping_button, GamepadButtonContains(setup_mapping_button, x, y)); + SetGamepadButtonHighlight(setup_mapping_button, GamepadButtonContains(setup_mapping_button, x, y), button_down); } else if (display_mode == CONTROLLER_MODE_BINDING) { - SetGamepadButtonHighlight(test_mapping_button, GamepadButtonContains(test_mapping_button, x, y)); - SetGamepadButtonHighlight(cancel_button, GamepadButtonContains(cancel_button, x, y)); - SetGamepadButtonHighlight(clear_button, GamepadButtonContains(clear_button, x, y)); - SetGamepadButtonHighlight(copy_button, GamepadButtonContains(copy_button, x, y)); - SetGamepadButtonHighlight(paste_button, GamepadButtonContains(paste_button, x, y)); + int gamepad_highlight_element; + char *joystick_highlight_element; + + gamepad_highlight_element = GetGamepadDisplayElementAt(gamepad_elements, controller->gamepad, x, y); + SetGamepadDisplayHighlight(gamepad_elements, gamepad_highlight_element, button_down); + + joystick_highlight_element = GetJoystickDisplayElementAt(joystick_elements, controller->joystick, x, y); + SetJoystickDisplayHighlight(joystick_elements, joystick_highlight_element, button_down); + SDL_free(joystick_highlight_element); + + SetGamepadButtonHighlight(done_mapping_button, GamepadButtonContains(done_mapping_button, x, y), button_down); + SetGamepadButtonHighlight(cancel_button, GamepadButtonContains(cancel_button, x, y), button_down); + SetGamepadButtonHighlight(clear_button, GamepadButtonContains(clear_button, x, y), button_down); + SetGamepadButtonHighlight(copy_button, GamepadButtonContains(copy_button, x, y), button_down); + SetGamepadButtonHighlight(paste_button, GamepadButtonContains(paste_button, x, y), button_down); } } +static int StandardizeAxisValue(int nValue) +{ + if (nValue > SDL_JOYSTICK_AXIS_MAX / 2) { + return SDL_JOYSTICK_AXIS_MAX; + } else if (nValue < SDL_JOYSTICK_AXIS_MIN / 2) { + return SDL_JOYSTICK_AXIS_MIN; + } else { + return 0; + } +} + +static void SetGamepadMapping(char *mapping) +{ + /* Make sure the mapping has a valid name */ + if (!MappingHasName(mapping)) { + SetMappingName(mapping, SDL_GetJoystickName(controller->joystick)); + } + + SDL_free(controller->mapping); + controller->mapping = mapping; + controller->has_bindings = MappingHasBindings(mapping); + + SDL_SetGamepadMapping(controller->id, mapping); +} + +static void SetCurrentBindingElement(int element) +{ + int i; + + binding_element = element; + binding_advance_time = 0; + + for (i = 0; i < controller->num_axes; ++i) { + controller->axis_state[i].m_nFarthestValue = controller->axis_state[i].m_nStartingValue; + } + + SetGamepadDisplaySelected(gamepad_elements, element); +} + +static void SetNextBindingElement() +{ + static int s_arrBindingOrder[] = { + /* Standard sequence */ + SDL_GAMEPAD_BUTTON_A, + SDL_GAMEPAD_BUTTON_B, + SDL_GAMEPAD_BUTTON_Y, + SDL_GAMEPAD_BUTTON_X, + SDL_GAMEPAD_ELEMENT_AXIS_LEFTX_NEGATIVE, + SDL_GAMEPAD_ELEMENT_AXIS_LEFTX_POSITIVE, + SDL_GAMEPAD_ELEMENT_AXIS_LEFTY_NEGATIVE, + SDL_GAMEPAD_ELEMENT_AXIS_LEFTY_POSITIVE, + SDL_GAMEPAD_BUTTON_LEFT_STICK, + SDL_GAMEPAD_ELEMENT_AXIS_RIGHTX_NEGATIVE, + SDL_GAMEPAD_ELEMENT_AXIS_RIGHTX_POSITIVE, + SDL_GAMEPAD_ELEMENT_AXIS_RIGHTY_NEGATIVE, + SDL_GAMEPAD_ELEMENT_AXIS_RIGHTY_POSITIVE, + SDL_GAMEPAD_BUTTON_RIGHT_STICK, + SDL_GAMEPAD_BUTTON_LEFT_SHOULDER, + SDL_GAMEPAD_ELEMENT_AXIS_LEFT_TRIGGER, + SDL_GAMEPAD_BUTTON_RIGHT_SHOULDER, + SDL_GAMEPAD_ELEMENT_AXIS_RIGHT_TRIGGER, + SDL_GAMEPAD_BUTTON_DPAD_UP, + SDL_GAMEPAD_BUTTON_DPAD_RIGHT, + SDL_GAMEPAD_BUTTON_DPAD_DOWN, + SDL_GAMEPAD_BUTTON_DPAD_LEFT, + SDL_GAMEPAD_BUTTON_BACK, + SDL_GAMEPAD_BUTTON_GUIDE, + SDL_GAMEPAD_BUTTON_START, + SDL_GAMEPAD_BUTTON_MISC1, + SDL_GAMEPAD_ELEMENT_INVALID, + + /* Paddle sequence */ + SDL_GAMEPAD_BUTTON_PADDLE1, + SDL_GAMEPAD_BUTTON_PADDLE2, + SDL_GAMEPAD_BUTTON_PADDLE3, + SDL_GAMEPAD_BUTTON_PADDLE4, + SDL_GAMEPAD_ELEMENT_INVALID, + }; + int i; + + if (binding_element == SDL_GAMEPAD_ELEMENT_INVALID) { + return; + } + + for (i = 0; i < SDL_arraysize(s_arrBindingOrder); ++i) { + if (binding_element == s_arrBindingOrder[i]) { + SetCurrentBindingElement(s_arrBindingOrder[i + 1]); + return; + } + } + SetCurrentBindingElement(SDL_GAMEPAD_ELEMENT_INVALID); +} + +static void CancelBinding(void) +{ + SetCurrentBindingElement(SDL_GAMEPAD_ELEMENT_INVALID); +} + +static void CommitBindingElement(const char *binding, SDL_bool force) +{ + char *mapping; + + if (binding_element == SDL_GAMEPAD_ELEMENT_INVALID) { + return; + } + + mapping = controller->mapping; + + /* If the controller generates multiple events for a single element, pick the best one */ + if (!force && binding_advance_time) { + SDL_bool ignore_binding = SDL_FALSE; + char *current = GetElementBinding(mapping, binding_element); + SDL_bool native_button = (binding_element < SDL_GAMEPAD_BUTTON_MAX); + SDL_bool native_axis = (binding_element >= SDL_GAMEPAD_BUTTON_MAX && + binding_element <= SDL_GAMEPAD_ELEMENT_AXIS_MAX); + SDL_bool native_trigger = (binding_element == SDL_GAMEPAD_ELEMENT_AXIS_LEFT_TRIGGER || + binding_element == SDL_GAMEPAD_ELEMENT_AXIS_RIGHT_TRIGGER); + SDL_bool native_dpad = (binding_element == SDL_GAMEPAD_BUTTON_DPAD_UP || + binding_element == SDL_GAMEPAD_BUTTON_DPAD_DOWN || + binding_element == SDL_GAMEPAD_BUTTON_DPAD_LEFT || + binding_element == SDL_GAMEPAD_BUTTON_DPAD_RIGHT); + + if (native_button) { + SDL_bool current_button = (current && *current == 'b'); + SDL_bool proposed_button = (binding && *binding == 'b'); + if (current_button && !proposed_button) { + ignore_binding = SDL_TRUE; + } + } + if (native_axis) { + SDL_bool current_axis = (current && (*current == '-' || *current == '+' || *current == 'a')); + if (current_axis) { + /* Ignore this unless the proposed binding extends the existing axis */ + ignore_binding = SDL_TRUE; + + if (native_trigger && + ((*current == '-' && *binding == '+' && + SDL_strcmp(current + 1, binding + 1) == 0) || + (*current == '+' && *binding == '-' && + SDL_strcmp(current + 1, binding + 1) == 0))) { + /* Merge two half axes into a whole axis for a trigger */ + ++binding; + ignore_binding = SDL_FALSE; + } + } + } + if (native_dpad) { + SDL_bool current_dpad = (current && *current == 'h'); + SDL_bool proposed_dpad = (binding && *binding == 'h'); + if (current_dpad && !proposed_dpad) { + ignore_binding = SDL_TRUE; + } + } + SDL_free(current); + + if (ignore_binding) { + return; + } + } + + mapping = ClearMappingBinding(mapping, binding); + mapping = SetElementBinding(mapping, binding_element, binding); + SetGamepadMapping(mapping); + + if (force) { + SetNextBindingElement(); + } else { + /* Wait to see if any more bindings come in */ + binding_advance_time = SDL_GetTicks() + 30; + } +} + +static void ClearBinding(void) +{ + CommitBindingElement(NULL, SDL_TRUE); +} + static void SetDisplayMode(ControllerDisplayMode mode) { float x, y; + Uint32 button_state; if (mode == CONTROLLER_MODE_BINDING) { /* Make a backup of the current mapping */ - backup_mapping = SDL_GetGamepadMapping(controller->gamepad); + if (controller->mapping) { + backup_mapping = SDL_strdup(controller->mapping); + } + mapping_controller = controller->id; + if (MappingHasBindings(backup_mapping)) { + SetCurrentBindingElement(SDL_GAMEPAD_ELEMENT_INVALID); + } else { + SetCurrentBindingElement(SDL_GAMEPAD_BUTTON_A); + } + } else { + if (backup_mapping) { + SDL_free(backup_mapping); + backup_mapping = NULL; + } + mapping_controller = 0; + CancelBinding(); } display_mode = mode; SetGamepadImageDisplayMode(image, mode); SetGamepadDisplayDisplayMode(gamepad_elements, mode); - SDL_GetMouseState(&x, &y); + button_state = SDL_GetMouseState(&x, &y); SDL_RenderCoordinatesFromWindow(screen, x, y, &x, &y); - UpdateButtonHighlights(x, y); + UpdateButtonHighlights(x, y, button_state ? SDL_TRUE : SDL_FALSE); } static void CancelMapping(void) { if (backup_mapping) { - SDL_SetGamepadMapping(controller->id, backup_mapping); - SDL_free(backup_mapping); + SetGamepadMapping(backup_mapping); backup_mapping = NULL; } SetDisplayMode(CONTROLLER_MODE_TESTING); @@ -263,34 +431,33 @@ static void CancelMapping(void) static void ClearMapping(void) { - SDL_SetGamepadMapping(controller->id, NULL); + SetGamepadMapping(NULL); + SetCurrentBindingElement(SDL_GAMEPAD_ELEMENT_INVALID); } static void CopyMapping(void) { - if (controller && controller->gamepad) { - char *mapping = SDL_GetGamepadMapping(controller->gamepad); - if (mapping) { - const char *name = SDL_GetGamepadName(controller->gamepad); - char *wildcard = SDL_strchr(mapping, '*'); - if (wildcard && name && *name) { - char *text; - size_t size; + if (controller && controller->mapping) { + char *mapping = controller->mapping; + char *wildcard = SDL_strchr(mapping, '*'); + const char *name = SDL_GetGamepadName(controller->gamepad); + if (wildcard && name && *name) { + char *text; + size_t size; - /* Personalize the mapping for this controller */ - *wildcard++ = '\0'; - size = SDL_strlen(mapping) + SDL_strlen(name) + SDL_strlen(wildcard) + 1; - text = SDL_malloc(size); - if (!text) { - return; - } - SDL_snprintf(text, size, "%s%s%s", mapping, name, wildcard); - SDL_SetClipboardText(text); - SDL_free(text); - } else { - SDL_SetClipboardText(mapping); + /* Personalize the mapping for this controller */ + *wildcard++ = '\0'; + size = SDL_strlen(mapping) + SDL_strlen(name) + SDL_strlen(wildcard) + 1; + text = SDL_malloc(size); + if (!text) { + return; } - SDL_free(mapping); + SDL_snprintf(text, size, "%s%s%s", mapping, name, wildcard); + SDL_SetClipboardText(text); + SDL_free(text); + *wildcard = '*'; + } else { + SDL_SetClipboardText(mapping); } } } @@ -299,10 +466,105 @@ static void PasteMapping(void) { if (controller) { char *mapping = SDL_GetClipboardText(); - if (*mapping) { - SDL_SetGamepadMapping(controller->id, mapping); + if (MappingHasBindings(mapping)) { + CancelBinding(); + SetGamepadMapping(mapping); + } else { + /* Not a valid mapping, ignore it */ + SDL_free(mapping); } - SDL_free(mapping); + } +} + +static const char *GetBindingInstruction(void) +{ + switch (binding_element) { + case SDL_GAMEPAD_ELEMENT_INVALID: + return "Select an element to bind from the list on the left"; + case SDL_GAMEPAD_BUTTON_A: + if (GetGamepadImageFaceStyle(image) == GAMEPAD_IMAGE_FACE_SONY) { + return "Press the Cross (X) button"; + } else { + return "Press the A button"; + } + case SDL_GAMEPAD_BUTTON_B: + if (GetGamepadImageFaceStyle(image) == GAMEPAD_IMAGE_FACE_SONY) { + return "Press the Circle button"; + } else { + return "Press the B button"; + } + case SDL_GAMEPAD_BUTTON_X: + if (GetGamepadImageFaceStyle(image) == GAMEPAD_IMAGE_FACE_SONY) { + return "Press the Square button"; + } else { + return "Press the X button"; + } + case SDL_GAMEPAD_BUTTON_Y: + if (GetGamepadImageFaceStyle(image) == GAMEPAD_IMAGE_FACE_SONY) { + return "Press the Triangle button"; + } else { + return "Press the Y button"; + } + case SDL_GAMEPAD_BUTTON_BACK: + return "Press the left center button (Back/View/Share)"; + case SDL_GAMEPAD_BUTTON_GUIDE: + return "Press the center button (Home/Guide)"; + case SDL_GAMEPAD_BUTTON_START: + return "Press the right center button (Start/Menu/Options)"; + case SDL_GAMEPAD_BUTTON_LEFT_STICK: + return "Press the left thumbstick button (LSB/L3)"; + case SDL_GAMEPAD_BUTTON_RIGHT_STICK: + return "Press the right thumbstick button (RSB/R3)"; + case SDL_GAMEPAD_BUTTON_LEFT_SHOULDER: + return "Press the left shoulder button (LB/L1)"; + case SDL_GAMEPAD_BUTTON_RIGHT_SHOULDER: + return "Press the right shoulder button (RB/R1)"; + case SDL_GAMEPAD_BUTTON_DPAD_UP: + return "Press the D-Pad up"; + case SDL_GAMEPAD_BUTTON_DPAD_DOWN: + return "Press the D-Pad down"; + case SDL_GAMEPAD_BUTTON_DPAD_LEFT: + return "Press the D-Pad left"; + case SDL_GAMEPAD_BUTTON_DPAD_RIGHT: + return "Press the D-Pad right"; + case SDL_GAMEPAD_BUTTON_MISC1: + return "Press the bottom center button (Share/Capture)"; + case SDL_GAMEPAD_BUTTON_PADDLE1: + return "Press the upper paddle under your right hand"; + case SDL_GAMEPAD_BUTTON_PADDLE2: + return "Press the upper paddle under your left hand"; + case SDL_GAMEPAD_BUTTON_PADDLE3: + return "Press the lower paddle under your right hand"; + case SDL_GAMEPAD_BUTTON_PADDLE4: + return "Press the lower paddle under your left hand"; + case SDL_GAMEPAD_BUTTON_TOUCHPAD: + return "Press down on the touchpad"; + case SDL_GAMEPAD_ELEMENT_AXIS_LEFTX_NEGATIVE: + return "Move the left thumbstick to the left"; + case SDL_GAMEPAD_ELEMENT_AXIS_LEFTX_POSITIVE: + return "Move the left thumbstick to the right"; + case SDL_GAMEPAD_ELEMENT_AXIS_LEFTY_NEGATIVE: + return "Move the left thumbstick up"; + case SDL_GAMEPAD_ELEMENT_AXIS_LEFTY_POSITIVE: + return "Move the left thumbstick down"; + case SDL_GAMEPAD_ELEMENT_AXIS_RIGHTX_NEGATIVE: + return "Move the right thumbstick to the left"; + case SDL_GAMEPAD_ELEMENT_AXIS_RIGHTX_POSITIVE: + return "Move the right thumbstick to the right"; + case SDL_GAMEPAD_ELEMENT_AXIS_RIGHTY_NEGATIVE: + return "Move the right thumbstick up"; + case SDL_GAMEPAD_ELEMENT_AXIS_RIGHTY_POSITIVE: + return "Move the right thumbstick down"; + case SDL_GAMEPAD_ELEMENT_AXIS_LEFT_TRIGGER: + return "Pull the left trigger (LT/L2)"; + case SDL_GAMEPAD_ELEMENT_AXIS_RIGHT_TRIGGER: + return "Pull the right trigger (RT/R2)"; + case SDL_GAMEPAD_ELEMENT_NAME: + return "Type the name of your controller"; + case SDL_GAMEPAD_ELEMENT_TYPE: + return "Select the type of your controller"; + default: + return ""; } } @@ -322,11 +584,6 @@ static void SetController(SDL_JoystickID id) { int i = FindController(id); - if (i >= 0 && display_mode == CONTROLLER_MODE_BINDING) { - /* Don't change controllers while binding */ - return; - } - if (i < 0 && num_controllers > 0) { i = 0; } @@ -336,7 +593,6 @@ static void SetController(SDL_JoystickID id) } else { controller = NULL; } - SetDisplayMode(CONTROLLER_MODE_TESTING); } static void AddController(SDL_JoystickID id, SDL_bool verbose) @@ -364,12 +620,19 @@ static void AddController(SDL_JoystickID id, SDL_bool verbose) return; } + controller = NULL; controllers = new_controllers; new_controller = &new_controllers[num_controllers++]; SDL_zerop(new_controller); new_controller->id = id; + new_controller->joystick = SDL_OpenJoystick(id); + new_controller->num_axes = SDL_GetNumJoystickAxes(new_controller->joystick); + new_controller->axis_state = (AxisState *)SDL_calloc(new_controller->num_axes, sizeof(*new_controller->axis_state)); + new_controller->gamepad = SDL_OpenGamepad(id); + new_controller->mapping = SDL_GetGamepadMapping(new_controller->gamepad); + new_controller->has_bindings = MappingHasBindings(new_controller->mapping); if (new_controller->gamepad) { SDL_Gamepad *gamepad = new_controller->gamepad; @@ -413,7 +676,11 @@ static void AddController(SDL_JoystickID id, SDL_bool verbose) } } - SetController(id); + if (mapping_controller) { + SetController(mapping_controller); + } else { + SetController(id); + } } static void DelController(SDL_JoystickID id) @@ -424,14 +691,24 @@ static void DelController(SDL_JoystickID id) return; } + if (display_mode == CONTROLLER_MODE_BINDING && id == controller->id) { + SetDisplayMode(CONTROLLER_MODE_TESTING); + } + /* Reset trigger state */ if (controllers[i].trigger_effect != 0) { controllers[i].trigger_effect = -1; CyclePS5TriggerEffect(&controllers[i]); } + if (controllers[i].mapping) { + SDL_free(controllers[i].mapping); + } if (controllers[i].gamepad) { SDL_CloseGamepad(controllers[i].gamepad); } + if (controllers[i].axis_state) { + SDL_free(controllers[i].axis_state); + } if (controllers[i].joystick) { SDL_CloseJoystick(controllers[i].joystick); } @@ -440,7 +717,12 @@ static void DelController(SDL_JoystickID id) if (i < num_controllers) { SDL_memcpy(&controllers[i], &controllers[i + 1], (num_controllers - i) * sizeof(*controllers)); } - SetController(0); + + if (mapping_controller) { + SetController(mapping_controller); + } else { + SetController(id); + } } static void HandleGamepadRemapped(SDL_JoystickID id) @@ -454,6 +736,10 @@ static void HandleGamepadRemapped(SDL_JoystickID id) if (!controllers[i].gamepad) { controllers[i].gamepad = SDL_OpenGamepad(id); } + if (controllers[i].mapping) { + SDL_free(controllers[i].mapping); + } + controllers[i].mapping = SDL_GetGamepadMapping(controllers[i].gamepad); } static Uint16 ConvertAxisToRumble(Sint16 axisval) @@ -472,13 +758,12 @@ static SDL_bool ShowingFront(void) SDL_bool showing_front = SDL_TRUE; int i; - if (controller->gamepad) { - /* Show the back of the gamepad if the paddles are being held */ - for (i = SDL_GAMEPAD_BUTTON_PADDLE1; i <= SDL_GAMEPAD_BUTTON_PADDLE4; ++i) { - if (SDL_GetGamepadButton(controller->gamepad, (SDL_GamepadButton)i) == SDL_PRESSED) { - showing_front = SDL_FALSE; - break; - } + /* Show the back of the gamepad if the paddles are being held or bound */ + for (i = SDL_GAMEPAD_BUTTON_PADDLE1; i <= SDL_GAMEPAD_BUTTON_PADDLE4; ++i) { + if (SDL_GetGamepadButton(controller->gamepad, (SDL_GamepadButton)i) == SDL_PRESSED || + binding_element == i) { + showing_front = SDL_FALSE; + break; } } if (SDL_GetModState() & SDL_KMOD_SHIFT) { @@ -560,16 +845,6 @@ static void CloseVirtualGamepad(void) } } -static SDL_GamepadButton FindButtonAtPosition(float x, float y) -{ - return GetGamepadImageButtonAt(image, x, y); -} - -static SDL_GamepadAxis FindAxisAtPosition(float x, float y) -{ - return GetGamepadImageAxisAt(image, x, y); -} - static void VirtualGamepadMouseMotion(float x, float y) { if (virtual_button_active != SDL_GAMEPAD_BUTTON_INVALID) { @@ -613,18 +888,36 @@ static void VirtualGamepadMouseMotion(float x, float y) static void VirtualGamepadMouseDown(float x, float y) { - SDL_GamepadButton button; - SDL_GamepadAxis axis; + int element = GetGamepadImageElementAt(image, x, y); - button = FindButtonAtPosition(x, y); - if (button != SDL_GAMEPAD_BUTTON_INVALID) { - virtual_button_active = button; - SDL_SetJoystickVirtualButton(virtual_joystick, virtual_button_active, SDL_PRESSED); + if (element == SDL_GAMEPAD_ELEMENT_INVALID) { + return; } - axis = FindAxisAtPosition(x, y); - if (axis != SDL_GAMEPAD_AXIS_INVALID) { - virtual_axis_active = axis; + if (element < SDL_GAMEPAD_BUTTON_MAX) { + virtual_button_active = (SDL_GamepadButton)element; + SDL_SetJoystickVirtualButton(virtual_joystick, virtual_button_active, SDL_PRESSED); + } else { + switch (element) { + case SDL_GAMEPAD_ELEMENT_AXIS_LEFTX_NEGATIVE: + case SDL_GAMEPAD_ELEMENT_AXIS_LEFTX_POSITIVE: + case SDL_GAMEPAD_ELEMENT_AXIS_LEFTY_NEGATIVE: + case SDL_GAMEPAD_ELEMENT_AXIS_LEFTY_POSITIVE: + virtual_axis_active = SDL_GAMEPAD_AXIS_LEFTX; + break; + case SDL_GAMEPAD_ELEMENT_AXIS_RIGHTX_NEGATIVE: + case SDL_GAMEPAD_ELEMENT_AXIS_RIGHTX_POSITIVE: + case SDL_GAMEPAD_ELEMENT_AXIS_RIGHTY_NEGATIVE: + case SDL_GAMEPAD_ELEMENT_AXIS_RIGHTY_POSITIVE: + virtual_axis_active = SDL_GAMEPAD_AXIS_RIGHTX; + break; + case SDL_GAMEPAD_ELEMENT_AXIS_LEFT_TRIGGER: + virtual_axis_active = SDL_GAMEPAD_AXIS_LEFT_TRIGGER; + break; + case SDL_GAMEPAD_ELEMENT_AXIS_RIGHT_TRIGGER: + virtual_axis_active = SDL_GAMEPAD_AXIS_RIGHT_TRIGGER; + break; + } virtual_axis_start_x = x; virtual_axis_start_y = y; } @@ -702,6 +995,45 @@ static void DrawGamepadInfo(SDL_Renderer *renderer) } } +static void DrawBindingTips(SDL_Renderer *renderer) +{ + const char *text; + SDL_Rect image_area, button_area; + int x, y; + + GetGamepadImageArea(image, &image_area); + GetGamepadButtonArea(done_mapping_button, &button_area); + x = image_area.x + image_area.w / 2; + y = image_area.y + image_area.h; + y += (button_area.y - y - FONT_CHARACTER_SIZE) / 2; + + text = GetBindingInstruction(); + + if (binding_element == SDL_GAMEPAD_ELEMENT_INVALID) { + SDLTest_DrawString(renderer, (float)x - (FONT_CHARACTER_SIZE * SDL_strlen(text)) / 2, (float)y, text); + } else { + Uint8 r, g, b, a; + SDL_FRect rect; + + y -= (FONT_CHARACTER_SIZE + BUTTON_MARGIN) / 2; + + rect.w = 2.0f + (FONT_CHARACTER_SIZE * SDL_strlen(text)) + 2.0f; + rect.h = 2.0f + FONT_CHARACTER_SIZE + 2.0f; + rect.x = (float)x - rect.w / 2; + rect.y = (float)y - 2.0f; + + SDL_GetRenderDrawColor(renderer, &r, &g, &b, &a); + SDL_SetRenderDrawColor(renderer, SELECTED_COLOR); + SDL_RenderFillRect(renderer, &rect); + SDL_SetRenderDrawColor(renderer, r, g, b, a); + SDLTest_DrawString(renderer, (float)x - (FONT_CHARACTER_SIZE * SDL_strlen(text)) / 2, (float)y, text); + + y += (FONT_CHARACTER_SIZE + BUTTON_MARGIN); + text = "(press SPACE to clear binding and ESC to cancel)"; + SDLTest_DrawString(renderer, (float)x - (FONT_CHARACTER_SIZE * SDL_strlen(text)) / 2, (float)y, text); + } +} + static void UpdateGamepadEffects(void) { if (display_mode != CONTROLLER_MODE_TESTING || !controller->gamepad) { @@ -779,13 +1111,93 @@ static void loop(void *arg) break; case SDL_EVENT_JOYSTICK_AXIS_MOTION: - if (event.jaxis.value <= (-SDL_JOYSTICK_AXIS_MAX / 2) || event.jaxis.value >= (SDL_JOYSTICK_AXIS_MAX / 2)) { - SetController(event.jaxis.which); + if (display_mode == CONTROLLER_MODE_TESTING) { + if (event.jaxis.value <= (-SDL_JOYSTICK_AXIS_MAX / 2) || event.jaxis.value >= (SDL_JOYSTICK_AXIS_MAX / 2)) { + SetController(event.jaxis.which); + } + } else if (display_mode == CONTROLLER_MODE_BINDING && + event.jaxis.which == controller->id && + event.jaxis.axis < controller->num_axes && + binding_element != SDL_GAMEPAD_ELEMENT_INVALID) { + const int MAX_ALLOWED_JITTER = SDL_JOYSTICK_AXIS_MAX / 80; /* ShanWan PS3 gamepad needed 96 */ + AxisState *pAxisState = &controller->axis_state[event.jaxis.axis]; + int nValue = event.jaxis.value; + int nCurrentDistance, nFarthestDistance; + if (!pAxisState->m_bMoving) { + Sint16 nInitialValue; + pAxisState->m_bMoving = SDL_GetJoystickAxisInitialState(controller->joystick, event.jaxis.axis, &nInitialValue); + pAxisState->m_nLastValue = nValue; + pAxisState->m_nStartingValue = nInitialValue; + pAxisState->m_nFarthestValue = nInitialValue; + } else if (SDL_abs(nValue - pAxisState->m_nLastValue) <= MAX_ALLOWED_JITTER) { + break; + } else { + pAxisState->m_nLastValue = nValue; + } + nCurrentDistance = SDL_abs(nValue - pAxisState->m_nStartingValue); + nFarthestDistance = SDL_abs(pAxisState->m_nFarthestValue - pAxisState->m_nStartingValue); + if (nCurrentDistance > nFarthestDistance) { + pAxisState->m_nFarthestValue = nValue; + nFarthestDistance = SDL_abs(pAxisState->m_nFarthestValue - pAxisState->m_nStartingValue); + } + +#ifdef DEBUG_AXIS_MAPPING + SDL_Log("AXIS %d nValue %d nCurrentDistance %d nFarthestDistance %d\n", event.jaxis.axis, nValue, nCurrentDistance, nFarthestDistance); +#endif + /* If we've gone out far enough and started to come back, let's bind this axis */ + if (nFarthestDistance >= 16000 && nCurrentDistance <= 10000) { + char binding[12]; + int axis_min = StandardizeAxisValue(pAxisState->m_nStartingValue); + int axis_max = StandardizeAxisValue(pAxisState->m_nFarthestValue); + + if (axis_min == 0 && axis_max == SDL_JOYSTICK_AXIS_MIN) { + /* The negative half axis */ + (void)SDL_snprintf(binding, sizeof(binding), "-a%d", event.jaxis.axis); + } else if (axis_min == 0 && axis_max == SDL_JOYSTICK_AXIS_MAX) { + /* The positive half axis */ + (void)SDL_snprintf(binding, sizeof(binding), "+a%d", event.jaxis.axis); + } else { + (void)SDL_snprintf(binding, sizeof(binding), "a%d", event.jaxis.axis); + if (axis_min > axis_max) { + /* Invert the axis */ + SDL_strlcat(binding, "~", SDL_arraysize(binding)); + } + } +#ifdef DEBUG_AXIS_MAPPING + SDL_Log("AXIS %d axis_min = %d, axis_max = %d, binding = %s\n", event.jaxis.axis, axis_min, axis_max, binding); +#endif + CommitBindingElement(binding, SDL_FALSE); + } } break; case SDL_EVENT_JOYSTICK_BUTTON_DOWN: - SetController(event.jbutton.which); + if (display_mode == CONTROLLER_MODE_TESTING) { + SetController(event.jbutton.which); + } + break; + + case SDL_EVENT_JOYSTICK_BUTTON_UP: + if (display_mode == CONTROLLER_MODE_BINDING && + event.jbutton.which == controller->id && + binding_element != SDL_GAMEPAD_ELEMENT_INVALID) { + char binding[12]; + + SDL_snprintf(binding, sizeof(binding), "b%d", event.jbutton.button); + CommitBindingElement(binding, SDL_FALSE); + } + break; + + case SDL_EVENT_JOYSTICK_HAT_MOTION: + if (display_mode == CONTROLLER_MODE_BINDING && + event.jhat.which == controller->id && + event.jhat.value != SDL_HAT_CENTERED && + binding_element != SDL_GAMEPAD_ELEMENT_INVALID) { + char binding[12]; + + SDL_snprintf(binding, sizeof(binding), "h%d.%d", event.jhat.hat, event.jhat.value); + CommitBindingElement(binding, SDL_FALSE); + } break; case SDL_EVENT_GAMEPAD_REMAPPED: @@ -821,8 +1233,10 @@ static void loop(void *arg) #define VERBOSE_AXES #ifdef VERBOSE_AXES case SDL_EVENT_GAMEPAD_AXIS_MOTION: - if (event.gaxis.value <= (-SDL_JOYSTICK_AXIS_MAX / 2) || event.gaxis.value >= (SDL_JOYSTICK_AXIS_MAX / 2)) { - SetController(event.gaxis.which); + if (display_mode == CONTROLLER_MODE_TESTING) { + if (event.gaxis.value <= (-SDL_JOYSTICK_AXIS_MAX / 2) || event.gaxis.value >= (SDL_JOYSTICK_AXIS_MAX / 2)) { + SetController(event.gaxis.which); + } } SDL_Log("Gamepad %" SDL_PRIu32 " axis %s changed to %d\n", event.gaxis.which, @@ -833,8 +1247,10 @@ static void loop(void *arg) case SDL_EVENT_GAMEPAD_BUTTON_DOWN: case SDL_EVENT_GAMEPAD_BUTTON_UP: - if (event.type == SDL_EVENT_GAMEPAD_BUTTON_DOWN) { - SetController(event.gbutton.which); + if (display_mode == CONTROLLER_MODE_TESTING) { + if (event.type == SDL_EVENT_GAMEPAD_BUTTON_DOWN) { + SetController(event.gbutton.which); + } } SDL_Log("Gamepad %" SDL_PRIu32 " button %s %s\n", event.gbutton.which, @@ -856,43 +1272,61 @@ static void loop(void *arg) break; case SDL_EVENT_MOUSE_BUTTON_DOWN: - if (virtual_joystick && controller->joystick == virtual_joystick) { + if (virtual_joystick && controller && controller->joystick == virtual_joystick) { VirtualGamepadMouseDown(event.button.x, event.button.y); } - UpdateButtonHighlights(event.button.x, event.button.y); + UpdateButtonHighlights(event.button.x, event.button.y, event.button.state ? SDL_TRUE : SDL_FALSE); break; case SDL_EVENT_MOUSE_BUTTON_UP: - if (virtual_joystick && controller->joystick == virtual_joystick) { + if (virtual_joystick && controller && controller->joystick == virtual_joystick) { VirtualGamepadMouseUp(event.button.x, event.button.y); } - ClearButtonHighlights(); - if (display_mode == CONTROLLER_MODE_TESTING) { if (GamepadButtonContains(setup_mapping_button, event.button.x, event.button.y)) { SetDisplayMode(CONTROLLER_MODE_BINDING); } } else if (display_mode == CONTROLLER_MODE_BINDING) { - if (GamepadButtonContains(test_mapping_button, event.button.x, event.button.y)) { + if (GamepadButtonContains(done_mapping_button, event.button.x, event.button.y)) { + if (controller->mapping) { + SDL_Log("Mapping complete:\n"); + SDL_Log("%s\n", controller->mapping); + } SetDisplayMode(CONTROLLER_MODE_TESTING); } else if (GamepadButtonContains(cancel_button, event.button.x, event.button.y)) { CancelMapping(); } else if (GamepadButtonContains(clear_button, event.button.x, event.button.y)) { ClearMapping(); - } else if (GamepadButtonContains(copy_button, event.button.x, event.button.y)) { + } else if (controller->has_bindings && + GamepadButtonContains(copy_button, event.button.x, event.button.y)) { CopyMapping(); } else if (GamepadButtonContains(paste_button, event.button.x, event.button.y)) { PasteMapping(); + } else { + int gamepad_element; + char *joystick_element; + + gamepad_element = GetGamepadDisplayElementAt(gamepad_elements, controller->gamepad, event.button.x, event.button.y); + if (gamepad_element != SDL_GAMEPAD_ELEMENT_INVALID) { + SetCurrentBindingElement(gamepad_element); + } + + joystick_element = GetJoystickDisplayElementAt(joystick_elements, controller->joystick, event.button.x, event.button.y); + if (joystick_element) { + CommitBindingElement(joystick_element, SDL_TRUE); + SDL_free(joystick_element); + } } } + UpdateButtonHighlights(event.button.x, event.button.y, event.button.state ? SDL_TRUE : SDL_FALSE); break; case SDL_EVENT_MOUSE_MOTION: - if (virtual_joystick && controller->joystick == virtual_joystick) { + if (virtual_joystick && controller && controller->joystick == virtual_joystick) { VirtualGamepadMouseMotion(event.motion.x, event.motion.y); } - UpdateButtonHighlights(event.motion.x, event.motion.y); + UpdateButtonHighlights(event.motion.x, event.motion.y, event.motion.state ? SDL_TRUE : SDL_FALSE); break; case SDL_EVENT_KEY_DOWN: @@ -919,8 +1353,14 @@ static void loop(void *arg) } else if (event.key.keysym.sym == SDLK_x && (event.key.keysym.mod & SDL_KMOD_CTRL)) { CopyMapping(); ClearMapping(); + } else if (event.key.keysym.sym == SDLK_SPACE) { + ClearBinding(); } else if (event.key.keysym.sym == SDLK_ESCAPE) { - CancelMapping(); + if (binding_element != SDL_GAMEPAD_ELEMENT_INVALID) { + CancelBinding(); + } else { + CancelMapping(); + } } } break; @@ -932,6 +1372,13 @@ static void loop(void *arg) } } + /* Wait 30 ms for joystick events to stop coming in, + in case a gamepad sends multiple events for a single control (e.g. axis and button for trigger) + */ + if (binding_advance_time && SDL_GetTicks() > (binding_advance_time + 30)) { + SetNextBindingElement(); + } + /* blank screen, set up for drawing this frame. */ SDL_SetRenderDrawColor(screen, 0xFF, 0xFF, 0xFF, SDL_ALPHA_OPAQUE); SDL_RenderClear(screen); @@ -940,6 +1387,10 @@ static void loop(void *arg) if (controller) { SetGamepadImageShowingFront(image, ShowingFront()); UpdateGamepadImageFromGamepad(image, controller->gamepad); + if (display_mode == CONTROLLER_MODE_BINDING && + binding_element != SDL_GAMEPAD_ELEMENT_INVALID) { + SetGamepadImageElement(image, binding_element, SDL_TRUE); + } RenderGamepadImage(image); RenderGamepadDisplay(gamepad_elements, controller->gamepad); @@ -948,10 +1399,11 @@ static void loop(void *arg) if (display_mode == CONTROLLER_MODE_TESTING) { RenderGamepadButton(setup_mapping_button); } else if (display_mode == CONTROLLER_MODE_BINDING) { - RenderGamepadButton(test_mapping_button); + DrawBindingTips(screen); + RenderGamepadButton(done_mapping_button); RenderGamepadButton(cancel_button); RenderGamepadButton(clear_button); - if (controller->gamepad) { + if (controller->has_bindings) { RenderGamepadButton(copy_button); } RenderGamepadButton(paste_button); @@ -1134,12 +1586,12 @@ int main(int argc, char *argv[]) area.y = SCREEN_HEIGHT - BUTTON_MARGIN - area.h; SetGamepadButtonArea(paste_button, &area); - test_mapping_button = CreateGamepadButton(screen, "Done"); - area.w = SDL_max(MINIMUM_BUTTON_WIDTH, GetGamepadButtonLabelWidth(test_mapping_button) + 2 * BUTTON_PADDING); - area.h = GetGamepadButtonLabelHeight(test_mapping_button) + 2 * BUTTON_PADDING; + done_mapping_button = CreateGamepadButton(screen, "Done"); + area.w = SDL_max(MINIMUM_BUTTON_WIDTH, GetGamepadButtonLabelWidth(done_mapping_button) + 2 * BUTTON_PADDING); + area.h = GetGamepadButtonLabelHeight(done_mapping_button) + 2 * BUTTON_PADDING; area.x = SCREEN_WIDTH / 2 - area.w / 2; area.y = SCREEN_HEIGHT - BUTTON_MARGIN - area.h; - SetGamepadButtonArea(test_mapping_button, &area); + SetGamepadButtonArea(done_mapping_button, &area); /* Process the initial gamepad list */ loop(NULL);