diff --git a/core/config/project_settings.cpp b/core/config/project_settings.cpp index 8183c7737bcf..63dbc0acd119 100644 --- a/core/config/project_settings.cpp +++ b/core/config/project_settings.cpp @@ -1613,6 +1613,13 @@ ProjectSettings::ProjectSettings() { GLOBAL_DEF_BASIC("input_devices/pointing/android/enable_pan_and_scale_gestures", false); GLOBAL_DEF_BASIC(PropertyInfo(Variant::INT, "input_devices/pointing/android/rotary_input_scroll_axis", PROPERTY_HINT_ENUM, "Horizontal,Vertical"), 1); GLOBAL_DEF("input_devices/pointing/android/override_volume_buttons", false); + GLOBAL_DEF_BASIC("input_devices/virtual_controller/ios/enable_controller", false); + GLOBAL_DEF_BASIC("input_devices/virtual_controller/ios/enable_left_thumbstick", true); + GLOBAL_DEF_BASIC("input_devices/virtual_controller/ios/enable_right_thumbstick", true); + GLOBAL_DEF_BASIC("input_devices/virtual_controller/ios/enable_button_a", true); + GLOBAL_DEF_BASIC("input_devices/virtual_controller/ios/enable_button_b", true); + GLOBAL_DEF_BASIC("input_devices/virtual_controller/ios/enable_button_x", true); + GLOBAL_DEF_BASIC("input_devices/virtual_controller/ios/enable_button_y", true); // These properties will not show up in the dialog. If you want to exclude whole groups, use add_hidden_prefix(). GLOBAL_DEF_INTERNAL("application/config/features", PackedStringArray()); diff --git a/core/input/input.cpp b/core/input/input.cpp index 6b9fec6f6003..92958bb14a42 100644 --- a/core/input/input.cpp +++ b/core/input/input.cpp @@ -170,6 +170,7 @@ void Input::_bind_methods() { ClassDB::bind_method(D_METHOD("is_emulating_mouse_from_touch"), &Input::is_emulating_mouse_from_touch); ClassDB::bind_method(D_METHOD("set_emulate_touch_from_mouse", "enable"), &Input::set_emulate_touch_from_mouse); ClassDB::bind_method(D_METHOD("is_emulating_touch_from_mouse"), &Input::is_emulating_touch_from_mouse); + ClassDB::bind_method(D_METHOD("get_virtual_controller"), &Input::get_virtual_controller); ADD_PROPERTY(PropertyInfo(Variant::INT, "mouse_mode"), "set_mouse_mode", "get_mouse_mode"); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "use_accumulated_input"), "set_use_accumulated_input", "is_using_accumulated_input"); @@ -1056,6 +1057,10 @@ bool Input::is_emulating_touch_from_mouse() const { return emulate_touch_from_mouse; } +VirtualController *Input::get_virtual_controller() { + return OS::get_singleton()->get_virtual_controller(); +} + // Calling this whenever the game window is focused helps unsticking the "touch mouse" // if the OS or its abstraction class hasn't properly reported that touch pointers raised void Input::ensure_touch_mouse_raised() { diff --git a/core/input/input.h b/core/input/input.h index 8fc1b9c5dacf..514f3b96c22d 100644 --- a/core/input/input.h +++ b/core/input/input.h @@ -31,6 +31,7 @@ #pragma once #include "core/input/input_event.h" +#include "core/input/virtual_controller.h" #include "core/object/object.h" #include "core/os/keyboard.h" #include "core/os/thread_safe.h" @@ -389,6 +390,8 @@ class Input : public Object { void set_use_accumulated_input(bool p_enable); bool is_using_accumulated_input(); + VirtualController *get_virtual_controller(); + void release_pressed_events(); void set_event_dispatch_function(EventDispatchFunc p_function); diff --git a/core/input/virtual_controller.cpp b/core/input/virtual_controller.cpp new file mode 100644 index 000000000000..a7f702a2c335 --- /dev/null +++ b/core/input/virtual_controller.cpp @@ -0,0 +1,49 @@ +/**************************************************************************/ +/* virtual_controller.cpp */ +/**************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/**************************************************************************/ +/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */ +/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/**************************************************************************/ + +#include "virtual_controller.h" + +void VirtualController::_bind_methods() { + ClassDB::bind_method(D_METHOD("enable"), &VirtualController::enable); + ClassDB::bind_method(D_METHOD("disable"), &VirtualController::disable); + ClassDB::bind_method(D_METHOD("is_enabled"), &VirtualController::is_enabled); + ClassDB::bind_method(D_METHOD("set_enabled_left_thumbstick", "enable"), &VirtualController::set_enabled_left_thumbstick); + ClassDB::bind_method(D_METHOD("is_enabled_left_thumbstick"), &VirtualController::is_enabled_left_thumbstick); + ClassDB::bind_method(D_METHOD("set_enabled_right_thumbstick", "enable"), &VirtualController::set_enabled_right_thumbstick); + ClassDB::bind_method(D_METHOD("is_enabled_right_thumbstick"), &VirtualController::is_enabled_right_thumbstick); + ClassDB::bind_method(D_METHOD("set_enabled_button_a", "enable"), &VirtualController::set_enabled_button_a); + ClassDB::bind_method(D_METHOD("is_enabled_button_a"), &VirtualController::is_enabled_button_a); + ClassDB::bind_method(D_METHOD("set_enabled_button_b", "enable"), &VirtualController::set_enabled_button_b); + ClassDB::bind_method(D_METHOD("is_enabled_button_b"), &VirtualController::is_enabled_button_b); + ClassDB::bind_method(D_METHOD("set_enabled_button_x", "enable"), &VirtualController::set_enabled_button_x); + ClassDB::bind_method(D_METHOD("is_enabled_button_x"), &VirtualController::is_enabled_button_x); + ClassDB::bind_method(D_METHOD("set_enabled_button_y", "enable"), &VirtualController::set_enabled_button_y); + ClassDB::bind_method(D_METHOD("is_enabled_button_y"), &VirtualController::is_enabled_button_y); +} diff --git a/core/input/virtual_controller.h b/core/input/virtual_controller.h new file mode 100644 index 000000000000..0188e7fb7128 --- /dev/null +++ b/core/input/virtual_controller.h @@ -0,0 +1,57 @@ +/**************************************************************************/ +/* virtual_controller.h */ +/**************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/**************************************************************************/ +/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */ +/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/**************************************************************************/ + +#pragma once + +#include "core/object/class_db.h" + +class VirtualController : public Object { + GDCLASS(VirtualController, Object); + +protected: + static void _bind_methods(); + +public: + virtual void enable() = 0; + virtual void disable() = 0; + virtual bool is_enabled() = 0; + virtual void set_enabled_left_thumbstick(bool p_enabled) = 0; + virtual bool is_enabled_left_thumbstick() = 0; + virtual void set_enabled_right_thumbstick(bool p_enabled) = 0; + virtual bool is_enabled_right_thumbstick() = 0; + virtual void set_enabled_button_a(bool p_enabled) = 0; + virtual bool is_enabled_button_a() = 0; + virtual void set_enabled_button_b(bool p_enabled) = 0; + virtual bool is_enabled_button_b() = 0; + virtual void set_enabled_button_x(bool p_enabled) = 0; + virtual bool is_enabled_button_x() = 0; + virtual void set_enabled_button_y(bool p_enabled) = 0; + virtual bool is_enabled_button_y() = 0; +}; diff --git a/core/os/os.cpp b/core/os/os.cpp index bbeb5b1fb285..131a78a8f3ad 100644 --- a/core/os/os.cpp +++ b/core/os/os.cpp @@ -771,6 +771,16 @@ void OS::benchmark_dump() { #endif } +VirtualController *OS::get_virtual_controller() const { + return nullptr; +} + +void OS::controller_connected() const { +} + +void OS::controller_disconnected() const { +} + OS::OS() { singleton = this; diff --git a/core/os/os.h b/core/os/os.h index 49028b9cad66..a6fc4c05749a 100644 --- a/core/os/os.h +++ b/core/os/os.h @@ -31,6 +31,7 @@ #pragma once #include "core/config/engine.h" +#include "core/input/virtual_controller.h" #include "core/io/logger.h" #include "core/io/remote_filesystem_client.h" #include "core/os/time_enums.h" @@ -365,6 +366,12 @@ class OS { virtual bool _test_create_rendering_device_and_gl(const String &p_display_driver) const { return true; } virtual bool _test_create_rendering_device(const String &p_display_driver) const { return true; } + virtual VirtualController *get_virtual_controller() const; + + virtual void controller_connected() const; + + virtual void controller_disconnected() const; + OS(); virtual ~OS(); }; diff --git a/core/register_core_types.cpp b/core/register_core_types.cpp index 568694bdf4d0..29fa95d4a339 100644 --- a/core/register_core_types.cpp +++ b/core/register_core_types.cpp @@ -42,6 +42,7 @@ #include "core/input/input.h" #include "core/input/input_map.h" #include "core/input/shortcut.h" +#include "core/input/virtual_controller.h" #include "core/io/config_file.h" #include "core/io/dir_access.h" #include "core/io/dtls_server.h" @@ -296,6 +297,8 @@ void register_core_types() { GDREGISTER_NATIVE_STRUCT(AudioFrame, "float left;float right"); GDREGISTER_NATIVE_STRUCT(ScriptLanguageExtensionProfilingInfo, "StringName signature;uint64_t call_count;uint64_t total_time;uint64_t self_time"); + GDREGISTER_ABSTRACT_CLASS(VirtualController); + worker_thread_pool = memnew(WorkerThreadPool); OS::get_singleton()->benchmark_end_measure("Core", "Register Types"); diff --git a/doc/classes/Input.xml b/doc/classes/Input.xml index ca54a44f489a..f6a4be1ab4a2 100644 --- a/doc/classes/Input.xml +++ b/doc/classes/Input.xml @@ -202,6 +202,13 @@ By default, the deadzone is automatically calculated from the average of the action deadzones. However, you can override the deadzone to be whatever you want (on the range of 0 to 1). + + + + Return the platform-specific virtual controller or [code]null[/code] if it is not available. See also [VirtualController]. + [b]Note:[/b] Currently implemented only on iOS 15.0+. + + diff --git a/doc/classes/ProjectSettings.xml b/doc/classes/ProjectSettings.xml index a028c0aa1db4..b06f9a162bb8 100644 --- a/doc/classes/ProjectSettings.xml +++ b/doc/classes/ProjectSettings.xml @@ -1472,6 +1472,28 @@ If [code]true[/code], the magnetometer sensor is enabled and [method Input.get_magnetometer] returns valid data. + + If [code]true[/code], the button "A" shows in the virtual controller. + + + If [code]true[/code], the button "B" shows in the virtual controller. + + + If [code]true[/code], the button "X" shows in the virtual controller. + + + If [code]true[/code], the button "Y" shows in the virtual controller. + + + If [code]true[/code], if there are no physical controllers connected, the game shows the virtual controller. + [b]Note:[/b] Currently implemented only on iOS 15.0+. + + + If [code]true[/code], the left thumbstick shows in the virtual controller. + + + If [code]true[/code], the right thumbstick shows in the virtual controller. + The locale to fall back to if a translation isn't available in a given language. If left empty, [code]en[/code] (English) will be used. [b]Note:[/b] Not to be confused with [TextServerFallback]. diff --git a/doc/classes/VirtualController.xml b/doc/classes/VirtualController.xml new file mode 100644 index 000000000000..b2d510a0a6ba --- /dev/null +++ b/doc/classes/VirtualController.xml @@ -0,0 +1,110 @@ + + + + A software emulation of a real controller that you configure specifically for your game. + + + Use a virtual controller to display software controls that you can customize over your game. You create a virtual controller from a configuration where you choose the input elements to display. When you connect the controller to the device, users interact with it similarly to a real controller. + [b]Note:[/b] Currently implemented only on iOS 15.0+. + + + + + + + + Disconnects the virtual controller from the device and removes it from the screen. + + + + + + If there are no physical controllers connected, connects the virtual controller to the device and displays it on the screen. + + + + + + Returns [code]true[/code] if the virtual controller is enabled. + + + + + + Returns [code]true[/code] if the button "A" is enabled. + + + + + + Returns [code]true[/code] if the button "B" is enabled. + + + + + + Returns [code]true[/code] if the button "X" is enabled. + + + + + + Returns [code]true[/code] if the button "Y" is enabled. + + + + + + Returns [code]true[/code] if the left thumbstick is enabled. + + + + + + Returns [code]true[/code] if the right thumbstick is enabled. + + + + + + + Changes the visibility of a button "A" element. Default is [code]true[/code]. + + + + + + + Changes the visibility of a button "B" element. Default is [code]true[/code]. + + + + + + + Changes the visibility of a button "A" element. Default is [code]true[/code]. + + + + + + + Changes the visibility of a button "Y" element. Default is [code]true[/code]. + + + + + + + Changes the visibility of a left thumbstick element. Default is [code]true[/code]. + + + + + + + Changes the visibility of a right thumbstick element. Default is [code]true[/code]. + + + + diff --git a/drivers/apple/joypad_apple.mm b/drivers/apple/joypad_apple.mm index 312b3da71b2f..7acc686b281e 100644 --- a/drivers/apple/joypad_apple.mm +++ b/drivers/apple/joypad_apple.mm @@ -34,6 +34,7 @@ #import #include "core/config/project_settings.h" +#include "core/os/os.h" #include "main/main.h" class API_AVAILABLE(macos(11), ios(14.0), tvos(14.0)) RumbleMotor { @@ -257,6 +258,7 @@ void play_strong_pattern(CHHapticPattern *p_pattern) { return; } add_joypad(controller); + OS::get_singleton()->controller_connected(); }]; disconnect_observer = [NSNotificationCenter.defaultCenter @@ -269,6 +271,7 @@ void play_strong_pattern(CHHapticPattern *p_pattern) { return; } remove_joypad(controller); + OS::get_singleton()->controller_disconnected(); }]; if (@available(macOS 11.3, iOS 14.5, tvOS 14.5, *)) { diff --git a/platform/ios/SCsub b/platform/ios/SCsub index d16dcb1a95a3..08e14bd4126f 100644 --- a/platform/ios/SCsub +++ b/platform/ios/SCsub @@ -77,6 +77,7 @@ ios_lib = [ "keyboard_input_view.mm", "key_mapping_ios.mm", "ios_terminal_logger.mm", + "virtual_controller_ios.mm", ] env_ios = env.Clone() diff --git a/platform/ios/os_ios.h b/platform/ios/os_ios.h index f4283958c1fe..3a0912aabc07 100644 --- a/platform/ios/os_ios.h +++ b/platform/ios/os_ios.h @@ -33,6 +33,7 @@ #ifdef IOS_ENABLED #import "ios.h" +#import "virtual_controller_ios.h" #import "drivers/apple/joypad_apple.h" #import "drivers/coreaudio/audio_driver_coreaudio.h" @@ -61,6 +62,8 @@ class OS_IOS : public OS_Unix { MainLoop *main_loop = nullptr; + IOSVirtualController *virtual_controller = nullptr; + virtual void initialize_core() override; virtual void initialize() override; @@ -126,6 +129,8 @@ class OS_IOS : public OS_Unix { virtual bool _check_internal_feature_support(const String &p_feature) override; + virtual VirtualController *get_virtual_controller() const override; + void on_focus_out(); void on_focus_in(); @@ -133,6 +138,10 @@ class OS_IOS : public OS_Unix { void on_exit_background(); virtual Rect2 calculate_boot_screen_rect(const Size2 &p_window_size, const Size2 &p_imgrect_size) const override; + + virtual void controller_connected() const override; + + virtual void controller_disconnected() const override; }; #endif // IOS_ENABLED diff --git a/platform/ios/os_ios.mm b/platform/ios/os_ios.mm index e1489f02f13c..1c2a25b730f8 100644 --- a/platform/ios/os_ios.mm +++ b/platform/ios/os_ios.mm @@ -168,6 +168,8 @@ Rect2 fit_keep_aspect_covered(const Vector2 &p_container, const Vector2 &p_rect) void OS_IOS::initialize_joypads() { joypad_apple = memnew(JoypadApple); + + virtual_controller = memnew(IOSVirtualController); } void OS_IOS::initialize_modules() { @@ -176,6 +178,10 @@ Rect2 fit_keep_aspect_covered(const Vector2 &p_container, const Vector2 &p_rect) } void OS_IOS::deinitialize_modules() { + if (virtual_controller) { + memdelete(virtual_controller); + } + if (joypad_apple) { memdelete(joypad_apple); } @@ -220,6 +226,10 @@ Rect2 fit_keep_aspect_covered(const Vector2 &p_container, const Vector2 &p_rect) if (Main::start() == EXIT_SUCCESS) { main_loop->initialize(); } + + if (virtual_controller) { + virtual_controller->update_state(); + } } void OS_IOS::finalize() { @@ -644,6 +654,10 @@ Rect2 fit_keep_aspect_covered(const Vector2 &p_container, const Vector2 &p_rect) return false; } +VirtualController *OS_IOS::get_virtual_controller() const { + return virtual_controller; +} + void OS_IOS::on_focus_out() { if (is_focused) { is_focused = false; @@ -717,4 +731,16 @@ Rect2 fit_keep_aspect_covered(const Vector2 &p_container, const Vector2 &p_rect) } } +void OS_IOS::controller_connected() const { + if (virtual_controller) { + virtual_controller->controller_connected(); + } +} + +void OS_IOS::controller_disconnected() const { + if (virtual_controller) { + virtual_controller->controller_disconnected(); + } +} + #endif // IOS_ENABLED diff --git a/platform/ios/virtual_controller_ios.h b/platform/ios/virtual_controller_ios.h new file mode 100644 index 000000000000..85fc3baf0973 --- /dev/null +++ b/platform/ios/virtual_controller_ios.h @@ -0,0 +1,83 @@ +/**************************************************************************/ +/* virtual_controller_ios.h */ +/**************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/**************************************************************************/ +/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */ +/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/**************************************************************************/ + +#pragma once + +#import "core/input/virtual_controller.h" + +#import + +class IOSVirtualController : public VirtualController { +private: +#if defined(__IPHONE_15_0) + API_AVAILABLE(ios(15.0)) + GCVirtualController *gcv_controller; +#endif + +public: + IOSVirtualController(); + ~IOSVirtualController(); + + virtual void enable() override; + virtual void disable() override; + virtual bool is_enabled() override; + virtual void set_enabled_left_thumbstick(bool p_enabled) override; + virtual bool is_enabled_left_thumbstick() override; + virtual void set_enabled_right_thumbstick(bool p_enabled) override; + virtual bool is_enabled_right_thumbstick() override; + virtual void set_enabled_button_a(bool p_enabled) override; + virtual bool is_enabled_button_a() override; + virtual void set_enabled_button_b(bool p_enabled) override; + virtual bool is_enabled_button_b() override; + virtual void set_enabled_button_x(bool p_enabled) override; + virtual bool is_enabled_button_x() override; + virtual void set_enabled_button_y(bool p_enabled) override; + virtual bool is_enabled_button_y() override; + + void controller_connected(); + void controller_disconnected(); + void update_state(); + +private: + void connect_controller(); + void disconnect_controller(); + void initialize(); + void elements_changed(GCInputElementName p_name, bool p_hidden); + void read_project_settings(); + +private: + bool enabled = false; + bool enabled_left_thumbstick = true; + bool enabled_right_thumbstick = true; + bool enabled_button_a = true; + bool enabled_button_b = true; + bool enabled_button_x = true; + bool enabled_button_y = true; +}; diff --git a/platform/ios/virtual_controller_ios.mm b/platform/ios/virtual_controller_ios.mm new file mode 100644 index 000000000000..546f75fed20d --- /dev/null +++ b/platform/ios/virtual_controller_ios.mm @@ -0,0 +1,247 @@ +/**************************************************************************/ +/* virtual_controller_ios.mm */ +/**************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/**************************************************************************/ +/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */ +/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/**************************************************************************/ + +#import "virtual_controller_ios.h" + +#include "core/config/project_settings.h" + +IOSVirtualController::IOSVirtualController() { + initialize(); +} + +IOSVirtualController::~IOSVirtualController() { + if (@available(iOS 15.0, *)) { + gcv_controller = nullptr; + } +} + +void IOSVirtualController::initialize() { + if (@available(iOS 15.0, *)) { + if (!gcv_controller) { + read_project_settings(); + + GCVirtualControllerConfiguration *config = [[GCVirtualControllerConfiguration alloc] init]; + + NSMutableSet *elements = [[NSMutableSet alloc] init]; + + if (is_enabled_left_thumbstick()) { + [elements addObject:GCInputLeftThumbstick]; + } + if (is_enabled_right_thumbstick()) { + [elements addObject:GCInputRightThumbstick]; + } + if (is_enabled_button_a()) { + [elements addObject:GCInputButtonA]; + } + if (is_enabled_button_b()) { + [elements addObject:GCInputButtonB]; + } + if (is_enabled_button_x()) { + [elements addObject:GCInputButtonX]; + } + if (is_enabled_button_y()) { + [elements addObject:GCInputButtonY]; + } + + config.elements = elements; + + dispatch_async(dispatch_get_main_queue(), ^{ + gcv_controller = [[GCVirtualController alloc] initWithConfiguration:config]; + }); + } + } +} + +void IOSVirtualController::read_project_settings() { + enabled = GLOBAL_GET("input_devices/virtual_controller/ios/enable_controller"); + enabled_left_thumbstick = GLOBAL_GET("input_devices/virtual_controller/ios/enable_left_thumbstick"); + enabled_right_thumbstick = GLOBAL_GET("input_devices/virtual_controller/ios/enable_right_thumbstick"); + enabled_button_a = GLOBAL_GET("input_devices/virtual_controller/ios/enable_button_a"); + enabled_button_b = GLOBAL_GET("input_devices/virtual_controller/ios/enable_button_b"); + enabled_button_x = GLOBAL_GET("input_devices/virtual_controller/ios/enable_button_x"); + enabled_button_y = GLOBAL_GET("input_devices/virtual_controller/ios/enable_button_y"); +} + +void IOSVirtualController::elements_changed(GCInputElementName p_name, bool p_hidden) { + if (@available(iOS 15.0, *)) { + dispatch_async(dispatch_get_main_queue(), ^{ + if (gcv_controller) { + [gcv_controller updateConfigurationForElement:p_name + configuration:^(GCVirtualControllerElementConfiguration *configuration) { + configuration.hidden = p_hidden; + return configuration; + }]; + } + }); + } +} + +void IOSVirtualController::enable() { + enabled = true; + update_state(); +} + +void IOSVirtualController::disable() { + enabled = false; + update_state(); +} + +void IOSVirtualController::update_state() { + if (is_enabled()) { + connect_controller(); + } else { + disconnect_controller(); + } +} + +bool IOSVirtualController::is_enabled() { + return enabled; +} + +void IOSVirtualController::connect_controller() { + if (@available(iOS 15.0, *)) { + dispatch_async(dispatch_get_main_queue(), ^{ + if (GCController.controllers.count == 0 && gcv_controller != nil) { + [gcv_controller connectWithReplyHandler:nil]; + } + }); + } +} + +void IOSVirtualController::disconnect_controller() { + if (@available(iOS 15.0, *)) { + dispatch_async(dispatch_get_main_queue(), ^{ + if (gcv_controller) { + [gcv_controller disconnect]; + } + }); + } +} + +void IOSVirtualController::controller_connected() { + if (@available(iOS 15.0, *)) { + if (gcv_controller != nil) { + BOOL has_physical_controller = NO; + for (GCController *controller in GCController.controllers) { + if (controller != gcv_controller.controller) { + has_physical_controller = YES; + break; + } + } + if (has_physical_controller) { + disconnect_controller(); + } + } + } +} + +void IOSVirtualController::controller_disconnected() { + if (is_enabled()) { + connect_controller(); + } +} + +void IOSVirtualController::set_enabled_left_thumbstick(bool p_enabled) { + if (enabled_left_thumbstick != p_enabled) { + enabled_left_thumbstick = p_enabled; + if (@available(iOS 15.0, *)) { + elements_changed(GCInputLeftThumbstick, !p_enabled); + } + } +} + +bool IOSVirtualController::is_enabled_left_thumbstick() { + return enabled_left_thumbstick; +} + +void IOSVirtualController::set_enabled_right_thumbstick(bool p_enabled) { + if (enabled_right_thumbstick != p_enabled) { + enabled_right_thumbstick = p_enabled; + if (@available(iOS 15.0, *)) { + elements_changed(GCInputRightThumbstick, !p_enabled); + } + } +} + +bool IOSVirtualController::is_enabled_right_thumbstick() { + return enabled_right_thumbstick; +} + +void IOSVirtualController::set_enabled_button_a(bool p_enabled) { + if (enabled_button_a != p_enabled) { + enabled_button_a = p_enabled; + if (@available(iOS 15.0, *)) { + elements_changed(GCInputButtonA, !p_enabled); + } + } +} + +bool IOSVirtualController::is_enabled_button_a() { + return enabled_button_a; +} + +void IOSVirtualController::set_enabled_button_b(bool p_enabled) { + if (enabled_button_b != p_enabled) { + enabled_button_b = p_enabled; + if (@available(iOS 15.0, *)) { + elements_changed(GCInputButtonB, !p_enabled); + } + } +} + +bool IOSVirtualController::is_enabled_button_b() { + return enabled_button_b; +} + +void IOSVirtualController::set_enabled_button_x(bool p_enabled) { + if (enabled_button_x != p_enabled) { + enabled_button_x = p_enabled; + if (@available(iOS 15.0, *)) { + elements_changed(GCInputButtonX, !p_enabled); + } + } +} + +bool IOSVirtualController::is_enabled_button_x() { + return enabled_button_x; +} + +void IOSVirtualController::set_enabled_button_y(bool p_enabled) { + if (enabled_button_y != p_enabled) { + enabled_button_y = p_enabled; + if (@available(iOS 15.0, *)) { + elements_changed(GCInputButtonY, !p_enabled); + } + } +} + +bool IOSVirtualController::is_enabled_button_y() { + return enabled_button_y; +}