diff --git a/core/config/project_settings.cpp b/core/config/project_settings.cpp index fc71a1fb27cb..86701020ed59 100644 --- a/core/config/project_settings.cpp +++ b/core/config/project_settings.cpp @@ -1529,6 +1529,9 @@ ProjectSettings::ProjectSettings() { GLOBAL_DEF("audio/general/ios/mix_with_others", false); _add_builtin_input_map(); + GLOBAL_DEF(PropertyInfo(Variant::INT, "input/keyboard_player_id_override", PROPERTY_HINT_ENUM, "P1,P2,P3,P4,P5,P6,P7,P8"), (int)PlayerId::P1); + GLOBAL_DEF(PropertyInfo(Variant::INT, "input/mouse_player_id_override", PROPERTY_HINT_ENUM, "P1,P2,P3,P4,P5,P6,P7,P8"), (int)PlayerId::P1); + GLOBAL_DEF(PropertyInfo(Variant::INT, "input/touch_player_id_override", PROPERTY_HINT_ENUM, "P1,P2,P3,P4,P5,P6,P7,P8"), (int)PlayerId::P1); // Keep the enum values in sync with the `DisplayServer::ScreenOrientation` enum. custom_prop_info["display/window/handheld/orientation"] = PropertyInfo(Variant::INT, "display/window/handheld/orientation", PROPERTY_HINT_ENUM, "Landscape,Portrait,Reverse Landscape,Reverse Portrait,Sensor Landscape,Sensor Portrait,Sensor"); diff --git a/core/config/project_settings.h b/core/config/project_settings.h index af4a3dcecef3..f290afaa21f4 100644 --- a/core/config/project_settings.h +++ b/core/config/project_settings.h @@ -94,6 +94,10 @@ class ProjectSettings : public Object { bool project_loaded = false; List input_presets; + PlayerId keyboard_player_id_override = PlayerId::P1; + PlayerId mouse_player_id_override = PlayerId::P1; + PlayerId touch_player_id_override = PlayerId::P1; + HashSet custom_features; HashMap>> feature_overrides; diff --git a/core/core_constants.cpp b/core/core_constants.cpp index 4b858c55142c..d1b34c0705b5 100644 --- a/core/core_constants.cpp +++ b/core/core_constants.cpp @@ -527,6 +527,26 @@ void register_global_constants() { BIND_CORE_BITFIELD_CLASS_FLAG(MouseButtonMask, MOUSE_BUTTON_MASK, MB_XBUTTON1); BIND_CORE_BITFIELD_CLASS_FLAG(MouseButtonMask, MOUSE_BUTTON_MASK, MB_XBUTTON2); + BIND_CORE_ENUM_CLASS_CONSTANT(PlayerId, PLAYER_ID, P1); + BIND_CORE_ENUM_CLASS_CONSTANT(PlayerId, PLAYER_ID, P2); + BIND_CORE_ENUM_CLASS_CONSTANT(PlayerId, PLAYER_ID, P3); + BIND_CORE_ENUM_CLASS_CONSTANT(PlayerId, PLAYER_ID, P4); + BIND_CORE_ENUM_CLASS_CONSTANT(PlayerId, PLAYER_ID, P5); + BIND_CORE_ENUM_CLASS_CONSTANT(PlayerId, PLAYER_ID, P6); + BIND_CORE_ENUM_CLASS_CONSTANT(PlayerId, PLAYER_ID, P7); + BIND_CORE_ENUM_CLASS_CONSTANT(PlayerId, PLAYER_ID, P8); + + BIND_CORE_BITFIELD_FLAG(PLAYER_NONE); + BIND_CORE_BITFIELD_FLAG(PLAYER_1); + BIND_CORE_BITFIELD_FLAG(PLAYER_2); + BIND_CORE_BITFIELD_FLAG(PLAYER_3); + BIND_CORE_BITFIELD_FLAG(PLAYER_4); + BIND_CORE_BITFIELD_FLAG(PLAYER_5); + BIND_CORE_BITFIELD_FLAG(PLAYER_6); + BIND_CORE_BITFIELD_FLAG(PLAYER_7); + BIND_CORE_BITFIELD_FLAG(PLAYER_8); + BIND_CORE_BITFIELD_FLAG(PLAYER_ALL); + BIND_CORE_ENUM_CLASS_CONSTANT(JoyButton, JOY_BUTTON, INVALID); BIND_CORE_ENUM_CLASS_CONSTANT(JoyButton, JOY_BUTTON, A); BIND_CORE_ENUM_CLASS_CONSTANT(JoyButton, JOY_BUTTON, B); @@ -649,6 +669,7 @@ void register_global_constants() { BIND_CORE_ENUM_CONSTANT(PROPERTY_HINT_LAYERS_3D_PHYSICS); BIND_CORE_ENUM_CONSTANT(PROPERTY_HINT_LAYERS_3D_NAVIGATION); BIND_CORE_ENUM_CONSTANT(PROPERTY_HINT_LAYERS_AVOIDANCE); + BIND_CORE_ENUM_CONSTANT(PROPERTY_HINT_LAYERS_PLAYER_MASK); BIND_CORE_ENUM_CONSTANT(PROPERTY_HINT_FILE); BIND_CORE_ENUM_CONSTANT(PROPERTY_HINT_DIR); diff --git a/core/input/input.compat.inc b/core/input/input.compat.inc index cbc8b1df0f50..efc828751a02 100644 --- a/core/input/input.compat.inc +++ b/core/input/input.compat.inc @@ -30,11 +30,56 @@ #ifndef DISABLE_DEPRECATED +bool Input::_is_action_pressed_bind_compat_102412(const StringName &p_action, bool p_exact) const { + return is_action_pressed(p_action, p_exact, PlayerId::P1); +} + +bool Input::_is_action_just_pressed_bind_compat_102412(const StringName &p_action, bool p_exact) const { + return is_action_just_pressed(p_action, p_exact, PlayerId::P1); +} + +bool Input::_is_action_just_released_bind_compat_102412(const StringName &p_action, bool p_exact) const { + return is_action_just_released(p_action, p_exact, PlayerId::P1); +} + +float Input::_get_action_strength_bind_compat_102412(const StringName &p_action, bool p_exact) const { + return get_action_strength(p_action, p_exact, PlayerId::P1); +} + +float Input::_get_action_raw_strength_bind_compat_102412(const StringName &p_action, bool p_exact) const { + return get_action_raw_strength(p_action, p_exact, PlayerId::P1); +} + +float Input::_get_axis_bind_compat_102412(const StringName &p_negative_action, const StringName &p_positive_action) const { + return get_axis(p_negative_action, p_positive_action, PlayerId::P1); +} + +Vector2 Input::_get_vector_bind_compat_102412(const StringName &p_negative_x, const StringName &p_positive_x, const StringName &p_negative_y, const StringName &p_positive_y, float p_deadzone) const { + return get_vector(p_negative_x, p_positive_x, p_negative_y, p_positive_y, p_deadzone, PlayerId::P1); +} + +void Input::_action_press_bind_compat_102412(const StringName &p_action, float p_strength) { + action_press(p_action, p_strength, PlayerId::P1); +} + +void Input::_action_release_bind_compat_102412(const StringName &p_action) { + action_release(p_action, PlayerId::P1); +} + void Input::_vibrate_handheld_bind_compat_91143(int p_duration_ms) { vibrate_handheld(p_duration_ms, -1.0); } void Input::_bind_compatibility_methods() { + ClassDB::bind_compatibility_method(D_METHOD("is_action_pressed", "action", "exact_match"), &Input::_is_action_pressed_bind_compat_102412, DEFVAL(false)); + ClassDB::bind_compatibility_method(D_METHOD("is_action_just_pressed", "action", "exact_match"), &Input::_is_action_just_pressed_bind_compat_102412, DEFVAL(false)); + ClassDB::bind_compatibility_method(D_METHOD("is_action_just_released", "action", "exact_match"), &Input::_is_action_just_released_bind_compat_102412, DEFVAL(false)); + ClassDB::bind_compatibility_method(D_METHOD("get_action_strength", "action", "exact_match"), &Input::_get_action_strength_bind_compat_102412, DEFVAL(false)); + ClassDB::bind_compatibility_method(D_METHOD("get_action_raw_strength", "action", "exact_match"), &Input::_get_action_raw_strength_bind_compat_102412, DEFVAL(false)); + ClassDB::bind_compatibility_method(D_METHOD("get_axis", "negative_action", "positive_action"), &Input::_get_axis_bind_compat_102412); + ClassDB::bind_compatibility_method(D_METHOD("get_vector", "negative_x", "positive_x", "negative_y", "positive_y", "deadzone"), &Input::_get_vector_bind_compat_102412, DEFVAL(-1.0f)); + ClassDB::bind_compatibility_method(D_METHOD("action_press", "action", "strength"), &Input::_action_press_bind_compat_102412, DEFVAL(1.f)); + ClassDB::bind_compatibility_method(D_METHOD("action_release", "action"), &Input::_action_release_bind_compat_102412); ClassDB::bind_compatibility_method(D_METHOD("vibrate_handheld", "duration_ms"), &Input::_vibrate_handheld_bind_compat_91143, DEFVAL(500)); } diff --git a/core/input/input.cpp b/core/input/input.cpp index 6b9fec6f6003..af5830dbcc08 100644 --- a/core/input/input.cpp +++ b/core/input/input.cpp @@ -122,18 +122,20 @@ void Input::_bind_methods() { ClassDB::bind_method(D_METHOD("is_key_label_pressed", "keycode"), &Input::is_key_label_pressed); ClassDB::bind_method(D_METHOD("is_mouse_button_pressed", "button"), &Input::is_mouse_button_pressed); ClassDB::bind_method(D_METHOD("is_joy_button_pressed", "device", "button"), &Input::is_joy_button_pressed); - 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("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); - ClassDB::bind_method(D_METHOD("get_vector", "negative_x", "positive_x", "negative_y", "positive_y", "deadzone"), &Input::get_vector, DEFVAL(-1.0f)); + ClassDB::bind_method(D_METHOD("is_action_pressed", "action", "exact_match", "player_id"), &Input::is_action_pressed, DEFVAL(false), DEFVAL(PlayerId::P1)); + ClassDB::bind_method(D_METHOD("is_action_just_pressed", "action", "exact_match", "player_id"), &Input::is_action_just_pressed, DEFVAL(false), DEFVAL(PlayerId::P1)); + ClassDB::bind_method(D_METHOD("is_action_just_released", "action", "exact_match", "player_id"), &Input::is_action_just_released, DEFVAL(false), DEFVAL(PlayerId::P1)); + ClassDB::bind_method(D_METHOD("get_action_strength", "action", "exact_match", "player_id"), &Input::get_action_strength, DEFVAL(false), DEFVAL(PlayerId::P1)); + ClassDB::bind_method(D_METHOD("get_action_raw_strength", "action", "exact_match", "player_id"), &Input::get_action_raw_strength, DEFVAL(false), DEFVAL(PlayerId::P1)); + ClassDB::bind_method(D_METHOD("get_axis", "negative_action", "positive_action", "player_id"), &Input::get_axis, DEFVAL(PlayerId::P1)); + ClassDB::bind_method(D_METHOD("get_vector", "negative_x", "positive_x", "negative_y", "positive_y", "deadzone", "player_id"), &Input::get_vector, DEFVAL(-1.0f), DEFVAL(PlayerId::P1)); ClassDB::bind_method(D_METHOD("add_joy_mapping", "mapping", "update_existing"), &Input::add_joy_mapping, DEFVAL(false)); ClassDB::bind_method(D_METHOD("remove_joy_mapping", "guid"), &Input::remove_joy_mapping); 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_player_id", "device"), &Input::get_joy_player_id); + ClassDB::bind_method(D_METHOD("set_joy_player_id", "device", "player_id"), &Input::set_joy_player_id); 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); @@ -157,8 +159,8 @@ void Input::_bind_methods() { ClassDB::bind_method(D_METHOD("set_mouse_mode", "mode"), &Input::set_mouse_mode); ClassDB::bind_method(D_METHOD("get_mouse_mode"), &Input::get_mouse_mode); ClassDB::bind_method(D_METHOD("warp_mouse", "position"), &Input::warp_mouse); - ClassDB::bind_method(D_METHOD("action_press", "action", "strength"), &Input::action_press, DEFVAL(1.f)); - ClassDB::bind_method(D_METHOD("action_release", "action"), &Input::action_release); + ClassDB::bind_method(D_METHOD("action_press", "action", "strength", "player_id"), &Input::action_press, DEFVAL(1.f), DEFVAL(PlayerId::P1)); + ClassDB::bind_method(D_METHOD("action_release", "action", "player_id"), &Input::action_release, DEFVAL(PlayerId::P1)); ClassDB::bind_method(D_METHOD("set_default_cursor_shape", "shape"), &Input::set_default_cursor_shape, DEFVAL(CURSOR_ARROW)); ClassDB::bind_method(D_METHOD("get_current_cursor_shape"), &Input::get_current_cursor_shape); ClassDB::bind_method(D_METHOD("set_custom_mouse_cursor", "image", "shape", "hotspot"), &Input::set_custom_mouse_cursor, DEFVAL(CURSOR_ARROW), DEFVAL(Vector2())); @@ -201,6 +203,19 @@ void Input::_bind_methods() { BIND_ENUM_CONSTANT(CURSOR_HSPLIT); BIND_ENUM_CONSTANT(CURSOR_HELP); + BIND_CONSTANT(PLAYERS_MAX); + + BIND_BITFIELD_FLAG(PLAYER_NONE); + BIND_BITFIELD_FLAG(PLAYER_1); + BIND_BITFIELD_FLAG(PLAYER_2); + BIND_BITFIELD_FLAG(PLAYER_3); + BIND_BITFIELD_FLAG(PLAYER_4); + BIND_BITFIELD_FLAG(PLAYER_5); + BIND_BITFIELD_FLAG(PLAYER_6); + BIND_BITFIELD_FLAG(PLAYER_7); + BIND_BITFIELD_FLAG(PLAYER_8); + BIND_BITFIELD_FLAG(PLAYER_ALL); + ADD_SIGNAL(MethodInfo("joy_connection_changed", PropertyInfo(Variant::INT, "device"), PropertyInfo(Variant::BOOL, "connected"))); } @@ -282,9 +297,12 @@ bool Input::is_anything_pressed() const { return true; } - for (const KeyValue &E : action_states) { - if (E.value.cache.pressed) { - return true; + for (const KeyValue> &player_entry : action_states) { + const HashMap &player_action_states = player_entry.value; + for (const KeyValue &action_entry : player_action_states) { + if (action_entry.value.cache.pressed) { + return true; + } } } @@ -302,9 +320,12 @@ bool Input::is_anything_pressed_except_mouse() const { return true; } - for (const KeyValue &E : action_states) { - if (E.value.cache.pressed) { - return true; + for (const KeyValue> &player_entry : action_states) { + const HashMap &player_action_states = player_entry.value; + for (const KeyValue &action_entry : player_action_states) { + if (action_entry.value.cache.pressed) { + return true; + } } } @@ -369,119 +390,144 @@ bool Input::is_joy_button_pressed(int p_device, JoyButton p_button) const { return joy_buttons_pressed.has(_combine_device(p_button, p_device)); } -bool Input::is_action_pressed(const StringName &p_action, bool p_exact) const { +bool Input::is_action_pressed(const StringName &p_action, bool p_exact, PlayerId p_player_id) const { ERR_FAIL_COND_V_MSG(!InputMap::get_singleton()->has_action(p_action), false, InputMap::get_singleton()->suggest_actions(p_action)); if (disable_input) { return false; } - HashMap::ConstIterator E = action_states.find(p_action); - if (!E) { + HashMap>::ConstIterator player_entry = action_states.find((int)p_player_id); + if (!player_entry) { return false; } - return E->value.cache.pressed && (p_exact ? E->value.exact : true); + HashMap::ConstIterator action_entry = player_entry->value.find(p_action); + if (!action_entry) { + return false; + } + + return action_entry->value.cache.pressed && (p_exact ? action_entry->value.exact : true); } -bool Input::is_action_just_pressed(const StringName &p_action, bool p_exact) const { +bool Input::is_action_just_pressed(const StringName &p_action, bool p_exact, PlayerId p_player_id) const { ERR_FAIL_COND_V_MSG(!InputMap::get_singleton()->has_action(p_action), false, InputMap::get_singleton()->suggest_actions(p_action)); if (disable_input) { return false; } - HashMap::ConstIterator E = action_states.find(p_action); - if (!E) { + HashMap>::ConstIterator player_entry = action_states.find((int)p_player_id); + if (!player_entry) { return false; } - if (p_exact && E->value.exact == false) { + HashMap::ConstIterator action_entry = player_entry->value.find(p_action); + if (!action_entry) { + return false; + } + + if (p_exact && action_entry->value.exact == false) { 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; + bool pressed_requirement = legacy_just_pressed_behavior ? action_entry->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(); + return pressed_requirement && action_entry->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(); + return pressed_requirement && action_entry->value.pressed_process_frame == Engine::get_singleton()->get_process_frames(); } } -bool Input::is_action_just_released(const StringName &p_action, bool p_exact) const { +bool Input::is_action_just_released(const StringName &p_action, bool p_exact, PlayerId p_player_id) const { ERR_FAIL_COND_V_MSG(!InputMap::get_singleton()->has_action(p_action), false, InputMap::get_singleton()->suggest_actions(p_action)); if (disable_input) { return false; } - HashMap::ConstIterator E = action_states.find(p_action); - if (!E) { + HashMap>::ConstIterator player_entry = action_states.find((int)p_player_id); + if (!player_entry) { + return false; + } + + HashMap::ConstIterator action_entry = player_entry->value.find(p_action); + if (!action_entry) { return false; } - if (p_exact && E->value.exact == false) { + if (p_exact && action_entry->value.exact == false) { 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; + bool released_requirement = legacy_just_pressed_behavior ? !action_entry->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(); + return released_requirement && action_entry->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(); + return released_requirement && action_entry->value.released_process_frame == Engine::get_singleton()->get_process_frames(); } } -float Input::get_action_strength(const StringName &p_action, bool p_exact) const { +float Input::get_action_strength(const StringName &p_action, bool p_exact, PlayerId p_player_id) const { ERR_FAIL_COND_V_MSG(!InputMap::get_singleton()->has_action(p_action), 0.0, InputMap::get_singleton()->suggest_actions(p_action)); if (disable_input) { return 0.0f; } - HashMap::ConstIterator E = action_states.find(p_action); - if (!E) { + HashMap>::ConstIterator player_entry = action_states.find((int)p_player_id); + if (!player_entry) { return 0.0f; } - if (p_exact && E->value.exact == false) { + HashMap::ConstIterator action_entry = player_entry->value.find(p_action); + if (!action_entry) { return 0.0f; } - return E->value.cache.strength; + if (p_exact && action_entry->value.exact == false) { + return 0.0f; + } + + return action_entry->value.cache.strength; } -float Input::get_action_raw_strength(const StringName &p_action, bool p_exact) const { +float Input::get_action_raw_strength(const StringName &p_action, bool p_exact, PlayerId p_player_id) const { ERR_FAIL_COND_V_MSG(!InputMap::get_singleton()->has_action(p_action), 0.0, InputMap::get_singleton()->suggest_actions(p_action)); if (disable_input) { return 0.0f; } - HashMap::ConstIterator E = action_states.find(p_action); - if (!E) { + HashMap>::ConstIterator player_entry = action_states.find((int)p_player_id); + if (!player_entry) { return 0.0f; } - if (p_exact && E->value.exact == false) { + HashMap::ConstIterator action_entry = player_entry->value.find(p_action); + if (!action_entry) { return 0.0f; } - return E->value.cache.raw_strength; + if (p_exact && action_entry->value.exact == false) { + return 0.0f; + } + + return action_entry->value.cache.raw_strength; } -float Input::get_axis(const StringName &p_negative_action, const StringName &p_positive_action) const { - return get_action_strength(p_positive_action) - get_action_strength(p_negative_action); +float Input::get_axis(const StringName &p_negative_action, const StringName &p_positive_action, PlayerId p_player_id) const { + return get_action_strength(p_positive_action, false, p_player_id) - get_action_strength(p_negative_action, false, p_player_id); } -Vector2 Input::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) const { +Vector2 Input::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, PlayerId p_player_id) const { Vector2 vector = Vector2( - get_action_raw_strength(p_positive_x) - get_action_raw_strength(p_negative_x), - get_action_raw_strength(p_positive_y) - get_action_raw_strength(p_negative_y)); + get_action_raw_strength(p_positive_x, false, p_player_id) - get_action_raw_strength(p_negative_x, false, p_player_id), + get_action_raw_strength(p_positive_y, false, p_player_id) - get_action_raw_strength(p_negative_y, false, p_player_id)); if (p_deadzone < 0.0f) { // If the deadzone isn't specified, get it from the average of the actions. @@ -564,11 +610,14 @@ void Input::joy_connection_changed(int p_idx, bool p_connected, const String &p_ // 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); - if (it) { - E.value.device_states.remove(it); - _update_action_cache(E.key, E.value); + for (KeyValue> &player_entry : action_states) { + HashMap &player_action_states = player_entry.value; + for (KeyValue &action_entry : player_action_states) { + HashMap::Iterator it = action_entry.value.device_states.find(p_idx); + if (it) { + action_entry.value.device_states.remove(it); + _update_action_cache(action_entry.key, action_entry.value); + } } } } @@ -576,6 +625,7 @@ void Input::joy_connection_changed(int p_idx, bool p_connected, const String &p_ Joypad js; js.name = p_connected ? p_name : ""; js.uid = p_connected ? p_guid : ""; + js.player_id = PlayerId(CLAMP(p_idx, 0, PLAYERS_MAX)); js.info = p_connected ? p_joypad_info : Dictionary(); if (p_connected) { @@ -595,6 +645,9 @@ void Input::joy_connection_changed(int p_idx, bool p_connected, const String &p_ } } _set_joypad_mapping(js, mapping); + + // Create player action states. + action_states[(int)js.player_id]; } else { js.connected = false; for (int i = 0; i < (int)JoyButton::MAX; i++) { @@ -604,6 +657,11 @@ void Input::joy_connection_changed(int p_idx, bool p_connected, const String &p_ for (int i = 0; i < (int)JoyAxis::MAX; i++) { set_joy_axis(p_idx, (JoyAxis)i, 0.0f); } + + // Remove player action states. + if (action_states.has((int)js.player_id)) { + action_states.erase((int)js.player_id); + } } joy_names[p_idx] = js; @@ -711,11 +769,14 @@ void Input::_parse_input_event_impl(const Ref &p_event, bool p_is_em if (event_dispatch_function && emulate_touch_from_mouse && !p_is_emulated && mb->get_button_index() == MouseButton::LEFT) { Ref touch_event; touch_event.instantiate(); + touch_event->set_pressed(mb->is_pressed()); touch_event->set_canceled(mb->is_canceled()); touch_event->set_position(mb->get_position()); touch_event->set_double_tap(mb->is_double_click()); touch_event->set_device(InputEvent::DEVICE_ID_EMULATION); + touch_event->set_player_from_device(); + _THREAD_SAFE_UNLOCK_ event_dispatch_function(touch_event); _THREAD_SAFE_LOCK_ @@ -746,6 +807,7 @@ void Input::_parse_input_event_impl(const Ref &p_event, bool p_is_em drag_event->set_velocity(get_last_mouse_velocity()); drag_event->set_screen_velocity(get_last_mouse_screen_velocity()); drag_event->set_device(InputEvent::DEVICE_ID_EMULATION); + drag_event->set_player_from_device(); _THREAD_SAFE_UNLOCK_ event_dispatch_function(drag_event); @@ -784,6 +846,7 @@ void Input::_parse_input_event_impl(const Ref &p_event, bool p_is_em button_event.instantiate(); button_event->set_device(InputEvent::DEVICE_ID_EMULATION); + button_event->set_player_from_device(); button_event->set_position(st->get_position()); button_event->set_global_position(st->get_position()); button_event->set_pressed(st->is_pressed()); @@ -818,6 +881,7 @@ void Input::_parse_input_event_impl(const Ref &p_event, bool p_is_em motion_event.instantiate(); motion_event->set_device(InputEvent::DEVICE_ID_EMULATION); + motion_event->set_player_from_device(); motion_event->set_tilt(sd->get_tilt()); motion_event->set_pen_inverted(sd->get_pen_inverted()); motion_event->set_pressure(sd->get_pressure()); @@ -870,14 +934,17 @@ void Input::_parse_input_event_impl(const Ref &p_event, bool p_is_em ERR_FAIL_COND_MSG(event_index >= (int)MAX_EVENT, vformat("Input singleton does not support more than %d events assigned to an action.", MAX_EVENT)); int device_id = p_event->get_device(); - bool is_pressed = p_event->is_action_pressed(E.key, true); - ActionState &action_state = action_states[E.key]; + PlayerId player_id = p_event->get_player(); + bool is_pressed = p_event->is_action_pressed(E.key, true, false, player_id); + + HashMap &player_action_states = action_states[(int)player_id]; + ActionState &action_state = player_action_states[E.key]; // Update the action's per-device state. ActionState::DeviceState &device_state = action_state.device_states[device_id]; device_state.pressed[event_index] = is_pressed; - device_state.strength[event_index] = p_event->get_action_strength(E.key); - device_state.raw_strength[event_index] = p_event->get_action_raw_strength(E.key); + device_state.strength[event_index] = p_event->get_action_strength(E.key, false, player_id); + device_state.raw_strength[event_index] = p_event->get_action_raw_strength(E.key, false, player_id); // Update the action's global state and cache. if (!is_pressed) { @@ -1014,11 +1081,14 @@ Point2 Input::warp_mouse_motion(const Ref &p_motion, cons return rel_warped; } -void Input::action_press(const StringName &p_action, float p_strength) { +void Input::action_press(const StringName &p_action, float p_strength, PlayerId p_player_id) { ERR_FAIL_COND_MSG(!InputMap::get_singleton()->has_action(p_action), InputMap::get_singleton()->suggest_actions(p_action)); + // Create or retrieve existing player action states. + HashMap &player_entry = action_states[(int)p_player_id]; + // Create or retrieve existing action. - ActionState &action_state = action_states[p_action]; + ActionState &action_state = player_entry[p_action]; // 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) { @@ -1031,11 +1101,15 @@ void Input::action_press(const StringName &p_action, float p_strength) { _update_action_cache(p_action, action_state); } -void Input::action_release(const StringName &p_action) { +void Input::action_release(const StringName &p_action, PlayerId p_player_id) { ERR_FAIL_COND_MSG(!InputMap::get_singleton()->has_action(p_action), InputMap::get_singleton()->suggest_actions(p_action)); + // Create or retrieve existing player action states. + HashMap &player_entry = action_states[(int)p_player_id]; + // Create or retrieve existing action. - ActionState &action_state = action_states[p_action]; + ActionState &action_state = player_entry[p_action]; + action_state.cache.pressed = false; action_state.cache.strength = 0.0; action_state.cache.raw_strength = 0.0; @@ -1067,6 +1141,7 @@ void Input::ensure_touch_mouse_raised() { button_event.instantiate(); button_event->set_device(InputEvent::DEVICE_ID_EMULATION); + button_event->set_player_from_device(); button_event->set_position(mouse_pos); button_event->set_global_position(mouse_pos); button_event->set_pressed(false); @@ -1104,6 +1179,7 @@ void Input::set_default_cursor_shape(CursorShape p_shape) { mm->set_position(mouse_pos); mm->set_global_position(mouse_pos); mm->set_device(InputEvent::DEVICE_ID_INTERNAL); + mm->set_player_from_device(); parse_input_event(mm); } @@ -1126,6 +1202,9 @@ void Input::parse_input_event(const Ref &p_event) { ERR_FAIL_COND(p_event.is_null()); + // Override player id of the event. + p_event->set_player_from_device(); + #ifdef DEBUG_ENABLED uint64_t curr_frame = Engine::get_singleton()->get_process_frames(); if (curr_frame != last_parsed_frame) { @@ -1211,9 +1290,12 @@ void Input::release_pressed_events() { joy_buttons_pressed.clear(); _joy_axis.clear(); - for (KeyValue &E : action_states) { - if (E.value.cache.pressed) { - action_release(E.key); + for (KeyValue> &player_entry : action_states) { + HashMap &player_action_states = player_entry.value; + for (KeyValue &action_entry : player_action_states) { + if (action_entry.value.cache.pressed) { + action_release(action_entry.key); + } } } } @@ -1363,6 +1445,7 @@ void Input::_button_event(int p_device, JoyButton p_index, bool p_pressed) { Ref ievent; ievent.instantiate(); ievent->set_device(p_device); + ievent->set_player_from_device(); ievent->set_button_index(p_index); ievent->set_pressed(p_pressed); @@ -1373,6 +1456,7 @@ void Input::_axis_event(int p_device, JoyAxis p_axis, float p_value) { Ref ievent; ievent.instantiate(); ievent->set_device(p_device); + ievent->set_player_from_device(); ievent->set_axis(p_axis); ievent->set_axis_value(p_value); @@ -1798,6 +1882,16 @@ String Input::get_joy_guid(int p_device) const { return joy_names[p_device].uid; } +PlayerId Input::get_joy_player_id(int p_device) const { + ERR_FAIL_COND_V(!joy_names.has(p_device), PlayerId::P1); + return joy_names[p_device].player_id; +} + +void Input::set_joy_player_id(int p_device, PlayerId p_player_id) { + ERR_FAIL_COND(!joy_names.has(p_device)); + joy_names[p_device].player_id = p_player_id; +} + Dictionary Input::get_joy_info(int p_device) const { ERR_FAIL_COND_V(!joy_names.has(p_device), Dictionary()); return joy_names[p_device].info; diff --git a/core/input/input.h b/core/input/input.h index 8fc1b9c5dacf..e8e0d09ce162 100644 --- a/core/input/input.h +++ b/core/input/input.h @@ -56,6 +56,19 @@ class Input : public Object { MOUSE_MODE_MAX, }; + struct Joypad { + StringName name; + StringName uid; + PlayerId player_id = PlayerId::P1; + bool connected = false; + bool last_buttons[(size_t)JoyButton::MAX] = { false }; + float last_axis[(size_t)JoyAxis::MAX] = { 0.0f }; + HatMask last_hat = HatMask::CENTER; + int mapping = -1; + int hat_current = 0; + Dictionary info; + }; + #undef CursorShape enum CursorShape { CURSOR_ARROW, @@ -130,7 +143,9 @@ class Input : public Object { } cache; }; - HashMap action_states; + // Key -> player_id. + // Value -> All available actions to that player. + HashMap> action_states; bool emulate_touch_from_mouse = false; bool emulate_mouse_from_touch = false; @@ -163,18 +178,6 @@ class Input : public Object { VelocityTrack(); }; - struct Joypad { - StringName name; - StringName uid; - bool connected = false; - bool last_buttons[(size_t)JoyButton::MAX] = { false }; - float last_axis[(size_t)JoyAxis::MAX] = { 0.0f }; - HatMask last_hat = HatMask::CENTER; - int mapping = -1; - int hat_current = 0; - Dictionary info; - }; - VelocityTrack mouse_velocity_track; HashMap touch_velocity_track; HashMap joy_names; @@ -277,6 +280,15 @@ class Input : public Object { EventDispatchFunc event_dispatch_function = nullptr; #ifndef DISABLE_DEPRECATED + bool _is_action_pressed_bind_compat_102412(const StringName &p_action, bool p_exact = false) const; + bool _is_action_just_pressed_bind_compat_102412(const StringName &p_action, bool p_exact = false) const; + bool _is_action_just_released_bind_compat_102412(const StringName &p_action, bool p_exact = false) const; + float _get_action_strength_bind_compat_102412(const StringName &p_action, bool p_exact = false) const; + float _get_action_raw_strength_bind_compat_102412(const StringName &p_action, bool p_exact = false) const; + float _get_axis_bind_compat_102412(const StringName &p_negative_action, const StringName &p_positive_action) const; + Vector2 _get_vector_bind_compat_102412(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; + void _action_press_bind_compat_102412(const StringName &p_action, float p_strength = 1.f); + void _action_release_bind_compat_102412(const StringName &p_action); void _vibrate_handheld_bind_compat_91143(int p_duration_ms = 500); static void _bind_compatibility_methods(); #endif // DISABLE_DEPRECATED @@ -305,14 +317,20 @@ class Input : public Object { bool is_key_label_pressed(Key p_keycode) const; bool is_mouse_button_pressed(MouseButton p_button) const; bool is_joy_button_pressed(int p_device, JoyButton p_button) const; - 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; - 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; + bool is_action_pressed(const StringName &p_action, bool p_exact = false, PlayerId p_player_id = PlayerId::P1) const; + bool is_action_just_pressed(const StringName &p_action, bool p_exact = false, PlayerId p_player_id = PlayerId::P1) const; + bool is_action_just_released(const StringName &p_action, bool p_exact = false, PlayerId p_player_id = PlayerId::P1) const; + float get_action_strength(const StringName &p_action, bool p_exact = false, PlayerId p_player_id = PlayerId::P1) const; + float get_action_raw_strength(const StringName &p_action, bool p_exact = false, PlayerId p_player_id = PlayerId::P1) const; + + float get_axis(const StringName &p_negative_action, const StringName &p_positive_action, PlayerId p_player_id = PlayerId::P1) const; + 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, PlayerId p_player_id = PlayerId::P1) const; + + static inline bool is_player_id_in_mask(BitField p_player_mask, PlayerId p_player_id) { + return p_player_mask.has_flag(player_id_to_mask(p_player_id)); + } - float get_axis(const StringName &p_negative_action, const StringName &p_positive_action) const; - 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; + HashMap _get_joy_names() const { return joy_names; } float get_joy_axis(int p_device, JoyAxis p_axis) const; String get_joy_name(int p_idx); @@ -349,8 +367,8 @@ class Input : public Object { void set_mouse_position(const Point2 &p_posf); - void action_press(const StringName &p_action, float p_strength = 1.f); - void action_release(const StringName &p_action); + void action_press(const StringName &p_action, float p_strength = 1.f, PlayerId p_player_id = PlayerId::P1); + void action_release(const StringName &p_action, PlayerId p_player_id = PlayerId::P1); void set_emulate_touch_from_mouse(bool p_emulate); bool is_emulating_touch_from_mouse() const; @@ -376,6 +394,8 @@ class Input : public Object { bool is_joy_known(int p_device); String get_joy_guid(int p_device) const; + PlayerId get_joy_player_id(int p_device) const; + void set_joy_player_id(int p_device, PlayerId p_player_id); bool should_ignore_device(int p_vendor_id, int p_product_id) const; Dictionary get_joy_info(int p_device) const; void set_fallback_mapping(const String &p_guid); diff --git a/core/input/input_enums.h b/core/input/input_enums.h index 16ddd0c474b1..29dc2e44246b 100644 --- a/core/input/input_enums.h +++ b/core/input/input_enums.h @@ -136,3 +136,35 @@ inline MouseButtonMask mouse_button_to_mask(MouseButton button) { return MouseButtonMask(1 << ((int)button - 1)); } + +enum class PlayerId : uint8_t { + P1 = 0, + P2 = 1, + P3 = 2, + P4 = 3, + P5 = 4, + P6 = 5, + P7 = 6, + P8 = 7, +}; + +enum { + PLAYERS_MAX = 8, +}; + +enum PlayerMask : uint8_t { + PLAYER_NONE = 0U, + PLAYER_1 = 1U << 0, + PLAYER_2 = 1U << 1, + PLAYER_3 = 1U << 2, + PLAYER_4 = 1U << 3, + PLAYER_5 = 1U << 4, + PLAYER_6 = 1U << 5, + PLAYER_7 = 1U << 6, + PLAYER_8 = 1U << 7, + PLAYER_ALL = 0xFFU, +}; + +inline PlayerMask player_id_to_mask(PlayerId id) { + return PlayerMask(1U << (uint8_t)id); +} diff --git a/core/input/input_event.compat.inc b/core/input/input_event.compat.inc new file mode 100644 index 000000000000..f3bcb1a5357a --- /dev/null +++ b/core/input/input_event.compat.inc @@ -0,0 +1,51 @@ +/**************************************************************************/ +/* input_event.compat.inc */ +/**************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/**************************************************************************/ +/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */ +/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/**************************************************************************/ + +#ifndef DISABLE_DEPRECATED + +bool InputEvent::_is_action_pressed_bind_compat_102412(const StringName &p_action, bool p_allow_echo, bool p_exact_match) const { + return is_action_pressed(p_action, p_allow_echo, p_exact_match, PlayerId::P1); +} + +bool InputEvent::_is_action_released_bind_compat_102412(const StringName &p_action, bool p_exact_match) const { + return is_action_released(p_action, p_exact_match, PlayerId::P1); +} + +float InputEvent::_get_action_strength_bind_compat_102412(const StringName &p_action, bool p_exact_match) const { + return get_action_strength(p_action, p_exact_match, PlayerId::P1); +} + +void InputEvent::_bind_compatibility_methods() { + ClassDB::bind_compatibility_method(D_METHOD("is_action_pressed", "action", "allow_echo", "exact_match"), &InputEvent::_is_action_pressed_bind_compat_102412, DEFVAL(false), DEFVAL(false)); + ClassDB::bind_compatibility_method(D_METHOD("is_action_released", "action", "exact_match"), &InputEvent::_is_action_released_bind_compat_102412, DEFVAL(false)); + ClassDB::bind_compatibility_method(D_METHOD("get_action_strength", "action", "exact_match"), &InputEvent::_get_action_strength_bind_compat_102412, DEFVAL(false)); +} + +#endif // DISABLE_DEPRECATED diff --git a/core/input/input_event.cpp b/core/input/input_event.cpp index 6e8cdc4b8e46..1314b3d4c706 100644 --- a/core/input/input_event.cpp +++ b/core/input/input_event.cpp @@ -29,7 +29,10 @@ /**************************************************************************/ #include "input_event.h" +#include "input_event.compat.inc" +#include "core/config/project_settings.h" +#include "core/input/input.h" #include "core/input/input_map.h" #include "core/input/shortcut.h" #include "core/os/keyboard.h" @@ -47,29 +50,98 @@ int InputEvent::get_device() const { return device; } +void InputEvent::set_player(PlayerId p_player) { + player = p_player; + emit_changed(); +} + +PlayerId InputEvent::get_player() const { + return player; +} + +void InputEvent::set_player_from_device() { + // ProjectSettings *ps = ProjectSettings::get_singleton(); + Ref event = Ref(this); + + // Keyboard events. + + Ref k = event; + if (k.is_valid()) { + player = (PlayerId)(GLOBAL_GET("input/keyboard_player_id_override").operator int()); + return; + } + + // Mouse events. + + Ref mb = event; + Ref mm = event; + if (mb.is_valid() || mm.is_valid()) { + player = (PlayerId)(GLOBAL_GET("input/mouse_player_id_override").operator int()); + return; + } + + // Joypad events. + + Ref jb = event; + Ref jm = event; + if (jb.is_valid() || jm.is_valid()) { + Input *input = Input::get_singleton(); + HashMap::Iterator E = input->_get_joy_names().find(device); + + if (!E) { + player = PlayerId::P1; + return; + } + + player = E->value.player_id; + } + + // Touch events. + + Ref st = event; + Ref sd = event; + Ref ge = event; + if (st.is_valid() || sd.is_valid() || ge.is_valid()) { + player = (PlayerId)(GLOBAL_GET("input/touch_player_id_override").operator int()); + return; + } +} + bool InputEvent::is_action(const StringName &p_action, bool p_exact_match) const { return InputMap::get_singleton()->event_is_action(Ref(const_cast(this)), p_action, p_exact_match); } -bool InputEvent::is_action_pressed(const StringName &p_action, bool p_allow_echo, bool p_exact_match) const { +bool InputEvent::is_action_pressed(const StringName &p_action, bool p_allow_echo, bool p_exact_match, PlayerId p_player_id) const { + if (player != p_player_id) { + return false; + } bool pressed_state; bool valid = InputMap::get_singleton()->event_get_action_status(Ref(const_cast(this)), p_action, p_exact_match, &pressed_state, nullptr, nullptr); return valid && pressed_state && (p_allow_echo || !is_echo()); } -bool InputEvent::is_action_released(const StringName &p_action, bool p_exact_match) const { +bool InputEvent::is_action_released(const StringName &p_action, bool p_exact_match, PlayerId p_player_id) const { + if (player != p_player_id) { + return false; + } bool pressed_state; bool valid = InputMap::get_singleton()->event_get_action_status(Ref(const_cast(this)), p_action, p_exact_match, &pressed_state, nullptr, nullptr); return valid && !pressed_state; } -float InputEvent::get_action_strength(const StringName &p_action, bool p_exact_match) const { +float InputEvent::get_action_strength(const StringName &p_action, bool p_exact_match, PlayerId p_player_id) const { + if (player != p_player_id) { + return 0.0f; + } float strength; bool valid = InputMap::get_singleton()->event_get_action_status(Ref(const_cast(this)), p_action, p_exact_match, nullptr, &strength, nullptr); return valid ? strength : 0.0f; } -float InputEvent::get_action_raw_strength(const StringName &p_action, bool p_exact_match) const { +float InputEvent::get_action_raw_strength(const StringName &p_action, bool p_exact_match, PlayerId p_player_id) const { + if (player != p_player_id) { + return 0.0f; + } float raw_strength; bool valid = InputMap::get_singleton()->event_get_action_status(Ref(const_cast(this)), p_action, p_exact_match, nullptr, nullptr, &raw_strength); return valid ? raw_strength : 0.0f; @@ -111,10 +183,14 @@ void InputEvent::_bind_methods() { ClassDB::bind_method(D_METHOD("set_device", "device"), &InputEvent::set_device); ClassDB::bind_method(D_METHOD("get_device"), &InputEvent::get_device); + ClassDB::bind_method(D_METHOD("set_player", "player"), &InputEvent::set_player); + ClassDB::bind_method(D_METHOD("get_player"), &InputEvent::get_player); + ClassDB::bind_method(D_METHOD("set_player_from_device"), &InputEvent::set_player_from_device); + ClassDB::bind_method(D_METHOD("is_action", "action", "exact_match"), &InputEvent::is_action, DEFVAL(false)); - ClassDB::bind_method(D_METHOD("is_action_pressed", "action", "allow_echo", "exact_match"), &InputEvent::is_action_pressed, DEFVAL(false), DEFVAL(false)); - ClassDB::bind_method(D_METHOD("is_action_released", "action", "exact_match"), &InputEvent::is_action_released, DEFVAL(false)); - ClassDB::bind_method(D_METHOD("get_action_strength", "action", "exact_match"), &InputEvent::get_action_strength, DEFVAL(false)); + ClassDB::bind_method(D_METHOD("is_action_pressed", "action", "allow_echo", "exact_match", "player_id"), &InputEvent::is_action_pressed, DEFVAL(false), DEFVAL(false), DEFVAL(PlayerId::P1)); + ClassDB::bind_method(D_METHOD("is_action_released", "action", "exact_match", "player_id"), &InputEvent::is_action_released, DEFVAL(false), DEFVAL(PlayerId::P1)); + ClassDB::bind_method(D_METHOD("get_action_strength", "action", "exact_match", "player_id"), &InputEvent::get_action_strength, DEFVAL(false), DEFVAL(PlayerId::P1)); ClassDB::bind_method(D_METHOD("is_canceled"), &InputEvent::is_canceled); ClassDB::bind_method(D_METHOD("is_pressed"), &InputEvent::is_pressed); @@ -132,6 +208,7 @@ void InputEvent::_bind_methods() { ClassDB::bind_method(D_METHOD("xformed_by", "xform", "local_ofs"), &InputEvent::xformed_by, DEFVAL(Vector2())); ADD_PROPERTY(PropertyInfo(Variant::INT, "device"), "set_device", "get_device"); + ADD_PROPERTY(PropertyInfo(Variant::INT, "player"), "set_player", "get_player"); BIND_CONSTANT(DEVICE_ID_EMULATION); } @@ -741,6 +818,7 @@ Ref InputEventMouseButton::xformed_by(const Transform2D &p_xform, co mb.instantiate(); mb->set_device(get_device()); + mb->set_player_from_device(); mb->set_window_id(get_window_id()); mb->set_modifiers_from_event(this); @@ -960,6 +1038,7 @@ Ref InputEventMouseMotion::xformed_by(const Transform2D &p_xform, co mm.instantiate(); mm->set_device(get_device()); + mm->set_player_from_device(); mm->set_window_id(get_window_id()); mm->set_modifiers_from_event(this); @@ -1363,6 +1442,7 @@ Ref InputEventScreenTouch::xformed_by(const Transform2D &p_xform, co Ref st; st.instantiate(); st->set_device(get_device()); + st->set_player_from_device(); st->set_window_id(get_window_id()); st->set_index(index); st->set_position(p_xform.xform(pos + p_local_ofs)); @@ -1488,6 +1568,7 @@ Ref InputEventScreenDrag::xformed_by(const Transform2D &p_xform, con sd.instantiate(); sd->set_device(get_device()); + sd->set_player_from_device(); sd->set_window_id(get_window_id()); sd->set_index(index); @@ -1706,6 +1787,7 @@ Ref InputEventMagnifyGesture::xformed_by(const Transform2D &p_xform, ev.instantiate(); ev->set_device(get_device()); + ev->set_player_from_device(); ev->set_window_id(get_window_id()); ev->set_modifiers_from_event(this); @@ -1748,6 +1830,7 @@ Ref InputEventPanGesture::xformed_by(const Transform2D &p_xform, con ev.instantiate(); ev->set_device(get_device()); + ev->set_player_from_device(); ev->set_window_id(get_window_id()); ev->set_modifiers_from_event(this); diff --git a/core/input/input_event.h b/core/input/input_event.h index 3aa2ad8afba2..81b0d7936353 100644 --- a/core/input/input_event.h +++ b/core/input/input_event.h @@ -53,8 +53,16 @@ class InputEvent : public Resource { GDCLASS(InputEvent, Resource); int device = 0; + PlayerId player = PlayerId::P1; protected: +#ifndef DISABLE_DEPRECATED + bool _is_action_pressed_bind_compat_102412(const StringName &p_action, bool p_allow_echo = false, bool p_exact_match = false) const; + bool _is_action_released_bind_compat_102412(const StringName &p_action, bool p_exact_match = false) const; + float _get_action_strength_bind_compat_102412(const StringName &p_action, bool p_exact_match = false) const; + static void _bind_compatibility_methods(); +#endif // DISABLE_DEPRECATED + bool canceled = false; bool pressed = false; @@ -67,11 +75,15 @@ class InputEvent : public Resource { void set_device(int p_device); int get_device() const; + void set_player(PlayerId p_player); + PlayerId get_player() const; + void set_player_from_device(); + bool is_action(const StringName &p_action, bool p_exact_match = false) const; - bool is_action_pressed(const StringName &p_action, bool p_allow_echo = false, bool p_exact_match = false) const; - bool is_action_released(const StringName &p_action, bool p_exact_match = false) const; - float get_action_strength(const StringName &p_action, bool p_exact_match = false) const; - float get_action_raw_strength(const StringName &p_action, bool p_exact_match = false) const; + bool is_action_pressed(const StringName &p_action, bool p_allow_echo = false, bool p_exact_match = false, PlayerId p_player_id = PlayerId::P1) const; + bool is_action_released(const StringName &p_action, bool p_exact_match = false, PlayerId p_player_id = PlayerId::P1) const; + float get_action_strength(const StringName &p_action, bool p_exact_match = false, PlayerId p_player_id = PlayerId::P1) const; + float get_action_raw_strength(const StringName &p_action, bool p_exact_match = false, PlayerId p_player_id = PlayerId::P1) const; bool is_canceled() const; bool is_pressed() const; diff --git a/core/input/input_map.cpp b/core/input/input_map.cpp index a15e4f187612..fff23c79ee56 100644 --- a/core/input/input_map.cpp +++ b/core/input/input_map.cpp @@ -433,6 +433,7 @@ const HashMap>> &InputMap::get_builtins() { inputs = List>(); inputs.push_back(InputEventJoypadButton::create_reference(JoyButton::Y)); + inputs.back()->get()->set_device(ALL_DEVICES); inputs.push_back(InputEventKey::create_reference(Key::SPACE)); default_builtin_cache.insert("ui_select", inputs); @@ -451,25 +452,33 @@ 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.back()->get()->set_device(ALL_DEVICES); inputs.push_back(InputEventJoypadMotion::create_reference(JoyAxis::LEFT_X, -1.0)); + inputs.back()->get()->set_device(ALL_DEVICES); 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.back()->get()->set_device(ALL_DEVICES); inputs.push_back(InputEventJoypadMotion::create_reference(JoyAxis::LEFT_X, 1.0)); + inputs.back()->get()->set_device(ALL_DEVICES); 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.back()->get()->set_device(ALL_DEVICES); inputs.push_back(InputEventJoypadMotion::create_reference(JoyAxis::LEFT_Y, -1.0)); + inputs.back()->get()->set_device(ALL_DEVICES); 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.back()->get()->set_device(ALL_DEVICES); inputs.push_back(InputEventJoypadMotion::create_reference(JoyAxis::LEFT_Y, 1.0)); + inputs.back()->get()->set_device(ALL_DEVICES); default_builtin_cache.insert("ui_down", inputs); inputs = List>(); diff --git a/core/object/object.h b/core/object/object.h index 41f20f7d0b71..53ab8803b0e1 100644 --- a/core/object/object.h +++ b/core/object/object.h @@ -89,6 +89,7 @@ enum PropertyHint { PROPERTY_HINT_TOOL_BUTTON, PROPERTY_HINT_ONESHOT, ///< the property will be changed by self after setting, such as AudioStreamPlayer.playing, Particles.emitting. PROPERTY_HINT_NO_NODEPATH, /// < this property will not contain a NodePath, regardless of type (Array, Dictionary, List, etc.). Needed for SceneTreeDock. + PROPERTY_HINT_LAYERS_PLAYER_MASK, PROPERTY_HINT_MAX, }; diff --git a/core/os/midi_driver.cpp b/core/os/midi_driver.cpp index 6c748b149800..c1862b22f9a3 100644 --- a/core/os/midi_driver.cpp +++ b/core/os/midi_driver.cpp @@ -110,6 +110,7 @@ void MIDIDriver::send_event(int p_device_index, uint8_t p_status, Ref event; event.instantiate(); event->set_device(p_device_index); + event->set_player_from_device(); event->set_channel(Parser::channel(p_status)); event->set_message(msg); switch (msg) { diff --git a/core/variant/binder_common.h b/core/variant/binder_common.h index e8dc74f8ee04..16dce4c853d3 100644 --- a/core/variant/binder_common.h +++ b/core/variant/binder_common.h @@ -173,6 +173,8 @@ VARIANT_ENUM_CAST(JoyButton); VARIANT_ENUM_CAST(MIDIMessage); VARIANT_ENUM_CAST(MouseButton); VARIANT_BITFIELD_CAST(MouseButtonMask); +VARIANT_ENUM_CAST(PlayerId) +VARIANT_BITFIELD_CAST(PlayerMask); VARIANT_ENUM_CAST(Orientation); VARIANT_ENUM_CAST(HorizontalAlignment); VARIANT_ENUM_CAST(VerticalAlignment); diff --git a/core/variant/variant.h b/core/variant/variant.h index 23329072a59c..01ffa7abefcd 100644 --- a/core/variant/variant.h +++ b/core/variant/variant.h @@ -561,6 +561,7 @@ class Variant { VARIANT_ENUM_CLASS_CONSTRUCTOR(KeyLocation) VARIANT_ENUM_CLASS_CONSTRUCTOR(MIDIMessage) VARIANT_ENUM_CLASS_CONSTRUCTOR(MouseButton) + VARIANT_ENUM_CLASS_CONSTRUCTOR(PlayerId) #undef VARIANT_ENUM_CLASS_CONSTRUCTOR diff --git a/doc/classes/@GlobalScope.xml b/doc/classes/@GlobalScope.xml index eb071836187a..68cad02bc116 100644 --- a/doc/classes/@GlobalScope.xml +++ b/doc/classes/@GlobalScope.xml @@ -2444,6 +2444,60 @@ Extra mouse button 2 mask. + + Player 1 (value: 0). + + + Player 2 (value: 1). + + + Player 3 (value: 2). + + + Player 4 (value: 3). + + + Player 5 (value: 4). + + + Player 6 (value: 5). + + + Player 7 (value: 6). + + + Player 8 (value: 7). + + + Player none mask. + + + Player 1 mask. + + + Player 2 mask. + + + Player 3 mask. + + + Player 4 mask. + + + Player 5 mask. + + + Player 6 mask. + + + Player 7 mask. + + + Player 8 mask. + + + Player all mask. + An invalid game controller button. @@ -2816,6 +2870,9 @@ Hints that an integer property is a bitmask using the optionally named avoidance layers. + + Hints that an integer property is a bitmask using the optionally named player mask layers. + Hints that a [String] property is a path to a file. Editing it will show a file dialog for picking the path. The hint string can be a set of filters with wildcards like [code]"*.png,*.jpg"[/code]. @@ -2954,7 +3011,7 @@ Hints that a property will be changed on its own after setting, such as [member AudioStreamPlayer.playing] or [member GPUParticles3D.emitting]. - + Represents the size of the [enum PropertyHint] enum. diff --git a/doc/classes/Control.xml b/doc/classes/Control.xml index 63c13437327b..a69eaee417cb 100644 --- a/doc/classes/Control.xml +++ b/doc/classes/Control.xml @@ -11,7 +11,8 @@ Godot propagates input events via viewports. Each [Viewport] is responsible for propagating [InputEvent]s to their child nodes. As the [member SceneTree.root] is a [Window], this already happens automatically for all UI elements in your game. Input events are propagated through the [SceneTree] from the root node to all child nodes by calling [method Node._input]. For UI elements specifically, it makes more sense to override the virtual method [method _gui_input], which filters out unrelated input events, such as by checking z-order, [member mouse_filter], focus, or if the event was inside of the control's bounding box. Call [method accept_event] so no other node receives the event. Once you accept an input, it becomes handled so [method Node._unhandled_input] will not process it. - Only one [Control] node can be in focus. Only the node in focus will receive events. To get the focus, call [method grab_focus]. [Control] nodes lose focus when another node grabs it, or if you hide the node in focus. + Up to [constant Input.PLAYERS_MAX] nodes can be in focus. Only focused nodes receive events. To assign focus, call [method grab_focus] specifying the player with the [enum PlayerId] argument. By default, [constant PLAYER_ID_P1] grabs focus of the node. [Control] nodes lose focus when another node grabs it or if you hide the nodes in focus. + Multiplayer logic still applies: if another player grabs focus of a node (or even the same one), the original player's focus remains valid and separate. Sets [member mouse_filter] to [constant MOUSE_FILTER_IGNORE] to tell a [Control] node to ignore mouse or touch events. You'll need it if you place an icon on top of a button. [Theme] resources change the control's appearance. The [member theme] of a [Control] node affects all of its direct and indirect children (as long as a chain of controls is uninterrupted). To override some of the theme items, call one of the [code]add_theme_*_override[/code] methods, like [method add_theme_font_override]. You can also override theme items in the Inspector. [b]Note:[/b] Theme items are [i]not[/i] [Object] properties. This means you can't access their values using [method Object.get] and [method Object.set]. Instead, use the [code]get_theme_*[/code] and [code]add_theme_*_override[/code] methods provided by this class. @@ -327,6 +328,12 @@ Prevents [code]*_theme_*_override[/code] methods from emitting [constant NOTIFICATION_THEME_CHANGED] until [method end_bulk_theme_override] is called. + + + + Traverses all the parents of the node recursively, and returns the combined mask with the bit AND operator. + + @@ -335,12 +342,14 @@ + Finds the next (below in the tree) [Control] that can receive the focus. + Finds the previous (above in the tree) [Control] that can receive the focus. @@ -348,6 +357,7 @@ + Finds the next [Control] that can receive the focus on the specified [enum Side]. [b]Note:[/b] This is different from [method get_focus_neighbor], which returns the path of a specified focus neighbor. @@ -408,6 +418,12 @@ [b]Note:[/b] To find the next [Control] on the specific [enum Side], even if a neighbor is not assigned, use [method find_valid_focus_neighbor]. + + + + Returns an array containing all the player ids that are focusing that node. + + @@ -590,6 +606,7 @@ + Steal the focus from another control and become the focused control (see [member focus_mode]). [b]Note:[/b] Using this method together with [method Callable.call_deferred] makes it more reliable, especially when called inside [method Node._ready]. @@ -597,6 +614,7 @@ + Returns [code]true[/code] if this is the current focused control. See [member focus_mode]. @@ -718,6 +736,7 @@ + Give up the focus. No other control will be able to receive input. @@ -1039,6 +1058,9 @@ By default, the node's pivot is its top-left corner. When you change its [member rotation] or [member scale], it will rotate or scale around this pivot. Set this property to [member size] / 2 to pivot around the Control's center. + + Determines which player ids can grab focus of this node. + The node's position, relative to its containing node. It corresponds to the rectangle's top-left corner. The property is not affected by [member pivot_offset]. diff --git a/doc/classes/Input.xml b/doc/classes/Input.xml index ca54a44f489a..e5ed24822361 100644 --- a/doc/classes/Input.xml +++ b/doc/classes/Input.xml @@ -17,6 +17,7 @@ + This will simulate pressing the specified action. The strength can be used for non-boolean actions, it's ranged between 0 and 1 representing the intensity of the given action. @@ -26,6 +27,7 @@ + If the specified action is already pressed, this will release it. @@ -58,6 +60,7 @@ + Returns a value between 0 and 1 representing the raw intensity of the given action, ignoring the action's deadzone. In most cases, you should use [method get_action_strength] instead. If [param exact_match] is [code]false[/code], it ignores additional input modifiers for [InputEventKey] and [InputEventMouseButton] events, and the direction for [InputEventJoypadMotion] events. @@ -67,6 +70,7 @@ + Returns a value between 0 and 1 representing the intensity of the given action. In a joypad, for example, the further away the axis (analog sticks or L2, R2 triggers) is from the dead zone, the closer the value will be to 1. If the action is mapped to a control that has no axis such as the keyboard, the value returned will be 0 or 1. If [param exact_match] is [code]false[/code], it ignores additional input modifiers for [InputEventKey] and [InputEventMouseButton] events, and the direction for [InputEventJoypadMotion] events. @@ -76,6 +80,7 @@ + Get axis input by specifying two actions, one negative and one positive. This is a shorthand for writing [code]Input.get_action_strength("positive_action") - Input.get_action_strength("negative_action")[/code]. @@ -149,6 +154,13 @@ 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 player id the device is assigned to. + + @@ -196,6 +208,7 @@ + Gets an input vector by specifying four actions for the positive and negative X and Y axes. This method is useful when getting vector input, such as from a joystick, directional pad, arrows, or WASD. The vector has its length limited to 1 and has a circular deadzone, which is useful for using vector input as movement. @@ -206,6 +219,7 @@ + Returns [code]true[/code] when the user has [i]started[/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 pressed down the button. This is useful for code that needs to run only once when an action is pressed, instead of every frame while it's pressed. @@ -219,6 +233,7 @@ + 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. @@ -230,6 +245,7 @@ + Returns [code]true[/code] if you are pressing the action event. If [param exact_match] is [code]false[/code], it ignores additional input modifiers for [InputEventKey] and [InputEventMouseButton] events, and the direction for [InputEventJoypadMotion] events. @@ -366,6 +382,14 @@ [b]Note:[/b] This value can be immediately overwritten by the hardware sensor value on Android and iOS. + + + + + + Sets the player id the device is assigned to. + + @@ -523,5 +547,38 @@ Help cursor. Usually a question mark. + + Max number of allowed players by the engine. + + + Player none, meaning no player allowed. + + + Mask with only player 1 activated. + + + Mask with only player 2 activated. + + + Mask with only player 3 activated. + + + Mask with only player 4 activated. + + + Mask with only player 5 activated. + + + Mask with only player 6 activated. + + + Mask with only player 7 activated. + + + Mask with only player 8 activated. + + + Mask with all players activated. + diff --git a/doc/classes/InputEvent.xml b/doc/classes/InputEvent.xml index 6cf490accbce..380ea659899b 100644 --- a/doc/classes/InputEvent.xml +++ b/doc/classes/InputEvent.xml @@ -31,6 +31,7 @@ + Returns a value between 0.0 and 1.0 depending on the given actions' state. Useful for getting the value of events of type [InputEventJoypadMotion]. If [param exact_match] is [code]false[/code], it ignores additional input modifiers for [InputEventKey] and [InputEventMouseButton] events, and the direction for [InputEventJoypadMotion] events. @@ -50,6 +51,7 @@ + Returns [code]true[/code] if the given action is being pressed (and is not an echo event for [InputEventKey] events, unless [param allow_echo] is [code]true[/code]). Not relevant for events of type [InputEventMouseMotion] or [InputEventScreenDrag]. If [param exact_match] is [code]false[/code], it ignores additional input modifiers for [InputEventKey] and [InputEventMouseButton] events, and the direction for [InputEventJoypadMotion] events. @@ -60,6 +62,7 @@ + Returns [code]true[/code] if the given action is released (i.e. not pressed). Not relevant for events of type [InputEventMouseMotion] or [InputEventScreenDrag]. If [param exact_match] is [code]false[/code], it ignores additional input modifiers for [InputEventKey] and [InputEventMouseButton] events, and the direction for [InputEventJoypadMotion] events. @@ -107,6 +110,12 @@ Returns [code]true[/code] if this input event is released. Not relevant for events of type [InputEventMouseMotion] or [InputEventScreenDrag]. + + + + Sets the player ID corresponding to the device ID internal mapping. + + @@ -121,6 +130,9 @@ The event's device ID. [b]Note:[/b] [member device] can be negative for special use cases that don't refer to devices physically present on the system. See [constant DEVICE_ID_EMULATION]. + + The event's player ID. + diff --git a/doc/classes/ProjectSettings.xml b/doc/classes/ProjectSettings.xml index 232fed5713f5..f7645a54886d 100644 --- a/doc/classes/ProjectSettings.xml +++ b/doc/classes/ProjectSettings.xml @@ -1115,6 +1115,15 @@ Delay for tooltips in the editor. + + The player id the keyboard is assigned to. + + + The player id the mouse is assigned to. + + + The player id the touch events are assigned to. + Default [InputEventAction] to confirm a focused button, menu or list item, or validate input. [b]Note:[/b] Default [code]ui_*[/code] actions cannot be removed as they are necessary for the internal logic of several [Control]s. The events assigned to the action can however be modified. @@ -2129,6 +2138,30 @@ Optional name for the navigation avoidance layer 32. If left empty, the layer will display as "Layer 32". + + Optional name for the player mask layer 1. If left empty, the layer will display as "Layer 1". + + + Optional name for the player mask layer 2. If left empty, the layer will display as "Layer 2". + + + Optional name for the player mask layer 3. If left empty, the layer will display as "Layer 3". + + + Optional name for the player mask layer 4. If left empty, the layer will display as "Layer 4". + + + Optional name for the player mask layer 5. If left empty, the layer will display as "Layer 5". + + + Optional name for the player mask layer 6. If left empty, the layer will display as "Layer 6". + + + Optional name for the player mask layer 7. If left empty, the layer will display as "Layer 7". + + + Optional name for the player mask layer 8. If left empty, the layer will display as "Layer 8". + Godot uses a message queue to defer some function calls. If you run out of space on it (you will see an error), you can increase the size here. diff --git a/doc/classes/Viewport.xml b/doc/classes/Viewport.xml index 644aa8e1784b..e389b1bc9194 100644 --- a/doc/classes/Viewport.xml +++ b/doc/classes/Viewport.xml @@ -150,6 +150,7 @@ + Returns the currently focused [Control] within this viewport. If no [Control] is focused, returns [code]null[/code]. @@ -176,6 +177,7 @@ + Removes the focus from the currently focused [Control] within this viewport. If no [Control] has the focus, does nothing. @@ -223,6 +225,7 @@ + Helper method which calls the [code]set_text()[/code] method on the currently focused [Control], provided that it is defined (e.g. if the focused Control is [Button] or [LineEdit]). diff --git a/editor/code_editor.cpp b/editor/code_editor.cpp index bc678b5ba1be..8561a97fa93b 100644 --- a/editor/code_editor.cpp +++ b/editor/code_editor.cpp @@ -587,10 +587,10 @@ void FindReplaceBar::_show_search(bool p_with_replace, bool p_show_only) { if (focus_replace) { search_text->deselect(); - callable_mp((Control *)replace_text, &Control::grab_focus).call_deferred(); + callable_mp((Control *)replace_text, &Control::grab_focus).call_deferred(PlayerId::P1); } else { replace_text->deselect(); - callable_mp((Control *)search_text, &Control::grab_focus).call_deferred(); + callable_mp((Control *)search_text, &Control::grab_focus).call_deferred(PlayerId::P1); } if (on_one_line) { diff --git a/editor/connections_dialog.cpp b/editor/connections_dialog.cpp index e6c15df9c494..64c86a596842 100644 --- a/editor/connections_dialog.cpp +++ b/editor/connections_dialog.cpp @@ -476,7 +476,7 @@ void ConnectDialog::_update_warning_label() { } void ConnectDialog::_post_popup() { - callable_mp((Control *)dst_method, &Control::grab_focus).call_deferred(); + callable_mp((Control *)dst_method, &Control::grab_focus).call_deferred(PlayerId::P1); callable_mp(dst_method, &LineEdit::select_all).call_deferred(); } diff --git a/editor/create_dialog.cpp b/editor/create_dialog.cpp index 5eebb1c492e5..176e9c41450e 100644 --- a/editor/create_dialog.cpp +++ b/editor/create_dialog.cpp @@ -486,7 +486,7 @@ void CreateDialog::_notification(int p_what) { case NOTIFICATION_VISIBILITY_CHANGED: { if (is_visible()) { - callable_mp((Control *)search_box, &Control::grab_focus).call_deferred(); // Still not visible. + callable_mp((Control *)search_box, &Control::grab_focus).call_deferred(PlayerId::P1); // Still not visible. search_box->select_all(); } else { EditorSettings::get_singleton()->set_project_metadata("dialog_bounds", "create_new_node", Rect2(get_position(), get_size())); diff --git a/editor/editor_properties.cpp b/editor/editor_properties.cpp index 2a12f5b2ced0..1d07a683c2df 100644 --- a/editor/editor_properties.cpp +++ b/editor/editor_properties.cpp @@ -1201,6 +1201,12 @@ void EditorPropertyLayers::setup(LayerType p_layer_type) { layer_group_size = 4; layer_count = 32; } break; + + case LAYER_PLAYER_MASK: { + basename = "layer_names/player_mask"; + layer_group_size = 4; + layer_count = 8; + } break; } Vector names; @@ -2771,7 +2777,7 @@ void EditorPropertyNodePath::_menu_option(int p_idx) { const NodePath &np = _get_node_path(); edit->set_text(np); edit->show(); - callable_mp((Control *)edit, &Control::grab_focus).call_deferred(); + callable_mp((Control *)edit, &Control::grab_focus).call_deferred(PlayerId::P1); } break; case ACTION_SELECT: { @@ -3566,7 +3572,8 @@ EditorProperty *EditorInspectorDefaultPlugin::get_editor_for_property(Object *p_ p_hint == PROPERTY_HINT_LAYERS_3D_PHYSICS || p_hint == PROPERTY_HINT_LAYERS_3D_RENDER || p_hint == PROPERTY_HINT_LAYERS_3D_NAVIGATION || - p_hint == PROPERTY_HINT_LAYERS_AVOIDANCE) { + p_hint == PROPERTY_HINT_LAYERS_AVOIDANCE || + p_hint == PROPERTY_HINT_LAYERS_PLAYER_MASK) { EditorPropertyLayers::LayerType lt = EditorPropertyLayers::LAYER_RENDER_2D; switch (p_hint) { case PROPERTY_HINT_LAYERS_2D_RENDER: @@ -3590,6 +3597,9 @@ EditorProperty *EditorInspectorDefaultPlugin::get_editor_for_property(Object *p_ case PROPERTY_HINT_LAYERS_AVOIDANCE: lt = EditorPropertyLayers::LAYER_AVOIDANCE; break; + case PROPERTY_HINT_LAYERS_PLAYER_MASK: + lt = EditorPropertyLayers::LAYER_PLAYER_MASK; + break; default: { } //compiler could be smarter here and realize this can't happen } diff --git a/editor/editor_properties.h b/editor/editor_properties.h index 6435825c676b..04ce413c845a 100644 --- a/editor/editor_properties.h +++ b/editor/editor_properties.h @@ -302,6 +302,7 @@ class EditorPropertyLayers : public EditorProperty { LAYER_RENDER_3D, LAYER_NAVIGATION_3D, LAYER_AVOIDANCE, + LAYER_PLAYER_MASK, }; private: diff --git a/editor/editor_properties_array_dict.cpp b/editor/editor_properties_array_dict.cpp index b4c787103704..fcb8f1ed8d5c 100644 --- a/editor/editor_properties_array_dict.cpp +++ b/editor/editor_properties_array_dict.cpp @@ -514,7 +514,7 @@ void EditorPropertyArray::update_property() { slot.set_index(idx); } if (slot.index == changing_type_index) { - callable_mp(slot.prop, &EditorProperty::grab_focus).call_deferred(0); + callable_mp(slot.prop, &EditorProperty::grab_focus).call_deferred(PlayerId::P1); changing_type_index = EditorPropertyArrayObject::NOT_CHANGING_TYPE; } slot.prop->update_property(); @@ -1383,7 +1383,7 @@ void EditorPropertyDictionary::update_property() { // We need to grab the focus of the property that is being changed, even if the type didn't actually changed. // Otherwise, focus will stay on the change type button, which is not very user friendly. if (changing_type_index == slot.index) { - callable_mp(slot.prop, &EditorProperty::grab_focus).call_deferred(0); + callable_mp(slot.prop, &EditorProperty::grab_focus).call_deferred(PlayerId::P1); changing_type_index = EditorPropertyDictionaryObject::NOT_CHANGING_TYPE; // Reset to avoid grabbing focus again. } diff --git a/editor/find_in_files.cpp b/editor/find_in_files.cpp index cc3f3f2259d7..1ddc2690f541 100644 --- a/editor/find_in_files.cpp +++ b/editor/find_in_files.cpp @@ -404,16 +404,16 @@ void FindInFilesDialog::set_search_text(const String &text) { _search_text_line_edit->set_text(text); _on_search_text_modified(text); } - callable_mp((Control *)_search_text_line_edit, &Control::grab_focus).call_deferred(); + callable_mp((Control *)_search_text_line_edit, &Control::grab_focus).call_deferred(PlayerId::P1); _search_text_line_edit->select_all(); } else if (_mode == REPLACE_MODE) { if (!text.is_empty()) { _search_text_line_edit->set_text(text); - callable_mp((Control *)_replace_text_line_edit, &Control::grab_focus).call_deferred(); + callable_mp((Control *)_replace_text_line_edit, &Control::grab_focus).call_deferred(PlayerId::P1); _replace_text_line_edit->select_all(); _on_search_text_modified(text); } else { - callable_mp((Control *)_search_text_line_edit, &Control::grab_focus).call_deferred(); + callable_mp((Control *)_search_text_line_edit, &Control::grab_focus).call_deferred(PlayerId::P1); _search_text_line_edit->select_all(); } } diff --git a/editor/gui/editor_file_dialog.cpp b/editor/gui/editor_file_dialog.cpp index e4437334289e..f5682d296ca0 100644 --- a/editor/gui/editor_file_dialog.cpp +++ b/editor/gui/editor_file_dialog.cpp @@ -2274,7 +2274,7 @@ void EditorFileDialog::set_show_search_filter(bool p_show) { search_string.clear(); filter_box->clear(); if (filter_box->has_focus()) { - item_list->call_deferred("grab_focus"); + item_list->call_deferred("grab_focus", PlayerId::P1); } } show_search_filter = p_show; diff --git a/editor/gui/editor_spin_slider.cpp b/editor/gui/editor_spin_slider.cpp index 5461c9b8773f..5c935a62b08e 100644 --- a/editor/gui/editor_spin_slider.cpp +++ b/editor/gui/editor_spin_slider.cpp @@ -681,7 +681,7 @@ void EditorSpinSlider::_focus_entered() { value_input->set_focus_next(find_next_valid_focus()->get_path()); value_input->set_focus_previous(find_prev_valid_focus()->get_path()); callable_mp((CanvasItem *)value_input_popup, &CanvasItem::show).call_deferred(); - callable_mp((Control *)value_input, &Control::grab_focus).call_deferred(); + callable_mp((Control *)value_input, &Control::grab_focus).call_deferred(PlayerId::P1); callable_mp(value_input, &LineEdit ::select_all).call_deferred(); emit_signal("value_focus_entered"); } diff --git a/editor/gui/scene_tree_editor.cpp b/editor/gui/scene_tree_editor.cpp index ffc432896a7f..b80b7be9e7d3 100644 --- a/editor/gui/scene_tree_editor.cpp +++ b/editor/gui/scene_tree_editor.cpp @@ -2236,7 +2236,7 @@ void SceneTreeDialog::_notification(int p_what) { tree->update_tree(); // Select the search bar by default. - callable_mp((Control *)filter, &Control::grab_focus).call_deferred(); + callable_mp((Control *)filter, &Control::grab_focus).call_deferred(PlayerId::P1); } } break; diff --git a/editor/input_event_configuration_dialog.cpp b/editor/input_event_configuration_dialog.cpp index 8d9617bc90fd..6b7b5fd13817 100644 --- a/editor/input_event_configuration_dialog.cpp +++ b/editor/input_event_configuration_dialog.cpp @@ -259,6 +259,7 @@ void InputEventConfigurationDialog::_on_listen_input_changed(const Refset_device(_get_current_device()); + received_event->set_player_from_device(); _set_event(received_event, received_original_event); } @@ -521,6 +522,7 @@ void InputEventConfigurationDialog::_input_list_item_selected() { // Maintain selected device mb->set_device(_get_current_device()); + mb->set_player_from_device(); _set_event(mb, mb, false); } break; @@ -530,6 +532,7 @@ void InputEventConfigurationDialog::_input_list_item_selected() { // Maintain selected device jb->set_device(_get_current_device()); + jb->set_player_from_device(); _set_event(jb, jb, false); } break; @@ -544,6 +547,7 @@ void InputEventConfigurationDialog::_input_list_item_selected() { // Maintain selected device jm->set_device(_get_current_device()); + jm->set_player_from_device(); _set_event(jm, jm, false); } break; @@ -554,6 +558,7 @@ void InputEventConfigurationDialog::_device_selection_changed(int p_option_butto // Subtract 1 as option index 0 corresponds to "All Devices" (value of -1) // and option index 1 corresponds to device 0, etc... event->set_device(p_option_button_index - 1); + event->set_player_from_device(); event_as_text->set_text(EventListenerLineEdit::get_event_text(event, true)); } diff --git a/editor/plugins/canvas_item_editor_plugin.cpp b/editor/plugins/canvas_item_editor_plugin.cpp index 423b6e0f8413..540f7410eb8f 100644 --- a/editor/plugins/canvas_item_editor_plugin.cpp +++ b/editor/plugins/canvas_item_editor_plugin.cpp @@ -2779,7 +2779,7 @@ void CanvasItemEditor::_gui_input_viewport(const Ref &p_event) { // Grab focus if (!viewport->has_focus() && (!get_viewport()->gui_get_focus_owner() || !get_viewport()->gui_get_focus_owner()->is_text_field())) { - callable_mp((Control *)viewport, &Control::grab_focus).call_deferred(); + callable_mp((Control *)viewport, &Control::grab_focus).call_deferred(PlayerId::P1); } } diff --git a/editor/plugins/script_text_editor.cpp b/editor/plugins/script_text_editor.cpp index f0f476b7f027..f0695c681c25 100644 --- a/editor/plugins/script_text_editor.cpp +++ b/editor/plugins/script_text_editor.cpp @@ -1445,27 +1445,27 @@ void ScriptTextEditor::_edit_option(int p_op) { switch (p_op) { case EDIT_UNDO: { tx->undo(); - callable_mp((Control *)tx, &Control::grab_focus).call_deferred(); + callable_mp((Control *)tx, &Control::grab_focus).call_deferred(PlayerId::P1); } break; case EDIT_REDO: { tx->redo(); - callable_mp((Control *)tx, &Control::grab_focus).call_deferred(); + callable_mp((Control *)tx, &Control::grab_focus).call_deferred(PlayerId::P1); } break; case EDIT_CUT: { tx->cut(); - callable_mp((Control *)tx, &Control::grab_focus).call_deferred(); + callable_mp((Control *)tx, &Control::grab_focus).call_deferred(PlayerId::P1); } break; case EDIT_COPY: { tx->copy(); - callable_mp((Control *)tx, &Control::grab_focus).call_deferred(); + callable_mp((Control *)tx, &Control::grab_focus).call_deferred(PlayerId::P1); } break; case EDIT_PASTE: { tx->paste(); - callable_mp((Control *)tx, &Control::grab_focus).call_deferred(); + callable_mp((Control *)tx, &Control::grab_focus).call_deferred(PlayerId::P1); } break; case EDIT_SELECT_ALL: { tx->select_all(); - callable_mp((Control *)tx, &Control::grab_focus).call_deferred(); + callable_mp((Control *)tx, &Control::grab_focus).call_deferred(PlayerId::P1); } break; case EDIT_MOVE_LINE_UP: { code_editor->get_text_editor()->move_lines_up(); diff --git a/editor/plugins/text_editor.cpp b/editor/plugins/text_editor.cpp index 5ba1b201e5b6..3da20050f995 100644 --- a/editor/plugins/text_editor.cpp +++ b/editor/plugins/text_editor.cpp @@ -362,27 +362,27 @@ void TextEditor::_edit_option(int p_op) { switch (p_op) { case EDIT_UNDO: { tx->undo(); - callable_mp((Control *)tx, &Control::grab_focus).call_deferred(); + callable_mp((Control *)tx, &Control::grab_focus).call_deferred(PlayerId::P1); } break; case EDIT_REDO: { tx->redo(); - callable_mp((Control *)tx, &Control::grab_focus).call_deferred(); + callable_mp((Control *)tx, &Control::grab_focus).call_deferred(PlayerId::P1); } break; case EDIT_CUT: { tx->cut(); - callable_mp((Control *)tx, &Control::grab_focus).call_deferred(); + callable_mp((Control *)tx, &Control::grab_focus).call_deferred(PlayerId::P1); } break; case EDIT_COPY: { tx->copy(); - callable_mp((Control *)tx, &Control::grab_focus).call_deferred(); + callable_mp((Control *)tx, &Control::grab_focus).call_deferred(PlayerId::P1); } break; case EDIT_PASTE: { tx->paste(); - callable_mp((Control *)tx, &Control::grab_focus).call_deferred(); + callable_mp((Control *)tx, &Control::grab_focus).call_deferred(PlayerId::P1); } break; case EDIT_SELECT_ALL: { tx->select_all(); - callable_mp((Control *)tx, &Control::grab_focus).call_deferred(); + callable_mp((Control *)tx, &Control::grab_focus).call_deferred(PlayerId::P1); } break; case EDIT_MOVE_LINE_UP: { code_editor->get_text_editor()->move_lines_up(); diff --git a/editor/plugins/text_shader_editor.cpp b/editor/plugins/text_shader_editor.cpp index 91058a894a3c..021368713c1c 100644 --- a/editor/plugins/text_shader_editor.cpp +++ b/editor/plugins/text_shader_editor.cpp @@ -734,7 +734,7 @@ void TextShaderEditor::_menu_option(int p_option) { } break; } if (p_option != SEARCH_FIND && p_option != SEARCH_REPLACE && p_option != SEARCH_GOTO_LINE) { - callable_mp((Control *)code_editor->get_text_editor(), &Control::grab_focus).call_deferred(); + callable_mp((Control *)code_editor->get_text_editor(), &Control::grab_focus).call_deferred(PlayerId::P1); } } diff --git a/editor/plugins/tiles/tile_set_editor.cpp b/editor/plugins/tiles/tile_set_editor.cpp index d92f7c5de4f9..6032186e06b1 100644 --- a/editor/plugins/tiles/tile_set_editor.cpp +++ b/editor/plugins/tiles/tile_set_editor.cpp @@ -994,7 +994,7 @@ void TileSourceInspectorPlugin::_show_id_edit_dialog(Object *p_for_source) { edited_source = p_for_source; id_input->set_value(p_for_source->get("id")); id_edit_dialog->popup_centered(Vector2i(400, 0) * EDSCALE); - callable_mp((Control *)id_input->get_line_edit(), &Control::grab_focus).call_deferred(); + callable_mp((Control *)id_input->get_line_edit(), &Control::grab_focus).call_deferred(PlayerId::P1); } void TileSourceInspectorPlugin::_confirm_change_id() { diff --git a/editor/project_manager/project_dialog.cpp b/editor/project_manager/project_dialog.cpp index cf0ffcfa71e5..0cd88afc44aa 100644 --- a/editor/project_manager/project_dialog.cpp +++ b/editor/project_manager/project_dialog.cpp @@ -774,7 +774,7 @@ void ProjectDialog::show_dialog(bool p_reset_name) { renderer_container->hide(); default_files_container->hide(); - callable_mp((Control *)project_name, &Control::grab_focus).call_deferred(); + callable_mp((Control *)project_name, &Control::grab_focus).call_deferred(PlayerId::P1); callable_mp(project_name, &LineEdit::select_all).call_deferred(); } else { if (p_reset_name) { @@ -819,7 +819,7 @@ void ProjectDialog::show_dialog(bool p_reset_name) { renderer_container->show(); default_files_container->show(); - callable_mp((Control *)project_name, &Control::grab_focus).call_deferred(); + callable_mp((Control *)project_name, &Control::grab_focus).call_deferred(PlayerId::P1); callable_mp(project_name, &LineEdit::select_all).call_deferred(); } else if (mode == MODE_INSTALL) { set_title(TTR("Install Project:") + " " + zip_title); @@ -832,7 +832,7 @@ void ProjectDialog::show_dialog(bool p_reset_name) { renderer_container->hide(); default_files_container->hide(); - callable_mp((Control *)project_path, &Control::grab_focus).call_deferred(); + callable_mp((Control *)project_path, &Control::grab_focus).call_deferred(PlayerId::P1); } auto_dir = ""; diff --git a/editor/scene_create_dialog.cpp b/editor/scene_create_dialog.cpp index 971735760b9a..bf82bb8433f0 100644 --- a/editor/scene_create_dialog.cpp +++ b/editor/scene_create_dialog.cpp @@ -65,7 +65,7 @@ void SceneCreateDialog::config(const String &p_dir) { directory = p_dir; root_name_edit->set_text(""); scene_name_edit->set_text(""); - callable_mp((Control *)scene_name_edit, &Control::grab_focus).call_deferred(); + callable_mp((Control *)scene_name_edit, &Control::grab_focus).call_deferred(PlayerId::P1); validation_panel->update(); } diff --git a/misc/extension_api_validation/4.4-stable.expected b/misc/extension_api_validation/4.4-stable.expected index 7ea8fc10789f..7f719cd9adfa 100644 --- a/misc/extension_api_validation/4.4-stable.expected +++ b/misc/extension_api_validation/4.4-stable.expected @@ -24,3 +24,29 @@ Validate extension JSON: Error: Field 'classes/OpenXRAPIExtension/methods/regist Validate extension JSON: Error: Field 'classes/OpenXRAPIExtension/methods/unregister_projection_views_extension/arguments/0': type changed value in new API, from "OpenXRExtensionWrapperExtension" to "OpenXRExtensionWrapper". Switched from `OpenXRExtensionWrapperExtension` to parent `OpenXRExtensionWrapper`. Compatibility methods registered. + +GH-102412 +--------- +Validate extension JSON: Error: Field 'classes/Control/methods/find_valid_focus_neighbor/arguments': size changed value in new API, from 1 to 2. +Validate extension JSON: Error: Field 'classes/Input/methods/action_press/arguments': size changed value in new API, from 2 to 3. +Validate extension JSON: Error: Field 'classes/Input/methods/action_release/arguments': size changed value in new API, from 1 to 2. +Validate extension JSON: Error: Field 'classes/Input/methods/get_action_raw_strength/arguments': size changed value in new API, from 2 to 3. +Validate extension JSON: Error: Field 'classes/Input/methods/get_action_strength/arguments': size changed value in new API, from 2 to 3. +Validate extension JSON: Error: Field 'classes/Input/methods/get_axis/arguments': size changed value in new API, from 2 to 3. +Validate extension JSON: Error: Field 'classes/Input/methods/get_vector/arguments': size changed value in new API, from 5 to 6. +Validate extension JSON: Error: Field 'classes/Input/methods/is_action_just_pressed/arguments': size changed value in new API, from 2 to 3. +Validate extension JSON: Error: Field 'classes/Input/methods/is_action_just_released/arguments': size changed value in new API, from 2 to 3. +Validate extension JSON: Error: Field 'classes/Input/methods/is_action_pressed/arguments': size changed value in new API, from 2 to 3. +Validate extension JSON: Error: Field 'classes/InputEvent/methods/get_action_strength/arguments': size changed value in new API, from 2 to 3. +Validate extension JSON: Error: Field 'classes/InputEvent/methods/is_action_pressed/arguments': size changed value in new API, from 3 to 4. +Validate extension JSON: Error: Field 'classes/InputEvent/methods/is_action_released/arguments': size changed value in new API, from 2 to 3. +Validate extension JSON: Error: Field 'classes/Viewport/methods/push_text_input/arguments': size changed value in new API, from 1 to 2. +Validate extension JSON: JSON file: Field was added in a way that breaks compatibility 'classes/Control/methods/find_next_valid_focus': arguments +Validate extension JSON: JSON file: Field was added in a way that breaks compatibility 'classes/Control/methods/find_prev_valid_focus': arguments +Validate extension JSON: JSON file: Field was added in a way that breaks compatibility 'classes/Control/methods/grab_focus': arguments +Validate extension JSON: JSON file: Field was added in a way that breaks compatibility 'classes/Control/methods/has_focus': arguments +Validate extension JSON: JSON file: Field was added in a way that breaks compatibility 'classes/Control/methods/release_focus': arguments +Validate extension JSON: JSON file: Field was added in a way that breaks compatibility 'classes/Viewport/methods/gui_get_focus_owner': arguments +Validate extension JSON: JSON file: Field was added in a way that breaks compatibility 'classes/Viewport/methods/gui_release_focus': arguments + +Optional argument added. Compatibility method registered. diff --git a/modules/gdscript/doc_classes/@GDScript.xml b/modules/gdscript/doc_classes/@GDScript.xml index caed2a808d45..c5e645818401 100644 --- a/modules/gdscript/doc_classes/@GDScript.xml +++ b/modules/gdscript/doc_classes/@GDScript.xml @@ -530,6 +530,17 @@ [/codeblock] + + + + Export an integer property as a bit flag field for player mask layers. The widget in the Inspector dock will use the layer names defined in [member ProjectSettings.layer_names/player_mask/layer_1]. + See also [constant PROPERTY_HINT_LAYERS_PLAYER_MASK]. + [codeblock] + @export_flags_player_mask var player_mask_layers: int + @export_flags_player_mask var player_mask_layers_array: Array[int] + [/codeblock] + + diff --git a/modules/gdscript/gdscript_parser.cpp b/modules/gdscript/gdscript_parser.cpp index 7cc23601405d..029f95bd93ce 100644 --- a/modules/gdscript/gdscript_parser.cpp +++ b/modules/gdscript/gdscript_parser.cpp @@ -119,6 +119,7 @@ GDScriptParser::GDScriptParser() { register_annotation(MethodInfo("@export_flags_3d_physics"), AnnotationInfo::VARIABLE, &GDScriptParser::export_annotations); register_annotation(MethodInfo("@export_flags_3d_navigation"), AnnotationInfo::VARIABLE, &GDScriptParser::export_annotations); register_annotation(MethodInfo("@export_flags_avoidance"), AnnotationInfo::VARIABLE, &GDScriptParser::export_annotations); + register_annotation(MethodInfo("@export_flags_player_mask"), AnnotationInfo::VARIABLE, &GDScriptParser::export_annotations); register_annotation(MethodInfo("@export_storage"), AnnotationInfo::VARIABLE, &GDScriptParser::export_storage_annotation); register_annotation(MethodInfo("@export_custom", PropertyInfo(Variant::INT, "hint", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_CLASS_IS_ENUM, "PropertyHint"), PropertyInfo(Variant::STRING, "hint_string"), PropertyInfo(Variant::INT, "usage", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_CLASS_IS_BITFIELD, "PropertyUsageFlags")), AnnotationInfo::VARIABLE, &GDScriptParser::export_custom_annotation, varray(PROPERTY_USAGE_DEFAULT)); register_annotation(MethodInfo("@export_tool_button", PropertyInfo(Variant::STRING, "text"), PropertyInfo(Variant::STRING, "icon")), AnnotationInfo::VARIABLE, &GDScriptParser::export_tool_button_annotation, varray("")); diff --git a/modules/gdscript/tests/scripts/utils.notest.gd b/modules/gdscript/tests/scripts/utils.notest.gd index 225bcb3008aa..a76143d69fb7 100644 --- a/modules/gdscript/tests/scripts/utils.notest.gd +++ b/modules/gdscript/tests/scripts/utils.notest.gd @@ -154,6 +154,8 @@ static func get_property_hint_name(hint: PropertyHint) -> String: return "PROPERTY_HINT_LAYERS_3D_NAVIGATION" PROPERTY_HINT_LAYERS_AVOIDANCE: return "PROPERTY_HINT_LAYERS_AVOIDANCE" + PROPERTY_HINT_LAYERS_PLAYER_MASK: + return "PROPERTY_HINT_LAYERS_PLAYER_MASK" PROPERTY_HINT_FILE: return "PROPERTY_HINT_FILE" PROPERTY_HINT_DIR: diff --git a/scene/gui/button.cpp b/scene/gui/button.cpp index 958e1f3a270c..209c3b092707 100644 --- a/scene/gui/button.cpp +++ b/scene/gui/button.cpp @@ -230,7 +230,15 @@ void Button::_notification(int p_what) { style->draw(ci, Rect2(Point2(), size)); } - if (has_focus()) { + bool has_any_focus = false; + for (int i = 0; i < PLAYERS_MAX; i++) { + if (has_focus((PlayerId)i)) { + has_any_focus = true; + break; + } + } + + if (has_any_focus) { theme_cache.focus->draw(ci, Rect2(Point2(), size)); } diff --git a/scene/gui/color_picker.cpp b/scene/gui/color_picker.cpp index 91195b429630..d046055c9d65 100644 --- a/scene/gui/color_picker.cpp +++ b/scene/gui/color_picker.cpp @@ -314,7 +314,7 @@ void ColorPicker::finish_shaders() { } void ColorPicker::set_focus_on_line_edit() { - callable_mp((Control *)c_text, &Control::grab_focus).call_deferred(); + callable_mp((Control *)c_text, &Control::grab_focus).call_deferred(PlayerId::P1); } void ColorPicker::set_focus_on_picker_shape() { diff --git a/scene/gui/control.compat.inc b/scene/gui/control.compat.inc new file mode 100644 index 000000000000..3edb9441838a --- /dev/null +++ b/scene/gui/control.compat.inc @@ -0,0 +1,66 @@ +/**************************************************************************/ +/* control.compat.inc */ +/**************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/**************************************************************************/ +/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */ +/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/**************************************************************************/ + +#ifndef DISABLE_DEPRECATED + +bool Control::_has_focus_bind_compat_102412() const { + return has_focus(PlayerId::P1); +} + +void Control::_grab_focus_bind_compat_102412() { + grab_focus(PlayerId::P1); +} + +void Control::_release_focus_bind_compat_102412() { + release_focus(PlayerId::P1); +} + +Control *Control::_find_next_valid_focus_bind_compat_102412() const { + return find_next_valid_focus(PlayerId::P1); +} + +Control *Control::_find_prev_valid_focus_bind_compat_102412() const { + return find_prev_valid_focus(PlayerId::P1); +} + +Control *Control::_find_valid_focus_neighbor_bind_compat_102412(Side p_size) const { + return find_valid_focus_neighbor(p_size, PlayerId::P1); +} + +void Control::_bind_compatibility_methods() { + ClassDB::bind_compatibility_method(D_METHOD("has_focus"), &Control::_has_focus_bind_compat_102412); + ClassDB::bind_compatibility_method(D_METHOD("grab_focus"), &Control::_grab_focus_bind_compat_102412); + ClassDB::bind_compatibility_method(D_METHOD("release_focus"), &Control::_release_focus_bind_compat_102412); + ClassDB::bind_compatibility_method(D_METHOD("find_next_valid_focus"), &Control::_find_next_valid_focus_bind_compat_102412); + ClassDB::bind_compatibility_method(D_METHOD("find_prev_valid_focus"), &Control::_find_prev_valid_focus_bind_compat_102412); + ClassDB::bind_compatibility_method(D_METHOD("find_valid_focus_neighbor", "side"), &Control::_find_valid_focus_neighbor_bind_compat_102412); +} + +#endif // DISABLE_DEPRECATED diff --git a/scene/gui/control.cpp b/scene/gui/control.cpp index a64de8fb3c5d..3757ef1de34c 100644 --- a/scene/gui/control.cpp +++ b/scene/gui/control.cpp @@ -29,6 +29,7 @@ /**************************************************************************/ #include "control.h" +#include "control.compat.inc" #include "container.h" #include "core/config/project_settings.h" @@ -2082,21 +2083,41 @@ Control::RecursiveBehavior Control::get_focus_recursive_behavior() const { return data.focus_recursive_behavior; } -bool Control::has_focus() const { +TypedArray Control::get_focused_players_id() const { + ERR_READ_THREAD_GUARD_V(TypedArray()); + + const Control *const *key_focus = get_viewport()->gui.key_focus; + TypedArray ret; + + for (int i = 0; i < PLAYERS_MAX; i++) { + if (key_focus[i] == this) { + ret.push_back(i); + } + } + + return ret; +} + +bool Control::has_focus(PlayerId p_player_id) const { ERR_READ_THREAD_GUARD_V(false); - return is_inside_tree() && get_viewport()->_gui_control_has_focus(this); + return is_inside_tree() && get_viewport()->_gui_control_has_focus(this, p_player_id); } -void Control::grab_focus() { +void Control::grab_focus(PlayerId p_player_id) { ERR_MAIN_THREAD_GUARD; ERR_FAIL_COND(!is_inside_tree()); + if (!Input::is_player_id_in_mask(calculate_ancestral_player_mask(), p_player_id)) { + // Can't grab focus if that player is not allowed. + return; + } + if (data.focus_mode == FOCUS_NONE) { WARN_PRINT("This control can't grab focus. Use set_focus_mode() to allow a control to get focus."); return; } - get_viewport()->_gui_control_grab_focus(this); + get_viewport()->_gui_control_grab_focus(this, p_player_id); } void Control::grab_click_focus() { @@ -2106,18 +2127,18 @@ void Control::grab_click_focus() { get_viewport()->_gui_grab_click_focus(this); } -void Control::release_focus() { +void Control::release_focus(PlayerId p_player_id) { ERR_MAIN_THREAD_GUARD; ERR_FAIL_COND(!is_inside_tree()); - if (!has_focus()) { + if (!has_focus(p_player_id)) { return; } - get_viewport()->gui_release_focus(); + get_viewport()->gui_release_focus(p_player_id); } -static Control *_next_control(Control *p_from) { +static Control *_next_control(Control *p_from, const PlayerId p_player_id = PlayerId::P1) { if (p_from->is_set_as_top_level()) { return nullptr; // Can't go above. } @@ -2136,14 +2157,18 @@ static Control *_next_control(Control *p_from) { continue; } + if (!Input::is_player_id_in_mask(c->calculate_ancestral_player_mask(), p_player_id)) { + continue; + } + return c; } // No next in parent, try the same in parent. - return _next_control(parent); + return _next_control(parent, p_player_id); } -Control *Control::find_next_valid_focus() const { +Control *Control::find_next_valid_focus(PlayerId p_player_id) const { ERR_READ_THREAD_GUARD_V(nullptr); // If the focus property is manually overwritten, attempt to use it. @@ -2152,7 +2177,8 @@ Control *Control::find_next_valid_focus() const { ERR_FAIL_NULL_V_MSG(n, nullptr, "Next focus node path is invalid: '" + data.focus_next + "'."); Control *c = Object::cast_to(n); ERR_FAIL_NULL_V_MSG(c, nullptr, "Next focus node is not a control: '" + n->get_name() + "'."); - if (c->is_visible_in_tree() && c->get_focus_mode_with_recursive() != FOCUS_NONE) { + bool is_player_id_in_mask = Input::is_player_id_in_mask(c->calculate_ancestral_player_mask(), p_player_id); + if (c->is_visible_in_tree() && c->get_focus_mode_with_recursive() != FOCUS_NONE && is_player_id_in_mask) { return c; } } @@ -2170,12 +2196,16 @@ Control *Control::find_next_valid_focus() const { continue; } + if (!Input::is_player_id_in_mask(c->calculate_ancestral_player_mask(), p_player_id)) { + continue; + } + next_child = c; break; } if (!next_child) { - next_child = _next_control(from); + next_child = _next_control(from, p_player_id); if (!next_child) { // Nothing else. Go up and find either window or subwindow. next_child = const_cast(this); @@ -2210,30 +2240,34 @@ Control *Control::find_next_valid_focus() const { return nullptr; } -static Control *_prev_control(Control *p_from) { +static Control *_prev_control(Control *p_from, const PlayerId p_player_id = PlayerId::P1) { for (int i = p_from->get_child_count() - 1; i >= 0; i--) { Control *c = Object::cast_to(p_from->get_child(i)); if (!c || !c->is_visible_in_tree() || c->is_set_as_top_level()) { continue; } + if (!Input::is_player_id_in_mask(c->calculate_ancestral_player_mask(), p_player_id)) { + continue; + } + // Find the last child as prev, try the same in the last child. - return _prev_control(c); + return _prev_control(c, p_player_id); } return p_from; // Not found in the children, return itself. } -Control *Control::find_prev_valid_focus() const { +Control *Control::find_prev_valid_focus(PlayerId p_player_id) const { ERR_READ_THREAD_GUARD_V(nullptr); - // If the focus property is manually overwritten, attempt to use it. if (!data.focus_prev.is_empty()) { Node *n = get_node_or_null(data.focus_prev); ERR_FAIL_NULL_V_MSG(n, nullptr, "Previous focus node path is invalid: '" + data.focus_prev + "'."); Control *c = Object::cast_to(n); ERR_FAIL_NULL_V_MSG(c, nullptr, "Previous focus node is not a control: '" + n->get_name() + "'."); - if (c->is_visible_in_tree() && c->get_focus_mode_with_recursive() != FOCUS_NONE) { + bool is_player_id_in_mask = Input::is_player_id_in_mask(c->calculate_ancestral_player_mask(), p_player_id); + if (c->is_visible_in_tree() && c->get_focus_mode_with_recursive() != FOCUS_NONE && is_player_id_in_mask) { return c; } } @@ -2248,7 +2282,7 @@ Control *Control::find_prev_valid_focus() const { if (from->is_set_as_top_level() || !from->data.parent_control) { // Find last of the children. - prev_child = _prev_control(from); // Wrap start here. + prev_child = _prev_control(from, p_player_id); // Wrap start here. } else { for (int i = (from->get_index() - 1); i >= 0; i--) { @@ -2258,6 +2292,10 @@ Control *Control::find_prev_valid_focus() const { continue; } + if (!Input::is_player_id_in_mask(c->calculate_ancestral_player_mask(), p_player_id)) { + continue; + } + prev_child = c; break; } @@ -2265,7 +2303,7 @@ Control *Control::find_prev_valid_focus() const { if (!prev_child) { prev_child = from->data.parent_control; } else { - prev_child = _prev_control(prev_child); + prev_child = _prev_control(prev_child, p_player_id); } } @@ -2315,9 +2353,34 @@ NodePath Control::get_focus_previous() const { return data.focus_prev; } +void Control::set_player_mask(BitField p_player_mask) { + ERR_MAIN_THREAD_GUARD; + data.player_mask = p_player_mask; + queue_redraw(); +} + +BitField Control::get_player_mask() const { + ERR_READ_THREAD_GUARD_V(BitField(PLAYER_ALL)); + if (data.initialized) { + return data.player_mask; + } else { + return BitField(PLAYER_ALL); + } +} + +BitField Control::calculate_ancestral_player_mask() const { + BitField mask = get_player_mask(); + Control *parent = Object::cast_to(get_parent()); + while (parent) { + mask = mask & parent->get_player_mask(); + parent = Object::cast_to(parent->get_parent()); + } + return mask; +} + #define MAX_NEIGHBOR_SEARCH_COUNT 512 -Control *Control::_get_focus_neighbor(Side p_side, int p_count) { +Control *Control::_get_focus_neighbor(Side p_side, int p_count, PlayerId p_player_id) { ERR_FAIL_INDEX_V((int)p_side, 4, nullptr); if (p_count >= MAX_NEIGHBOR_SEARCH_COUNT) { @@ -2328,11 +2391,12 @@ Control *Control::_get_focus_neighbor(Side p_side, int p_count) { ERR_FAIL_NULL_V_MSG(n, nullptr, "Neighbor focus node path is invalid: '" + data.focus_neighbor[p_side] + "'."); Control *c = Object::cast_to(n); ERR_FAIL_NULL_V_MSG(c, nullptr, "Neighbor focus node is not a control: '" + n->get_name() + "'."); - if (c->is_visible_in_tree() && c->get_focus_mode_with_recursive() != FOCUS_NONE) { + bool is_player_id_in_mask = Input::is_player_id_in_mask(c->calculate_ancestral_player_mask(), p_player_id); + if (c->is_visible_in_tree() && c->get_focus_mode_with_recursive() != FOCUS_NONE && is_player_id_in_mask) { return c; } - c = c->_get_focus_neighbor(p_side, p_count + 1); + c = c->_get_focus_neighbor(p_side, p_count + 1, p_player_id); return c; } @@ -2495,8 +2559,8 @@ void Control::_apply_mouse_behavior_recursively(RecursiveBehavior p_mouse_recurs } } -Control *Control::find_valid_focus_neighbor(Side p_side) const { - return const_cast(this)->_get_focus_neighbor(p_side); +Control *Control::find_valid_focus_neighbor(Side p_side, PlayerId p_player_id) const { + return const_cast(this)->_get_focus_neighbor(p_side, 0, p_player_id); } void Control::_window_find_focus_neighbor(const Vector2 &p_dir, Node *p_at, const Rect2 &p_rect, const Rect2 &p_clamp, real_t p_min, real_t &r_closest_dist_squared, Control **r_closest) { @@ -2508,6 +2572,13 @@ void Control::_window_find_focus_neighbor(const Vector2 &p_dir, Node *p_at, cons Container *container = Object::cast_to(p_at); bool in_container = container ? container->is_ancestor_of(this) : false; + if (c) { + bool is_player_mask_compatible = c->calculate_ancestral_player_mask() & calculate_ancestral_player_mask(); + if (!is_player_mask_compatible) { + return; + } + } + if (c && c != this && c->get_focus_mode_with_recursive() == FOCUS_ALL && !in_container && p_clamp.intersects(c->get_global_rect())) { Rect2 r_c = c->get_global_rect(); r_c = r_c.intersection(p_clamp); @@ -3675,12 +3746,13 @@ void Control::_bind_methods() { ClassDB::bind_method(D_METHOD("get_focus_mode_with_recursive"), &Control::get_focus_mode_with_recursive); ClassDB::bind_method(D_METHOD("set_focus_recursive_behavior", "focus_recursive_behavior"), &Control::set_focus_recursive_behavior); ClassDB::bind_method(D_METHOD("get_focus_recursive_behavior"), &Control::get_focus_recursive_behavior); - ClassDB::bind_method(D_METHOD("has_focus"), &Control::has_focus); - ClassDB::bind_method(D_METHOD("grab_focus"), &Control::grab_focus); - ClassDB::bind_method(D_METHOD("release_focus"), &Control::release_focus); - ClassDB::bind_method(D_METHOD("find_prev_valid_focus"), &Control::find_prev_valid_focus); - ClassDB::bind_method(D_METHOD("find_next_valid_focus"), &Control::find_next_valid_focus); - ClassDB::bind_method(D_METHOD("find_valid_focus_neighbor", "side"), &Control::find_valid_focus_neighbor); + ClassDB::bind_method(D_METHOD("get_focused_players_id"), &Control::get_focused_players_id); + ClassDB::bind_method(D_METHOD("has_focus", "player_id"), &Control::has_focus, DEFVAL(PlayerId::P1)); + ClassDB::bind_method(D_METHOD("grab_focus", "player"), &Control::grab_focus, DEFVAL(PlayerId::P1)); + ClassDB::bind_method(D_METHOD("release_focus", "player"), &Control::release_focus, DEFVAL(PlayerId::P1)); + ClassDB::bind_method(D_METHOD("find_prev_valid_focus", "player_id"), &Control::find_prev_valid_focus, DEFVAL(PlayerId::P1)); + ClassDB::bind_method(D_METHOD("find_next_valid_focus", "player_id"), &Control::find_next_valid_focus, DEFVAL(PlayerId::P1)); + ClassDB::bind_method(D_METHOD("find_valid_focus_neighbor", "side", "player_id"), &Control::find_valid_focus_neighbor, DEFVAL(PlayerId::P1)); ClassDB::bind_method(D_METHOD("set_h_size_flags", "flags"), &Control::set_h_size_flags); ClassDB::bind_method(D_METHOD("get_h_size_flags"), &Control::get_h_size_flags); @@ -3766,6 +3838,10 @@ void Control::_bind_methods() { ClassDB::bind_method(D_METHOD("set_focus_previous", "previous"), &Control::set_focus_previous); ClassDB::bind_method(D_METHOD("get_focus_previous"), &Control::get_focus_previous); + ClassDB::bind_method(D_METHOD("set_player_mask", "player"), &Control::set_player_mask); + ClassDB::bind_method(D_METHOD("get_player_mask"), &Control::get_player_mask); + ClassDB::bind_method(D_METHOD("calculate_ancestral_player_mask"), &Control::calculate_ancestral_player_mask); + ClassDB::bind_method(D_METHOD("force_drag", "data", "preview"), &Control::force_drag); ClassDB::bind_method(D_METHOD("set_mouse_filter", "filter"), &Control::set_mouse_filter); @@ -3871,6 +3947,7 @@ void Control::_bind_methods() { ADD_PROPERTY(PropertyInfo(Variant::NODE_PATH, "focus_previous", PROPERTY_HINT_NODE_PATH_VALID_TYPES, "Control"), "set_focus_previous", "get_focus_previous"); ADD_PROPERTY(PropertyInfo(Variant::INT, "focus_mode", PROPERTY_HINT_ENUM, "None,Click,All"), "set_focus_mode", "get_focus_mode"); ADD_PROPERTY(PropertyInfo(Variant::INT, "focus_recursive_behavior", PROPERTY_HINT_ENUM, "Inherited,Disabled,Enabled"), "set_focus_recursive_behavior", "get_focus_recursive_behavior"); + ADD_PROPERTY(PropertyInfo(Variant::INT, "player_mask", PROPERTY_HINT_LAYERS_PLAYER_MASK), "set_player_mask", "get_player_mask"); ADD_GROUP("Mouse", "mouse_"); ADD_PROPERTY(PropertyInfo(Variant::INT, "mouse_filter", PROPERTY_HINT_ENUM, "Stop,Pass (Propagate Up),Ignore"), "set_mouse_filter", "get_mouse_filter"); diff --git a/scene/gui/control.h b/scene/gui/control.h index 8188b3ba31d0..016697a98483 100644 --- a/scene/gui/control.h +++ b/scene/gui/control.h @@ -241,6 +241,9 @@ class Control : public CanvasItem { NodePath focus_next; NodePath focus_prev; + // Accept inputs from all players by default. + BitField player_mask = PLAYER_ALL; + ObjectID shortcut_context; // Theming. @@ -328,7 +331,7 @@ class Control : public CanvasItem { // Focus. void _window_find_focus_neighbor(const Vector2 &p_dir, Node *p_at, const Rect2 &p_rect, const Rect2 &p_clamp, real_t p_min, real_t &r_closest_dist_squared, Control **r_closest); - Control *_get_focus_neighbor(Side p_side, int p_count = 0); + Control *_get_focus_neighbor(Side p_side, int p_count = 0, PlayerId p_player_id = PlayerId::P1); bool _is_focus_disabled_recursively() const; void _apply_focus_behavior_recursively(RecursiveBehavior p_focus_recursive_behavior, bool p_force); void _apply_mouse_behavior_recursively(RecursiveBehavior p_focus_recursive_behavior, bool p_force); @@ -346,6 +349,16 @@ class Control : public CanvasItem { String get_tooltip_text() const; protected: +#ifndef DISABLE_DEPRECATED + bool _has_focus_bind_compat_102412() const; + void _grab_focus_bind_compat_102412(); + void _release_focus_bind_compat_102412(); + Control *_find_next_valid_focus_bind_compat_102412() const; + Control *_find_prev_valid_focus_bind_compat_102412() const; + Control *_find_valid_focus_neighbor_bind_compat_102412(Side p_size) const; + static void _bind_compatibility_methods(); +#endif // DISABLE_DEPRECATED + // Dynamic properties. bool _set(const StringName &p_name, const Variant &p_value); @@ -563,14 +576,15 @@ class Control : public CanvasItem { FocusMode get_focus_mode_with_recursive() const; void set_focus_recursive_behavior(RecursiveBehavior p_recursive_mouse_behavior); RecursiveBehavior get_focus_recursive_behavior() const; - bool has_focus() const; - void grab_focus(); + TypedArray get_focused_players_id() const; + bool has_focus(PlayerId p_player_id = PlayerId::P1) const; + void grab_focus(PlayerId p_player_id = PlayerId::P1); void grab_click_focus(); - void release_focus(); + void release_focus(PlayerId p_player_id = PlayerId::P1); - Control *find_next_valid_focus() const; - Control *find_prev_valid_focus() const; - Control *find_valid_focus_neighbor(Side p_size) const; + Control *find_next_valid_focus(PlayerId p_player_id = PlayerId::P1) const; + Control *find_prev_valid_focus(PlayerId p_player_id = PlayerId::P1) const; + Control *find_valid_focus_neighbor(Side p_size, PlayerId p_player_id = PlayerId::P1) const; void set_focus_neighbor(Side p_side, const NodePath &p_neighbor); NodePath get_focus_neighbor(Side p_side) const; @@ -580,6 +594,10 @@ class Control : public CanvasItem { void set_focus_previous(const NodePath &p_prev); NodePath get_focus_previous() const; + void set_player_mask(BitField p_player_mask); + BitField get_player_mask() const; + BitField calculate_ancestral_player_mask() const; + // Rendering. void set_default_cursor_shape(CursorShape p_shape); diff --git a/scene/gui/file_dialog.cpp b/scene/gui/file_dialog.cpp index ac3fb1ed6d6d..8c183148a784 100644 --- a/scene/gui/file_dialog.cpp +++ b/scene/gui/file_dialog.cpp @@ -1679,7 +1679,7 @@ void FileDialog::set_show_filename_filter(bool p_show) { filename_filter->grab_focus(); } else { if (filename_filter->has_focus()) { - tree->call_deferred("grab_focus"); + tree->call_deferred("grab_focus", PlayerId::P1); } } show_filename_filter = p_show; diff --git a/scene/main/viewport.compat.inc b/scene/main/viewport.compat.inc new file mode 100644 index 000000000000..a48f5e52c810 --- /dev/null +++ b/scene/main/viewport.compat.inc @@ -0,0 +1,56 @@ +/**************************************************************************/ +/* viewport.compat.inc */ +/**************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/**************************************************************************/ +/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */ +/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/**************************************************************************/ + +#ifndef DISABLE_DEPRECATED + +void Viewport::_gui_remove_focus_for_window_bind_compat_102412(Node *p_window) { + _gui_remove_focus_for_window(p_window, PlayerId::P1); +} + +void Viewport::_push_text_input_bind_compat_102412(const String &p_text) { + push_text_input(p_text, PlayerId::P1); +} + +void Viewport::_gui_release_focus_bind_compat_102412() { + gui_release_focus(PlayerId::P1); +} + +Control *Viewport::_gui_get_focus_owner_bind_compat_102412() const { + return gui_get_focus_owner(PlayerId::P1); +} + +void Viewport::_bind_compatibility_methods() { + ClassDB::bind_compatibility_method(D_METHOD("_gui_remove_focus_for_window"), &Viewport::_gui_remove_focus_for_window_bind_compat_102412); + ClassDB::bind_compatibility_method(D_METHOD("push_text_input", "text"), &Viewport::_push_text_input_bind_compat_102412); + ClassDB::bind_compatibility_method(D_METHOD("gui_release_focus"), &Viewport::_gui_release_focus_bind_compat_102412); + ClassDB::bind_compatibility_method(D_METHOD("gui_get_focus_owner"), &Viewport::_gui_get_focus_owner_bind_compat_102412); +} + +#endif // DISABLE_DEPRECATED diff --git a/scene/main/viewport.cpp b/scene/main/viewport.cpp index 56703372ee0e..5358b847c453 100644 --- a/scene/main/viewport.cpp +++ b/scene/main/viewport.cpp @@ -29,6 +29,7 @@ /**************************************************************************/ #include "viewport.h" +#include "viewport.compat.inc" #include "core/config/project_settings.h" #include "core/debugger/engine_debugger.h" @@ -753,6 +754,7 @@ void Viewport::_process_picking() { mm.instantiate(); mm->set_device(InputEvent::DEVICE_ID_INTERNAL); + mm->set_player_from_device(); mm->set_position(get_mouse_position()); mm->set_global_position(mm->get_position()); mm->set_alt_pressed(Input::get_singleton()->is_key_pressed(Key::ALT)); @@ -1791,6 +1793,9 @@ bool Viewport::_gui_drop(Control *p_at_control, Point2 p_at_pos, bool p_just_che void Viewport::_gui_input_event(Ref p_event) { ERR_FAIL_COND(p_event.is_null()); + const PlayerId player_id = p_event->get_player(); + const int p_id = (int)player_id; + Ref mb = p_event; if (mb.is_valid()) { Point2 mpos = mb->get_position(); @@ -1842,8 +1847,8 @@ void Viewport::_gui_input_event(Ref p_event) { if (control->get_focus_mode_with_recursive() != Control::FOCUS_NONE) { // Grabbing unhovered focus can cause issues when mouse is dragged // with another button held down. - if (control != gui.key_focus && gui.mouse_over_hierarchy.has(control)) { - control->grab_focus(); + if (control != gui.key_focus[(int)player_id] && gui.mouse_over_hierarchy.has(control)) { + control->grab_focus(player_id); } break; } @@ -2168,13 +2173,13 @@ void Viewport::_gui_input_event(Ref p_event) { } } - if (gui.key_focus && !gui.key_focus->is_visible_in_tree()) { - gui.key_focus->release_focus(); + if (gui.key_focus[p_id] && !gui.key_focus[p_id]->is_visible_in_tree()) { + gui.key_focus[p_id]->release_focus(); } - if (gui.key_focus) { - if (gui.key_focus->can_process()) { - gui.key_focus->_call_gui_input(p_event); + if (gui.key_focus[p_id]) { + if (gui.key_focus[p_id]->can_process()) { + gui.key_focus[p_id]->_call_gui_input(p_event); } if (is_input_handled()) { @@ -2182,7 +2187,7 @@ void Viewport::_gui_input_event(Ref p_event) { } } - Control *from = gui.key_focus ? gui.key_focus : nullptr; + Control *from = gui.key_focus[p_id] ? gui.key_focus[p_id] : nullptr; if (from && p_event->is_pressed()) { Control *next = nullptr; @@ -2191,56 +2196,56 @@ 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"))) { - next = from->find_next_valid_focus(); + if (p_event->is_action_pressed(SNAME("ui_focus_next"), false, false, player_id) && input->is_action_just_pressed(SNAME("ui_focus_next"), false, player_id)) { + next = from->find_next_valid_focus(player_id); } - if (p_event->is_action_pressed(SNAME("ui_focus_prev")) && input->is_action_just_pressed(SNAME("ui_focus_prev"))) { - next = from->find_prev_valid_focus(); + if (p_event->is_action_pressed(SNAME("ui_focus_prev"), false, false, player_id) && input->is_action_just_pressed(SNAME("ui_focus_prev"), false, player_id)) { + next = from->find_prev_valid_focus(player_id); } - if (p_event->is_action_pressed(SNAME("ui_up")) && input->is_action_just_pressed(SNAME("ui_up"))) { - next = from->_get_focus_neighbor(SIDE_TOP); + if (p_event->is_action_pressed(SNAME("ui_up"), false, false, player_id) && input->is_action_just_pressed(SNAME("ui_up"), false, player_id)) { + next = from->_get_focus_neighbor(SIDE_TOP, 0, player_id); } - if (p_event->is_action_pressed(SNAME("ui_left")) && input->is_action_just_pressed(SNAME("ui_left"))) { - next = from->_get_focus_neighbor(SIDE_LEFT); + if (p_event->is_action_pressed(SNAME("ui_left"), false, false, player_id) && input->is_action_just_pressed(SNAME("ui_left"), false, player_id)) { + next = from->_get_focus_neighbor(SIDE_LEFT, 0, player_id); } - if (p_event->is_action_pressed(SNAME("ui_right")) && input->is_action_just_pressed(SNAME("ui_right"))) { - next = from->_get_focus_neighbor(SIDE_RIGHT); + if (p_event->is_action_pressed(SNAME("ui_right"), false, false, player_id) && input->is_action_just_pressed(SNAME("ui_right"), false, player_id)) { + next = from->_get_focus_neighbor(SIDE_RIGHT, 0, player_id); } - if (p_event->is_action_pressed(SNAME("ui_down")) && input->is_action_just_pressed(SNAME("ui_down"))) { - next = from->_get_focus_neighbor(SIDE_BOTTOM); + if (p_event->is_action_pressed(SNAME("ui_down"), false, false, player_id) && input->is_action_just_pressed(SNAME("ui_down"), false, player_id)) { + next = from->_get_focus_neighbor(SIDE_BOTTOM, 0, player_id); } } else { - if (p_event->is_action_pressed(SNAME("ui_focus_next"), true, true)) { - next = from->find_next_valid_focus(); + if (p_event->is_action_pressed(SNAME("ui_focus_next"), true, true, player_id)) { + next = from->find_next_valid_focus(player_id); } - if (p_event->is_action_pressed(SNAME("ui_focus_prev"), true, true)) { - next = from->find_prev_valid_focus(); + if (p_event->is_action_pressed(SNAME("ui_focus_prev"), true, true, player_id)) { + next = from->find_prev_valid_focus(player_id); } - if (p_event->is_action_pressed(SNAME("ui_up"), true, true)) { - next = from->_get_focus_neighbor(SIDE_TOP); + if (p_event->is_action_pressed(SNAME("ui_up"), true, true, player_id)) { + next = from->_get_focus_neighbor(SIDE_TOP, 0, player_id); } - if (p_event->is_action_pressed(SNAME("ui_left"), true, true)) { - next = from->_get_focus_neighbor(SIDE_LEFT); + if (p_event->is_action_pressed(SNAME("ui_left"), true, true, player_id)) { + next = from->_get_focus_neighbor(SIDE_LEFT, 0, player_id); } - if (p_event->is_action_pressed(SNAME("ui_right"), true, true)) { - next = from->_get_focus_neighbor(SIDE_RIGHT); + if (p_event->is_action_pressed(SNAME("ui_right"), true, true, player_id)) { + next = from->_get_focus_neighbor(SIDE_RIGHT, 0, player_id); } - if (p_event->is_action_pressed(SNAME("ui_down"), true, true)) { - next = from->_get_focus_neighbor(SIDE_BOTTOM); + if (p_event->is_action_pressed(SNAME("ui_down"), true, true, player_id)) { + next = from->_get_focus_neighbor(SIDE_BOTTOM, 0, player_id); } } if (next) { - next->grab_focus(); + next->grab_focus(player_id); set_input_as_handled(); } } @@ -2342,18 +2347,20 @@ void Viewport::_gui_remove_root_control(List::Element *RI) { gui.roots.erase(RI); } -void Viewport::_gui_unfocus_control(Control *p_control) { - if (gui.key_focus == p_control) { - gui.key_focus->release_focus(); +void Viewport::_gui_unfocus_control(Control *p_control, PlayerId p_player_id) { + const int p_id = (int)p_player_id; + if (gui.key_focus[p_id] == p_control) { + gui.key_focus[p_id]->release_focus(); } } -void Viewport::_gui_hide_control(Control *p_control) { +void Viewport::_gui_hide_control(Control *p_control, PlayerId p_player_id) { if (gui.mouse_focus == p_control) { _drop_mouse_focus(); } - if (gui.key_focus == p_control) { + const int p_id = (int)p_player_id; + if (gui.key_focus[p_id] == p_control) { gui_release_focus(); } if (gui.mouse_over == p_control || gui.mouse_over_hierarchy.has(p_control)) { @@ -2367,13 +2374,15 @@ void Viewport::_gui_hide_control(Control *p_control) { } } -void Viewport::_gui_remove_control(Control *p_control) { +void Viewport::_gui_remove_control(Control *p_control, PlayerId p_player_id) { if (gui.mouse_focus == p_control) { gui.mouse_focus = nullptr; gui.mouse_focus_mask.clear(); } - if (gui.key_focus == p_control) { - gui.key_focus = nullptr; + + const int p_id = (int)p_player_id; + if (gui.key_focus[p_id] == p_control) { + gui.key_focus[p_id] = nullptr; } if (gui.mouse_over == p_control || gui.mouse_over_hierarchy.has(p_control)) { _drop_mouse_over(p_control->get_parent_control()); @@ -2500,24 +2509,27 @@ Window *Viewport::get_base_window() { return w; } -void Viewport::_gui_remove_focus_for_window(Node *p_window) { +void Viewport::_gui_remove_focus_for_window(Node *p_window, PlayerId p_player_id) { if (get_base_window() == p_window) { - gui_release_focus(); + gui_release_focus(p_player_id); } } -bool Viewport::_gui_control_has_focus(const Control *p_control) { - return gui.key_focus == p_control; +bool Viewport::_gui_control_has_focus(const Control *p_control, PlayerId p_player_id) { + const int p_id = (int)p_player_id; + return gui.key_focus[p_id] == p_control; } -void Viewport::_gui_control_grab_focus(Control *p_control) { - if (gui.key_focus && gui.key_focus == p_control) { +void Viewport::_gui_control_grab_focus(Control *p_control, PlayerId p_player_id) { + const int p_id = (int)p_player_id; + if (gui.key_focus[p_id] && gui.key_focus[p_id] == p_control) { // No need for change. return; } - get_tree()->call_group("_viewports", "_gui_remove_focus_for_window", get_base_window()); + get_tree()->call_group("_viewports", "_gui_remove_focus_for_window", get_base_window(), p_id); if (p_control->is_inside_tree() && p_control->get_viewport() == this) { - gui.key_focus = p_control; + gui.key_focus[p_id] = p_control; + // TODO: Anything to do here with this signal? emit_signal(SNAME("gui_focus_changed"), p_control); p_control->notification(Control::NOTIFICATION_FOCUS_ENTER); p_control->queue_redraw(); @@ -2549,6 +2561,7 @@ void Viewport::_drop_mouse_focus() { mb->set_button_index(MouseButton(i + 1)); mb->set_pressed(false); mb->set_device(InputEvent::DEVICE_ID_INTERNAL); + mb->set_player_from_device(); c->_call_gui_input(mb); } } @@ -2606,6 +2619,7 @@ void Viewport::_post_gui_grab_click_focus() { mb->set_button_index(MouseButton(i + 1)); mb->set_pressed(false); mb->set_device(InputEvent::DEVICE_ID_INTERNAL); + mb->set_player_from_device(); gui.mouse_focus->_call_gui_input(mb); } } @@ -2624,6 +2638,7 @@ void Viewport::_post_gui_grab_click_focus() { mb->set_button_index(MouseButton(i + 1)); mb->set_pressed(true); mb->set_device(InputEvent::DEVICE_ID_INTERNAL); + mb->set_player_from_device(); callable_mp(gui.mouse_focus, &Control::_call_gui_input).call_deferred(mb); } } @@ -2632,15 +2647,15 @@ void Viewport::_post_gui_grab_click_focus() { /////////////////////////////// -void Viewport::push_text_input(const String &p_text) { +void Viewport::push_text_input(const String &p_text, PlayerId p_player_id) { ERR_MAIN_THREAD_GUARD; if (gui.subwindow_focused) { gui.subwindow_focused->push_text_input(p_text); return; } - if (gui.key_focus) { - gui.key_focus->call("set_text", p_text); + if (gui.key_focus[(int)p_player_id]) { + gui.key_focus[(int)p_player_id]->call("set_text", p_text); } } @@ -3476,19 +3491,21 @@ int Viewport::gui_get_canvas_sort_index() { return gui.canvas_sort_index++; } -void Viewport::gui_release_focus() { +void Viewport::gui_release_focus(PlayerId p_player_id) { ERR_MAIN_THREAD_GUARD; - if (gui.key_focus) { - Control *f = gui.key_focus; - gui.key_focus = nullptr; + const int p_id = (int)p_player_id; + if (gui.key_focus[p_id]) { + Control *f = gui.key_focus[p_id]; + gui.key_focus[p_id] = nullptr; f->notification(Control::NOTIFICATION_FOCUS_EXIT, true); f->queue_redraw(); } } -Control *Viewport::gui_get_focus_owner() const { +Control *Viewport::gui_get_focus_owner(PlayerId p_player_id) const { ERR_READ_THREAD_GUARD_V(nullptr); - return gui.key_focus; + const int p_id = (int)p_player_id; + return gui.key_focus[p_id]; } Control *Viewport::gui_get_hovered_control() const { @@ -4815,7 +4832,7 @@ void Viewport::_bind_methods() { ClassDB::bind_method(D_METHOD("get_physics_object_picking_first_only"), &Viewport::get_physics_object_picking_first_only); ClassDB::bind_method(D_METHOD("get_viewport_rid"), &Viewport::get_viewport_rid); - ClassDB::bind_method(D_METHOD("push_text_input", "text"), &Viewport::push_text_input); + ClassDB::bind_method(D_METHOD("push_text_input", "text", "player_id"), &Viewport::push_text_input); ClassDB::bind_method(D_METHOD("push_input", "event", "in_local_coords"), &Viewport::push_input, DEFVAL(false)); #ifndef DISABLE_DEPRECATED ClassDB::bind_method(D_METHOD("push_unhandled_input", "event", "in_local_coords"), &Viewport::push_unhandled_input, DEFVAL(false)); @@ -4832,14 +4849,14 @@ void Viewport::_bind_methods() { ClassDB::bind_method(D_METHOD("gui_is_dragging"), &Viewport::gui_is_dragging); ClassDB::bind_method(D_METHOD("gui_is_drag_successful"), &Viewport::gui_is_drag_successful); - ClassDB::bind_method(D_METHOD("gui_release_focus"), &Viewport::gui_release_focus); - ClassDB::bind_method(D_METHOD("gui_get_focus_owner"), &Viewport::gui_get_focus_owner); + ClassDB::bind_method(D_METHOD("gui_release_focus", "player_id"), &Viewport::gui_release_focus, DEFVAL(PlayerId::P1)); + ClassDB::bind_method(D_METHOD("gui_get_focus_owner", "player_id"), &Viewport::gui_get_focus_owner, DEFVAL(PlayerId::P1)); ClassDB::bind_method(D_METHOD("gui_get_hovered_control"), &Viewport::gui_get_hovered_control); ClassDB::bind_method(D_METHOD("set_disable_input", "disable"), &Viewport::set_disable_input); ClassDB::bind_method(D_METHOD("is_input_disabled"), &Viewport::is_input_disabled); - ClassDB::bind_method(D_METHOD("_gui_remove_focus_for_window"), &Viewport::_gui_remove_focus_for_window); + ClassDB::bind_method(D_METHOD("_gui_remove_focus_for_window", "player"), &Viewport::_gui_remove_focus_for_window, DEFVAL(PlayerId::P1)); ClassDB::bind_method(D_METHOD("set_positional_shadow_atlas_size", "size"), &Viewport::set_positional_shadow_atlas_size); ClassDB::bind_method(D_METHOD("get_positional_shadow_atlas_size"), &Viewport::get_positional_shadow_atlas_size); diff --git a/scene/main/viewport.h b/scene/main/viewport.h index 8ae3b9ee47bd..061826ab3644 100644 --- a/scene/main/viewport.h +++ b/scene/main/viewport.h @@ -366,7 +366,7 @@ class Viewport : public Node { Control *mouse_focus = nullptr; Control *mouse_click_grabber = nullptr; BitField mouse_focus_mask; - Control *key_focus = nullptr; + Control *key_focus[PLAYERS_MAX] = { nullptr }; Control *mouse_over = nullptr; LocalVector mouse_over_hierarchy; bool sending_mouse_enter_exit_notifications = false; @@ -438,18 +438,18 @@ class Viewport : public Node { void _gui_cancel_tooltip(); void _gui_show_tooltip(); - void _gui_remove_control(Control *p_control); - void _gui_hide_control(Control *p_control); + void _gui_remove_control(Control *p_control, PlayerId p_player_id = PlayerId::P1); + void _gui_hide_control(Control *p_control, PlayerId p_player_id = PlayerId::P1); void _gui_update_mouse_over(); void _gui_force_drag(Control *p_base, const Variant &p_data, Control *p_control); void _gui_set_drag_preview(Control *p_base, Control *p_control); Control *_gui_get_drag_preview(); - void _gui_remove_focus_for_window(Node *p_window); - void _gui_unfocus_control(Control *p_control); - bool _gui_control_has_focus(const Control *p_control); - void _gui_control_grab_focus(Control *p_control); + void _gui_remove_focus_for_window(Node *p_window, PlayerId p_player_id = PlayerId::P1); + void _gui_unfocus_control(Control *p_control, PlayerId p_player_id = PlayerId::P1); + bool _gui_control_has_focus(const Control *p_control, PlayerId p_player_id = PlayerId::P1); + void _gui_control_grab_focus(Control *p_control, PlayerId p_player_id = PlayerId::P1); void _gui_grab_click_focus(Control *p_control); void _post_gui_grab_click_focus(); void _gui_accept_event(); @@ -491,6 +491,14 @@ class Viewport : public Node { void _window_start_resize(SubWindowResize p_edge, Window *p_window); protected: +#ifndef DISABLE_DEPRECATED + void _gui_remove_focus_for_window_bind_compat_102412(Node *p_window); + void _push_text_input_bind_compat_102412(const String &p_text); + void _gui_release_focus_bind_compat_102412(); + Control *_gui_get_focus_owner_bind_compat_102412() const; + static void _bind_compatibility_methods(); +#endif // DISABLE_DEPRECATED + bool _set_size(const Size2i &p_size, const Size2 &p_size_2d_override, bool p_allocated); Size2i _get_size() const; @@ -590,7 +598,7 @@ class Viewport : public Node { Vector2 get_camera_coords(const Vector2 &p_viewport_coords) const; Vector2 get_camera_rect_size() const; - void push_text_input(const String &p_text); + void push_text_input(const String &p_text, PlayerId p_player_id = PlayerId::P1); void push_input(const Ref &p_event, bool p_local_coords = false); #ifndef DISABLE_DEPRECATED void push_unhandled_input(const Ref &p_event, bool p_local_coords = false); @@ -620,8 +628,8 @@ class Viewport : public Node { void gui_reset_canvas_sort_index(); int gui_get_canvas_sort_index(); - void gui_release_focus(); - Control *gui_get_focus_owner() const; + void gui_release_focus(PlayerId p_player_id = PlayerId::P1); + Control *gui_get_focus_owner(PlayerId p_player_id = PlayerId::P1) const; Control *gui_get_hovered_control() const; PackedStringArray get_configuration_warnings() const override; diff --git a/scene/main/window.cpp b/scene/main/window.cpp index 62c26bd836aa..51bc8bfd5da3 100644 --- a/scene/main/window.cpp +++ b/scene/main/window.cpp @@ -853,6 +853,7 @@ void Window::update_mouse_cursor_state() { mm->set_position(pos); mm->set_global_position(xform.xform(pos)); mm->set_device(InputEvent::DEVICE_ID_INTERNAL); + mm->set_player_from_device(); push_input(mm, true); } diff --git a/scene/register_scene_types.cpp b/scene/register_scene_types.cpp index 5de5e5a1c006..f5e50fa50afe 100644 --- a/scene/register_scene_types.cpp +++ b/scene/register_scene_types.cpp @@ -1278,6 +1278,10 @@ void register_scene_types() { GLOBAL_DEF_BASIC(vformat("%s/layer_%d", PNAME("layer_names/avoidance"), i + 1), ""); } + for (int i = 0; i < 8; i++) { + GLOBAL_DEF_BASIC(vformat("%s/layer_%d", PNAME("layer_names/player_mask"), i + 1), ""); + } + if (RenderingServer::get_singleton()) { // RenderingServer needs to exist for this to succeed. ColorPicker::init_shaders(); diff --git a/tests/core/input/test_input_event.h b/tests/core/input/test_input_event.h index 7de5e631378f..e6b340f3251e 100644 --- a/tests/core/input/test_input_event.h +++ b/tests/core/input/test_input_event.h @@ -48,6 +48,7 @@ TEST_CASE("[InputEvent] Signal is emitted when device is changed") { empty_args.push_back(args1); input_event->set_device(1); + input_event->set_player_from_device(); SIGNAL_CHECK("changed", empty_args); CHECK(input_event->get_device() == 1);