From 10fd7163d43df6b72a7d449347dea4be1127f60a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pa=CC=84vels=20Nadtoc=CC=8Cajevs?= <7645683+bruvzg@users.noreply.github.com> Date: Tue, 12 Aug 2025 11:56:35 +0300 Subject: [PATCH] Add methods to check which event first triggered "just pressed/released" state. --- core/input/input.cpp | 68 ++++++++++++++++++++++++++++++++++++++++ core/input/input.h | 4 +++ doc/classes/Input.xml | 29 +++++++++++++++-- scene/gui/popup_menu.cpp | 4 +-- scene/gui/slider.cpp | 8 ++--- scene/gui/tab_bar.cpp | 4 +-- scene/main/viewport.cpp | 14 ++++----- 7 files changed, 114 insertions(+), 17 deletions(-) diff --git a/core/input/input.cpp b/core/input/input.cpp index 50e6abaea2c..d9d3859b225 100644 --- a/core/input/input.cpp +++ b/core/input/input.cpp @@ -123,6 +123,8 @@ void Input::_bind_methods() { ClassDB::bind_method(D_METHOD("is_action_pressed", "action", "exact_match"), &Input::is_action_pressed, DEFVAL(false)); ClassDB::bind_method(D_METHOD("is_action_just_pressed", "action", "exact_match"), &Input::is_action_just_pressed, DEFVAL(false)); ClassDB::bind_method(D_METHOD("is_action_just_released", "action", "exact_match"), &Input::is_action_just_released, DEFVAL(false)); + ClassDB::bind_method(D_METHOD("is_action_just_pressed_by_event", "action", "event", "exact_match"), &Input::is_action_just_pressed_by_event, DEFVAL(false)); + ClassDB::bind_method(D_METHOD("is_action_just_released_by_event", "action", "event", "exact_match"), &Input::is_action_just_released_by_event, DEFVAL(false)); ClassDB::bind_method(D_METHOD("get_action_strength", "action", "exact_match"), &Input::get_action_strength, DEFVAL(false)); ClassDB::bind_method(D_METHOD("get_action_raw_strength", "action", "exact_match"), &Input::get_action_raw_strength, DEFVAL(false)); ClassDB::bind_method(D_METHOD("get_axis", "negative_action", "positive_action"), &Input::get_axis); @@ -411,6 +413,37 @@ bool Input::is_action_just_pressed(const StringName &p_action, bool p_exact) con } } +bool Input::is_action_just_pressed_by_event(const StringName &p_action, const Ref &p_event, bool p_exact) const { + ERR_FAIL_COND_V_MSG(!InputMap::get_singleton()->has_action(p_action), false, InputMap::get_singleton()->suggest_actions(p_action)); + ERR_FAIL_COND_V(p_event.is_null(), false); + + if (disable_input) { + return false; + } + + HashMap::ConstIterator E = action_states.find(p_action); + if (!E) { + return false; + } + + if (p_exact && E->value.exact == false) { + return false; + } + + if (E->value.pressed_event_id != p_event->get_instance_id()) { + return false; + } + + // Backward compatibility for legacy behavior, only return true if currently pressed. + bool pressed_requirement = legacy_just_pressed_behavior ? E->value.cache.pressed : true; + + if (Engine::get_singleton()->is_in_physics_frame()) { + return pressed_requirement && E->value.pressed_physics_frame == Engine::get_singleton()->get_physics_frames(); + } else { + return pressed_requirement && E->value.pressed_process_frame == Engine::get_singleton()->get_process_frames(); + } +} + bool Input::is_action_just_released(const StringName &p_action, bool p_exact) const { ERR_FAIL_COND_V_MSG(!InputMap::get_singleton()->has_action(p_action), false, InputMap::get_singleton()->suggest_actions(p_action)); @@ -437,6 +470,37 @@ bool Input::is_action_just_released(const StringName &p_action, bool p_exact) co } } +bool Input::is_action_just_released_by_event(const StringName &p_action, const Ref &p_event, bool p_exact) const { + ERR_FAIL_COND_V_MSG(!InputMap::get_singleton()->has_action(p_action), false, InputMap::get_singleton()->suggest_actions(p_action)); + ERR_FAIL_COND_V(p_event.is_null(), false); + + if (disable_input) { + return false; + } + + HashMap::ConstIterator E = action_states.find(p_action); + if (!E) { + return false; + } + + if (p_exact && E->value.exact == false) { + return false; + } + + if (E->value.released_event_id != p_event->get_instance_id()) { + return false; + } + + // Backward compatibility for legacy behavior, only return true if currently released. + bool released_requirement = legacy_just_pressed_behavior ? !E->value.cache.pressed : true; + + if (Engine::get_singleton()->is_in_physics_frame()) { + return released_requirement && E->value.released_physics_frame == Engine::get_singleton()->get_physics_frames(); + } else { + return released_requirement && E->value.released_process_frame == Engine::get_singleton()->get_process_frames(); + } +} + float Input::get_action_strength(const StringName &p_action, bool p_exact) const { ERR_FAIL_COND_V_MSG(!InputMap::get_singleton()->has_action(p_action), 0.0, InputMap::get_singleton()->suggest_actions(p_action)); @@ -895,10 +959,12 @@ void Input::_parse_input_event_impl(const Ref &p_event, bool p_is_em _update_action_cache(E.key, action_state); // As input may come in part way through a physics tick, the earliest we can react to it is the next physics tick. if (action_state.cache.pressed && !was_pressed) { + action_state.pressed_event_id = p_event->get_instance_id(); action_state.pressed_physics_frame = Engine::get_singleton()->get_physics_frames() + 1; action_state.pressed_process_frame = Engine::get_singleton()->get_process_frames(); } if (!action_state.cache.pressed && was_pressed) { + action_state.released_event_id = p_event->get_instance_id(); action_state.released_physics_frame = Engine::get_singleton()->get_physics_frames() + 1; action_state.released_process_frame = Engine::get_singleton()->get_process_frames(); } @@ -1027,6 +1093,7 @@ void Input::action_press(const StringName &p_action, float p_strength) { // As input may come in part way through a physics tick, the earliest we can react to it is the next physics tick. if (!action_state.cache.pressed) { + action_state.pressed_event_id = ObjectID(); action_state.pressed_physics_frame = Engine::get_singleton()->get_physics_frames() + 1; action_state.pressed_process_frame = Engine::get_singleton()->get_process_frames(); } @@ -1045,6 +1112,7 @@ void Input::action_release(const StringName &p_action) { action_state.cache.strength = 0.0; action_state.cache.raw_strength = 0.0; // As input may come in part way through a physics tick, the earliest we can react to it is the next physics tick. + action_state.released_event_id = ObjectID(); action_state.released_physics_frame = Engine::get_singleton()->get_physics_frames() + 1; action_state.released_process_frame = Engine::get_singleton()->get_process_frames(); action_state.device_states.clear(); diff --git a/core/input/input.h b/core/input/input.h index 10d357a1f92..66867b19582 100644 --- a/core/input/input.h +++ b/core/input/input.h @@ -109,6 +109,8 @@ private: uint64_t pressed_process_frame = UINT64_MAX; uint64_t released_physics_frame = UINT64_MAX; uint64_t released_process_frame = UINT64_MAX; + ObjectID pressed_event_id; + ObjectID released_event_id; bool exact = true; struct DeviceState { @@ -306,6 +308,8 @@ public: bool is_action_pressed(const StringName &p_action, bool p_exact = false) const; bool is_action_just_pressed(const StringName &p_action, bool p_exact = false) const; bool is_action_just_released(const StringName &p_action, bool p_exact = false) const; + bool is_action_just_pressed_by_event(const StringName &p_action, const Ref &p_event, bool p_exact = false) const; + bool is_action_just_released_by_event(const StringName &p_action, const Ref &p_event, bool p_exact = false) const; float get_action_strength(const StringName &p_action, bool p_exact = false) const; float get_action_raw_strength(const StringName &p_action, bool p_exact = false) const; diff --git a/doc/classes/Input.xml b/doc/classes/Input.xml index 436215305b5..69977420964 100644 --- a/doc/classes/Input.xml +++ b/doc/classes/Input.xml @@ -212,7 +212,20 @@ If [param exact_match] is [code]false[/code], it ignores additional input modifiers for [InputEventKey] and [InputEventMouseButton] events, and the direction for [InputEventJoypadMotion] events. [b]Note:[/b] Returning [code]true[/code] does not imply that the action is [i]still[/i] pressed. An action can be pressed and released again rapidly, and [code]true[/code] will still be returned so as not to miss input. [b]Note:[/b] Due to keyboard ghosting, [method is_action_just_pressed] may return [code]false[/code] even if one of the action's keys is pressed. See [url=$DOCS_URL/tutorials/inputs/input_examples.html#keyboard-events]Input examples[/url] in the documentation for more information. - [b]Note:[/b] During input handling (e.g. [method Node._input]), use [method InputEvent.is_action_pressed] instead to query the action state of the current event. + [b]Note:[/b] During input handling (e.g. [method Node._input]), use [method InputEvent.is_action_pressed] instead to query the action state of the current event. See also [method is_action_just_pressed_by_event]. + + + + + + + + + Returns [code]true[/code] when the user has [i]started[/i] pressing the action event in the current frame or physics tick, and the first event that triggered action press in the current frame/physics tick was [param event]. It will only return [code]true[/code] on the frame or tick that the user pressed down the button. + This is useful for code that needs to run only once when an action is pressed, and the action is processed during input handling (e.g. [method Node._input]). + If [param exact_match] is [code]false[/code], it ignores additional input modifiers for [InputEventKey] and [InputEventMouseButton] events, and the direction for [InputEventJoypadMotion] events. + [b]Note:[/b] Returning [code]true[/code] does not imply that the action is [i]still[/i] pressed. An action can be pressed and released again rapidly, and [code]true[/code] will still be returned so as not to miss input. + [b]Note:[/b] Due to keyboard ghosting, [method is_action_just_pressed] may return [code]false[/code] even if one of the action's keys is pressed. See [url=$DOCS_URL/tutorials/inputs/input_examples.html#keyboard-events]Input examples[/url] in the documentation for more information. @@ -223,7 +236,19 @@ Returns [code]true[/code] when the user [i]stops[/i] pressing the action event in the current frame or physics tick. It will only return [code]true[/code] on the frame or tick that the user releases the button. [b]Note:[/b] Returning [code]true[/code] does not imply that the action is [i]still[/i] not pressed. An action can be released and pressed again rapidly, and [code]true[/code] will still be returned so as not to miss input. If [param exact_match] is [code]false[/code], it ignores additional input modifiers for [InputEventKey] and [InputEventMouseButton] events, and the direction for [InputEventJoypadMotion] events. - [b]Note:[/b] During input handling (e.g. [method Node._input]), use [method InputEvent.is_action_released] instead to query the action state of the current event. + [b]Note:[/b] During input handling (e.g. [method Node._input]), use [method InputEvent.is_action_released] instead to query the action state of the current event. See also [method is_action_just_released_by_event]. + + + + + + + + + Returns [code]true[/code] when the user [i]stops[/i] pressing the action event in the current frame or physics tick, and the first event that triggered action release in the current frame/physics tick was [param event]. It will only return [code]true[/code] on the frame or tick that the user releases the button. + This is useful when an action is processed during input handling (e.g. [method Node._input]). + [b]Note:[/b] Returning [code]true[/code] does not imply that the action is [i]still[/i] not pressed. An action can be released and pressed again rapidly, and [code]true[/code] will still be returned so as not to miss input. + If [param exact_match] is [code]false[/code], it ignores additional input modifiers for [InputEventKey] and [InputEventMouseButton] events, and the direction for [InputEventJoypadMotion] events. diff --git a/scene/gui/popup_menu.cpp b/scene/gui/popup_menu.cpp index 864896b6442..585a1dedf49 100644 --- a/scene/gui/popup_menu.cpp +++ b/scene/gui/popup_menu.cpp @@ -483,7 +483,7 @@ void PopupMenu::_input_from_window_internal(const Ref &p_event) { } 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)) { + if (!input->is_action_just_pressed_by_event("ui_down", p_event, true)) { return; } joypad_event_process = true; @@ -526,7 +526,7 @@ void PopupMenu::_input_from_window_internal(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)) { + if (!input->is_action_just_pressed_by_event("ui_up", p_event, true)) { return; } joypad_event_process = true; diff --git a/scene/gui/slider.cpp b/scene/gui/slider.cpp index 9b7c58ce9f1..d32d631390f 100644 --- a/scene/gui/slider.cpp +++ b/scene/gui/slider.cpp @@ -136,7 +136,7 @@ void Slider::gui_input(const Ref &p_event) { return; } if (is_joypad_event) { - if (!input->is_action_just_pressed("ui_left", true)) { + if (!input->is_action_just_pressed_by_event("ui_left", p_event, true)) { return; } set_process_internal(true); @@ -152,7 +152,7 @@ void Slider::gui_input(const Ref &p_event) { return; } if (is_joypad_event) { - if (!input->is_action_just_pressed("ui_right", true)) { + if (!input->is_action_just_pressed_by_event("ui_right", p_event, true)) { return; } set_process_internal(true); @@ -168,7 +168,7 @@ void Slider::gui_input(const Ref &p_event) { return; } if (is_joypad_event) { - if (!input->is_action_just_pressed("ui_up", true)) { + if (!input->is_action_just_pressed_by_event("ui_up", p_event, true)) { return; } set_process_internal(true); @@ -180,7 +180,7 @@ void Slider::gui_input(const Ref &p_event) { return; } if (is_joypad_event) { - if (!input->is_action_just_pressed("ui_down", true)) { + if (!input->is_action_just_pressed_by_event("ui_down", p_event, true)) { return; } set_process_internal(true); diff --git a/scene/gui/tab_bar.cpp b/scene/gui/tab_bar.cpp index 3420733e051..4c9c3f5ecef 100644 --- a/scene/gui/tab_bar.cpp +++ b/scene/gui/tab_bar.cpp @@ -315,7 +315,7 @@ void TabBar::gui_input(const Ref &p_event) { bool is_joypad_event = (joypadmotion_event.is_valid() || joypadbutton_event.is_valid()); if (p_event->is_action("ui_right", true)) { if (is_joypad_event) { - if (!input->is_action_just_pressed("ui_right", true)) { + if (!input->is_action_just_pressed_by_event("ui_right", p_event, true)) { return; } set_process_internal(true); @@ -325,7 +325,7 @@ void TabBar::gui_input(const Ref &p_event) { } } else if (p_event->is_action("ui_left", true)) { if (is_joypad_event) { - if (!input->is_action_just_pressed("ui_left", true)) { + if (!input->is_action_just_pressed_by_event("ui_left", p_event, true)) { return; } set_process_internal(true); diff --git a/scene/main/viewport.cpp b/scene/main/viewport.cpp index 1f81119eace..448c1a15d86 100644 --- a/scene/main/viewport.cpp +++ b/scene/main/viewport.cpp @@ -2301,15 +2301,15 @@ void Viewport::_gui_input_event(Ref p_event) { if (joypadmotion_event.is_valid()) { Input *input = Input::get_singleton(); - if (p_event->is_action_pressed(SNAME("ui_focus_next")) && input->is_action_just_pressed(SNAME("ui_focus_next"))) { + if (p_event->is_action_pressed(SNAME("ui_focus_next")) && input->is_action_just_pressed_by_event(SNAME("ui_focus_next"), p_event)) { next = from->find_next_valid_focus(); } - if (p_event->is_action_pressed(SNAME("ui_focus_prev")) && input->is_action_just_pressed(SNAME("ui_focus_prev"))) { + if (p_event->is_action_pressed(SNAME("ui_focus_prev")) && input->is_action_just_pressed_by_event(SNAME("ui_focus_prev"), p_event)) { next = from->find_prev_valid_focus(); } - if (p_event->is_action_pressed(SNAME("ui_accessibility_drag_and_drop")) && input->is_action_just_pressed(SNAME("ui_accessibility_drag_and_drop"))) { + if (p_event->is_action_pressed(SNAME("ui_accessibility_drag_and_drop")) && input->is_action_just_pressed_by_event(SNAME("ui_accessibility_drag_and_drop"), p_event)) { if (gui_is_dragging()) { from->accessibility_drop(); } else { @@ -2317,19 +2317,19 @@ void Viewport::_gui_input_event(Ref p_event) { } } - if (p_event->is_action_pressed(SNAME("ui_up")) && input->is_action_just_pressed(SNAME("ui_up"))) { + if (p_event->is_action_pressed(SNAME("ui_up")) && input->is_action_just_pressed_by_event(SNAME("ui_up"), p_event)) { next = from->_get_focus_neighbor(SIDE_TOP); } - if (p_event->is_action_pressed(SNAME("ui_left")) && input->is_action_just_pressed(SNAME("ui_left"))) { + if (p_event->is_action_pressed(SNAME("ui_left")) && input->is_action_just_pressed_by_event(SNAME("ui_left"), p_event)) { next = from->_get_focus_neighbor(SIDE_LEFT); } - if (p_event->is_action_pressed(SNAME("ui_right")) && input->is_action_just_pressed(SNAME("ui_right"))) { + if (p_event->is_action_pressed(SNAME("ui_right")) && input->is_action_just_pressed_by_event(SNAME("ui_right"), p_event)) { next = from->_get_focus_neighbor(SIDE_RIGHT); } - if (p_event->is_action_pressed(SNAME("ui_down")) && input->is_action_just_pressed(SNAME("ui_down"))) { + if (p_event->is_action_pressed(SNAME("ui_down")) && input->is_action_just_pressed_by_event(SNAME("ui_down"), p_event)) { next = from->_get_focus_neighbor(SIDE_BOTTOM); } } else {