diff --git a/ui/xemu-input.c b/ui/xemu-input.c index 0db2932368d..e473b495af4 100644 --- a/ui/xemu-input.c +++ b/ui/xemu-input.c @@ -298,6 +298,10 @@ void xemu_input_process_sdl_events(const SDL_Event *event) } } else if (event->type == SDL_CONTROLLERDEVICEREMAPPED) { DPRINTF("Controller Remapped: %d\n", event->cdevice.which); + } else if (is_remapping_active) { + xemu_input_keyboard_rebind(event); + } else if (restore_controls) { + xemu_input_restore_defaults(); } } @@ -353,6 +357,138 @@ void xemu_input_update_sdl_kbd_controller_state(ControllerState *state) if (kbd[sdl_kbd_scancode_map[24]]) state->axis[CONTROLLER_AXIS_RTRIG] = 32767; } +void xemu_input_restore_defaults(void) +{ + sdl_kbd_scancode_map[0] = SDL_SCANCODE_A; + sdl_kbd_scancode_map[1] = SDL_SCANCODE_B; + sdl_kbd_scancode_map[2] = SDL_SCANCODE_X; + sdl_kbd_scancode_map[3] = SDL_SCANCODE_Y; + sdl_kbd_scancode_map[4] = SDL_SCANCODE_LEFT; + sdl_kbd_scancode_map[5] = SDL_SCANCODE_UP; + sdl_kbd_scancode_map[6] = SDL_SCANCODE_RIGHT; + sdl_kbd_scancode_map[7] = SDL_SCANCODE_DOWN; + sdl_kbd_scancode_map[8] = SDL_SCANCODE_BACKSPACE; + sdl_kbd_scancode_map[9] = SDL_SCANCODE_RETURN; + sdl_kbd_scancode_map[10] = SDL_SCANCODE_1; + sdl_kbd_scancode_map[11] = SDL_SCANCODE_2; + sdl_kbd_scancode_map[12] = SDL_SCANCODE_3; + sdl_kbd_scancode_map[13] = SDL_SCANCODE_4; + sdl_kbd_scancode_map[14] = SDL_SCANCODE_5; + sdl_kbd_scancode_map[15] = SDL_SCANCODE_E; + sdl_kbd_scancode_map[16] = SDL_SCANCODE_S; + sdl_kbd_scancode_map[17] = SDL_SCANCODE_F; + sdl_kbd_scancode_map[18] = SDL_SCANCODE_D; + sdl_kbd_scancode_map[19] = SDL_SCANCODE_W; + sdl_kbd_scancode_map[20] = SDL_SCANCODE_I; + sdl_kbd_scancode_map[21] = SDL_SCANCODE_J; + sdl_kbd_scancode_map[22] = SDL_SCANCODE_L; + sdl_kbd_scancode_map[23] = SDL_SCANCODE_K; + sdl_kbd_scancode_map[24] = SDL_SCANCODE_O; + + g_config.input.keyboard_controller_scancode_map.a = SDL_SCANCODE_A; + g_config.input.keyboard_controller_scancode_map.b = SDL_SCANCODE_B; + g_config.input.keyboard_controller_scancode_map.x = SDL_SCANCODE_X; + g_config.input.keyboard_controller_scancode_map.y = SDL_SCANCODE_Y; + g_config.input.keyboard_controller_scancode_map.dpad_left = SDL_SCANCODE_LEFT; + g_config.input.keyboard_controller_scancode_map.dpad_up = SDL_SCANCODE_UP; + g_config.input.keyboard_controller_scancode_map.dpad_right = SDL_SCANCODE_RIGHT; + g_config.input.keyboard_controller_scancode_map.dpad_down = SDL_SCANCODE_DOWN; + g_config.input.keyboard_controller_scancode_map.back = SDL_SCANCODE_BACKSPACE; + g_config.input.keyboard_controller_scancode_map.start = SDL_SCANCODE_RETURN; + g_config.input.keyboard_controller_scancode_map.white = SDL_SCANCODE_1; + g_config.input.keyboard_controller_scancode_map.black = SDL_SCANCODE_2; + g_config.input.keyboard_controller_scancode_map.lstick_btn = SDL_SCANCODE_3; + g_config.input.keyboard_controller_scancode_map.rstick_btn = SDL_SCANCODE_4; + g_config.input.keyboard_controller_scancode_map.guide = SDL_SCANCODE_5; + g_config.input.keyboard_controller_scancode_map.lstick_up = SDL_SCANCODE_E; + g_config.input.keyboard_controller_scancode_map.lstick_left = SDL_SCANCODE_S; + g_config.input.keyboard_controller_scancode_map.lstick_right = SDL_SCANCODE_F; + g_config.input.keyboard_controller_scancode_map.lstick_down = SDL_SCANCODE_D; + g_config.input.keyboard_controller_scancode_map.ltrigger = SDL_SCANCODE_W; + g_config.input.keyboard_controller_scancode_map.rstick_up = SDL_SCANCODE_I; + g_config.input.keyboard_controller_scancode_map.rstick_left = SDL_SCANCODE_J; + g_config.input.keyboard_controller_scancode_map.rstick_right = SDL_SCANCODE_L; + g_config.input.keyboard_controller_scancode_map.rstick_down = SDL_SCANCODE_K; + g_config.input.keyboard_controller_scancode_map.rtrigger = SDL_SCANCODE_O; + + char *buf = g_strdup_printf("INFO: Keys restored to default."); + xemu_queue_notification(buf); + free(buf); + + restore_controls = false; +} + +void xemu_input_keyboard_rebind(const SDL_Event *ev) +{ + //Check if the user aborts the remapping process. + if (abort_rebinding) { + char *buf = g_strdup_printf("INFO: Remapping process aborted"); + xemu_queue_notification(buf); + free(buf); + is_remapping_active = false; + abort_rebinding = false; + } + + if (ev->type == SDL_KEYDOWN) { + sdl_kbd_scancode_map[currently_remapping] = ev->key.keysym.scancode; + //check for duplicated keybindings, if found, rebind that button. + for (size_t i = 0; i < currently_remapping; i++) { + if (sdl_kbd_scancode_map[currently_remapping] == sdl_kbd_scancode_map[i]) { + already_mapped = i; + currently_remapping--; + char *buf = g_strdup_printf("WARNING: Keybind already in use for: %s. Try another key.", bindings[already_mapped]); + xemu_queue_notification(buf); + free(buf); + break; + } + } + + if ((sdl_kbd_scancode_map[currently_remapping] < SDL_SCANCODE_UNKNOWN) || + (sdl_kbd_scancode_map[currently_remapping] >= SDL_NUM_SCANCODES)) { + char *buf = g_strdup_printf("WARNING: Keyboard scancode out of range, try another key."); + xemu_queue_notification(buf); + free(buf); + currently_remapping--; + } + + currently_remapping++; + + //If the user has mapped all the buttons, store the new bindings. + if (currently_remapping == 25) { + g_config.input.keyboard_controller_scancode_map.a = sdl_kbd_scancode_map[0]; + g_config.input.keyboard_controller_scancode_map.b = sdl_kbd_scancode_map[1]; + g_config.input.keyboard_controller_scancode_map.x = sdl_kbd_scancode_map[2]; + g_config.input.keyboard_controller_scancode_map.y = sdl_kbd_scancode_map[3]; + g_config.input.keyboard_controller_scancode_map.dpad_left = sdl_kbd_scancode_map[4]; + g_config.input.keyboard_controller_scancode_map.dpad_up = sdl_kbd_scancode_map[5]; + g_config.input.keyboard_controller_scancode_map.dpad_right = sdl_kbd_scancode_map[6]; + g_config.input.keyboard_controller_scancode_map.dpad_down = sdl_kbd_scancode_map[7]; + g_config.input.keyboard_controller_scancode_map.back = sdl_kbd_scancode_map[8]; + g_config.input.keyboard_controller_scancode_map.start = sdl_kbd_scancode_map[9]; + g_config.input.keyboard_controller_scancode_map.white = sdl_kbd_scancode_map[10]; + g_config.input.keyboard_controller_scancode_map.black = sdl_kbd_scancode_map[11]; + g_config.input.keyboard_controller_scancode_map.lstick_btn = sdl_kbd_scancode_map[12]; + g_config.input.keyboard_controller_scancode_map.rstick_btn = sdl_kbd_scancode_map[13]; + g_config.input.keyboard_controller_scancode_map.guide = sdl_kbd_scancode_map[14]; + g_config.input.keyboard_controller_scancode_map.lstick_up = sdl_kbd_scancode_map[15]; + g_config.input.keyboard_controller_scancode_map.lstick_left = sdl_kbd_scancode_map[16]; + g_config.input.keyboard_controller_scancode_map.lstick_right = sdl_kbd_scancode_map[17]; + g_config.input.keyboard_controller_scancode_map.lstick_down = sdl_kbd_scancode_map[18]; + g_config.input.keyboard_controller_scancode_map.ltrigger = sdl_kbd_scancode_map[19]; + g_config.input.keyboard_controller_scancode_map.rstick_up = sdl_kbd_scancode_map[20]; + g_config.input.keyboard_controller_scancode_map.rstick_left = sdl_kbd_scancode_map[21]; + g_config.input.keyboard_controller_scancode_map.rstick_right = sdl_kbd_scancode_map[22]; + g_config.input.keyboard_controller_scancode_map.rstick_down = sdl_kbd_scancode_map[23]; + g_config.input.keyboard_controller_scancode_map.rtrigger = sdl_kbd_scancode_map[24]; + + char *buf = g_strdup_printf("INFO: Successfully remapped keyboard."); + xemu_queue_notification(buf); + free(buf); + is_remapping_active = false; + } + } +} + void xemu_input_update_sdl_controller_state(ControllerState *state) { state->buttons = 0; diff --git a/ui/xemu-input.h b/ui/xemu-input.h index 8a8ba6544ea..f60a10bb36e 100644 --- a/ui/xemu-input.h +++ b/ui/xemu-input.h @@ -96,6 +96,13 @@ typedef QTAILQ_HEAD(, ControllerState) ControllerStateList; extern ControllerStateList available_controllers; extern ControllerState *bound_controllers[4]; +extern bool is_remapping_active; +extern bool abort_rebinding; +extern bool restore_controls; +extern int currently_remapping; +extern int already_mapped; +extern const char *bindings[25]; + #ifdef __cplusplus extern "C" { #endif @@ -107,6 +114,8 @@ void xemu_input_update_controller(ControllerState *state); void xemu_input_update_sdl_kbd_controller_state(ControllerState *state); void xemu_input_update_sdl_controller_state(ControllerState *state); void xemu_input_update_rumble(ControllerState *state); +void xemu_input_keyboard_rebind(const SDL_Event *ev); +void xemu_input_restore_defaults(void); ControllerState *xemu_input_get_bound(int index); void xemu_input_bind(int index, ControllerState *state, int save); int xemu_input_get_controller_default_bind_port(ControllerState *state, int start); diff --git a/ui/xui/gl-helpers.cc b/ui/xui/gl-helpers.cc index 94d8d21ac90..c05e2360100 100644 --- a/ui/xui/gl-helpers.cc +++ b/ui/xui/gl-helpers.cc @@ -501,25 +501,28 @@ void RenderController(float frame_x, float frame_y, uint32_t primary_color, uint32_t jewel_color = secondary_color; - // Check to see if the guide button is pressed - const uint32_t animate_guide_button_duration = 2000; - if (state->buttons & CONTROLLER_BUTTON_GUIDE) { - state->animate_guide_button_end = now + animate_guide_button_duration; - } + // Check to see if the guide button is pressed only when not remapping. + if (!is_remapping_active) { + const uint32_t animate_guide_button_duration = 2000; + if (state->buttons & CONTROLLER_BUTTON_GUIDE) { + state->animate_guide_button_end = now + animate_guide_button_duration; + } - if (now < state->animate_guide_button_end) { - t = 1.0f - (float)(state->animate_guide_button_end-now)/(float)animate_guide_button_duration; - float sin_wav = (1-sin(M_PI * t / 2.0f)); + if (now < state->animate_guide_button_end) { + t = 1.0f - (float)(state->animate_guide_button_end-now)/(float)animate_guide_button_duration; + float sin_wav = (1-sin(M_PI * t / 2.0f)); - // Animate guide button by highlighting logo jewel and fading out over time - alpha = sin_wav * 255.0f; - jewel_color = primary_color + alpha; + // Animate guide button by highlighting logo jewel and fading out over time + alpha = sin_wav * 255.0f; + jewel_color = primary_color + alpha; - // Add a little extra flare: wiggle the frame around while we rumble - frame_x += ((float)(rand() % 5)-2.5) * (1-t); - frame_y += ((float)(rand() % 5)-2.5) * (1-t); - rumble_l = rumble_r = sin_wav; + // Add a little extra flare: wiggle the frame around while we rumble + frame_x += ((float)(rand() % 5)-2.5) * (1-t); + frame_y += ((float)(rand() % 5)-2.5) * (1-t); + rumble_l = rumble_r = sin_wav; + } } + // Render controller texture RenderDecal(g_decal_shader, frame_x + 0, frame_y + 0, @@ -534,51 +537,70 @@ void RenderController(float frame_x, float frame_y, uint32_t primary_color, // The controller has alpha cutouts where the buttons are. Draw a surface // behind the buttons if they are activated - for (int i = 0; i < 12; i++) { - if (state->buttons & (1 << i)) { - RenderDecal(g_decal_shader, frame_x + buttons[i].x, - frame_y + buttons[i].y, buttons[i].w, buttons[i].h, 0, - 0, 1, 1, 0, 0, primary_color + 0xff); + // Do not highlight the buttons while remapping. + if (!is_remapping_active) { + for (int i = 0; i < 12; i++) { + if (state->buttons & (1 << i)) { + RenderDecal(g_decal_shader, frame_x + buttons[i].x, + frame_y + buttons[i].y, buttons[i].w, buttons[i].h, 0, + 0, 1, 1, 0, 0, primary_color + 0xff); + } } } - + glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); // Blend with controller - // Render left thumbstick + // Render left thumbstick, do not make move/highlight while remapping. float w = tex_items[obj_lstick].w; float h = tex_items[obj_lstick].h; float c_x = frame_x+lstick_ctr.x; float c_y = frame_y+lstick_ctr.y; float lstick_x = (float)state->axis[CONTROLLER_AXIS_LSTICK_X]/32768.0; float lstick_y = (float)state->axis[CONTROLLER_AXIS_LSTICK_Y]/32768.0; + int lstick_press_anim = CONTROLLER_BUTTON_LSTICK; + + if (is_remapping_active) { + lstick_x = 0; + lstick_y = 0; + lstick_press_anim = SDL_SCANCODE_UNKNOWN; + } + RenderDecal(g_decal_shader, (int)(c_x - w / 2.0f + 10.0f * lstick_x), (int)(c_y - h / 2.0f + 10.0f * lstick_y), w, h, tex_items[obj_lstick].x, tex_items[obj_lstick].y, w, h, - (state->buttons & CONTROLLER_BUTTON_LSTICK) ? secondary_color : + (state->buttons & lstick_press_anim) ? secondary_color : primary_color, - (state->buttons & CONTROLLER_BUTTON_LSTICK) ? primary_color : + (state->buttons & lstick_press_anim) ? primary_color : secondary_color, 0); - // Render right thumbstick + // Render right thumbstick, do not make move/highlight while remapping w = tex_items[obj_rstick].w; h = tex_items[obj_rstick].h; c_x = frame_x+rstick_ctr.x; c_y = frame_y+rstick_ctr.y; float rstick_x = (float)state->axis[CONTROLLER_AXIS_RSTICK_X]/32768.0; float rstick_y = (float)state->axis[CONTROLLER_AXIS_RSTICK_Y]/32768.0; + int rstick_press_anim = CONTROLLER_BUTTON_RSTICK; + + if (is_remapping_active) { + rstick_x = 0; + rstick_y = 0; + rstick_press_anim = SDL_SCANCODE_UNKNOWN; + } + RenderDecal(g_decal_shader, (int)(c_x - w / 2.0f + 10.0f * rstick_x), (int)(c_y - h / 2.0f + 10.0f * rstick_y), w, h, tex_items[obj_rstick].x, tex_items[obj_rstick].y, w, h, - (state->buttons & CONTROLLER_BUTTON_RSTICK) ? secondary_color : + (state->buttons & rstick_press_anim) ? secondary_color : primary_color, - (state->buttons & CONTROLLER_BUTTON_RSTICK) ? primary_color : + (state->buttons & rstick_press_anim) ? primary_color : secondary_color, 0); glBlendFunc(GL_ONE, GL_ZERO); // Don't blend, just overwrite values in buffer - // Render trigger bars + // Render trigger bars, do not highlight when remapping. float ltrig = state->axis[CONTROLLER_AXIS_LTRIG] / 32767.0; float rtrig = state->axis[CONTROLLER_AXIS_RTRIG] / 32767.0; const uint32_t animate_trigger_duration = 1000; @@ -596,6 +618,12 @@ void RenderController(float frame_x, float frame_y, uint32_t primary_color, alpha += fmin(sin_wav * 0x40, 0x80); } + if (is_remapping_active) { + ltrig = 0; + rtrig = 0; + alpha = 0x80; + } + RenderMeter(g_decal_shader, original_frame_x + 10, original_frame_y + tex_items[obj_controller].h + 20, 150, 5, ltrig, primary_color + alpha, primary_color + 0xff); @@ -604,9 +632,12 @@ void RenderController(float frame_x, float frame_y, uint32_t primary_color, original_frame_y + tex_items[obj_controller].h + 20, 150, 5, rtrig, primary_color + alpha, primary_color + 0xff); - // Apply rumble updates - state->rumble_l = (int)(rumble_l * (float)0xffff); - state->rumble_r = (int)(rumble_r * (float)0xffff); + + // Apply rumble updates when not remapping + if (!is_remapping_active) { + state->rumble_l = (int)(rumble_l * (float)0xffff); + state->rumble_r = (int)(rumble_r * (float)0xffff); + } glBindVertexArray(0); glUseProgram(0); diff --git a/ui/xui/main-menu.cc b/ui/xui/main-menu.cc index 19d12f9cc78..3668abbfded 100644 --- a/ui/xui/main-menu.cc +++ b/ui/xui/main-menu.cc @@ -38,6 +38,16 @@ #include "../xemu-xbe.h" MainMenuScene g_main_menu; +bool is_remapping_active = false; +bool duplicate_found = false; +bool restore_controls = false; +bool abort_rebinding = false; +int currently_remapping = 0; +int already_mapped = 0; +const char *bindings[25] = {"A", "B", "X", "Y", "DPAD LEFT", "DPAD UP", "DPAD RIGHT", "DPAD DOWN", "BACK", "START", + "WHITE", "BLACK", "LEFT STICK BUTTON", "RIGHT STICK BUTTON", "GUIDE", "LEFT STICK UP", + "LEFT STICK LEFT", "LEFT STICK RIGHT", "LEFT STICK DOWN", "LEFT TRIGGER", "RIGHT STICK UP", + "RIGHT STICK LEFT", "RIGHT STICK RIGHT", "RIGHT STICK DOWN", "RIGHT TRIGGER"}; MainMenuTabView::~MainMenuTabView() {} void MainMenuTabView::Draw() {} @@ -258,11 +268,43 @@ void MainMenuInputView::Draw() ImGui::SetCursorPos(pos); SectionTitle("Options"); - Toggle("Auto-bind controllers", &g_config.input.auto_bind, - "Bind newly connected controllers to any open port"); - Toggle("Background controller input capture", + Toggle("Background controller input capture\n", &g_config.input.background_input_capture, "Capture even if window is unfocused (requires restart)"); + + /* Interface and checks for keyboard remapping. + -Remove focus on input window while binding to avoide moving inside the UI. + -Abort remapping if you exit the window and restore defaults. + -Give a toggle to the user while he's remapping to stop the remapping manually. + -If the user is rebinding, do not render the "reset to default" option. + NOTE: The keyboard config is overwrited only when the mapping is complete + */ + + if (Toggle("Rebind keyboard controls", &is_remapping_active, + "If out of input window, process is aborted and defaults restored.")) { + currently_remapping = 0; + is_remapping_active = true; + } + + if (g_config.general.last_viewed_menu_index != 1 && is_remapping_active) { + abort_rebinding = true; + restore_controls = true; + } + + if (!is_remapping_active) { + if (Toggle("Reset controls to default", &restore_controls, + "Resets the keyboard mapping to default. (No reboot required)")) { + restore_controls = true; + } + } + + if (is_remapping_active) { + if (Toggle("Abort rebinding", &abort_rebinding, "Abort the rebinding process.")) { + abort_rebinding = true; + } + ImGui::SetKeyboardFocusHere(1); + ImGui::Text("\nPress the key you want to bind for: %s", bindings[currently_remapping]); + } } void MainMenuDisplayView::Draw() @@ -955,6 +997,7 @@ MainMenuScene::MainMenuScene() m_current_view_index = 0; m_next_view_index = m_current_view_index; + } void MainMenuScene::ShowGeneral() @@ -1006,9 +1049,12 @@ void MainMenuScene::Show() void MainMenuScene::Hide() { - m_background.Hide(); - m_nav_control_view.Hide(); - m_animation.EaseOut(); + //ESC button will not make exit the menu while remapping. + if (!is_remapping_active) { + m_background.Hide(); + m_nav_control_view.Hide(); + m_animation.EaseOut(); + } } bool MainMenuScene::IsAnimating()