diff --git a/test/gamepadutils.c b/test/gamepadutils.c index d6059abdd..aecd2a6c5 100644 --- a/test/gamepadutils.c +++ b/test/gamepadutils.c @@ -114,9 +114,9 @@ struct GamepadImage int x; int y; SDL_bool showing_front; - SDL_bool showing_battery; SDL_bool showing_touchpad; GamepadImageFaceStyle face_style; + ControllerDisplayMode display_mode; SDL_bool buttons[SDL_GAMEPAD_BUTTON_MAX]; int axes[SDL_GAMEPAD_AXIS_MAX]; @@ -231,57 +231,13 @@ void SetGamepadImageFaceStyle(GamepadImage *ctx, GamepadImageFaceStyle face_styl ctx->face_style = face_style; } -void SetGamepadImageShowingBattery(GamepadImage *ctx, SDL_bool showing_battery) +void SetGamepadImageDisplayMode(GamepadImage *ctx, ControllerDisplayMode display_mode) { if (!ctx) { return; } - ctx->showing_battery = showing_battery; -} - -void SetGamepadImageShowingTouchpad(GamepadImage *ctx, SDL_bool showing_touchpad) -{ - if (!ctx) { - return; - } - - ctx->showing_touchpad = showing_touchpad; -} - -void GetGamepadImageArea(GamepadImage *ctx, int *x, int *y, int *width, int *height) -{ - if (!ctx) { - if (x) { - *x = 0; - } - if (y) { - *y = 0; - } - if (width) { - *width = 0; - } - if (height) { - *height = 0; - } - return; - } - - if (x) { - *x = ctx->x; - } - if (y) { - *y = ctx->y; - } - if (width) { - *width = ctx->gamepad_width; - } - if (height) { - *height = ctx->gamepad_height; - if (ctx->showing_touchpad) { - *height += ctx->touchpad_height; - } - } + ctx->display_mode = display_mode; } int GetGamepadImageButtonWidth(GamepadImage *ctx) @@ -482,12 +438,14 @@ void UpdateGamepadImageFromGamepad(GamepadImage *ctx, SDL_Gamepad *gamepad) SDL_GetGamepadTouchpadFinger(gamepad, 0, i, &finger->state, &finger->x, &finger->y, &finger->pressure); } + ctx->showing_touchpad = SDL_TRUE; } else { if (ctx->fingers) { SDL_free(ctx->fingers); ctx->fingers = NULL; ctx->num_fingers = 0; } + ctx->showing_touchpad = SDL_FALSE; } } @@ -568,7 +526,7 @@ void RenderGamepadImage(GamepadImage *ctx) } } - if (ctx->showing_battery) { + if (ctx->display_mode == CONTROLLER_MODE_TESTING && ctx->battery_level != SDL_JOYSTICK_POWER_UNKNOWN) { dst.x = (float)ctx->x + ctx->gamepad_width - ctx->battery_width; dst.y = (float)ctx->y; dst.w = (float)ctx->battery_width; @@ -576,7 +534,7 @@ void RenderGamepadImage(GamepadImage *ctx) SDL_RenderTexture(ctx->renderer, ctx->battery_texture[1 + ctx->battery_level], NULL, &dst); } - if (ctx->showing_touchpad) { + if (ctx->display_mode == CONTROLLER_MODE_TESTING && ctx->showing_touchpad) { dst.x = (float)ctx->x + (ctx->gamepad_width - ctx->touchpad_width) / 2; dst.y = (float)ctx->y + ctx->gamepad_height; dst.w = (float)ctx->touchpad_width; @@ -673,6 +631,8 @@ struct GamepadDisplay float gyro_data[3]; Uint64 last_sensor_update; + ControllerDisplayMode display_mode; + SDL_Rect area; }; @@ -691,16 +651,22 @@ GamepadDisplay *CreateGamepadDisplay(SDL_Renderer *renderer) return ctx; } -void SetGamepadDisplayArea(GamepadDisplay *ctx, int x, int y, int w, int h) +void SetGamepadDisplayDisplayMode(GamepadDisplay *ctx, ControllerDisplayMode display_mode) { if (!ctx) { return; } - ctx->area.x = x; - ctx->area.y = y; - ctx->area.w = w; - ctx->area.h = h; + ctx->display_mode = display_mode; +} + +void SetGamepadDisplayArea(GamepadDisplay *ctx, const SDL_Rect *area) +{ + if (!ctx) { + return; + } + + SDL_copyp(&ctx->area, area); } static SDL_bool GetBindingString(const char *label, char *mapping, char *text, size_t size) @@ -794,7 +760,7 @@ void RenderGamepadDisplay(GamepadDisplay *ctx, SDL_Gamepad *gamepad) const float arrow_extent = 48.0f; SDL_FRect dst, rect; Uint8 r, g, b, a; - char *mapping; + char *mapping = NULL; SDL_bool has_accel; SDL_bool has_gyro; @@ -812,6 +778,11 @@ void RenderGamepadDisplay(GamepadDisplay *ctx, SDL_Gamepad *gamepad) 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; + } + SDL_snprintf(text, sizeof(text), "%s:", gamepad_button_names[i]); SDLTest_DrawString(ctx->renderer, x + center - SDL_strlen(text) * FONT_CHARACTER_SIZE, y, text); @@ -827,9 +798,11 @@ void RenderGamepadDisplay(GamepadDisplay *ctx, SDL_Gamepad *gamepad) dst.h = (float)ctx->button_height; SDL_RenderTexture(ctx->renderer, ctx->button_texture, NULL, &dst); - if (GetButtonBindingString(button, mapping, binding, sizeof(binding))) { - dst.x += dst.w + 2 * margin; - SDLTest_DrawString(ctx->renderer, dst.x, y, binding); + if (ctx->display_mode == CONTROLLER_MODE_BINDING) { + if (GetButtonBindingString(button, mapping, binding, sizeof(binding))) { + dst.x += dst.w + 2 * margin; + SDLTest_DrawString(ctx->renderer, dst.x, y, binding); + } } y += ctx->button_height + 2.0f; @@ -838,7 +811,14 @@ void RenderGamepadDisplay(GamepadDisplay *ctx, SDL_Gamepad *gamepad) for (i = 0; i < SDL_GAMEPAD_AXIS_MAX; ++i) { SDL_GamepadAxis axis = (SDL_GamepadAxis)i; SDL_bool has_negative = (axis != SDL_GAMEPAD_AXIS_LEFT_TRIGGER && axis != SDL_GAMEPAD_AXIS_RIGHT_TRIGGER); - Sint16 value = SDL_GetGamepadAxis(gamepad, axis); + Sint16 value; + + if (ctx->display_mode == CONTROLLER_MODE_TESTING && + !SDL_GamepadHasAxis(gamepad, axis)) { + continue; + } + + value = SDL_GetGamepadAxis(gamepad, axis); SDL_snprintf(text, sizeof(text), "%s:", gamepad_axis_names[i]); SDLTest_DrawString(ctx->renderer, x + center - SDL_strlen(text) * FONT_CHARACTER_SIZE, y, text); @@ -875,12 +855,14 @@ void RenderGamepadDisplay(GamepadDisplay *ctx, SDL_Gamepad *gamepad) SDL_RenderFillRect(ctx->renderer, &rect); } - if (has_negative && GetAxisBindingString(axis, -1, mapping, binding, sizeof(binding))) { - float text_x; + if (ctx->display_mode == CONTROLLER_MODE_BINDING && has_negative) { + if (GetAxisBindingString(axis, -1, mapping, binding, sizeof(binding))) { + float text_x; - SDL_SetRenderDrawColor(ctx->renderer, r, g, b, a); - text_x = dst.x + arrow_extent / 2 - ((float)FONT_CHARACTER_SIZE * SDL_strlen(binding)) / 2; - SDLTest_DrawString(ctx->renderer, text_x, y, binding); + SDL_SetRenderDrawColor(ctx->renderer, r, g, b, a); + text_x = dst.x + arrow_extent / 2 - ((float)FONT_CHARACTER_SIZE * SDL_strlen(binding)) / 2; + SDLTest_DrawString(ctx->renderer, text_x, y, binding); + } } dst.x += arrow_extent; @@ -894,12 +876,14 @@ void RenderGamepadDisplay(GamepadDisplay *ctx, SDL_Gamepad *gamepad) SDL_RenderFillRect(ctx->renderer, &rect); } - if (GetAxisBindingString(axis, 1, mapping, binding, sizeof(binding))) { - float text_x; + if (ctx->display_mode == CONTROLLER_MODE_BINDING) { + if (GetAxisBindingString(axis, 1, mapping, binding, sizeof(binding))) { + float text_x; - SDL_SetRenderDrawColor(ctx->renderer, r, g, b, a); - text_x = dst.x + arrow_extent / 2 - ((float)FONT_CHARACTER_SIZE * SDL_strlen(binding)) / 2; - SDLTest_DrawString(ctx->renderer, text_x, y, binding); + SDL_SetRenderDrawColor(ctx->renderer, r, g, b, a); + text_x = dst.x + arrow_extent / 2 - ((float)FONT_CHARACTER_SIZE * SDL_strlen(binding)) / 2; + SDLTest_DrawString(ctx->renderer, text_x, y, binding); + } } dst.x += arrow_extent; @@ -916,72 +900,74 @@ void RenderGamepadDisplay(GamepadDisplay *ctx, SDL_Gamepad *gamepad) y += ctx->button_height + 2; } - if (SDL_GetNumGamepadTouchpads(gamepad) > 0) { - int num_fingers = SDL_GetNumGamepadTouchpadFingers(gamepad, 0); - for (i = 0; i < num_fingers; ++i) { - Uint8 state; - float finger_x, finger_y, finger_pressure; + if (ctx->display_mode == CONTROLLER_MODE_TESTING) { + if (SDL_GetNumGamepadTouchpads(gamepad) > 0) { + int num_fingers = SDL_GetNumGamepadTouchpadFingers(gamepad, 0); + for (i = 0; i < num_fingers; ++i) { + Uint8 state; + float finger_x, finger_y, finger_pressure; - if (SDL_GetGamepadTouchpadFinger(gamepad, 0, i, &state, &finger_x, &finger_y, &finger_pressure) < 0) { - continue; + if (SDL_GetGamepadTouchpadFinger(gamepad, 0, i, &state, &finger_x, &finger_y, &finger_pressure) < 0) { + continue; + } + + SDL_snprintf(text, sizeof(text), "Touch finger %d:", i); + SDLTest_DrawString(ctx->renderer, x + center - SDL_strlen(text) * FONT_CHARACTER_SIZE, y, text); + + if (state) { + SDL_SetTextureColorMod(ctx->button_texture, 10, 255, 21); + } else { + SDL_SetTextureColorMod(ctx->button_texture, 255, 255, 255); + } + + dst.x = x + center + 2.0f; + dst.y = y + FONT_CHARACTER_SIZE / 2 - ctx->button_height / 2; + dst.w = (float)ctx->button_width; + dst.h = (float)ctx->button_height; + SDL_RenderTexture(ctx->renderer, ctx->button_texture, NULL, &dst); + + if (state) { + SDL_snprintf(text, sizeof(text), "(%.2f,%.2f)", finger_x, finger_y); + SDLTest_DrawString(ctx->renderer, x + center + ctx->button_width + 4.0f, y, text); + } + + y += ctx->button_height + 2.0f; } - - SDL_snprintf(text, sizeof(text), "Touch finger %d:", i); - SDLTest_DrawString(ctx->renderer, x + center - SDL_strlen(text) * FONT_CHARACTER_SIZE, y, text); - - if (state) { - SDL_SetTextureColorMod(ctx->button_texture, 10, 255, 21); - } else { - SDL_SetTextureColorMod(ctx->button_texture, 255, 255, 255); - } - - dst.x = x + center + 2.0f; - dst.y = y + FONT_CHARACTER_SIZE / 2 - ctx->button_height / 2; - dst.w = (float)ctx->button_width; - dst.h = (float)ctx->button_height; - SDL_RenderTexture(ctx->renderer, ctx->button_texture, NULL, &dst); - - if (state) { - SDL_snprintf(text, sizeof(text), "(%.2f,%.2f)", finger_x, finger_y); - SDLTest_DrawString(ctx->renderer, x + center + ctx->button_width + 4.0f, y, text); - } - - y += ctx->button_height + 2.0f; } - } - has_accel = SDL_GamepadHasSensor(gamepad, SDL_SENSOR_ACCEL); - has_gyro = SDL_GamepadHasSensor(gamepad, SDL_SENSOR_GYRO); - if (has_accel || has_gyro) { - const int SENSOR_UPDATE_INTERVAL_MS = 100; - Uint64 now = SDL_GetTicks(); + has_accel = SDL_GamepadHasSensor(gamepad, SDL_SENSOR_ACCEL); + has_gyro = SDL_GamepadHasSensor(gamepad, SDL_SENSOR_GYRO); + if (has_accel || has_gyro) { + const int SENSOR_UPDATE_INTERVAL_MS = 100; + Uint64 now = SDL_GetTicks(); + + if (now >= ctx->last_sensor_update + SENSOR_UPDATE_INTERVAL_MS) { + if (has_accel) { + SDL_GetGamepadSensorData(gamepad, SDL_SENSOR_ACCEL, ctx->accel_data, SDL_arraysize(ctx->accel_data)); + } + if (has_gyro) { + SDL_GetGamepadSensorData(gamepad, SDL_SENSOR_GYRO, ctx->gyro_data, SDL_arraysize(ctx->gyro_data)); + } + ctx->last_sensor_update = now; + } - if (now >= ctx->last_sensor_update + SENSOR_UPDATE_INTERVAL_MS) { if (has_accel) { - SDL_GetGamepadSensorData(gamepad, SDL_SENSOR_ACCEL, ctx->accel_data, SDL_arraysize(ctx->accel_data)); + SDL_strlcpy(text, "Accelerometer:", sizeof(text)); + SDLTest_DrawString(ctx->renderer, x + center - SDL_strlen(text) * FONT_CHARACTER_SIZE, y, text); + SDL_snprintf(text, sizeof(text), "(%.2f,%.2f,%.2f)", ctx->accel_data[0], ctx->accel_data[1], ctx->accel_data[2]); + SDLTest_DrawString(ctx->renderer, x + center + 2.0f, y, text); + + y += ctx->button_height + 2.0f; } + if (has_gyro) { - SDL_GetGamepadSensorData(gamepad, SDL_SENSOR_GYRO, ctx->gyro_data, SDL_arraysize(ctx->gyro_data)); + SDL_strlcpy(text, "Gyro:", sizeof(text)); + SDLTest_DrawString(ctx->renderer, x + center - SDL_strlen(text) * FONT_CHARACTER_SIZE, y, text); + SDL_snprintf(text, sizeof(text), "(%.2f,%.2f,%.2f)", ctx->gyro_data[0], ctx->gyro_data[1], ctx->gyro_data[2]); + SDLTest_DrawString(ctx->renderer, x + center + 2.0f, y, text); + + y += ctx->button_height + 2.0f; } - ctx->last_sensor_update = now; - } - - if (has_accel) { - SDL_strlcpy(text, "Accelerometer:", sizeof(text)); - SDLTest_DrawString(ctx->renderer, x + center - SDL_strlen(text) * FONT_CHARACTER_SIZE, y, text); - SDL_snprintf(text, sizeof(text), "(%.2f,%.2f,%.2f)", ctx->accel_data[0], ctx->accel_data[1], ctx->accel_data[2]); - SDLTest_DrawString(ctx->renderer, x + center + 2.0f, y, text); - - y += ctx->button_height + 2.0f; - } - - if (has_gyro) { - SDL_strlcpy(text, "Gyro:", sizeof(text)); - SDLTest_DrawString(ctx->renderer, x + center - SDL_strlen(text) * FONT_CHARACTER_SIZE, y, text); - SDL_snprintf(text, sizeof(text), "(%.2f,%.2f,%.2f)", ctx->gyro_data[0], ctx->gyro_data[1], ctx->gyro_data[2]); - SDLTest_DrawString(ctx->renderer, x + center + 2.0f, y, text); - - y += ctx->button_height + 2.0f; } } @@ -1028,16 +1014,13 @@ JoystickDisplay *CreateJoystickDisplay(SDL_Renderer *renderer) return ctx; } -void SetJoystickDisplayArea(JoystickDisplay *ctx, int x, int y, int w, int h) +void SetJoystickDisplayArea(JoystickDisplay *ctx, const SDL_Rect *area) { if (!ctx) { return; } - ctx->area.x = x; - ctx->area.y = y; - ctx->area.w = w; - ctx->area.h = h; + SDL_copyp(&ctx->area, area); } void RenderJoystickDisplay(JoystickDisplay *ctx, SDL_Joystick *joystick) @@ -1259,16 +1242,16 @@ GamepadButton *CreateGamepadButton(SDL_Renderer *renderer, const char *label) return ctx; } -void SetGamepadButtonArea(GamepadButton *ctx, int x, int y, int w, int h) +void SetGamepadButtonArea(GamepadButton *ctx, const SDL_Rect *area) { if (!ctx) { return; } - ctx->area.x = (float)x; - ctx->area.y = (float)y; - ctx->area.w = (float)w; - ctx->area.h = (float)h; + ctx->area.x = (float)area->x; + ctx->area.y = (float)area->y; + ctx->area.w = (float)area->w; + ctx->area.h = (float)area->h; } void SetGamepadButtonHighlight(GamepadButton *ctx, SDL_bool highlight) diff --git a/test/gamepadutils.h b/test/gamepadutils.h index f9443bb63..523e0b28c 100644 --- a/test/gamepadutils.h +++ b/test/gamepadutils.h @@ -14,6 +14,12 @@ typedef struct GamepadImage GamepadImage; +typedef enum +{ + CONTROLLER_MODE_TESTING, + CONTROLLER_MODE_BINDING, +} ControllerDisplayMode; + typedef enum { GAMEPAD_IMAGE_FACE_BLANK, @@ -26,9 +32,7 @@ extern GamepadImage *CreateGamepadImage(SDL_Renderer *renderer); extern void SetGamepadImagePosition(GamepadImage *ctx, int x, int y); extern void SetGamepadImageShowingFront(GamepadImage *ctx, SDL_bool showing_front); extern void SetGamepadImageFaceStyle(GamepadImage *ctx, GamepadImageFaceStyle face_style); -extern void SetGamepadImageShowingBattery(GamepadImage *ctx, SDL_bool showing_battery); -extern void SetGamepadImageShowingTouchpad(GamepadImage *ctx, SDL_bool showing_touchpad); -extern void GetGamepadImageArea(GamepadImage *ctx, int *x, int *y, int *width, int *height); +extern void SetGamepadImageDisplayMode(GamepadImage *ctx, ControllerDisplayMode display_mode); extern int GetGamepadImageButtonWidth(GamepadImage *ctx); extern int GetGamepadImageButtonHeight(GamepadImage *ctx); extern int GetGamepadImageAxisWidth(GamepadImage *ctx); @@ -50,7 +54,8 @@ extern void DestroyGamepadImage(GamepadImage *ctx); typedef struct GamepadDisplay GamepadDisplay; extern GamepadDisplay *CreateGamepadDisplay(SDL_Renderer *renderer); -extern void SetGamepadDisplayArea(GamepadDisplay *ctx, int x, int y, int w, int h); +extern void SetGamepadDisplayDisplayMode(GamepadDisplay *ctx, ControllerDisplayMode display_mode); +extern void SetGamepadDisplayArea(GamepadDisplay *ctx, const SDL_Rect *area); extern void RenderGamepadDisplay(GamepadDisplay *ctx, SDL_Gamepad *gamepad); extern void DestroyGamepadDisplay(GamepadDisplay *ctx); @@ -59,7 +64,7 @@ extern void DestroyGamepadDisplay(GamepadDisplay *ctx); typedef struct JoystickDisplay JoystickDisplay; extern JoystickDisplay *CreateJoystickDisplay(SDL_Renderer *renderer); -extern void SetJoystickDisplayArea(JoystickDisplay *ctx, int x, int y, int w, int h); +extern void SetJoystickDisplayArea(JoystickDisplay *ctx, const SDL_Rect *area); extern void RenderJoystickDisplay(JoystickDisplay *ctx, SDL_Joystick *joystick); extern void DestroyJoystickDisplay(JoystickDisplay *ctx); @@ -68,7 +73,7 @@ extern void DestroyJoystickDisplay(JoystickDisplay *ctx); typedef struct GamepadButton GamepadButton; extern GamepadButton *CreateGamepadButton(SDL_Renderer *renderer, const char *label); -extern void SetGamepadButtonArea(GamepadButton *ctx, int x, int y, int w, int h); +extern void SetGamepadButtonArea(GamepadButton *ctx, const SDL_Rect *area); extern void SetGamepadButtonHighlight(GamepadButton *ctx, SDL_bool highlight); extern int GetGamepadButtonLabelWidth(GamepadButton *ctx); extern int GetGamepadButtonLabelHeight(GamepadButton *ctx); diff --git a/test/testcontroller.c b/test/testcontroller.c index 099f06ad3..078a29140 100644 --- a/test/testcontroller.c +++ b/test/testcontroller.c @@ -27,7 +27,8 @@ #define TITLE_HEIGHT 48 #define PANEL_SPACING 25 #define PANEL_WIDTH 250 -#define BUTTON_MARGIN 8 +#define MINIMUM_BUTTON_WIDTH 96 +#define BUTTON_MARGIN 16 #define BUTTON_PADDING 12 #define GAMEPAD_WIDTH 512 #define GAMEPAD_HEIGHT 480 @@ -56,10 +57,17 @@ typedef struct static SDL_Window *window = NULL; static SDL_Renderer *screen = NULL; +static ControllerDisplayMode display_mode = CONTROLLER_MODE_TESTING; 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 *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; @@ -209,6 +217,104 @@ static void CyclePS5TriggerEffect(Controller *device) SDL_SendGamepadEffect(device->gamepad, &state, sizeof(state)); } +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); +} + +static void UpdateButtonHighlights(float x, float y) +{ + ClearButtonHighlights(); + + if (display_mode == CONTROLLER_MODE_TESTING) { + SetGamepadButtonHighlight(setup_mapping_button, GamepadButtonContains(setup_mapping_button, x, y)); + } 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)); + } +} + +static void SetDisplayMode(ControllerDisplayMode mode) +{ + float x, y; + + if (mode == CONTROLLER_MODE_BINDING) { + /* Make a backup of the current mapping */ + backup_mapping = SDL_GetGamepadMapping(controller->gamepad); + } + + display_mode = mode; + SetGamepadImageDisplayMode(image, mode); + SetGamepadDisplayDisplayMode(gamepad_elements, mode); + + SDL_GetMouseState(&x, &y); + SDL_RenderCoordinatesFromWindow(screen, x, y, &x, &y); + UpdateButtonHighlights(x, y); +} + +static void CancelMapping(void) +{ + if (backup_mapping) { + SDL_SetGamepadMapping(controller->id, backup_mapping); + SDL_free(backup_mapping); + backup_mapping = NULL; + } + SetDisplayMode(CONTROLLER_MODE_TESTING); +} + +static void ClearMapping(void) +{ + SDL_SetGamepadMapping(controller->id, NULL); +} + +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; + + /* 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); + } + SDL_free(mapping); + } + } +} + +static void PasteMapping(void) +{ + if (controller) { + char *mapping = SDL_GetClipboardText(); + if (*mapping) { + SDL_SetGamepadMapping(controller->id, mapping); + } + SDL_free(mapping); + } +} + static int FindController(SDL_JoystickID id) { int i; @@ -225,29 +331,21 @@ 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; } if (i >= 0) { controller = &controllers[i]; - - if (controller->gamepad) { - SetGamepadImageShowingBattery(image, SDL_TRUE); - } else { - SetGamepadImageShowingBattery(image, SDL_FALSE); - } - if (SDL_GetNumGamepadTouchpads(controller->gamepad) > 0) { - SetGamepadImageShowingTouchpad(image, SDL_TRUE); - } else { - SetGamepadImageShowingTouchpad(image, SDL_FALSE); - } } else { controller = NULL; - - SetGamepadImageShowingBattery(image, SDL_FALSE); - SetGamepadImageShowingTouchpad(image, SDL_FALSE); } + SetDisplayMode(CONTROLLER_MODE_TESTING); } static void AddController(SDL_JoystickID id, SDL_bool verbose) @@ -354,6 +452,19 @@ static void DelController(SDL_JoystickID id) SetController(0); } +static void HandleGamepadRemapped(SDL_JoystickID id) +{ + int i = FindController(id); + + if (i < 0) { + return; + } + + if (!controllers[i].gamepad) { + controllers[i].gamepad = SDL_OpenGamepad(id); + } +} + static Uint16 ConvertAxisToRumble(Sint16 axisval) { /* Only start rumbling if the axis is past the halfway point */ @@ -413,6 +524,10 @@ static void OpenVirtualGamepad(void) SDL_VirtualJoystickDesc desc; SDL_JoystickID virtual_id; + if (virtual_joystick) { + return; + } + SDL_zero(desc); desc.version = SDL_VIRTUAL_JOYSTICK_DESC_VERSION; desc.type = SDL_JOYSTICK_TYPE_GAMEPAD; @@ -578,47 +693,76 @@ static void DrawGamepadInfo(SDL_Renderer *renderer) SDLTest_DrawString(renderer, x, y, text); } - SDL_snprintf(text, SDL_arraysize(text), "VID: 0x%.4x PID: 0x%.4x", - SDL_GetJoystickVendor(controller->joystick), - SDL_GetJoystickProduct(controller->joystick)); - y = (float)SCREEN_HEIGHT - 8.0f - FONT_LINE_HEIGHT; - x = (float)SCREEN_WIDTH - 8.0f - (FONT_CHARACTER_SIZE * SDL_strlen(text)); - SDLTest_DrawString(renderer, x, y, text); - - serial = SDL_GetJoystickSerial(controller->joystick); - if (serial && *serial) { - SDL_snprintf(text, SDL_arraysize(text), "Serial: %s", serial); - x = (float)SCREEN_WIDTH / 2 - (FONT_CHARACTER_SIZE * SDL_strlen(text)) / 2; + if (display_mode == CONTROLLER_MODE_TESTING) { + SDL_snprintf(text, SDL_arraysize(text), "VID: 0x%.4x PID: 0x%.4x", + SDL_GetJoystickVendor(controller->joystick), + SDL_GetJoystickProduct(controller->joystick)); y = (float)SCREEN_HEIGHT - 8.0f - FONT_LINE_HEIGHT; + x = (float)SCREEN_WIDTH - 8.0f - (FONT_CHARACTER_SIZE * SDL_strlen(text)); SDLTest_DrawString(renderer, x, y, text); + + serial = SDL_GetJoystickSerial(controller->joystick); + if (serial && *serial) { + SDL_snprintf(text, SDL_arraysize(text), "Serial: %s", serial); + x = (float)SCREEN_WIDTH / 2 - (FONT_CHARACTER_SIZE * SDL_strlen(text)) / 2; + y = (float)SCREEN_HEIGHT - 8.0f - FONT_LINE_HEIGHT; + SDLTest_DrawString(renderer, x, y, text); + } } } -static void CopyMappingToClipboard() +static void UpdateGamepadEffects(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 (display_mode != CONTROLLER_MODE_TESTING || !controller->gamepad) { + return; + } - /* 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); + /* Update LED based on left thumbstick position */ + { + Sint16 x = SDL_GetGamepadAxis(controller->gamepad, SDL_GAMEPAD_AXIS_LEFTX); + Sint16 y = SDL_GetGamepadAxis(controller->gamepad, SDL_GAMEPAD_AXIS_LEFTY); + + if (!set_LED) { + set_LED = (x < -8000 || x > 8000 || y > 8000); + } + if (set_LED) { + Uint8 r, g, b; + + if (x < 0) { + r = (Uint8)(((~x) * 255) / 32767); + b = 0; } else { - SDL_SetClipboardText(mapping); + r = 0; + b = (Uint8)(((int)(x)*255) / 32767); } - SDL_free(mapping); + if (y > 0) { + g = (Uint8)(((int)(y)*255) / 32767); + } else { + g = 0; + } + + SDL_SetGamepadLED(controller->gamepad, r, g, b); + } + } + + if (controller->trigger_effect == 0) { + /* Update rumble based on trigger state */ + { + Sint16 left = SDL_GetGamepadAxis(controller->gamepad, SDL_GAMEPAD_AXIS_LEFT_TRIGGER); + Sint16 right = SDL_GetGamepadAxis(controller->gamepad, SDL_GAMEPAD_AXIS_RIGHT_TRIGGER); + Uint16 low_frequency_rumble = ConvertAxisToRumble(left); + Uint16 high_frequency_rumble = ConvertAxisToRumble(right); + SDL_RumbleGamepad(controller->gamepad, low_frequency_rumble, high_frequency_rumble, 250); + } + + /* Update trigger rumble based on thumbstick state */ + { + Sint16 left = SDL_GetGamepadAxis(controller->gamepad, SDL_GAMEPAD_AXIS_LEFTY); + Sint16 right = SDL_GetGamepadAxis(controller->gamepad, SDL_GAMEPAD_AXIS_RIGHTY); + Uint16 left_rumble = ConvertAxisToRumble(~left); + Uint16 right_rumble = ConvertAxisToRumble(~right); + + SDL_RumbleGamepadTriggers(controller->gamepad, left_rumble, right_rumble, 250); } } } @@ -653,6 +797,10 @@ static void loop(void *arg) SetController(event.jbutton.which); break; + case SDL_EVENT_GAMEPAD_REMAPPED: + HandleGamepadRemapped(event.gdevice.which); + break; + case SDL_EVENT_GAMEPAD_TOUCHPAD_DOWN: case SDL_EVENT_GAMEPAD_TOUCHPAD_MOTION: case SDL_EVENT_GAMEPAD_TOUCHPAD_UP: @@ -702,11 +850,13 @@ static void loop(void *arg) SDL_GetGamepadStringForButton((SDL_GamepadButton) event.gbutton.button), event.gbutton.state ? "pressed" : "released"); - /* Cycle PS5 trigger effects when the microphone button is pressed */ - if (event.type == SDL_EVENT_GAMEPAD_BUTTON_DOWN && - controller && SDL_GetGamepadType(controller->gamepad) == SDL_GAMEPAD_TYPE_PS5 && - event.gbutton.button == SDL_GAMEPAD_BUTTON_MISC1) { - CyclePS5TriggerEffect(controller); + if (display_mode == CONTROLLER_MODE_TESTING) { + /* Cycle PS5 trigger effects when the microphone button is pressed */ + if (event.type == SDL_EVENT_GAMEPAD_BUTTON_DOWN && + controller && SDL_GetGamepadType(controller->gamepad) == SDL_GAMEPAD_TYPE_PS5 && + event.gbutton.button == SDL_GAMEPAD_BUTTON_MISC1) { + CyclePS5TriggerEffect(controller); + } } break; @@ -715,48 +865,72 @@ static void loop(void *arg) break; case SDL_EVENT_MOUSE_BUTTON_DOWN: - if (virtual_joystick) { + if (virtual_joystick && controller->joystick == virtual_joystick) { VirtualGamepadMouseDown(event.button.x, event.button.y); } - SetGamepadButtonHighlight(copy_button, GamepadButtonContains(copy_button, event.button.x, event.button.y)); + UpdateButtonHighlights(event.button.x, event.button.y); break; case SDL_EVENT_MOUSE_BUTTON_UP: - if (virtual_joystick) { + if (virtual_joystick && controller->joystick == virtual_joystick) { VirtualGamepadMouseUp(event.button.x, event.button.y); } - if (GamepadButtonContains(copy_button, event.button.x, event.button.y)) { - CopyMappingToClipboard(); + + 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)) { + 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)) { + CopyMapping(); + } else if (GamepadButtonContains(paste_button, event.button.x, event.button.y)) { + PasteMapping(); + } } - SetGamepadButtonHighlight(copy_button, SDL_FALSE); break; case SDL_EVENT_MOUSE_MOTION: - if (virtual_joystick) { + if (virtual_joystick && controller->joystick == virtual_joystick) { VirtualGamepadMouseMotion(event.motion.x, event.motion.y); } - SetGamepadButtonHighlight(copy_button, event.motion.state && GamepadButtonContains(copy_button, event.motion.x, event.motion.y)); + UpdateButtonHighlights(event.motion.x, event.motion.y); break; case SDL_EVENT_KEY_DOWN: - if (event.key.keysym.sym >= SDLK_0 && event.key.keysym.sym <= SDLK_9) { - if (controller && controller->gamepad) { - int player_index = (event.key.keysym.sym - SDLK_0); + if (display_mode == CONTROLLER_MODE_TESTING) { + if (event.key.keysym.sym >= SDLK_0 && event.key.keysym.sym <= SDLK_9) { + if (controller && controller->gamepad) { + int player_index = (event.key.keysym.sym - SDLK_0); - SDL_SetGamepadPlayerIndex(controller->gamepad, player_index); + SDL_SetGamepadPlayerIndex(controller->gamepad, player_index); + } + break; + } else if (event.key.keysym.sym == SDLK_a) { + OpenVirtualGamepad(); + } else if (event.key.keysym.sym == SDLK_d) { + CloseVirtualGamepad(); + } else if (event.key.keysym.sym == SDLK_ESCAPE) { + done = SDL_TRUE; + } + } else if (display_mode == CONTROLLER_MODE_BINDING) { + if (event.key.keysym.sym == SDLK_c && (event.key.keysym.mod & SDL_KMOD_CTRL)) { + CopyMapping(); + } else if (event.key.keysym.sym == SDLK_v && (event.key.keysym.mod & SDL_KMOD_CTRL)) { + PasteMapping(); + } else if (event.key.keysym.sym == SDLK_x && (event.key.keysym.mod & SDL_KMOD_CTRL)) { + CopyMapping(); + ClearMapping(); + } else if (event.key.keysym.sym == SDLK_ESCAPE) { + CancelMapping(); } - break; - } - if (event.key.keysym.sym == SDLK_a) { - OpenVirtualGamepad(); - break; - } - if (event.key.keysym.sym == SDLK_d) { - CloseVirtualGamepad(); - break; - } - if (event.key.keysym.sym == SDLK_ESCAPE) { - done = SDL_TRUE; } break; case SDL_EVENT_QUIT: @@ -780,62 +954,21 @@ static void loop(void *arg) RenderGamepadDisplay(gamepad_elements, controller->gamepad); RenderJoystickDisplay(joystick_elements, controller->joystick); - if (controller->gamepad) { - RenderGamepadButton(copy_button); + if (display_mode == CONTROLLER_MODE_TESTING) { + RenderGamepadButton(setup_mapping_button); + } else if (display_mode == CONTROLLER_MODE_BINDING) { + RenderGamepadButton(test_mapping_button); + RenderGamepadButton(cancel_button); + RenderGamepadButton(clear_button); + if (controller->gamepad) { + RenderGamepadButton(copy_button); + } + RenderGamepadButton(paste_button); } DrawGamepadInfo(screen); - if (controller->gamepad) { - /* Update LED based on left thumbstick position */ - { - Sint16 x = SDL_GetGamepadAxis(controller->gamepad, SDL_GAMEPAD_AXIS_LEFTX); - Sint16 y = SDL_GetGamepadAxis(controller->gamepad, SDL_GAMEPAD_AXIS_LEFTY); - - if (!set_LED) { - set_LED = (x < -8000 || x > 8000 || y > 8000); - } - if (set_LED) { - Uint8 r, g, b; - - if (x < 0) { - r = (Uint8)(((~x) * 255) / 32767); - b = 0; - } else { - r = 0; - b = (Uint8)(((int)(x)*255) / 32767); - } - if (y > 0) { - g = (Uint8)(((int)(y)*255) / 32767); - } else { - g = 0; - } - - SDL_SetGamepadLED(controller->gamepad, r, g, b); - } - } - - if (controller->trigger_effect == 0) { - /* Update rumble based on trigger state */ - { - Sint16 left = SDL_GetGamepadAxis(controller->gamepad, SDL_GAMEPAD_AXIS_LEFT_TRIGGER); - Sint16 right = SDL_GetGamepadAxis(controller->gamepad, SDL_GAMEPAD_AXIS_RIGHT_TRIGGER); - Uint16 low_frequency_rumble = ConvertAxisToRumble(left); - Uint16 high_frequency_rumble = ConvertAxisToRumble(right); - SDL_RumbleGamepad(controller->gamepad, low_frequency_rumble, high_frequency_rumble, 250); - } - - /* Update trigger rumble based on thumbstick state */ - { - Sint16 left = SDL_GetGamepadAxis(controller->gamepad, SDL_GAMEPAD_AXIS_LEFTY); - Sint16 right = SDL_GetGamepadAxis(controller->gamepad, SDL_GAMEPAD_AXIS_RIGHTY); - Uint16 left_rumble = ConvertAxisToRumble(~left); - Uint16 right_rumble = ConvertAxisToRumble(~right); - - SDL_RumbleGamepadTriggers(controller->gamepad, left_rumble, right_rumble, 250); - } - } - } + UpdateGamepadEffects(); } else { DrawGamepadWaiting(screen); } @@ -854,7 +987,7 @@ int main(int argc, char *argv[]) int i; float content_scale; int screen_width, screen_height; - int button_width, button_height; + SDL_Rect area; int gamepad_index = -1; SDLTest_CommonState *state; @@ -960,18 +1093,62 @@ int main(int argc, char *argv[]) return 2; } SetGamepadImagePosition(image, PANEL_WIDTH + PANEL_SPACING, TITLE_HEIGHT); - SetGamepadImageShowingBattery(image, SDL_TRUE); gamepad_elements = CreateGamepadDisplay(screen); - SetGamepadDisplayArea(gamepad_elements, 0, TITLE_HEIGHT, PANEL_WIDTH, GAMEPAD_HEIGHT); + area.x = 0; + area.y = TITLE_HEIGHT; + area.w = PANEL_WIDTH; + area.h = GAMEPAD_HEIGHT; + SetGamepadDisplayArea(gamepad_elements, &area); joystick_elements = CreateJoystickDisplay(screen); - SetJoystickDisplayArea(joystick_elements, PANEL_WIDTH + PANEL_SPACING + GAMEPAD_WIDTH + PANEL_SPACING, TITLE_HEIGHT, PANEL_WIDTH, GAMEPAD_HEIGHT); + area.x = PANEL_WIDTH + PANEL_SPACING + GAMEPAD_WIDTH + PANEL_SPACING; + area.y = TITLE_HEIGHT; + area.w = PANEL_WIDTH; + area.h = GAMEPAD_HEIGHT; + SetJoystickDisplayArea(joystick_elements, &area); - copy_button = CreateGamepadButton(screen, "Copy to Clipboard"); - button_width = GetGamepadButtonLabelWidth(copy_button) + 2 * BUTTON_PADDING; - button_height = GetGamepadButtonLabelHeight(copy_button) + 2 * BUTTON_PADDING; - SetGamepadButtonArea(copy_button, BUTTON_MARGIN, SCREEN_HEIGHT - BUTTON_MARGIN - button_height, button_width, button_height); + setup_mapping_button = CreateGamepadButton(screen, "Setup Mapping"); + area.w = SDL_max(MINIMUM_BUTTON_WIDTH, GetGamepadButtonLabelWidth(setup_mapping_button) + 2 * BUTTON_PADDING); + area.h = GetGamepadButtonLabelHeight(setup_mapping_button) + 2 * BUTTON_PADDING; + area.x = BUTTON_MARGIN; + area.y = SCREEN_HEIGHT - BUTTON_MARGIN - area.h; + SetGamepadButtonArea(setup_mapping_button, &area); + + cancel_button = CreateGamepadButton(screen, "Cancel"); + area.w = SDL_max(MINIMUM_BUTTON_WIDTH, GetGamepadButtonLabelWidth(cancel_button) + 2 * BUTTON_PADDING); + area.h = GetGamepadButtonLabelHeight(cancel_button) + 2 * BUTTON_PADDING; + area.x = BUTTON_MARGIN; + area.y = SCREEN_HEIGHT - BUTTON_MARGIN - area.h; + SetGamepadButtonArea(cancel_button, &area); + + clear_button = CreateGamepadButton(screen, "Clear"); + area.x += area.w + BUTTON_PADDING; + area.w = SDL_max(MINIMUM_BUTTON_WIDTH, GetGamepadButtonLabelWidth(clear_button) + 2 * BUTTON_PADDING); + area.h = GetGamepadButtonLabelHeight(clear_button) + 2 * BUTTON_PADDING; + area.y = SCREEN_HEIGHT - BUTTON_MARGIN - area.h; + SetGamepadButtonArea(clear_button, &area); + + copy_button = CreateGamepadButton(screen, "Copy"); + area.x += area.w + BUTTON_PADDING; + area.w = SDL_max(MINIMUM_BUTTON_WIDTH, GetGamepadButtonLabelWidth(copy_button) + 2 * BUTTON_PADDING); + area.h = GetGamepadButtonLabelHeight(copy_button) + 2 * BUTTON_PADDING; + area.y = SCREEN_HEIGHT - BUTTON_MARGIN - area.h; + SetGamepadButtonArea(copy_button, &area); + + paste_button = CreateGamepadButton(screen, "Paste"); + area.x += area.w + BUTTON_PADDING; + area.w = SDL_max(MINIMUM_BUTTON_WIDTH, GetGamepadButtonLabelWidth(paste_button) + 2 * BUTTON_PADDING); + area.h = GetGamepadButtonLabelHeight(paste_button) + 2 * BUTTON_PADDING; + 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; + area.x = SCREEN_WIDTH / 2 - area.w / 2; + area.y = SCREEN_HEIGHT - BUTTON_MARGIN - area.h; + SetGamepadButtonArea(test_mapping_button, &area); /* Process the initial gamepad list */ loop(NULL);