From 166ca77f201c86e22d7a6c737729976d2affdbd5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Luc-Fr=C3=A9d=C3=A9ric=20Langis?= Date: Thu, 9 Feb 2023 11:25:56 -0500 Subject: [PATCH] feat(gamepad): improve gamepad behavior with slider and popup_menu --- core/input/input_event.cpp | 9 ++++ core/input/input_event.h | 2 + core/input/input_map.cpp | 4 ++ scene/gui/popup_menu.cpp | 93 ++++++++++++++++++++++++++++++++++++++ scene/gui/popup_menu.h | 4 ++ scene/gui/slider.cpp | 63 +++++++++++++++++++++++++- scene/gui/slider.h | 4 ++ 7 files changed, 178 insertions(+), 1 deletion(-) diff --git a/core/input/input_event.cpp b/core/input/input_event.cpp index 46f07fe0411..d91a7bf5a57 100644 --- a/core/input/input_event.cpp +++ b/core/input/input_event.cpp @@ -1096,6 +1096,15 @@ String InputEventJoypadMotion::to_string() { return vformat("InputEventJoypadMotion: axis=%d, axis_value=%.2f", axis, axis_value); } +Ref InputEventJoypadMotion::create_reference(JoyAxis p_axis, float p_value) { + Ref ie; + ie.instantiate(); + ie->set_axis(p_axis); + ie->set_axis_value(p_value); + + return ie; +} + void InputEventJoypadMotion::_bind_methods() { ClassDB::bind_method(D_METHOD("set_axis", "axis"), &InputEventJoypadMotion::set_axis); ClassDB::bind_method(D_METHOD("get_axis"), &InputEventJoypadMotion::get_axis); diff --git a/core/input/input_event.h b/core/input/input_event.h index 4be42d0bd20..ecf21001416 100644 --- a/core/input/input_event.h +++ b/core/input/input_event.h @@ -321,6 +321,8 @@ public: virtual String as_text() const override; virtual String to_string() override; + static Ref create_reference(JoyAxis p_axis, float p_value); + InputEventJoypadMotion() {} }; diff --git a/core/input/input_map.cpp b/core/input/input_map.cpp index 910778324ce..ddfde0e7cdf 100644 --- a/core/input/input_map.cpp +++ b/core/input/input_map.cpp @@ -399,21 +399,25 @@ const HashMap>> &InputMap::get_builtins() { inputs = List>(); inputs.push_back(InputEventKey::create_reference(Key::LEFT)); inputs.push_back(InputEventJoypadButton::create_reference(JoyButton::DPAD_LEFT)); + inputs.push_back(InputEventJoypadMotion::create_reference(JoyAxis::LEFT_X, -1.0)); default_builtin_cache.insert("ui_left", inputs); inputs = List>(); inputs.push_back(InputEventKey::create_reference(Key::RIGHT)); inputs.push_back(InputEventJoypadButton::create_reference(JoyButton::DPAD_RIGHT)); + inputs.push_back(InputEventJoypadMotion::create_reference(JoyAxis::LEFT_X, 1.0)); default_builtin_cache.insert("ui_right", inputs); inputs = List>(); inputs.push_back(InputEventKey::create_reference(Key::UP)); inputs.push_back(InputEventJoypadButton::create_reference(JoyButton::DPAD_UP)); + inputs.push_back(InputEventJoypadMotion::create_reference(JoyAxis::LEFT_Y, -1.0)); default_builtin_cache.insert("ui_up", inputs); inputs = List>(); inputs.push_back(InputEventKey::create_reference(Key::DOWN)); inputs.push_back(InputEventJoypadButton::create_reference(JoyButton::DPAD_DOWN)); + inputs.push_back(InputEventJoypadMotion::create_reference(JoyAxis::LEFT_Y, 1.0)); default_builtin_cache.insert("ui_down", inputs); inputs = List>(); diff --git a/scene/gui/popup_menu.cpp b/scene/gui/popup_menu.cpp index 03860c74496..78bc64d9bd6 100644 --- a/scene/gui/popup_menu.cpp +++ b/scene/gui/popup_menu.cpp @@ -295,7 +295,18 @@ void PopupMenu::gui_input(const Ref &p_event) { ERR_FAIL_COND(p_event.is_null()); if (!items.is_empty()) { + Input *input = Input::get_singleton(); + Ref joypadmotion_event = p_event; + Ref joypadbutton_event = p_event; + bool is_joypad_event = (joypadmotion_event.is_valid() || joypadbutton_event.is_valid()); + if (p_event->is_action("ui_down", true) && p_event->is_pressed()) { + if (is_joypad_event) { + if (!input->is_action_just_pressed("ui_down", true)) { + return; + } + set_process_internal(true); + } int search_from = mouse_over + 1; if (search_from >= items.size()) { search_from = 0; @@ -328,6 +339,12 @@ void PopupMenu::gui_input(const Ref &p_event) { } } } else if (p_event->is_action("ui_up", true) && p_event->is_pressed()) { + if (is_joypad_event) { + if (!input->is_action_just_pressed("ui_up", true)) { + return; + } + set_process_internal(true); + } int search_from = mouse_over - 1; if (search_from < 0) { search_from = items.size() - 1; @@ -905,6 +922,82 @@ void PopupMenu::_notification(int p_what) { } break; case NOTIFICATION_INTERNAL_PROCESS: { + Input *input = Input::get_singleton(); + + if (input->is_action_just_released("ui_up") || input->is_action_just_released("ui_down")) { + gamepad_event_delay_ms = DEFAULT_GAMEPAD_EVENT_DELAY_MS; + set_process_internal(false); + return; + } + gamepad_event_delay_ms -= get_process_delta_time(); + if (gamepad_event_delay_ms <= 0) { + if (input->is_action_pressed("ui_down")) { + gamepad_event_delay_ms = GAMEPAD_EVENT_REPEAT_RATE_MS + gamepad_event_delay_ms; + int search_from = mouse_over + 1; + if (search_from >= items.size()) { + search_from = 0; + } + + bool match_found = false; + for (int i = search_from; i < items.size(); i++) { + if (!items[i].separator && !items[i].disabled) { + mouse_over = i; + emit_signal(SNAME("id_focused"), i); + scroll_to_item(i); + control->queue_redraw(); + match_found = true; + break; + } + } + + if (!match_found) { + // If the last item is not selectable, try re-searching from the start. + for (int i = 0; i < search_from; i++) { + if (!items[i].separator && !items[i].disabled) { + mouse_over = i; + emit_signal(SNAME("id_focused"), i); + scroll_to_item(i); + control->queue_redraw(); + break; + } + } + } + } + + if (input->is_action_pressed("ui_up")) { + gamepad_event_delay_ms = GAMEPAD_EVENT_REPEAT_RATE_MS + gamepad_event_delay_ms; + int search_from = mouse_over - 1; + if (search_from < 0) { + search_from = items.size() - 1; + } + + bool match_found = false; + for (int i = search_from; i >= 0; i--) { + if (!items[i].separator && !items[i].disabled) { + mouse_over = i; + emit_signal(SNAME("id_focused"), i); + scroll_to_item(i); + control->queue_redraw(); + match_found = true; + break; + } + } + + if (!match_found) { + // If the first item is not selectable, try re-searching from the end. + for (int i = items.size() - 1; i >= search_from; i--) { + if (!items[i].separator && !items[i].disabled) { + mouse_over = i; + emit_signal(SNAME("id_focused"), i); + scroll_to_item(i); + control->queue_redraw(); + break; + } + } + } + } + } + // Only used when using operating system windows. if (!activated_by_keyboard && !is_embedded() && autohide_areas.size()) { Point2 mouse_pos = DisplayServer::get_singleton()->mouse_get_position(); diff --git a/scene/gui/popup_menu.h b/scene/gui/popup_menu.h index 74b739ac0f4..fa9c8cadfca 100644 --- a/scene/gui/popup_menu.h +++ b/scene/gui/popup_menu.h @@ -133,6 +133,10 @@ class PopupMenu : public Popup { ScrollContainer *scroll_container = nullptr; Control *control = nullptr; + const float DEFAULT_GAMEPAD_EVENT_DELAY_MS = 0.5; + const float GAMEPAD_EVENT_REPEAT_RATE_MS = 1.0 / 20; + float gamepad_event_delay_ms = DEFAULT_GAMEPAD_EVENT_DELAY_MS; + struct ThemeCache { Ref panel_style; Ref hover_style; diff --git a/scene/gui/slider.cpp b/scene/gui/slider.cpp index 292a4cfea2c..8e8b0cf11ed 100644 --- a/scene/gui/slider.cpp +++ b/scene/gui/slider.cpp @@ -112,30 +112,58 @@ void Slider::gui_input(const Ref &p_event) { } } + Input *input = Input::get_singleton(); + Ref joypadmotion_event = p_event; + Ref joypadbutton_event = p_event; + bool is_joypad_event = (joypadmotion_event.is_valid() || joypadbutton_event.is_valid()); + if (!mm.is_valid() && !mb.is_valid()) { if (p_event->is_action_pressed("ui_left", true)) { if (orientation != HORIZONTAL) { return; } + if (is_joypad_event) { + if (!input->is_action_just_pressed("ui_left", true)) { + return; + } + set_process_internal(true); + } set_value(get_value() - (custom_step >= 0 ? custom_step : get_step())); accept_event(); } else if (p_event->is_action_pressed("ui_right", true)) { if (orientation != HORIZONTAL) { return; } + if (is_joypad_event) { + if (!input->is_action_just_pressed("ui_right", true)) { + return; + } + set_process_internal(true); + } set_value(get_value() + (custom_step >= 0 ? custom_step : get_step())); accept_event(); } else if (p_event->is_action_pressed("ui_up", true)) { if (orientation != VERTICAL) { return; } - + if (is_joypad_event) { + if (!input->is_action_just_pressed("ui_up", true)) { + return; + } + set_process_internal(true); + } set_value(get_value() + (custom_step >= 0 ? custom_step : get_step())); accept_event(); } else if (p_event->is_action_pressed("ui_down", true)) { if (orientation != VERTICAL) { return; } + if (is_joypad_event) { + if (!input->is_action_just_pressed("ui_down", true)) { + return; + } + set_process_internal(true); + } set_value(get_value() - (custom_step >= 0 ? custom_step : get_step())); accept_event(); } else if (p_event->is_action("ui_home", true) && p_event->is_pressed()) { @@ -163,6 +191,39 @@ void Slider::_update_theme_item_cache() { void Slider::_notification(int p_what) { switch (p_what) { + case NOTIFICATION_INTERNAL_PROCESS: { + Input *input = Input::get_singleton(); + + if (input->is_action_just_released("ui_left") || input->is_action_just_released("ui_right") || input->is_action_just_released("ui_up") || input->is_action_just_released("ui_down")) { + gamepad_event_delay_ms = DEFAULT_GAMEPAD_EVENT_DELAY_MS; + set_process_internal(false); + return; + } + + gamepad_event_delay_ms -= get_process_delta_time(); + if (gamepad_event_delay_ms <= 0) { + gamepad_event_delay_ms = GAMEPAD_EVENT_REPEAT_RATE_MS + gamepad_event_delay_ms; + if (orientation == HORIZONTAL) { + if (input->is_action_pressed("ui_left")) { + set_value(get_value() - (custom_step >= 0 ? custom_step : get_step())); + } + + if (input->is_action_pressed("ui_right")) { + set_value(get_value() + (custom_step >= 0 ? custom_step : get_step())); + } + } else if (orientation == VERTICAL) { + if (input->is_action_pressed("ui_down")) { + set_value(get_value() - (custom_step >= 0 ? custom_step : get_step())); + } + + if (input->is_action_pressed("ui_up")) { + set_value(get_value() + (custom_step >= 0 ? custom_step : get_step())); + } + } + } + + } break; + case NOTIFICATION_THEME_CHANGED: { update_minimum_size(); queue_redraw(); diff --git a/scene/gui/slider.h b/scene/gui/slider.h index 42778684af1..d6e4093bb60 100644 --- a/scene/gui/slider.h +++ b/scene/gui/slider.h @@ -49,6 +49,10 @@ class Slider : public Range { bool editable = true; bool scrollable = true; + const float DEFAULT_GAMEPAD_EVENT_DELAY_MS = 0.5; + const float GAMEPAD_EVENT_REPEAT_RATE_MS = 1.0 / 20; + float gamepad_event_delay_ms = DEFAULT_GAMEPAD_EVENT_DELAY_MS; + struct ThemeCache { Ref slider_style; Ref grabber_area_style;