From 8e2bab8525b05ab965c02a35f84c42ba13844db8 Mon Sep 17 00:00:00 2001 From: Stefano Date: Sun, 28 Apr 2024 12:45:37 +0200 Subject: [PATCH 1/7] First version that allows reading from a joypad --- .../keyboard-joypad/KeyboardJoypad.cpp | 380 +++++++++++++++--- 1 file changed, 317 insertions(+), 63 deletions(-) diff --git a/src/devices/keyboard-joypad/KeyboardJoypad.cpp b/src/devices/keyboard-joypad/KeyboardJoypad.cpp index 3cc6334..f87e072 100644 --- a/src/devices/keyboard-joypad/KeyboardJoypad.cpp +++ b/src/devices/keyboard-joypad/KeyboardJoypad.cpp @@ -52,11 +52,32 @@ struct ButtonState { ButtonType type{ ButtonType::REGULAR }; std::vector keys; std::vector values; + std::vector joypadAxisIndices; + std::vector joypadButtonIndices; int col{ 0 }; bool active{ false }; bool buttonPressed{ false }; - void render(const ImVec4& button_active_color, const ImVec4& button_inactive_color, const ImVec2& buttonSize, bool hold_active, std::vector& outputValues) + float deadzone(float input, float deadzone) const + { + if (input >= 0) + { + if (input > deadzone) + return (input - deadzone) / (1.0f - deadzone); + else return 0.0; + } + else + { + if (input < -deadzone) + return (input + deadzone) / (1.0f - deadzone); + else return 0.0; + } + } + + void render(const ImVec4& button_active_color, const ImVec4& button_inactive_color, + const ImVec2& buttonSize, bool hold_active, double joypadDeadzone, + const std::vector& joypadAxisValues, const std::vector& joypadButtonValues, + std::vector& outputValues) { bool regularButton = type == ButtonType::REGULAR; bool toggleButton = type == ButtonType::TOGGLE; @@ -74,6 +95,38 @@ struct ButtonState { } } + for (int i : joypadButtonIndices) + { + if (i >= 0 && i < joypadButtonValues.size()) + { + if (joypadButtonValues[static_cast(i)]) + { + anyKeyPressed = true; + } + else + { + anyKeyReleased = true; + } + } + else if (i >= 0 && joypadButtonValues.size() > 0) + { + yCErrorOnce(KEYBOARDJOYPAD) << "The joypad button index" << i << "is out of range."; + } + } + + float valueFromJoypadAxes = 0.0; + for (int i : joypadAxisIndices) + { + if (i >= 0 && i < joypadAxisValues.size()) + { + valueFromJoypadAxes += deadzone(joypadAxisValues[static_cast(i)], static_cast(joypadDeadzone)); + } + else if (i >= 0 && joypadAxisValues.size() > 0) + { + yCErrorOnce(KEYBOARDJOYPAD) << "The joypad axis index" << i << "is out of range."; + } + } + if (anyKeyPressed) { buttonPressed = true; @@ -94,7 +147,7 @@ struct ButtonState { } ImGuiStyle& style = ImGui::GetStyle(); - const ImVec4& buttonColor = active ? button_active_color : button_inactive_color; + const ImVec4& buttonColor = active || std::abs(valueFromJoypadAxes) > 0 ? button_active_color : button_inactive_color; style.Colors[ImGuiCol_Button] = buttonColor; style.Colors[ImGuiCol_ButtonHovered] = buttonColor; style.Colors[ImGuiCol_ButtonActive] = buttonColor; @@ -118,7 +171,7 @@ struct ButtonState { for (auto& value : values) { - outputValues[value.index] += value.sign * active; + outputValues[value.index] += value.sign * (active + valueFromJoypadAxes); } } }; @@ -186,11 +239,13 @@ struct Settings { float min_font_multiplier = 0.5; float max_font_multiplier = 4.0; float gui_period = 0.033f; + float deadzone = 0.1f; int window_width = 1280; int window_height = 720; int buttons_per_row = 3; bool allow_window_closing = false; std::atomic single_threaded { false }; + std::vector joypad_indices; bool parseFromConfigFile(yarp::os::Searchable& cfg) { @@ -229,6 +284,12 @@ struct Settings { return false; } + if (!parseFloat(cfg, "deadzone", 0.f, 1.f, deadzone)) + { + return false; + } + //TODO: Edit README + if (!parseInt(cfg, "window_width", 1, static_cast(1e4), window_width)) { return false; @@ -278,6 +339,57 @@ struct Settings { return false; } + if (cfg.check("joypad_indices")) + { + yarp::os::Value joypadsValue = cfg.find("joypad_indices"); + if (joypadsValue.isInt32() || joypadsValue.isInt64()) + { + int joypad_index = static_cast(joypadsValue.asInt64()); + if (joypad_index > GLFW_JOYSTICK_LAST) + { + yCError(KEYBOARDJOYPAD) << "The value of \"joypad_indices\" is out of range." + << "It should be between" << GLFW_JOYSTICK_1 << "and" << GLFW_JOYSTICK_LAST; + return false; + } + if (joypad_index >= GLFW_JOYSTICK_1) + { + joypad_indices.push_back(joypad_index); + } + } + else if (!joypadsValue.isList()) + { + yCError(KEYBOARDJOYPAD) << "\"joypad_indices\" is found but it is neither an int nor a list."; + return false; + } + + yarp::os::Bottle* joypads_index_list = joypadsValue.asList(); + + for (size_t i = 0; i < joypads_index_list->size(); i++) + { + if (!joypads_index_list->get(i).isInt64() && !joypads_index_list->get(i).isInt32()) + { + yCError(KEYBOARDJOYPAD) << "The value at index" << i << "of the \"joypad_indices\" list is not an integer."; + return false; + } + + int joypad_index = static_cast(joypads_index_list->get(i).asInt64()); + if (joypad_index < GLFW_JOYSTICK_1 || joypad_index > GLFW_JOYSTICK_LAST) + { + yCError(KEYBOARDJOYPAD) << "The value at index" << i << "of the joypads_index list is out of range." + << "It should be between" << GLFW_JOYSTICK_1 << "and" << GLFW_JOYSTICK_LAST; + return false; + } + + joypad_indices.push_back(joypad_index); + } + } + else + { + yCInfo(KEYBOARDJOYPAD) << "The key \"joypads_index\" is not present in the configuration file." + << "Using only the joypad with index 0 (if present)."; + joypad_indices.push_back(GLFW_JOYSTICK_1); + } + return true; } }; @@ -303,6 +415,10 @@ struct AxesSettings std::string wasd_label = "WASD"; std::string arrows_label = "Arrows"; + int ad_joypad_axis_index = 0; + int ws_joypad_axis_index = 1; + int left_right_joypad_axis_index = 2; + int up_down_joypad_axis_index = 3; bool parseFromConfigFile(yarp::os::Searchable& cfg) { @@ -314,62 +430,63 @@ struct AxesSettings axes[Axis::LEFT_RIGHT].push_back({+1, 2}); axes[Axis::UP_DOWN].push_back({+1, 3}); number_of_axes = 4; - return true; } - - if (!cfg.find("axes").isList()) - { - yCError(KEYBOARDJOYPAD) << "The value of \"axes\" is not a list"; - return false; - } - - yarp::os::Bottle* axes_list = cfg.find("axes").asList(); - - for (size_t i = 0; i < axes_list->size(); i++) + else { - if (!axes_list->get(i).isString()) + if (!cfg.find("axes").isList()) { - yCError(KEYBOARDJOYPAD) << "The value at index" << i << "of the axes list is not a string."; + yCError(KEYBOARDJOYPAD) << "The value of \"axes\" is not a list"; return false; } - std::string axis = axes_list->get(i).asString(); + yarp::os::Bottle* axes_list = cfg.find("axes").asList(); - //Check if the first character is a - or a + and remove it - int sign = +1; - if (axis[0] == '-' || axis[0] == '+') + for (size_t i = 0; i < axes_list->size(); i++) { - sign = axis[0] == '-' ? -1 : +1; - axis = axis.substr(1); - } + if (!axes_list->get(i).isString()) + { + yCError(KEYBOARDJOYPAD) << "The value at index" << i << "of the axes list is not a string."; + return false; + } - std::transform(axis.begin(), axis.end(), axis.begin(), ::tolower); + std::string axis = axes_list->get(i).asString(); - if (axis == "ws") - { - axes[Axis::WS].push_back({sign, i}); - } - else if (axis == "ad") - { - axes[Axis::AD].push_back({sign, i}); - } - else if (axis == "up_down") - { - axes[Axis::UP_DOWN].push_back({sign, i}); - } - else if (axis == "left_right") - { - axes[Axis::LEFT_RIGHT].push_back({sign, i}); - } - else if (axis != "" && axis != "none") - { - yCError(KEYBOARDJOYPAD) << "The value of the axes list (" << axis << ") is not a valid axis." - << "Allowed values(\"ws\", \"ad\", \"up_down\", \"left_right\"," - << "eventually with a + or - as prefix, \"none\" and \"\")"; - return false; + //Check if the first character is a - or a + and remove it + int sign = +1; + if (axis[0] == '-' || axis[0] == '+') + { + sign = axis[0] == '-' ? -1 : +1; + axis = axis.substr(1); + } + + std::transform(axis.begin(), axis.end(), axis.begin(), ::tolower); + + if (axis == "ws") + { + axes[Axis::WS].push_back({ sign, i }); + } + else if (axis == "ad") + { + axes[Axis::AD].push_back({ sign, i }); + } + else if (axis == "up_down") + { + axes[Axis::UP_DOWN].push_back({ sign, i }); + } + else if (axis == "left_right") + { + axes[Axis::LEFT_RIGHT].push_back({ sign, i }); + } + else if (axis != "" && axis != "none") + { + yCError(KEYBOARDJOYPAD) << "The value of the axes list (" << axis << ") is not a valid axis." + << "Allowed values(\"ws\", \"ad\", \"up_down\", \"left_right\"," + << "eventually with a + or - as prefix, \"none\" and \"\")"; + return false; + } } + number_of_axes = axes_list->size(); } - number_of_axes = axes_list->size(); if (cfg.check("wasd_label")) { @@ -391,10 +508,34 @@ struct AxesSettings << "Using the default value:" << arrows_label; } + std::vector> joypad_axis_index = { {"ad_joypad_axis_index", ad_joypad_axis_index}, + {"ws_joypad_axis_index", ws_joypad_axis_index}, + {"left_right_joypad_axis_index", left_right_joypad_axis_index}, + {"up_down_joypad_axis_index", up_down_joypad_axis_index} }; + + for (auto& [name, index] : joypad_axis_index) + { + if (!parseInt(cfg, name, -1, 100, index)) + { + return false; + } + } + //TODO: Edit README + return true; } }; +struct JoypadInfo +{ + std::string name; + int index; + int axes; + int buttons; + size_t axes_offset; + size_t buttons_offset; +}; + class yarp::dev::KeyboardJoypad::Impl { public: @@ -419,6 +560,10 @@ class yarp::dev::KeyboardJoypad::Impl std::vector> sticks_values; std::vector buttons_values; + std::vector joypads; + std::vector joypad_axis_values; + std::vector joypad_button_values; + double last_gui_update_time = 0.0; std::thread::id gui_thread_id; @@ -538,6 +683,12 @@ class yarp::dev::KeyboardJoypad::Impl newButton.keys.push_back(static_cast(ImGuiKey_0 + button[0] - '0')); newButton.keys.push_back(static_cast(ImGuiKey_Keypad0 + button[0] - '0')); } + else if (button.size() > 1 && button[0] == 'J' && std::find_if(button.begin() + 1, + button.end(), [](unsigned char c) { return !std::isdigit(c); }) == button.end()) //J followed by a number + { + int joypad_button = std::stoi(button.substr(1)); + newButton.joypadButtonIndices.push_back(joypad_button); //TODO: README + } else if (supportedButtons.find(button) != supportedButtons.end()) { newButton.keys.push_back(supportedButtons[button]); @@ -606,7 +757,9 @@ class yarp::dev::KeyboardJoypad::Impl ImGui::SetWindowFontScale(settings.font_multiplier); } - void renderButtonsTable(ButtonsTable& buttons_table, bool hold_active, std::vector& values) const + void renderButtonsTable(ButtonsTable& buttons_table, bool hold_active, double joypadDeadzone, + const std::vector& joypadAxisValues, const std::vector& joypadButtonValues, + std::vector& values) const { //Define the size of the buttons ImVec2 buttonSize(settings.button_size, settings.button_size); @@ -622,7 +775,7 @@ class yarp::dev::KeyboardJoypad::Impl { ImGui::TableSetColumnIndex(button.col); - button.render(button_active_color, button_inactive_color, buttonSize, hold_active, values); + button.render(button_active_color, button_inactive_color, buttonSize, hold_active, joypadDeadzone, joypadAxisValues, joypadButtonValues, values); } if (row.empty()) { @@ -681,6 +834,40 @@ class yarp::dev::KeyboardJoypad::Impl ImGui_ImplGlfw_InitForOpenGL(this->window, true); ImGui_ImplOpenGL3_Init(); + for (int i = GLFW_JOYSTICK_1; i <= GLFW_JOYSTICK_LAST; ++i) { + if (glfwJoystickPresent(i)) { + int axes_count, button_count; + glfwGetJoystickAxes(i, &axes_count); + glfwGetJoystickButtons(i, &button_count); + std::string name {glfwGetJoystickName(i)}; + this->joypads.push_back({ .name = name, .index = i, .axes = axes_count, .buttons = button_count }); + yCInfo(KEYBOARDJOYPAD) << "Joypad" << name << "is available (index" << i + << "axes =" << axes_count << "buttons = " << button_count << ")."; + } + } + + if (!this->joypads.size()) { + yCInfo(KEYBOARDJOYPAD) << "No joypad found."; + } + + size_t axes_offset = 0; + size_t buttons_offset = 0; + + for (int& joypad_index : this->settings.joypad_indices) + { + if (joypad_index >= this->joypads.size()) + { + yCWarning(KEYBOARDJOYPAD) << "The joypad with index" << joypad_index << "is not available. It will be skipped"; + continue; + } + this->joypads[static_cast(joypad_index)].axes_offset = axes_offset; + this->joypads[static_cast(joypad_index)].buttons_offset = buttons_offset; + axes_offset += this->joypads[static_cast(joypad_index)].axes; + buttons_offset += this->joypads[static_cast(joypad_index)].buttons; + } + this->joypad_axis_values.resize(axes_offset, 0.0); + this->joypad_button_values.resize(buttons_offset, false); + this->button_inactive_color = ImGui::GetStyle().Colors[ImGuiCol_Button]; this->button_active_color = ImVec4(0.7f, 0.5f, 0.3f, 1.0f); @@ -690,14 +877,8 @@ class yarp::dev::KeyboardJoypad::Impl return true; } - void update() + void prepareFrame() { - - if (!this->initialized || this->gui_thread_id != std::this_thread::get_id()) - { - return; - } - glfwPollEvents(); // Start the Dear ImGui frame @@ -720,19 +901,76 @@ class yarp::dev::KeyboardJoypad::Impl value = 0; } + for (auto& value : this->joypad_axis_values) + { + value = 0; + } + + for (size_t i = 0; i < this->joypad_button_values.size(); ++i) //The for(auto& v : ..) does not work with bool arrays + { + this->joypad_button_values[i] = false; + } + } + + void update() + { + + if (!this->initialized || this->gui_thread_id != std::this_thread::get_id()) + { + return; + } + + this->prepareFrame(); + + for (auto& joypad : this->joypads) + { + if (!glfwJoystickPresent(joypad.index)) + { + continue; + } + + int new_axes, new_buttons; + const float* axes = glfwGetJoystickAxes(joypad.index, &new_axes); + const unsigned char* buttons = glfwGetJoystickButtons(joypad.index, &new_buttons); + + for (size_t i = 0; i < std::min(joypad.axes, new_axes); ++i) + { + this->joypad_axis_values[joypad.axes_offset + i] = axes[i]; + } + + for (size_t i = 0; i < std::min(joypad.buttons, new_buttons); ++i) + { + this->joypad_button_values[joypad.buttons_offset + i] = buttons[i] == GLFW_PRESS; + } + } + ImVec2 position(this->settings.button_size, this->settings.button_size); float button_table_height = position.y; for (auto& stick : this->sticks) { position.y = this->settings.button_size; //Keep the sticks on the save level this->prepareWindow(position, stick.name); - this->renderButtonsTable(stick, false, this->axes_values); + this->renderButtonsTable(stick, false, this->settings.deadzone, + this->joypad_axis_values, this->joypad_button_values,this->axes_values); ImGui::End(); position.x += (stick.numberOfColumns + 1) * this->settings.button_size; // Move the next table to the right (n columns + 1 space) position.y += (stick.rows.size() + 1) * this->settings.button_size; // Move the next table down (n rows + 1 space) button_table_height = std::max(button_table_height, position.y); } + //Clamp axes values to the range -1, 1 + for (auto& axis_value : this->axes_values) + { + if (axis_value > 1) + { + axis_value = 1; + } + else if (axis_value < -1) + { + axis_value = -1; + } + } + //Update sticks values from axes values for (size_t i = 0; i < this->sticks_to_axes.size(); ++i) { @@ -749,15 +987,30 @@ class yarp::dev::KeyboardJoypad::Impl ImGui::BeginTable("Buttons_layout", 1, ImGuiTableFlags_NoSavedSettings | ImGuiTableFlags_SizingMask_ | ImGuiTableFlags_BordersInner); ImGui::TableNextRow(); ImGui::TableSetColumnIndex(0); - this->ctrl_button.render(this->button_active_color, this->button_inactive_color, ImVec2(this->settings.button_size, this->settings.button_size), false, this->ctrl_value); + this->ctrl_button.render(this->button_active_color, this->button_inactive_color, ImVec2(this->settings.button_size, this->settings.button_size), false, this->settings.deadzone, + this->joypad_axis_values, this->joypad_button_values, this->ctrl_value); ImGui::TableNextRow(); ImGui::TableSetColumnIndex(0); bool hold_active = this->ctrl_value.front() > 0; - this->renderButtonsTable(this->buttons, hold_active, this->buttons_values); + this->renderButtonsTable(this->buttons, hold_active, this->settings.deadzone, + this->joypad_axis_values, this->joypad_button_values, this->buttons_values); ImGui::EndTable(); ImGui::End(); } + //Apply clamping to buttons values in the range 0, 1 and round them to 0 or 1 + for (auto& button_value : this->buttons_values) + { + if (button_value > 0) + { + button_value = 1; + } + else + { + button_value = 0; + } + } + position.x = this->settings.button_size; //Reset the x position position.y = button_table_height; //Move the next table down @@ -771,6 +1024,7 @@ class yarp::dev::KeyboardJoypad::Impl ImGui::Text("Window size: %d x %d", width, height); ImGui::SliderFloat("Button size", &this->settings.button_size, this->settings.min_button_size, this->settings.max_button_size); ImGui::SliderFloat("Font multiplier", &this->settings.font_multiplier, this->settings.min_font_multiplier, this->settings.max_font_multiplier); + //TODO: Add a slider for the deadzone ImGui::End(); // Rendering @@ -891,7 +1145,7 @@ bool yarp::dev::KeyboardJoypad::open(yarp::os::Searchable& cfg) } wasd.rows.push_back({ {.alias = "A", .type = ButtonType::TOGGLE, .keys = {ImGuiKey_A}, .values = a_values, .col = 0}, - {.alias = "D", .type = ButtonType::TOGGLE, .keys = {ImGuiKey_D}, .values = d_values, .col = 2} }); + {.alias = "D", .type = ButtonType::TOGGLE, .keys = {ImGuiKey_D}, .values = d_values, .joypadAxisIndices = {m_pimpl->axes_settings.ad_joypad_axis_index}, .col = 2} }); } else { @@ -910,7 +1164,7 @@ bool yarp::dev::KeyboardJoypad::open(yarp::os::Searchable& cfg) m_pimpl->sticks_values.back().push_back(0); } - wasd.rows.push_back({ {.alias = "S", .type = ButtonType::TOGGLE, .keys = {ImGuiKey_S}, .values = values, .col = ad} }); + wasd.rows.push_back({ {.alias = "S", .type = ButtonType::TOGGLE, .keys = {ImGuiKey_S}, .values = values, .joypadAxisIndices = {m_pimpl->axes_settings.ws_joypad_axis_index}, .col = ad}}); } } @@ -945,7 +1199,7 @@ bool yarp::dev::KeyboardJoypad::open(yarp::os::Searchable& cfg) m_pimpl->sticks_values.back().push_back(0); } arrows.rows.push_back({ {.alias = "left", .type = ButtonType::TOGGLE, .keys = {ImGuiKey_LeftArrow}, .values = l_values, .col = 0}, - {.alias = "right", .type = ButtonType::TOGGLE, .keys = {ImGuiKey_RightArrow}, .values = r_values,.col = 2} }); + {.alias = "right", .type = ButtonType::TOGGLE, .keys = {ImGuiKey_RightArrow}, .values = r_values,.joypadAxisIndices = {m_pimpl->axes_settings.left_right_joypad_axis_index}, .col = 2} }); } else { @@ -963,7 +1217,7 @@ bool yarp::dev::KeyboardJoypad::open(yarp::os::Searchable& cfg) m_pimpl->sticks_to_axes.back().push_back(values.front().index); m_pimpl->sticks_values.back().push_back(0); } - arrows.rows.push_back({ {.alias = "bottom", .type = ButtonType::TOGGLE, .keys = {ImGuiKey_DownArrow}, .values = values, .col = left_right} }); + arrows.rows.push_back({ {.alias = "bottom", .type = ButtonType::TOGGLE, .keys = {ImGuiKey_DownArrow}, .values = values, .joypadAxisIndices = {m_pimpl->axes_settings.up_down_joypad_axis_index}, .col = left_right} }); } } From e58781316cb158f6fd1bc79f06b983ddb72abda4 Mon Sep 17 00:00:00 2001 From: Stefano Date: Sun, 28 Apr 2024 12:56:28 +0200 Subject: [PATCH 2/7] Added joypad deadzone slider --- .../keyboard-joypad/KeyboardJoypad.cpp | 40 ++++++++++++------- 1 file changed, 25 insertions(+), 15 deletions(-) diff --git a/src/devices/keyboard-joypad/KeyboardJoypad.cpp b/src/devices/keyboard-joypad/KeyboardJoypad.cpp index f87e072..de3543b 100644 --- a/src/devices/keyboard-joypad/KeyboardJoypad.cpp +++ b/src/devices/keyboard-joypad/KeyboardJoypad.cpp @@ -534,6 +534,7 @@ struct JoypadInfo int buttons; size_t axes_offset; size_t buttons_offset; + bool active; }; class yarp::dev::KeyboardJoypad::Impl @@ -563,6 +564,7 @@ class yarp::dev::KeyboardJoypad::Impl std::vector joypads; std::vector joypad_axis_values; std::vector joypad_button_values; + bool using_joypad = false; double last_gui_update_time = 0.0; std::thread::id gui_thread_id; @@ -862,8 +864,10 @@ class yarp::dev::KeyboardJoypad::Impl } this->joypads[static_cast(joypad_index)].axes_offset = axes_offset; this->joypads[static_cast(joypad_index)].buttons_offset = buttons_offset; + this->joypads[static_cast(joypad_index)].active = true; axes_offset += this->joypads[static_cast(joypad_index)].axes; buttons_offset += this->joypads[static_cast(joypad_index)].buttons; + this->using_joypad = true; } this->joypad_axis_values.resize(axes_offset, 0.0); this->joypad_button_values.resize(buttons_offset, false); @@ -922,25 +926,28 @@ class yarp::dev::KeyboardJoypad::Impl this->prepareFrame(); - for (auto& joypad : this->joypads) + if (this->using_joypad) { - if (!glfwJoystickPresent(joypad.index)) + for (auto& joypad : this->joypads) { - continue; - } + if (!glfwJoystickPresent(joypad.index) || !joypad.active) + { + continue; + } - int new_axes, new_buttons; - const float* axes = glfwGetJoystickAxes(joypad.index, &new_axes); - const unsigned char* buttons = glfwGetJoystickButtons(joypad.index, &new_buttons); + int new_axes, new_buttons; + const float* axes = glfwGetJoystickAxes(joypad.index, &new_axes); + const unsigned char* buttons = glfwGetJoystickButtons(joypad.index, &new_buttons); - for (size_t i = 0; i < std::min(joypad.axes, new_axes); ++i) - { - this->joypad_axis_values[joypad.axes_offset + i] = axes[i]; - } + for (size_t i = 0; i < std::min(joypad.axes, new_axes); ++i) + { + this->joypad_axis_values[joypad.axes_offset + i] = axes[i]; + } - for (size_t i = 0; i < std::min(joypad.buttons, new_buttons); ++i) - { - this->joypad_button_values[joypad.buttons_offset + i] = buttons[i] == GLFW_PRESS; + for (size_t i = 0; i < std::min(joypad.buttons, new_buttons); ++i) + { + this->joypad_button_values[joypad.buttons_offset + i] = buttons[i] == GLFW_PRESS; + } } } @@ -1024,7 +1031,10 @@ class yarp::dev::KeyboardJoypad::Impl ImGui::Text("Window size: %d x %d", width, height); ImGui::SliderFloat("Button size", &this->settings.button_size, this->settings.min_button_size, this->settings.max_button_size); ImGui::SliderFloat("Font multiplier", &this->settings.font_multiplier, this->settings.min_font_multiplier, this->settings.max_font_multiplier); - //TODO: Add a slider for the deadzone + if (this->using_joypad) + { + ImGui::SliderFloat("Joypad Deadzone", &this->settings.deadzone, 0.0, 1.0); + } ImGui::End(); // Rendering From 21ea922b0db2df7d2f1c4a5cfa06ffe17974dbf6 Mon Sep 17 00:00:00 2001 From: Stefano Date: Sun, 28 Apr 2024 15:30:13 +0200 Subject: [PATCH 3/7] Fixed usage of joypad axes. Print the values of the joypad --- .../keyboard-joypad/KeyboardJoypad.cpp | 101 +++++++++++++----- 1 file changed, 73 insertions(+), 28 deletions(-) diff --git a/src/devices/keyboard-joypad/KeyboardJoypad.cpp b/src/devices/keyboard-joypad/KeyboardJoypad.cpp index de3543b..96a5803 100644 --- a/src/devices/keyboard-joypad/KeyboardJoypad.cpp +++ b/src/devices/keyboard-joypad/KeyboardJoypad.cpp @@ -28,6 +28,8 @@ #include #include #include +#include +#include #include @@ -52,7 +54,7 @@ struct ButtonState { ButtonType type{ ButtonType::REGULAR }; std::vector keys; std::vector values; - std::vector joypadAxisIndices; + std::vector joypadAxisInputs; std::vector joypadButtonIndices; int col{ 0 }; bool active{ false }; @@ -60,18 +62,9 @@ struct ButtonState { float deadzone(float input, float deadzone) const { - if (input >= 0) - { - if (input > deadzone) - return (input - deadzone) / (1.0f - deadzone); - else return 0.0; - } - else - { - if (input < -deadzone) - return (input + deadzone) / (1.0f - deadzone); - else return 0.0; - } + if (input > deadzone) + return (input - deadzone) / (1.0f - deadzone); + else return 0.0; } void render(const ImVec4& button_active_color, const ImVec4& button_inactive_color, @@ -115,15 +108,16 @@ struct ButtonState { } float valueFromJoypadAxes = 0.0; - for (int i : joypadAxisIndices) + for (auto& axis : joypadAxisInputs) { - if (i >= 0 && i < joypadAxisValues.size()) + if (axis.index >= 0 && axis.index < joypadAxisValues.size()) { - valueFromJoypadAxes += deadzone(joypadAxisValues[static_cast(i)], static_cast(joypadDeadzone)); + valueFromJoypadAxes += deadzone(axis.sign * joypadAxisValues[static_cast(axis.index)], static_cast(joypadDeadzone)); } - else if (i >= 0 && joypadAxisValues.size() > 0) + else if (axis.index >= 0 && joypadAxisValues.size() > 0) { - yCErrorOnce(KEYBOARDJOYPAD) << "The joypad axis index" << i << "is out of range."; + yCError(KEYBOARDJOYPAD) << "The joypad axis index" << axis.index << "is out of range."; + axis.index = -1; } } @@ -147,7 +141,7 @@ struct ButtonState { } ImGuiStyle& style = ImGui::GetStyle(); - const ImVec4& buttonColor = active || std::abs(valueFromJoypadAxes) > 0 ? button_active_color : button_inactive_color; + const ImVec4& buttonColor = active || valueFromJoypadAxes > 0 ? button_active_color : button_inactive_color; style.Colors[ImGuiCol_Button] = buttonColor; style.Colors[ImGuiCol_ButtonHovered] = buttonColor; style.Colors[ImGuiCol_ButtonActive] = buttonColor; @@ -339,7 +333,7 @@ struct Settings { return false; } - if (cfg.check("joypad_indices")) + if (cfg.check("joypad_indices")) //TODO: README { yarp::os::Value joypadsValue = cfg.find("joypad_indices"); if (joypadsValue.isInt32() || joypadsValue.isInt64()) @@ -1034,6 +1028,41 @@ class yarp::dev::KeyboardJoypad::Impl if (this->using_joypad) { ImGui::SliderFloat("Joypad Deadzone", &this->settings.deadzone, 0.0, 1.0); + // Display the joypad values + std::string connectedJoypads = "Connected Joypads: "; + for (size_t i = 0; i < this->joypads.size(); ++i) + { + connectedJoypads += this->joypads[i].name; + if (i != this->joypads.size() - 1) + { + connectedJoypads += ", "; + } + } + ImGui::Text(connectedJoypads.c_str()); + std::string axes_values = "Joypad axes values: "; + for (size_t i = 0; i < this->joypad_axis_values.size(); ++i) + { + // Print the values of the axes in the format "axis_index: value" with a 1 decimal precision + std::stringstream stream; + stream << std::fixed << std::setprecision(2) << this->joypad_axis_values[i]; + std::string sign = this->joypad_axis_values[i] > 0 ? "+" : ""; + axes_values += "<" + std::to_string(i) + "> " + sign + stream.str(); + if (i != this->joypad_axis_values.size() - 1) + { + axes_values += ", "; + } + } + ImGui::Text(axes_values.c_str()); + std::string buttons_values = "Joypad buttons values: "; + for (size_t i = 0; i < this->joypad_button_values.size(); ++i) + { + buttons_values += "<" + std::to_string(i) + "> " + (this->joypad_button_values[i] ? "1" : "0"); + if (i != this->joypad_button_values.size() - 1) + { + buttons_values += ", "; + } + } + ImGui::Text(buttons_values.c_str()); } ImGui::End(); @@ -1137,7 +1166,9 @@ bool yarp::dev::KeyboardJoypad::open(yarp::os::Searchable& cfg) { values.push_back({ .sign = -ws_settings.sign, .index = ws_settings.index }); } - wasd.rows.push_back({ {.alias = "W", .type = ButtonType::TOGGLE, .keys = {ImGuiKey_W}, .values = values, .col = ad} }); + wasd.rows.push_back({ {.alias = "W", .type = ButtonType::TOGGLE, .keys = {ImGuiKey_W}, .values = values, + .joypadAxisInputs = {{.sign = -1, .index = static_cast(m_pimpl->axes_settings.ws_joypad_axis_index)}}, + .col = ad} }); } if (ad) { @@ -1154,8 +1185,12 @@ bool yarp::dev::KeyboardJoypad::open(yarp::os::Searchable& cfg) m_pimpl->sticks_values.back().push_back(0); } - wasd.rows.push_back({ {.alias = "A", .type = ButtonType::TOGGLE, .keys = {ImGuiKey_A}, .values = a_values, .col = 0}, - {.alias = "D", .type = ButtonType::TOGGLE, .keys = {ImGuiKey_D}, .values = d_values, .joypadAxisIndices = {m_pimpl->axes_settings.ad_joypad_axis_index}, .col = 2} }); + wasd.rows.push_back({ {.alias = "A", .type = ButtonType::TOGGLE, .keys = {ImGuiKey_A}, .values = a_values, + .joypadAxisInputs = {{.sign = -1, .index = static_cast(m_pimpl->axes_settings.ad_joypad_axis_index)}}, + .col = 0}, + {.alias = "D", .type = ButtonType::TOGGLE, .keys = {ImGuiKey_D}, .values = d_values, + .joypadAxisInputs = {{.sign = +1, .index = static_cast(m_pimpl->axes_settings.ad_joypad_axis_index)}}, + .col = 2} }); } else { @@ -1174,7 +1209,9 @@ bool yarp::dev::KeyboardJoypad::open(yarp::os::Searchable& cfg) m_pimpl->sticks_values.back().push_back(0); } - wasd.rows.push_back({ {.alias = "S", .type = ButtonType::TOGGLE, .keys = {ImGuiKey_S}, .values = values, .joypadAxisIndices = {m_pimpl->axes_settings.ws_joypad_axis_index}, .col = ad}}); + wasd.rows.push_back({ {.alias = "S", .type = ButtonType::TOGGLE, .keys = {ImGuiKey_S}, .values = values, + .joypadAxisInputs = {{.sign = +1, .index = static_cast(m_pimpl->axes_settings.ws_joypad_axis_index)}}, + .col = ad}}); } } @@ -1192,7 +1229,9 @@ bool yarp::dev::KeyboardJoypad::open(yarp::os::Searchable& cfg) { values.push_back({ .sign = -ws_settings.sign, .index = ws_settings.index }); } - arrows.rows.push_back({ {.alias = "top", .type = ButtonType::TOGGLE, .keys = {ImGuiKey_UpArrow}, .values = values, .col = left_right} }); + arrows.rows.push_back({ {.alias = "top", .type = ButtonType::TOGGLE, .keys = {ImGuiKey_UpArrow}, .values = values, + .joypadAxisInputs = {{.sign = -1, .index = static_cast(m_pimpl->axes_settings.up_down_joypad_axis_index)}}, + .col = left_right} }); } if (left_right) { @@ -1208,8 +1247,12 @@ bool yarp::dev::KeyboardJoypad::open(yarp::os::Searchable& cfg) m_pimpl->sticks_to_axes.back().push_back(l_values.front().index); m_pimpl->sticks_values.back().push_back(0); } - arrows.rows.push_back({ {.alias = "left", .type = ButtonType::TOGGLE, .keys = {ImGuiKey_LeftArrow}, .values = l_values, .col = 0}, - {.alias = "right", .type = ButtonType::TOGGLE, .keys = {ImGuiKey_RightArrow}, .values = r_values,.joypadAxisIndices = {m_pimpl->axes_settings.left_right_joypad_axis_index}, .col = 2} }); + arrows.rows.push_back({ {.alias = "left", .type = ButtonType::TOGGLE, .keys = {ImGuiKey_LeftArrow}, .values = l_values, + .joypadAxisInputs = {{.sign = -1, .index = static_cast(m_pimpl->axes_settings.left_right_joypad_axis_index)}}, + .col = 0}, + {.alias = "right", .type = ButtonType::TOGGLE, .keys = {ImGuiKey_RightArrow}, .values = r_values, + .joypadAxisInputs = {{.sign = +1, .index = static_cast(m_pimpl->axes_settings.left_right_joypad_axis_index)}}, + .col = 2} }); } else { @@ -1227,7 +1270,9 @@ bool yarp::dev::KeyboardJoypad::open(yarp::os::Searchable& cfg) m_pimpl->sticks_to_axes.back().push_back(values.front().index); m_pimpl->sticks_values.back().push_back(0); } - arrows.rows.push_back({ {.alias = "bottom", .type = ButtonType::TOGGLE, .keys = {ImGuiKey_DownArrow}, .values = values, .joypadAxisIndices = {m_pimpl->axes_settings.up_down_joypad_axis_index}, .col = left_right} }); + arrows.rows.push_back({ {.alias = "bottom", .type = ButtonType::TOGGLE, .keys = {ImGuiKey_DownArrow}, .values = values, + .joypadAxisInputs = {{.sign = +1, .index = static_cast(m_pimpl->axes_settings.up_down_joypad_axis_index)}}, + .col = left_right} }); } } From b7f24218b1179bb55b55c0a5dc8663dea8f1068a Mon Sep 17 00:00:00 2001 From: Stefano Date: Mon, 29 Apr 2024 12:51:27 +0200 Subject: [PATCH 4/7] Using different include path on MacOS --- src/devices/keyboard-joypad/KeyboardJoypad.cpp | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/devices/keyboard-joypad/KeyboardJoypad.cpp b/src/devices/keyboard-joypad/KeyboardJoypad.cpp index 96a5803..8c49ee4 100644 --- a/src/devices/keyboard-joypad/KeyboardJoypad.cpp +++ b/src/devices/keyboard-joypad/KeyboardJoypad.cpp @@ -11,7 +11,12 @@ #define GL_SILENCE_DEPRECATION #include + +#if defined(__APPLE__) +#include +#else #include +#endif #include From ce36ced46e8a93e4152b746fded8c0dcd17d72c0 Mon Sep 17 00:00:00 2001 From: Stefano Date: Mon, 29 Apr 2024 12:57:29 +0200 Subject: [PATCH 5/7] Enabled macOS testing in CI --- .github/workflows/conda-forge-ci.yml | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/.github/workflows/conda-forge-ci.yml b/.github/workflows/conda-forge-ci.yml index 02a7b79..ca48f7a 100644 --- a/.github/workflows/conda-forge-ci.yml +++ b/.github/workflows/conda-forge-ci.yml @@ -15,18 +15,24 @@ jobs: strategy: matrix: build_type: [Release] - os: [ubuntu-latest, windows-2019] + os: [ubuntu-latest, windows-2019, macos-14, macos-12] imgui: [mamba, vendored] fail-fast: false steps: - uses: actions/checkout@v2 - - uses: conda-incubator/setup-miniconda@v2 + - uses: conda-incubator/setup-miniconda@v3 + if: matrix.os != 'macos-14' with: miniforge-variant: Mambaforge miniforge-version: latest + - uses: conda-incubator/setup-miniconda@v3 + if: matrix.os == 'macos-14' + with: + installer-url: https://github.com/conda-forge/miniforge/releases/download/23.11.0-0/Mambaforge-23.11.0-0-MacOSX-arm64.sh + - name: Dependencies shell: bash -l {0} run: | @@ -50,8 +56,8 @@ jobs: # See https://github.com/robotology/robotology-superbuild/pull/1606 mamba install expat freeglut libselinux-cos7-x86_64 xorg-libxau libxcb xorg-libxdamage xorg-libxext xorg-libxfixes xorg-libxxf86vm xorg-libxrandr mesa-libgl-cos7-x86_64 mesa-libgl-devel-cos7-x86_64 - - name: Configure [Linux] - if: contains(matrix.os, 'ubuntu') + - name: Configure [Linux, macOS] + if: contains(matrix.os, 'ubuntu') || contains(matrix.os, 'macos') shell: bash -l {0} run: | mkdir -p build From 8dbedeb13ee7edfaaff715f53befea3bddbc59ab Mon Sep 17 00:00:00 2001 From: Stefano Date: Mon, 29 Apr 2024 14:56:35 +0200 Subject: [PATCH 6/7] Added print of output values --- .../keyboard-joypad/KeyboardJoypad.cpp | 28 +++++++++++++++++++ 1 file changed, 28 insertions(+) diff --git a/src/devices/keyboard-joypad/KeyboardJoypad.cpp b/src/devices/keyboard-joypad/KeyboardJoypad.cpp index 8c49ee4..6541e24 100644 --- a/src/devices/keyboard-joypad/KeyboardJoypad.cpp +++ b/src/devices/keyboard-joypad/KeyboardJoypad.cpp @@ -1043,6 +1043,7 @@ class yarp::dev::KeyboardJoypad::Impl connectedJoypads += ", "; } } + ImGui::Separator(); ImGui::Text(connectedJoypads.c_str()); std::string axes_values = "Joypad axes values: "; for (size_t i = 0; i < this->joypad_axis_values.size(); ++i) @@ -1069,6 +1070,33 @@ class yarp::dev::KeyboardJoypad::Impl } ImGui::Text(buttons_values.c_str()); } + ImGui::Separator(); + std::string output_axes_values = "Output axes values: "; + for (size_t i = 0; i < this->axes_values.size(); ++i) + { + // Print the values of the axes in the format "axis_index: value" with a 1 decimal precision + std::stringstream stream; + stream << std::fixed << std::setprecision(2) << this->axes_values[i]; + std::string sign = this->axes_values[i] > 0 ? "+" : ""; + output_axes_values += "<" + std::to_string(i) + "> " + sign + stream.str(); + if (i != this->axes_values.size() - 1) + { + output_axes_values += ", "; + } + } + ImGui::Text(output_axes_values.c_str()); + std::string output_buttons_values = "Output buttons values: "; + for (size_t i = 0; i < this->buttons_values.size(); ++i) + { + std::stringstream stream; + stream << std::fixed << std::setprecision(1) << this->buttons_values[i]; + output_buttons_values += "<" + std::to_string(i) + "> " + stream.str(); + if (i != this->buttons_values.size() - 1) + { + output_buttons_values += ", "; + } + } + ImGui::Text(output_buttons_values.c_str()); ImGui::End(); // Rendering From af10579478a80ac9ca341c90ed7efa12d8c46af2 Mon Sep 17 00:00:00 2001 From: Stefano Date: Mon, 29 Apr 2024 18:54:14 +0200 Subject: [PATCH 7/7] Edired README and removed TODOs --- README.md | 22 ++++++++++++++++--- .../keyboard-joypad/KeyboardJoypad.cpp | 20 +++++++++-------- 2 files changed, 30 insertions(+), 12 deletions(-) diff --git a/README.md b/README.md index 9ee0ac3..b3434b2 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,16 @@ # yarp-device-keyboard-joypad -Repository containing the implementation of a yarp device to use a keyboard as a joypad. +Repository containing the implementation of a yarp device to emulate a joypad using the keyboard, the touchscreen and/or an actual joypad. + +## Example usage +By running the following command, +``` +yarpdev --device keyboardJoypad --name /keyboard --buttons "(none, test, c:prova, c:prova, v, b, test, c:prova, 8, g-h:Multi)" --axes "(ws, ad, ad, none, -left_right, up_down)" +``` +a GUI will be opened with a similar layout: + +https://github.com/ami-iit/yarp-device-keyboard-joypad/assets/18591940/7717159e-8eef-4a5b-9273-814fa55f5560 + + ## Dependencies - [``YARP``](https://github.com/robotology/yarp) (>= 3.4.0) @@ -30,8 +41,13 @@ The device can be configured using the following parameters. They are all option - ``axes``: definition of the list of axes. The allowed values are "ws", "ad", "up_down" and "left_right". It is possible to select the default sign for an axis prepending a "+" or a "-" to the axis name. For example, "+ws" will set the "ws" axis with the default sign, while "-ws" will set the "ws" axis with the inverted sign. It is also possible to repeat some axis, and use "none" or "" to have dummy axes with always zero value. The order matters. (default: ("ad", "ws", "left_right", "up_down")) - ``wasd_label``: label for the "WASD" widget (default: "WASD") - ``arrows_label``: label for the "Arrows" widget (default: "Arrows") -- ``buttons``: definition of the list of buttons. The allowed values are all the letters from A to Z, all the numbers from 0 to 9, "SPACE", "ENTER", "ESCAPE", "BACKSPACE", "DELETE", "LEFT", "RIGHT", "UP", "DOWN". It is possible to repeat some button. It is possible to specify an alias after a ":". For example "A:Some Text" will create a button with the label "Some Text" that can be activated by pressing "A". It is possible to use "none" or "" to indicate a dummy button always zero. It is possible to spcify multiple keys using the "-" delimiter. For example, "A-B:Some Text" creates a button named "Some Text" that can be activated pressing either A or B. It is possible to repeat buttons. The order matters. (default: ()) - +- ``buttons``: definition of the list of buttons. The allowed values are all the letters from A to Z, all the numbers from 0 to 9, "SPACE", "ENTER", "ESCAPE", "BACKSPACE", "DELETE", "LEFT", "RIGHT", "UP", "DOWN". With "J" followed by a number it is possible to map a joypad button, when connected. It is possible to repeat some button. It is possible to specify an alias after a ":". For example "A:Some Text" will create a button with the label "Some Text" that can be activated by pressing "A". It is possible to use "none" or "" to indicate a dummy button always zero. It is possible to specify multiple keys using the "-" delimiter. For example, "A-B-J5:Some Text" creates a button named "Some Text" that can be activated pressing either A, or B, or the joypad button with index 5. It is possible to repeat buttons. The order matters. (default: ()) +- ``joypad_indices``: definition of the joypads to consider in case multiple joypads are connected. The value can be a single integer or a list of integers. The indices are 0-based. In case a joypad is not found, it is ignored. The axis and buttons values are stack together in the order provided. (default: 0) +- ``joypad_deadzone``: deadzone for the joypad axes (default: 0.1) +- ``ad_joypad_axis_index``: index of the axis for the "ad" axis in the joypad (default: 0) +- ``ws_joypad_axis_index``: index of the axis for the "ws" axis in the joypad (default: 1) +- ``left_right_joypad_axis_index``: index of the axis for the "left_right" axis in the joypad (default: 2) +- ``up_down_joypad_axis_index``: index of the axis for the "up_down" axis in the joypad (default: 3) ## Maintainers * Stefano Dafarra ([@S-Dafarra](https://github.com/S-Dafarra)) \ No newline at end of file diff --git a/src/devices/keyboard-joypad/KeyboardJoypad.cpp b/src/devices/keyboard-joypad/KeyboardJoypad.cpp index 6541e24..9d4cf4e 100644 --- a/src/devices/keyboard-joypad/KeyboardJoypad.cpp +++ b/src/devices/keyboard-joypad/KeyboardJoypad.cpp @@ -283,11 +283,10 @@ struct Settings { return false; } - if (!parseFloat(cfg, "deadzone", 0.f, 1.f, deadzone)) + if (!parseFloat(cfg, "joypad_deadzone", 0.f, 1.f, deadzone)) { return false; } - //TODO: Edit README if (!parseInt(cfg, "window_width", 1, static_cast(1e4), window_width)) { @@ -338,7 +337,7 @@ struct Settings { return false; } - if (cfg.check("joypad_indices")) //TODO: README + if (cfg.check("joypad_indices")) { yarp::os::Value joypadsValue = cfg.find("joypad_indices"); if (joypadsValue.isInt32() || joypadsValue.isInt64()) @@ -519,7 +518,6 @@ struct AxesSettings return false; } } - //TODO: Edit README return true; } @@ -688,7 +686,7 @@ class yarp::dev::KeyboardJoypad::Impl button.end(), [](unsigned char c) { return !std::isdigit(c); }) == button.end()) //J followed by a number { int joypad_button = std::stoi(button.substr(1)); - newButton.joypadButtonIndices.push_back(joypad_button); //TODO: README + newButton.joypadButtonIndices.push_back(joypad_button); } else if (supportedButtons.find(button) != supportedButtons.end()) { @@ -1032,9 +1030,9 @@ class yarp::dev::KeyboardJoypad::Impl ImGui::SliderFloat("Font multiplier", &this->settings.font_multiplier, this->settings.min_font_multiplier, this->settings.max_font_multiplier); if (this->using_joypad) { - ImGui::SliderFloat("Joypad Deadzone", &this->settings.deadzone, 0.0, 1.0); + ImGui::SliderFloat("Joypad deadzone", &this->settings.deadzone, 0.0, 1.0); // Display the joypad values - std::string connectedJoypads = "Connected Joypads: "; + std::string connectedJoypads = "Connected joypads: "; for (size_t i = 0; i < this->joypads.size(); ++i) { connectedJoypads += this->joypads[i].name; @@ -1051,7 +1049,7 @@ class yarp::dev::KeyboardJoypad::Impl // Print the values of the axes in the format "axis_index: value" with a 1 decimal precision std::stringstream stream; stream << std::fixed << std::setprecision(2) << this->joypad_axis_values[i]; - std::string sign = this->joypad_axis_values[i] > 0 ? "+" : ""; + std::string sign = this->joypad_axis_values[i] >= 0 ? "+" : ""; axes_values += "<" + std::to_string(i) + "> " + sign + stream.str(); if (i != this->joypad_axis_values.size() - 1) { @@ -1077,7 +1075,7 @@ class yarp::dev::KeyboardJoypad::Impl // Print the values of the axes in the format "axis_index: value" with a 1 decimal precision std::stringstream stream; stream << std::fixed << std::setprecision(2) << this->axes_values[i]; - std::string sign = this->axes_values[i] > 0 ? "+" : ""; + std::string sign = this->axes_values[i] >= 0 ? "+" : ""; output_axes_values += "<" + std::to_string(i) + "> " + sign + stream.str(); if (i != this->axes_values.size() - 1) { @@ -1086,6 +1084,10 @@ class yarp::dev::KeyboardJoypad::Impl } ImGui::Text(output_axes_values.c_str()); std::string output_buttons_values = "Output buttons values: "; + if (this->buttons_values.empty()) + { + output_buttons_values += "None"; + } for (size_t i = 0; i < this->buttons_values.size(); ++i) { std::stringstream stream;