From e0d13f022682baf5f40c6ef0bc513416b33e5a7f Mon Sep 17 00:00:00 2001 From: matricola787 Date: Sun, 2 Feb 2025 10:11:26 +0100 Subject: [PATCH] Support Input and UI for multiple players --- core/config/project_settings.cpp | 3 + core/config/project_settings.h | 4 + core/core_constants.cpp | 10 + core/input/input.cpp | 222 +++++++++++++------ core/input/input.h | 55 +++-- core/input/input_enums.h | 32 +++ core/input/input_event.cpp | 96 +++++++- core/input/input_event.h | 13 +- core/input/input_map.cpp | 9 + core/object/object.h | 1 + core/os/midi_driver.cpp | 1 + core/variant/binder_common.h | 2 + core/variant/variant.h | 1 + doc/classes/@GlobalScope.xml | 27 +++ doc/classes/Control.xml | 3 +- doc/classes/Input.xml | 43 ++++ doc/classes/InputEvent.xml | 12 + doc/classes/ProjectSettings.xml | 21 ++ editor/code_editor.cpp | 4 +- editor/connections_dialog.cpp | 2 +- editor/create_dialog.cpp | 2 +- editor/editor_properties.cpp | 14 +- editor/editor_properties.h | 1 + editor/editor_properties_array_dict.cpp | 4 +- editor/find_in_files.cpp | 6 +- editor/gui/editor_file_dialog.cpp | 2 +- editor/gui/editor_spin_slider.cpp | 2 +- editor/gui/scene_tree_editor.cpp | 2 +- editor/input_event_configuration_dialog.cpp | 5 + editor/plugins/canvas_item_editor_plugin.cpp | 2 +- editor/plugins/script_text_editor.cpp | 12 +- editor/plugins/text_editor.cpp | 12 +- editor/plugins/text_shader_editor.cpp | 2 +- editor/plugins/tiles/tile_set_editor.cpp | 2 +- editor/project_manager/project_dialog.cpp | 6 +- editor/scene_create_dialog.cpp | 2 +- modules/gdscript/doc_classes/@GDScript.xml | 11 + modules/gdscript/gdscript_parser.cpp | 1 + scene/gui/button.cpp | 10 +- scene/gui/color_picker.cpp | 2 +- scene/gui/control.cpp | 157 ++++++++++--- scene/gui/control.h | 22 +- scene/gui/file_dialog.cpp | 2 +- scene/main/viewport.cpp | 140 ++++++------ scene/main/viewport.h | 20 +- scene/main/window.cpp | 1 + tests/core/input/test_input_event.h | 1 + 47 files changed, 759 insertions(+), 245 deletions(-) diff --git a/core/config/project_settings.cpp b/core/config/project_settings.cpp index fdcd4f9b606..5acd6ed4267 100644 --- a/core/config/project_settings.cpp +++ b/core/config/project_settings.cpp @@ -1546,6 +1546,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 02049290f6b..b56c0104f81 100644 --- a/core/config/project_settings.h +++ b/core/config/project_settings.h @@ -95,6 +95,10 @@ protected: 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 236caa0bc4b..b8126cee724 100644 --- a/core/core_constants.cpp +++ b/core/core_constants.cpp @@ -527,6 +527,15 @@ 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_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 +658,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.cpp b/core/input/input.cpp index b705968b87e..7d5f18b8936 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; } - 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 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 bbe185de3cb..4d3601788bf 100644 --- a/core/input/input.h +++ b/core/input/input.h @@ -57,6 +57,19 @@ public: 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, @@ -131,7 +144,9 @@ private: } 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; @@ -164,18 +179,6 @@ private: 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; @@ -306,14 +309,20 @@ public: 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) 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; + 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)); + } + + 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); @@ -350,8 +359,8 @@ public: 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; @@ -377,6 +386,8 @@ public: 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 182a5aabe54..2baf8b6160a 100644 --- a/core/input/input_enums.h +++ b/core/input/input_enums.h @@ -138,4 +138,36 @@ 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); +} + #endif // INPUT_ENUMS_H diff --git a/core/input/input_event.cpp b/core/input/input_event.cpp index bd793ef6b8f..1d4013e54fc 100644 --- a/core/input/input_event.cpp +++ b/core/input/input_event.cpp @@ -30,6 +30,8 @@ #include "input_event.h" +#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 +49,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 +182,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 +207,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 +817,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 +1037,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 +1441,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 +1567,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 +1786,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 +1829,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 19176f748e9..4a174f3630d 100644 --- a/core/input/input_event.h +++ b/core/input/input_event.h @@ -54,6 +54,7 @@ class InputEvent : public Resource { GDCLASS(InputEvent, Resource); int device = 0; + PlayerId player = PlayerId::P1; protected: bool canceled = false; @@ -68,11 +69,15 @@ public: 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 bcd3b63a035..703ef38c721 100644 --- a/core/input/input_map.cpp +++ b/core/input/input_map.cpp @@ -432,6 +432,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); @@ -450,25 +451,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 16d8e19eaa0..5231110ff61 100644 --- a/core/object/object.h +++ b/core/object/object.h @@ -90,6 +90,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 6c748b14980..c1862b22f9a 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 b6810930cca..4fd7b34c581 100644 --- a/core/variant/binder_common.h +++ b/core/variant/binder_common.h @@ -174,6 +174,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 633b9c9e831..6ed24fa3102 100644 --- a/core/variant/variant.h +++ b/core/variant/variant.h @@ -561,6 +561,7 @@ public: 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 fb2dfa3dde5..8a6c43c204f 100644 --- a/doc/classes/@GlobalScope.xml +++ b/doc/classes/@GlobalScope.xml @@ -2444,6 +2444,30 @@ Extra mouse button 2 mask. + + Player 1. + + + Player 1. + + + Player 1. + + + Player 1. + + + Player 1. + + + Player 1. + + + Player 1. + + + Player 1. + An invalid game controller button. @@ -2816,6 +2840,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]. diff --git a/doc/classes/Control.xml b/doc/classes/Control.xml index 451f399d2c4..57f0196e399 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. diff --git a/doc/classes/Input.xml b/doc/classes/Input.xml index e0d8be91577..acfb21b8a79 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. @@ -57,6 +59,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. @@ -66,6 +69,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. @@ -75,6 +79,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]. @@ -192,6 +197,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. @@ -202,6 +208,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. @@ -215,6 +222,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. @@ -226,6 +234,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. @@ -519,5 +528,39 @@ 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 6cf490accbc..faf58109a0e 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 6bdd24ecb83..a94b287cbeb 100644 --- a/doc/classes/ProjectSettings.xml +++ b/doc/classes/ProjectSettings.xml @@ -2113,6 +2113,27 @@ 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". + 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/editor/code_editor.cpp b/editor/code_editor.cpp index ef5d878ca0a..be9a6870922 100644 --- a/editor/code_editor.cpp +++ b/editor/code_editor.cpp @@ -594,10 +594,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 429467d3403..323f2e96870 100644 --- a/editor/connections_dialog.cpp +++ b/editor/connections_dialog.cpp @@ -470,7 +470,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 7f1670bcb64..b215494b36c 100644 --- a/editor/create_dialog.cpp +++ b/editor/create_dialog.cpp @@ -491,7 +491,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 73f6c70f342..bba0f2b867d 100644 --- a/editor/editor_properties.cpp +++ b/editor/editor_properties.cpp @@ -1195,6 +1195,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; @@ -2757,7 +2763,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: { @@ -3535,7 +3541,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: @@ -3559,6 +3566,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 6fe8cc3c3f3..04e7b6ad3cd 100644 --- a/editor/editor_properties.h +++ b/editor/editor_properties.h @@ -303,6 +303,7 @@ public: 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 9663157ff99..a0d4eeda00d 100644 --- a/editor/editor_properties_array_dict.cpp +++ b/editor/editor_properties_array_dict.cpp @@ -517,7 +517,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(); @@ -1337,7 +1337,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 cc3f3f2259d..1ddc2690f54 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 0f4d1161d74..2ab82c66b49 100644 --- a/editor/gui/editor_file_dialog.cpp +++ b/editor/gui/editor_file_dialog.cpp @@ -2236,7 +2236,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 a9d8e3ae6c6..b6d4f67d3ca 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 d299e8bb4b8..0434b1ed3e3 100644 --- a/editor/gui/scene_tree_editor.cpp +++ b/editor/gui/scene_tree_editor.cpp @@ -2227,7 +2227,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 8d9617bc90f..6b7b5fd1381 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 4256e74619a..94155a907a0 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 641efa83157..bedcedaefa8 100644 --- a/editor/plugins/script_text_editor.cpp +++ b/editor/plugins/script_text_editor.cpp @@ -1447,27 +1447,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 5ba1b201e5b..3da20050f99 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 693cf2436ef..faee9bda182 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 df89bf285bd..370ade4f298 100644 --- a/editor/plugins/tiles/tile_set_editor.cpp +++ b/editor/plugins/tiles/tile_set_editor.cpp @@ -989,7 +989,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 6f2e9da3e0c..f72ebfb9e6d 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 c197950a2b1..434d8d2665e 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/modules/gdscript/doc_classes/@GDScript.xml b/modules/gdscript/doc_classes/@GDScript.xml index caed2a808d4..c5e64581840 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 64639178717..08814dbc8c7 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/scene/gui/button.cpp b/scene/gui/button.cpp index 8975b85e108..ef4da8306b3 100644 --- a/scene/gui/button.cpp +++ b/scene/gui/button.cpp @@ -226,7 +226,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 ebefa7ef476..97d41a972d7 100644 --- a/scene/gui/color_picker.cpp +++ b/scene/gui/color_picker.cpp @@ -274,7 +274,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::_update_controls() { diff --git a/scene/gui/control.cpp b/scene/gui/control.cpp index 92b5f327775..cf38cd76f56 100644 --- a/scene/gui/control.cpp +++ b/scene/gui/control.cpp @@ -2014,21 +2014,41 @@ Control::FocusMode Control::get_focus_mode() const { return data.focus_mode; } -bool Control::has_focus() const { - ERR_READ_THREAD_GUARD_V(false); - return is_inside_tree() && get_viewport()->_gui_control_has_focus(this); +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; } -void Control::grab_focus() { +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, p_player_id); +} + +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() { @@ -2038,18 +2058,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. } @@ -2064,7 +2084,8 @@ static Control *_next_control(Control *p_from) { ERR_FAIL_INDEX_V(next, parent->get_child_count(), nullptr); for (int i = (next + 1); i < parent->get_child_count(); i++) { Control *c = Object::cast_to(parent->get_child(i)); - if (!c || !c->is_visible_in_tree() || c->is_set_as_top_level()) { + bool is_player_mask_compatible = Input::is_player_id_in_mask(c->calculate_ancestral_player_mask(), p_player_id); + if (!c || !c->is_visible_in_tree() || c->is_set_as_top_level() || !is_player_mask_compatible) { continue; } @@ -2072,10 +2093,10 @@ static Control *_next_control(Control *p_from) { } // 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); Control *from = const_cast(this); @@ -2087,7 +2108,17 @@ 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() && c->get_focus_mode() != FOCUS_NONE) { + bool valid = true; + if (!c->is_visible()) { + valid = false; + } + if (c->get_focus_mode() == FOCUS_NONE) { + valid = false; + } + if (!Input::is_player_id_in_mask(c->calculate_ancestral_player_mask(), p_player_id)) { + valid = false; + } + if (valid) { return c; } } @@ -2102,12 +2133,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); while (next_child && !next_child->is_set_as_top_level()) { @@ -2142,11 +2177,12 @@ 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) { Control *child = nullptr; 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()) { + bool is_player_mask_compatible = Input::is_player_id_in_mask(c->calculate_ancestral_player_mask(), p_player_id); + if (!c || !c->is_visible_in_tree() || c->is_set_as_top_level() || !is_player_mask_compatible) { continue; } @@ -2159,13 +2195,12 @@ static Control *_prev_control(Control *p_from) { } // No prev in parent, try the same in parent. - return _prev_control(child); + return _prev_control(child, p_player_id); } -Control *Control::find_prev_valid_focus() const { +Control *Control::find_prev_valid_focus(PlayerId p_player_id) const { ERR_READ_THREAD_GUARD_V(nullptr); Control *from = const_cast(this); - while (true) { // If the focus property is manually overwritten, attempt to use it. @@ -2174,7 +2209,17 @@ Control *Control::find_prev_valid_focus() const { 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() && c->get_focus_mode() != FOCUS_NONE) { + bool valid = true; + if (!c->is_visible()) { + valid = false; + } + if (c->get_focus_mode() == FOCUS_NONE) { + valid = false; + } + if (!Input::is_player_id_in_mask(c->calculate_ancestral_player_mask(), p_player_id)) { + valid = false; + } + if (valid) { return c; } } @@ -2186,7 +2231,7 @@ Control *Control::find_prev_valid_focus() const { if (from->is_set_as_top_level() || !Object::cast_to(from->get_parent())) { // Find last of the children. - prev_child = _prev_control(from); + prev_child = _prev_control(from, p_player_id); } else { for (int i = (from->get_index() - 1); i >= 0; i--) { @@ -2196,6 +2241,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; } @@ -2203,7 +2252,7 @@ Control *Control::find_prev_valid_focus() const { if (!prev_child) { prev_child = Object::cast_to(from->get_parent()); } else { - prev_child = _prev_control(prev_child); + prev_child = _prev_control(prev_child, p_player_id); } } @@ -2253,9 +2302,39 @@ 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 { + // Editor doesn't need multiplayer UI features (also prevents a crash). + if (Engine::get_singleton()->is_editor_hint()) { + return BitField(PLAYER_ALL); + } + + 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) { @@ -2273,11 +2352,14 @@ Control *Control::_get_focus_neighbor(Side p_side, int p_count) { if (c->get_focus_mode() == FOCUS_NONE) { valid = false; } + if (!Input::is_player_id_in_mask(c->calculate_ancestral_player_mask(), p_player_id)) { + valid = false; + } if (valid) { 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; } @@ -2382,8 +2464,8 @@ Control *Control::_get_focus_neighbor(Side p_side, int p_count) { return result; } -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) { @@ -2395,6 +2477,11 @@ 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; + bool is_player_mask_compatible = c->calculate_ancestral_player_mask() & calculate_ancestral_player_mask(); + if (c && !is_player_mask_compatible) { + return; + } + if (c && c != this && c->get_focus_mode() == 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); @@ -3518,12 +3605,13 @@ void Control::_bind_methods() { ClassDB::bind_method(D_METHOD("get_global_rect"), &Control::get_global_rect); ClassDB::bind_method(D_METHOD("set_focus_mode", "mode"), &Control::set_focus_mode); ClassDB::bind_method(D_METHOD("get_focus_mode"), &Control::get_focus_mode); - 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); @@ -3609,6 +3697,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); @@ -3709,6 +3801,7 @@ void Control::_bind_methods() { ADD_PROPERTY(PropertyInfo(Variant::NODE_PATH, "focus_next", PROPERTY_HINT_NODE_PATH_VALID_TYPES, "Control"), "set_focus_next", "get_focus_next"); 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, "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 496df44a3d2..794ad85042a 100644 --- a/scene/gui/control.h +++ b/scene/gui/control.h @@ -232,6 +232,9 @@ private: NodePath focus_next; NodePath focus_prev; + // Accept inputs from all players by default. + BitField player_mask = PLAYER_ALL; + ObjectID shortcut_context; // Theming. @@ -315,7 +318,7 @@ private: // 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); // Theming. @@ -540,14 +543,15 @@ public: void set_focus_mode(FocusMode p_focus_mode); FocusMode get_focus_mode() 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; @@ -557,6 +561,10 @@ public: 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 b9450fd77f0..39b02265870 100644 --- a/scene/gui/file_dialog.cpp +++ b/scene/gui/file_dialog.cpp @@ -1637,7 +1637,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.cpp b/scene/main/viewport.cpp index 76b2502bade..120ab0f8c04 100644 --- a/scene/main/viewport.cpp +++ b/scene/main/viewport.cpp @@ -751,6 +751,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)); @@ -1786,6 +1787,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(); @@ -1837,8 +1841,8 @@ void Viewport::_gui_input_event(Ref p_event) { if (control->get_focus_mode() != 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; } @@ -2163,13 +2167,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()) { @@ -2177,7 +2181,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; @@ -2186,56 +2190,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(); } } @@ -2337,18 +2341,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)) { @@ -2362,13 +2368,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()); @@ -2495,24 +2503,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(); @@ -2544,6 +2555,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); } } @@ -2601,6 +2613,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); } } @@ -2619,6 +2632,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); } } @@ -2627,15 +2641,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); } } @@ -3455,19 +3469,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 { @@ -4794,7 +4810,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)); @@ -4809,14 +4825,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); + ClassDB::bind_method(D_METHOD("gui_get_focus_owner", "player_id"), &Viewport::gui_get_focus_owner); 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 63bec5b01c3..acf771dc420 100644 --- a/scene/main/viewport.h +++ b/scene/main/viewport.h @@ -367,7 +367,7 @@ private: 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; @@ -439,18 +439,18 @@ private: 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(); @@ -591,7 +591,7 @@ public: 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); @@ -619,8 +619,8 @@ public: 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 1bb01313e72..2963b9fa01d 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/tests/core/input/test_input_event.h b/tests/core/input/test_input_event.h index c6a287e47b5..92699043a19 100644 --- a/tests/core/input/test_input_event.h +++ b/tests/core/input/test_input_event.h @@ -49,6 +49,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);