diff --git a/core/input/input.cpp b/core/input/input.cpp index 6b9fec6f6003..e8b4be7660be 100644 --- a/core/input/input.cpp +++ b/core/input/input.cpp @@ -73,6 +73,138 @@ static const char *_joy_axes[(size_t)JoyAxis::SDL_MAX] = { "righttrigger", }; +static const char *_joy_button_names_unknown[(size_t)JoyButton::SDL_MAX] = { + "Bottom Action", + "Right Action", + "Left Action", + "Top Action", + "Select", + "Guide", + "Start", + "Left Stick", + "Right Stick", + "Left Shoulder", + "Right Shoulder", + "D-pad Up", + "D-pad Down", + "D-pad Left", + "D-pad Right", + "Misc", + "Paddle 1", + "Paddle 2", + "Paddle 3", + "Paddle 4", + "Touchpad", +}; + +static const char *_joy_button_names_nintendo_generic[(size_t)JoyButton::SDL_MAX] = { + "B", + "A", + "Y", + "X", + "-", + "", + "+", + "", + "", + "L", + "R", + "", + "", + "", + "", + "Capture", + "", + "", + "", + "", + "", +}; + +static const char *_joy_button_names_playstation_generic[(size_t)JoyButton::SDL_MAX] = { + "Cross", + "Circle", + "Square", + "Triangle", + "Select", + "PS", + "", + "L3", + "R3", + "L1", + "R1", + "", + "", + "", + "", + "Microphone", + "", + "", + "", + "", + "", +}; + +static const char *_joy_button_names_xbox_generic[(size_t)JoyButton::SDL_MAX] = { + "A", + "B", + "X", + "Y", + "Back", + "Home", + "Menu", + "L/LS", + "R/RS", + "LB", + "RB", + "", + "", + "", + "", + "Share", + "", + "", + "", + "", + "", +}; + +static const char *_joy_axis_names_unknown[(size_t)JoyAxis::SDL_MAX] = { + "Left Stick X-Axis", + "Left Stick Y-Axis", + "Right Stick X-Axis", + "Right Stick Y-Axis", + "Left Trigger", + "Right Trigger", +}; + +static const char *_joy_axis_names_nintendo_generic[(size_t)JoyAxis::SDL_MAX] = { + "", + "", + "", + "", + "ZL", + "ZR", +}; + +static const char *_joy_axis_names_playstation_generic[(size_t)JoyAxis::SDL_MAX] = { + "", + "", + "", + "", + "L2", + "R2", +}; + +static const char *_joy_axis_names_xbox_generic[(size_t)JoyAxis::SDL_MAX] = { + "", + "", + "", + "", + "LT", + "RT", +}; + Input *Input::singleton = nullptr; void (*Input::set_mouse_mode_func)(Input::MouseMode) = nullptr; @@ -134,12 +266,15 @@ void Input::_bind_methods() { ClassDB::bind_method(D_METHOD("is_joy_known", "device"), &Input::is_joy_known); ClassDB::bind_method(D_METHOD("get_joy_axis", "device", "axis"), &Input::get_joy_axis); ClassDB::bind_method(D_METHOD("get_joy_name", "device"), &Input::get_joy_name); + ClassDB::bind_method(D_METHOD("get_joy_scheme", "device"), &Input::get_joy_scheme); ClassDB::bind_method(D_METHOD("get_joy_guid", "device"), &Input::get_joy_guid); ClassDB::bind_method(D_METHOD("get_joy_info", "device"), &Input::get_joy_info); ClassDB::bind_method(D_METHOD("should_ignore_device", "vendor_id", "product_id"), &Input::should_ignore_device); ClassDB::bind_method(D_METHOD("get_connected_joypads"), &Input::get_connected_joypads); ClassDB::bind_method(D_METHOD("get_joy_vibration_strength", "device"), &Input::get_joy_vibration_strength); ClassDB::bind_method(D_METHOD("get_joy_vibration_duration", "device"), &Input::get_joy_vibration_duration); + ClassDB::bind_method(D_METHOD("get_joy_button_string", "button", "scheme"), &Input::get_joy_button_string, DEFVAL(JoyScheme::JOY_SCHEME_UNKNOWN)); + ClassDB::bind_method(D_METHOD("get_joy_axis_string", "axis", "value", "scheme"), &Input::get_joy_axis_string, DEFVAL(0), DEFVAL(JoyScheme::JOY_SCHEME_UNKNOWN)); ClassDB::bind_method(D_METHOD("start_joy_vibration", "device", "weak_magnitude", "strong_magnitude", "duration"), &Input::start_joy_vibration, DEFVAL(0)); ClassDB::bind_method(D_METHOD("stop_joy_vibration", "device"), &Input::stop_joy_vibration); ClassDB::bind_method(D_METHOD("vibrate_handheld", "duration_ms", "amplitude"), &Input::vibrate_handheld, DEFVAL(500), DEFVAL(-1.0)); @@ -201,6 +336,11 @@ void Input::_bind_methods() { BIND_ENUM_CONSTANT(CURSOR_HSPLIT); BIND_ENUM_CONSTANT(CURSOR_HELP); + BIND_ENUM_CONSTANT(JOY_SCHEME_UNKNOWN); + BIND_ENUM_CONSTANT(JOY_SCHEME_NINTENDO_GENERIC); + BIND_ENUM_CONSTANT(JOY_SCHEME_PLAYSTATION_GENERIC); + BIND_ENUM_CONSTANT(JOY_SCHEME_XBOX_GENERIC); + ADD_SIGNAL(MethodInfo("joy_connection_changed", PropertyInfo(Variant::INT, "device"), PropertyInfo(Variant::BOOL, "connected"))); } @@ -519,9 +659,26 @@ float Input::get_joy_axis(int p_device, JoyAxis p_axis) const { } } -String Input::get_joy_name(int p_idx) { +String Input::get_joy_name(int p_device) { _THREAD_SAFE_METHOD_ - return joy_names[p_idx].name; + return joy_names[p_device].name; +} + +Input::JoyScheme Input::get_joy_scheme(int p_device) { + String joy_name = get_joy_name(p_device).to_lower(); + + if (joy_name.contains("nintendo") || joy_name.contains("joy-con") || joy_name.contains("gamecube")) { + return JOY_SCHEME_NINTENDO_GENERIC; + } + if (joy_name.contains("sony") || joy_name.contains("playstation") || joy_name.contains("dualshock") || + joy_name.contains("ps1") || joy_name.contains("ps2") || joy_name.contains("ps3") || joy_name.contains("ps4") || joy_name.contains("ps5")) { + return JOY_SCHEME_PLAYSTATION_GENERIC; + } + if (joy_name.contains("microsoft") || joy_name.contains("xbox")) { + return JOY_SCHEME_XBOX_GENERIC; + } + + return JOY_SCHEME_UNKNOWN; } Vector2 Input::get_joy_vibration_strength(int p_device) { @@ -548,6 +705,73 @@ float Input::get_joy_vibration_duration(int p_device) { } } +String Input::get_joy_button_string(JoyButton p_button, JoyScheme p_scheme) { + String button_name; + switch (p_scheme) { + case JOY_SCHEME_NINTENDO_GENERIC: { + button_name = _joy_button_names_nintendo_generic[(int)p_button]; + } break; + case JOY_SCHEME_PLAYSTATION_GENERIC: { + button_name = _joy_button_names_playstation_generic[(int)p_button]; + } break; + case JOY_SCHEME_XBOX_GENERIC: { + button_name = _joy_button_names_xbox_generic[(int)p_button]; + } break; + default: { + } + } + + if (button_name.is_empty()) { + button_name = _joy_button_names_unknown[(int)p_button]; + } + + return button_name; +} + +String Input::get_joy_axis_string(JoyAxis p_axis, float p_value, JoyScheme p_scheme) { + if (p_axis == JoyAxis::TRIGGER_LEFT || p_axis == JoyAxis::TRIGGER_RIGHT) { + String axis_name; + switch (p_scheme) { + case JOY_SCHEME_NINTENDO_GENERIC: { + axis_name = _joy_axis_names_nintendo_generic[(int)p_axis]; + } break; + case JOY_SCHEME_PLAYSTATION_GENERIC: { + axis_name = _joy_axis_names_playstation_generic[(int)p_axis]; + } break; + case JOY_SCHEME_XBOX_GENERIC: { + axis_name = _joy_axis_names_xbox_generic[(int)p_axis]; + } break; + default: { + } + } + + if (axis_name.is_empty()) { + axis_name = _joy_axis_names_unknown[(int)p_axis]; + } + + return axis_name; + } + + if (p_value != 0.0) { + String stick; + if (p_axis == JoyAxis::LEFT_X || p_axis == JoyAxis::LEFT_Y) { + stick = "Left Stick "; + } else if (p_axis == JoyAxis::RIGHT_X || p_axis == JoyAxis::RIGHT_Y) { + stick = "Right Stick "; + } + + if (p_axis == JoyAxis::LEFT_X || p_axis == JoyAxis::RIGHT_X) { + stick += p_value < 0 ? "Left" : "Right"; + } else if (p_axis == JoyAxis::LEFT_Y || p_axis == JoyAxis::RIGHT_Y) { + stick += p_value < 0 ? "Up" : "Down"; + } + + return stick; + } + + return _joy_axis_names_unknown[(int)p_axis]; +} + static String _hex_str(uint8_t p_byte) { static const char *dict = "0123456789abcdef"; char ret[3]; @@ -559,13 +783,13 @@ static String _hex_str(uint8_t p_byte) { return ret; } -void Input::joy_connection_changed(int p_idx, bool p_connected, const String &p_name, const String &p_guid, const Dictionary &p_joypad_info) { +void Input::joy_connection_changed(int p_device, bool p_connected, const String &p_name, const String &p_guid, const Dictionary &p_joypad_info) { _THREAD_SAFE_METHOD_ // Clear the pressed status if a Joypad gets disconnected. if (!p_connected) { for (KeyValue &E : action_states) { - HashMap::Iterator it = E.value.device_states.find(p_idx); + HashMap::Iterator it = E.value.device_states.find(p_device); if (it) { E.value.device_states.remove(it); _update_action_cache(E.key, E.value); @@ -598,17 +822,17 @@ void Input::joy_connection_changed(int p_idx, bool p_connected, const String &p_ } else { js.connected = false; for (int i = 0; i < (int)JoyButton::MAX; i++) { - JoyButton c = _combine_device((JoyButton)i, p_idx); + JoyButton c = _combine_device((JoyButton)i, p_device); joy_buttons_pressed.erase(c); } for (int i = 0; i < (int)JoyAxis::MAX; i++) { - set_joy_axis(p_idx, (JoyAxis)i, 0.0f); + set_joy_axis(p_device, (JoyAxis)i, 0.0f); } } - joy_names[p_idx] = js; + joy_names[p_device] = js; // Ensure this signal is emitted on the main thread, as some platforms (e.g. Linux) call this from a different thread. - call_deferred("emit_signal", SNAME("joy_connection_changed"), p_idx, p_connected); + call_deferred("emit_signal", SNAME("joy_connection_changed"), p_device, p_connected); } Vector3 Input::get_gravity() const { @@ -1367,6 +1591,22 @@ void Input::_button_event(int p_device, JoyButton p_index, bool p_pressed) { ievent->set_pressed(p_pressed); parse_input_event(ievent); + + if (p_index == JoyButton::A) { + Ref s_ievent; + s_ievent.instantiate(); + s_ievent->set_device(p_device); + s_ievent->set_button_index(get_joy_scheme(p_device) == JOY_SCHEME_NINTENDO_GENERIC ? JoyButton::SEMANTIC_NO : JoyButton::SEMANTIC_YES); + s_ievent->set_pressed(p_pressed); + parse_input_event(s_ievent); + } else if (p_index == JoyButton::B) { + Ref s_ievent; + s_ievent.instantiate(); + s_ievent->set_device(p_device); + s_ievent->set_button_index(get_joy_scheme(p_device) == JOY_SCHEME_NINTENDO_GENERIC ? JoyButton::SEMANTIC_YES : JoyButton::SEMANTIC_NO); + s_ievent->set_pressed(p_pressed); + parse_input_event(s_ievent); + } } void Input::_axis_event(int p_device, JoyAxis p_axis, float p_value) { diff --git a/core/input/input.h b/core/input/input.h index 8fc1b9c5dacf..0fd57b0ae5c9 100644 --- a/core/input/input.h +++ b/core/input/input.h @@ -30,6 +30,7 @@ #pragma once +#include "core/input/input_enums.h" #include "core/input/input_event.h" #include "core/object/object.h" #include "core/os/keyboard.h" @@ -75,7 +76,14 @@ class Input : public Object { CURSOR_VSPLIT, CURSOR_HSPLIT, CURSOR_HELP, - CURSOR_MAX + CURSOR_MAX, + }; + + enum JoyScheme { + JOY_SCHEME_UNKNOWN, + JOY_SCHEME_NINTENDO_GENERIC, + JOY_SCHEME_PLAYSTATION_GENERIC, + JOY_SCHEME_XBOX_GENERIC, }; enum { @@ -315,12 +323,15 @@ class Input : public Object { Vector2 get_vector(const StringName &p_negative_x, const StringName &p_positive_x, const StringName &p_negative_y, const StringName &p_positive_y, float p_deadzone = -1.0f) const; float get_joy_axis(int p_device, JoyAxis p_axis) const; - String get_joy_name(int p_idx); + String get_joy_name(int p_device); + JoyScheme get_joy_scheme(int p_device); TypedArray get_connected_joypads(); Vector2 get_joy_vibration_strength(int p_device); float get_joy_vibration_duration(int p_device); uint64_t get_joy_vibration_timestamp(int p_device); - void joy_connection_changed(int p_idx, bool p_connected, const String &p_name, const String &p_guid = "", const Dictionary &p_joypad_info = Dictionary()); + String get_joy_button_string(JoyButton p_button, JoyScheme p_scheme = JoyScheme::JOY_SCHEME_UNKNOWN); + String get_joy_axis_string(JoyAxis p_axis, float p_value = 0, JoyScheme p_scheme = JoyScheme::JOY_SCHEME_UNKNOWN); + void joy_connection_changed(int p_device, bool p_connected, const String &p_name, const String &p_guid = "", const Dictionary &p_joypad_info = Dictionary()); Vector3 get_gravity() const; Vector3 get_accelerometer() const; @@ -402,3 +413,4 @@ class Input : public Object { VARIANT_ENUM_CAST(Input::MouseMode); VARIANT_ENUM_CAST(Input::CursorShape); +VARIANT_ENUM_CAST(Input::JoyScheme); diff --git a/core/input/input_enums.h b/core/input/input_enums.h index 16ddd0c474b1..1e350b9d2b47 100644 --- a/core/input/input_enums.h +++ b/core/input/input_enums.h @@ -62,6 +62,9 @@ enum class JoyAxis { enum class JoyButton { INVALID = -1, + SEMANTIC_YES = -2, + SEMANTIC_NO = -3, + MIN = -4, A = 0, B = 1, X = 2, diff --git a/core/input/input_event.cpp b/core/input/input_event.cpp index 6e8cdc4b8e46..c279c60f768c 100644 --- a/core/input/input_event.cpp +++ b/core/input/input_event.cpp @@ -1285,11 +1285,22 @@ static const char *_joy_button_descriptions[(size_t)JoyButton::SDL_MAX] = { TTRC("PS4/5 Touchpad"), }; +static const char *_virtual_joy_button_descriptions[(size_t)JoyButton::INVALID - (size_t)JoyButton::MIN - 1] = { + TTRC("Semantic \"Yes\", Sony Cross, Xbox A, Nintendo A"), + TTRC("Semantic \"No\", Sony Circle, Xbox B, Nintendo B"), +}; + String InputEventJoypadButton::as_text() const { String text = vformat(RTR("Joypad Button %d"), (int64_t)button_index); if (button_index > JoyButton::INVALID && button_index < JoyButton::SDL_MAX) { text += vformat(" (%s)", TTRGET(_joy_button_descriptions[(size_t)button_index])); + } else if (button_index < JoyButton::INVALID && button_index > JoyButton::MIN) { + text = vformat(RTR("Virtual Joypad Button %d"), (int64_t)button_index); + size_t virtual_index = (size_t)button_index; + virtual_index -= ((size_t)JoyButton::INVALID - 1); // Zero-indexed + virtual_index *= -1; + text += vformat(" (%s)", TTRGET(_virtual_joy_button_descriptions[virtual_index])); } if (pressure != 0) { diff --git a/doc/classes/Input.xml b/doc/classes/Input.xml index ca54a44f489a..d9789eb901c7 100644 --- a/doc/classes/Input.xml +++ b/doc/classes/Input.xml @@ -117,6 +117,38 @@ Returns the current value of the joypad axis at given index (see [enum JoyAxis]). + + + + + + + Returns the string equivalent of the given [param axis]. If [param value] is set to anything besides [code]0[/code], the axis orientation will be replaced with the direction. If [param scheme] is set, it will adapt the result to the given control scheme (see [method get_joy_scheme]). + [b]Examples:[/b] + [codeblocks] + [gdscript] + get_joy_axis_string(JoyAxis.LEFT_X) # Returns "Left Stick X-Axis". + get_joy_axis_string(JoyAxis.LEFT_X, -0.5) # Returns "Left Stick Left". + get_joy_axis_string(JoyAxis.TRIGGER_RIGHT, 0.5, Input.JoyScheme.XBOX) # Returns "RT". + [/gdscript] + [/codeblocks] + + + + + + + + Returns the string equivalent of the given gamepad [param button]. If [param scheme] is set, it will adapt the result to the given control scheme (see [method get_joy_scheme]). + [b]Examples:[/b] + [codeblocks] + [gdscript] + get_joy_button_string(JoyButton.A) # Returns "Bottom Action". + get_joy_button_string(JoyButton.A, Input.JoyScheme.JOY_SCHEME_PLAYSTATION_GENERIC) # Returns "Cross". + [/gdscript] + [/codeblocks] + + @@ -149,6 +181,14 @@ Returns the name of the joypad at the specified device index, e.g. [code]PS4 Controller[/code]. Godot uses the [url=https://github.com/gabomdq/SDL_GameControllerDB]SDL2 game controller database[/url] to determine gamepad names. + + + + + Returns the device's control scheme. + [b]Note:[/b] On non-official gamepads, the most likely result will be [constant JOY_SCHEME_UNKNOWN]. + + @@ -523,5 +563,17 @@ Help cursor. Usually a question mark. + + Unspecified gamepad button layout. + + + Generic button layout used by Nintendo gamepads. + + + Generic button layout used by PlayStation gamepads. + + + Generic button layout used by Xbox gamepads. + diff --git a/editor/input_event_configuration_dialog.cpp b/editor/input_event_configuration_dialog.cpp index 8d9617bc90fd..7187f876a748 100644 --- a/editor/input_event_configuration_dialog.cpp +++ b/editor/input_event_configuration_dialog.cpp @@ -326,6 +326,21 @@ void InputEventConfigurationDialog::_update_input_list() { joyb_root->set_collapsed(collapse); joyb_root->set_meta("__type", INPUT_JOY_BUTTON); + for (int i = -2; i > (int)JoyButton::MIN; i--) { + Ref joyb; + joyb.instantiate(); + joyb->set_button_index((JoyButton)i); + String desc = EventListenerLineEdit::get_event_text(joyb, false); + + if (!search_term.is_empty() && !desc.contains(search_term)) { + continue; + } + + TreeItem *item = input_list_tree->create_item(joyb_root); + item->set_text(0, desc); + item->set_meta("__index", i); + } + for (int i = 0; i < (int)JoyButton::MAX; i++) { Ref joyb; joyb.instantiate();