summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--.editorconfig4
-rw-r--r--.github/workflows/ios_builds.yml50
-rw-r--r--.github/workflows/linux_builds.yml2
-rw-r--r--.github/workflows/macos_builds.yml4
-rw-r--r--.github/workflows/static_checks.yml2
-rw-r--r--.github/workflows/windows_builds.yml2
-rw-r--r--README.md2
-rw-r--r--SConstruct22
-rw-r--r--core/bind/core_bind.cpp1
-rw-r--r--core/callable_method_pointer.h4
-rw-r--r--core/global_constants.cpp32
-rw-r--r--core/global_constants.h1
-rw-r--r--core/math/math_funcs.h12
-rw-r--r--core/object.cpp95
-rw-r--r--core/object.h3
-rw-r--r--core/project_settings.cpp19
-rw-r--r--core/project_settings.h10
-rw-r--r--core/script_language.cpp23
-rw-r--r--core/script_language.h5
-rw-r--r--core/ustring.cpp6
-rw-r--r--core/variant.cpp4
-rw-r--r--doc/classes/@GlobalScope.xml2
-rw-r--r--doc/classes/DisplayServer.xml18
-rw-r--r--doc/classes/ProjectSettings.xml6
-rw-r--r--doc/classes/TileSet.xml12
-rw-r--r--drivers/vulkan/SCsub32
-rw-r--r--editor/code_editor.cpp2
-rw-r--r--editor/doc_data.cpp32
-rw-r--r--editor/doc_data.h1
-rw-r--r--editor/editor_autoload_settings.cpp4
-rw-r--r--editor/input_map_editor.cpp70
-rw-r--r--editor/plugins/animation_player_editor_plugin.cpp2
-rw-r--r--editor/plugins/node_3d_editor_plugin.cpp2
-rw-r--r--editor/plugins/script_editor_plugin.cpp139
-rw-r--r--editor/plugins/script_editor_plugin.h3
-rw-r--r--editor/plugins/script_text_editor.cpp236
-rw-r--r--editor/plugins/script_text_editor.h43
-rw-r--r--editor/plugins/shader_editor_plugin.cpp25
-rw-r--r--editor/plugins/text_editor.cpp50
-rw-r--r--editor/plugins/text_editor.h21
-rw-r--r--editor/plugins/tile_set_editor_plugin.cpp2
-rw-r--r--editor/scene_tree_dock.cpp2
-rw-r--r--editor/script_create_dialog.cpp4
-rw-r--r--gles_builders.py51
-rw-r--r--main/SCsub24
-rw-r--r--main/main.cpp14
-rw-r--r--main/tests/SCsub9
-rw-r--r--methods.py9
-rw-r--r--misc/dist/ios_xcode/godot_ios.xcodeproj/project.pbxproj12
-rw-r--r--misc/dist/ios_xcode/godot_ios/vulkan/icd.d/MoltenVK_icd.json7
-rw-r--r--modules/SCsub13
-rw-r--r--modules/arkit/arkit_interface.h34
-rw-r--r--modules/arkit/arkit_interface.mm371
-rw-r--r--modules/arkit/arkit_session_delegate.h6
-rw-r--r--modules/bullet/shape_bullet.cpp3
-rw-r--r--modules/camera/camera_ios.mm93
-rw-r--r--modules/gdnative/gdnative_builders.py4
-rw-r--r--modules/gdnative/nativescript/nativescript.cpp28
-rw-r--r--modules/gdnative/nativescript/nativescript.h3
-rw-r--r--modules/gdnative/pluginscript/pluginscript_instance.h6
-rw-r--r--modules/gdscript/doc_classes/@GDScript.xml25
-rw-r--r--modules/gdscript/gdscript.cpp33
-rw-r--r--modules/gdscript/gdscript.h5
-rw-r--r--modules/gdscript/gdscript_compiler.cpp2
-rw-r--r--modules/gdscript/gdscript_functions.cpp2
-rw-r--r--modules/modules_builders.py11
-rw-r--r--modules/mono/build_scripts/mono_configure.py3
-rw-r--r--modules/mono/csharp_script.cpp50
-rw-r--r--modules/mono/csharp_script.h4
-rw-r--r--modules/mono/editor/Godot.NET.Sdk/Godot.NET.Sdk.sln16
-rw-r--r--modules/mono/editor/Godot.NET.Sdk/Godot.NET.Sdk/Godot.NET.Sdk.csproj35
-rw-r--r--modules/mono/editor/Godot.NET.Sdk/Godot.NET.Sdk/Godot.NET.Sdk.nuspec22
-rw-r--r--modules/mono/editor/Godot.NET.Sdk/Godot.NET.Sdk/Sdk/Sdk.props112
-rw-r--r--modules/mono/editor/Godot.NET.Sdk/Godot.NET.Sdk/Sdk/Sdk.targets17
-rw-r--r--modules/mono/editor/GodotTools/GodotTools.Core/FileUtils.cs5
-rw-r--r--modules/mono/editor/GodotTools/GodotTools.ProjectEditor/IdentifierUtils.cs56
-rw-r--r--modules/mono/editor/GodotTools/GodotTools.ProjectEditor/ProjectExtensions.cs118
-rw-r--r--modules/mono/editor/GodotTools/GodotTools.ProjectEditor/ProjectGenerator.cs169
-rw-r--r--modules/mono/editor/GodotTools/GodotTools.ProjectEditor/ProjectUtils.cs341
-rw-r--r--modules/mono/editor/GodotTools/GodotTools/BottomPanel.cs72
-rw-r--r--modules/mono/editor/GodotTools/GodotTools/BuildManager.cs30
-rw-r--r--modules/mono/editor/GodotTools/GodotTools/CsProjOperations.cs116
-rw-r--r--modules/mono/editor/GodotTools/GodotTools/Export/ExportPlugin.cs24
-rw-r--r--modules/mono/editor/GodotTools/GodotTools/GodotSharpEditor.cs98
-rw-r--r--modules/mono/editor/GodotTools/GodotTools/Internals/ScriptClassParser.cs4
-rw-r--r--modules/mono/editor/bindings_generator.cpp1
-rw-r--r--modules/mono/editor/csharp_project.cpp69
-rw-r--r--modules/mono/glue/GodotSharp/GodotSharp/GodotSharp.csproj35
-rw-r--r--modules/mono/glue/GodotSharp/GodotSharp/Properties/AssemblyInfo.cs24
-rw-r--r--modules/mono/glue/GodotSharp/GodotSharpEditor/GodotSharpEditor.csproj48
-rw-r--r--modules/mono/glue/GodotSharp/GodotSharpEditor/Properties/AssemblyInfo.cs25
-rw-r--r--modules/visual_script/visual_script_editor.cpp14
-rw-r--r--modules/visual_script/visual_script_editor.h1
-rw-r--r--platform/android/detect.py11
-rw-r--r--platform/android/display_server_android.cpp4
-rw-r--r--platform/android/display_server_android.h2
-rw-r--r--platform/android/export/export.cpp41
-rw-r--r--platform/android/export/gradle_export_util.h100
-rw-r--r--platform/android/java/app/build.gradle2
-rw-r--r--platform/android/java/app/config.gradle16
-rw-r--r--platform/android/java/lib/src/org/godotengine/godot/GodotIO.java4
-rw-r--r--platform/android/java/lib/src/org/godotengine/godot/input/GodotEditText.java20
-rw-r--r--platform/android/java/lib/src/org/godotengine/godot/input/GodotTextInputWrapper.java4
-rw-r--r--platform/android/java_godot_io_wrapper.cpp6
-rw-r--r--platform/android/java_godot_io_wrapper.h2
-rw-r--r--platform/iphone/SCsub12
-rw-r--r--platform/iphone/app_delegate.h19
-rw-r--r--platform/iphone/app_delegate.mm659
-rw-r--r--platform/iphone/detect.py40
-rw-r--r--platform/iphone/display_layer.h58
-rw-r--r--platform/iphone/display_layer.mm186
-rw-r--r--platform/iphone/display_server_iphone.h202
-rw-r--r--platform/iphone/display_server_iphone.mm751
-rw-r--r--platform/iphone/game_center.h6
-rw-r--r--platform/iphone/game_center.mm61
-rw-r--r--platform/iphone/gl_view.h123
-rw-r--r--platform/iphone/gl_view.mm702
-rw-r--r--platform/iphone/godot_iphone.mm (renamed from platform/iphone/godot_iphone.cpp)56
-rw-r--r--platform/iphone/godot_view.h56
-rw-r--r--platform/iphone/godot_view.mm499
-rw-r--r--platform/iphone/godot_view_gesture_recognizer.h44
-rw-r--r--platform/iphone/godot_view_gesture_recognizer.m171
-rw-r--r--platform/iphone/godot_view_renderer.h (renamed from modules/mono/editor/csharp_project.h)18
-rw-r--r--platform/iphone/godot_view_renderer.mm146
-rw-r--r--platform/iphone/icloud.h6
-rw-r--r--platform/iphone/icloud.mm30
-rw-r--r--platform/iphone/in_app_store.h4
-rw-r--r--platform/iphone/in_app_store.mm40
-rw-r--r--platform/iphone/ios.mm18
-rw-r--r--platform/iphone/joypad_iphone.h50
-rw-r--r--platform/iphone/joypad_iphone.mm380
-rw-r--r--platform/iphone/main.m15
-rw-r--r--platform/iphone/os_iphone.cpp632
-rw-r--r--platform/iphone/os_iphone.h151
-rw-r--r--platform/iphone/os_iphone.mm369
-rw-r--r--platform/iphone/view_controller.h20
-rw-r--r--platform/iphone/view_controller.mm356
-rw-r--r--platform/iphone/vulkan_context_iphone.h5
-rw-r--r--platform/iphone/vulkan_context_iphone.mm22
-rw-r--r--platform/linuxbsd/display_server_x11.cpp12
-rw-r--r--platform/linuxbsd/display_server_x11.h1
-rw-r--r--platform/osx/detect.py9
-rw-r--r--platform/osx/display_server_osx.h1
-rw-r--r--platform/osx/display_server_osx.mm33
-rw-r--r--platform/uwp/detect.py9
-rw-r--r--platform/uwp/os_uwp.cpp2
-rw-r--r--platform/uwp/os_uwp.h2
-rw-r--r--platform/windows/detect.py4
-rw-r--r--platform/windows/display_server_windows.cpp16
-rw-r--r--platform/windows/display_server_windows.h1
-rw-r--r--scene/3d/node_3d.cpp4
-rw-r--r--scene/gui/line_edit.cpp9
-rw-r--r--scene/gui/scroll_bar.cpp2
-rw-r--r--scene/gui/text_edit.cpp2
-rw-r--r--scene/main/canvas_item.cpp2
-rw-r--r--scene/main/node.cpp14
-rw-r--r--scene/main/scene_tree.cpp14
-rw-r--r--scene/main/scene_tree.h1
-rw-r--r--scene/main/viewport.cpp8
-rw-r--r--scene/main/window.cpp1
-rw-r--r--scene/register_scene_types.cpp2
-rw-r--r--scene/resources/visual_shader.cpp71
-rw-r--r--scene/resources/visual_shader.h6
-rw-r--r--scene/resources/visual_shader_nodes.cpp4
-rw-r--r--scene/resources/visual_shader_nodes.h2
-rw-r--r--servers/display_server.cpp9
-rw-r--r--servers/display_server.h3
-rw-r--r--tests/SCsub22
-rw-r--r--tests/test_astar.cpp (renamed from main/tests/test_astar.cpp)0
-rw-r--r--tests/test_astar.h (renamed from main/tests/test_astar.h)0
-rw-r--r--tests/test_basis.cpp (renamed from main/tests/test_basis.cpp)0
-rw-r--r--tests/test_basis.h (renamed from main/tests/test_basis.h)0
-rw-r--r--tests/test_class_db.cpp (renamed from main/tests/test_class_db.cpp)0
-rw-r--r--tests/test_class_db.h (renamed from main/tests/test_class_db.h)0
-rw-r--r--tests/test_gdscript.cpp (renamed from main/tests/test_gdscript.cpp)0
-rw-r--r--tests/test_gdscript.h (renamed from main/tests/test_gdscript.h)0
-rw-r--r--tests/test_gui.cpp (renamed from main/tests/test_gui.cpp)0
-rw-r--r--tests/test_gui.h (renamed from main/tests/test_gui.h)0
-rw-r--r--tests/test_main.cpp (renamed from main/tests/test_main.cpp)2
-rw-r--r--tests/test_main.h (renamed from main/tests/test_main.h)0
-rw-r--r--tests/test_math.cpp (renamed from main/tests/test_math.cpp)0
-rw-r--r--tests/test_math.h (renamed from main/tests/test_math.h)0
-rw-r--r--tests/test_oa_hash_map.cpp (renamed from main/tests/test_oa_hash_map.cpp)0
-rw-r--r--tests/test_oa_hash_map.h (renamed from main/tests/test_oa_hash_map.h)0
-rw-r--r--tests/test_ordered_hash_map.cpp (renamed from main/tests/test_ordered_hash_map.cpp)0
-rw-r--r--tests/test_ordered_hash_map.h (renamed from main/tests/test_ordered_hash_map.h)0
-rw-r--r--tests/test_physics_2d.cpp (renamed from main/tests/test_physics_2d.cpp)0
-rw-r--r--tests/test_physics_2d.h (renamed from main/tests/test_physics_2d.h)0
-rw-r--r--tests/test_physics_3d.cpp (renamed from main/tests/test_physics_3d.cpp)0
-rw-r--r--tests/test_physics_3d.h (renamed from main/tests/test_physics_3d.h)0
-rw-r--r--tests/test_render.cpp (renamed from main/tests/test_render.cpp)0
-rw-r--r--tests/test_render.h (renamed from main/tests/test_render.h)0
-rw-r--r--tests/test_shader_lang.cpp (renamed from main/tests/test_shader_lang.cpp)0
-rw-r--r--tests/test_shader_lang.h (renamed from main/tests/test_shader_lang.h)0
-rw-r--r--tests/test_string.h (renamed from main/tests/test_string.h)0
-rw-r--r--tests/test_validate_testing.h (renamed from main/tests/test_validate_testing.h)0
-rw-r--r--thirdparty/README.md3
-rw-r--r--thirdparty/doctest/doctest.h6
-rw-r--r--thirdparty/doctest/patches/fix-arm64-mac.patch18
-rw-r--r--thirdparty/vulkan/patches/VMA-assert-remove.patch29
-rw-r--r--thirdparty/vulkan/vk_mem_alloc.h18
201 files changed, 5431 insertions, 4315 deletions
diff --git a/.editorconfig b/.editorconfig
index f335026e1e..49517a5104 100644
--- a/.editorconfig
+++ b/.editorconfig
@@ -20,3 +20,7 @@ indent_size = 4
[.travis.yml]
indent_style = space
indent_size = 2
+
+[*.{csproj,props,targets,nuspec}]
+indent_style = space
+indent_size = 2
diff --git a/.github/workflows/ios_builds.yml b/.github/workflows/ios_builds.yml
new file mode 100644
index 0000000000..0657a6cb18
--- /dev/null
+++ b/.github/workflows/ios_builds.yml
@@ -0,0 +1,50 @@
+name: iOS Builds
+on: [push, pull_request]
+
+# Global Cache Settings
+env:
+ GODOT_BASE_BRANCH: master
+ SCONS_CACHE_LIMIT: 4096
+
+jobs:
+ ios-template:
+ runs-on: "macos-latest"
+ name: Template (target=release, tools=no)
+
+ steps:
+ - uses: actions/checkout@v2
+
+ # Upload cache on completion and check it out now
+ - name: Load .scons_cache directory
+ id: ios-template-cache
+ uses: actions/cache@v2
+ with:
+ path: ${{github.workspace}}/.scons_cache/
+ key: ${{github.job}}-${{env.GODOT_BASE_BRANCH}}-${{github.ref}}-${{github.sha}}
+ restore-keys: |
+ ${{github.job}}-${{env.GODOT_BASE_BRANCH}}-${{github.ref}}-${{github.sha}}
+ ${{github.job}}-${{env.GODOT_BASE_BRANCH}}-${{github.ref}}
+ ${{github.job}}-${{env.GODOT_BASE_BRANCH}}
+
+ # Use python 3.x release (works cross platform)
+ - name: Set up Python 3.x
+ uses: actions/setup-python@v2
+ with:
+ # Semantic version range syntax or exact version of a Python version
+ python-version: '3.x'
+ # Optional - x64 or x86 architecture, defaults to x64
+ architecture: 'x64'
+
+ # You can test your matrix by printing the current Python version
+ - name: Configuring Python packages
+ run: |
+ python -c "import sys; print(sys.version)"
+ python -m pip install scons
+ python --version
+ scons --version
+
+ - name: Compilation
+ env:
+ SCONS_CACHE: ${{github.workspace}}/.scons_cache/
+ run: |
+ scons -j2 verbose=yes warnings=all werror=yes platform=iphone target=release tools=no
diff --git a/.github/workflows/linux_builds.yml b/.github/workflows/linux_builds.yml
index be7737593a..a05bb09931 100644
--- a/.github/workflows/linux_builds.yml
+++ b/.github/workflows/linux_builds.yml
@@ -60,7 +60,7 @@ jobs:
env:
SCONS_CACHE: ${{github.workspace}}/.scons_cache/
run: |
- scons -j2 verbose=yes warnings=all werror=yes platform=linuxbsd tools=yes target=release_debug module_mono_enabled=yes mono_glue=no
+ scons -j2 verbose=yes warnings=all werror=yes platform=linuxbsd tools=yes tests=yes target=release_debug module_mono_enabled=yes mono_glue=no
# Execute unit tests for the editor
- name: Unit Tests
diff --git a/.github/workflows/macos_builds.yml b/.github/workflows/macos_builds.yml
index 4a9fa910d0..d9f41f09f7 100644
--- a/.github/workflows/macos_builds.yml
+++ b/.github/workflows/macos_builds.yml
@@ -1,4 +1,4 @@
-name: MacOS Builds
+name: macOS Builds
on: [push, pull_request]
# Global Cache Settings
@@ -49,7 +49,7 @@ jobs:
env:
SCONS_CACHE: ${{github.workspace}}/.scons_cache/
run: |
- scons -j2 verbose=yes warnings=all werror=yes platform=osx tools=yes target=release_debug
+ scons -j2 verbose=yes warnings=all werror=yes platform=osx tools=yes tests=yes target=release_debug
# Execute unit tests for the editor
- name: Unit Tests
diff --git a/.github/workflows/static_checks.yml b/.github/workflows/static_checks.yml
index 87339da776..2a7a4e6625 100644
--- a/.github/workflows/static_checks.yml
+++ b/.github/workflows/static_checks.yml
@@ -13,7 +13,7 @@ jobs:
run: |
sudo apt-get update -qq
sudo apt-get install -qq dos2unix recode clang-format
- sudo pip3 install black pygments
+ sudo pip3 install git+https://github.com/psf/black@master pygments
- name: File formatting checks (file_format.sh)
run: |
diff --git a/.github/workflows/windows_builds.yml b/.github/workflows/windows_builds.yml
index c58bae8275..2bc3fcfdaa 100644
--- a/.github/workflows/windows_builds.yml
+++ b/.github/workflows/windows_builds.yml
@@ -54,7 +54,7 @@ jobs:
env:
SCONS_CACHE: /.scons_cache/
run: |
- scons -j2 verbose=yes warnings=all werror=yes platform=windows tools=yes target=release_debug
+ scons -j2 verbose=yes warnings=all werror=yes platform=windows tools=yes tests=yes target=release_debug
# Execute unit tests for the editor
- name: Unit Tests
diff --git a/README.md b/README.md
index 5cfa10c5dd..e962f90333 100644
--- a/README.md
+++ b/README.md
@@ -66,9 +66,7 @@ There are also a number of other learning resources provided by the community,
such as text and video tutorials, demos, etc. Consult the [community channels](https://godotengine.org/community)
for more info.
-[![Travis Build Status](https://travis-ci.org/godotengine/godot.svg?branch=master)](https://travis-ci.org/godotengine/godot)
[![Actions Build Status](https://github.com/godotengine/godot/workflows/Godot/badge.svg?branch=master)](https://github.com/godotengine/godot/actions)
-[![AppVeyor Build Status](https://ci.appveyor.com/api/projects/status/bfiihqq6byxsjxxh/branch/master?svg=true)](https://ci.appveyor.com/project/akien-mga/godot)
[![Code Triagers Badge](https://www.codetriage.com/godotengine/godot/badges/users.svg)](https://www.codetriage.com/godotengine/godot)
[![Translate on Weblate](https://hosted.weblate.org/widgets/godot-engine/-/godot/svg-badge.svg)](https://hosted.weblate.org/engage/godot-engine/?utm_source=widget)
[![Total alerts on LGTM](https://img.shields.io/lgtm/alerts/g/godotengine/godot.svg?logo=lgtm&logoWidth=18)](https://lgtm.com/projects/g/godotengine/godot/alerts)
diff --git a/SConstruct b/SConstruct
index e23aa1cdbc..7ec926f99b 100644
--- a/SConstruct
+++ b/SConstruct
@@ -115,6 +115,7 @@ opts.Add(EnumVariable("target", "Compilation target", "debug", ("debug", "releas
opts.Add(EnumVariable("optimize", "Optimization type", "speed", ("speed", "size")))
opts.Add(BoolVariable("tools", "Build the tools (a.k.a. the Godot editor)", True))
+opts.Add(BoolVariable("tests", "Build the unit tests", False))
opts.Add(BoolVariable("use_lto", "Use link-time optimization", False))
opts.Add(BoolVariable("use_precise_math_checks", "Math checks use very precise epsilon (debug option)", False))
@@ -249,6 +250,10 @@ if env_base["target"] == "debug":
# http://scons.org/doc/production/HTML/scons-user/ch06s04.html
env_base.SetOption("implicit_cache", 1)
+if not env_base["tools"]:
+ # Export templates can't run unit test tool.
+ env_base["tests"] = False
+
if env_base["no_editor_splash"]:
env_base.Append(CPPDEFINES=["NO_EDITOR_SPLASH"])
@@ -312,6 +317,8 @@ if selected_platform in platform_list:
env["verbose"] = True
env["warnings"] = "extra"
env["werror"] = True
+ if env["tools"]:
+ env["tests"] = True
if env["vsproj"]:
env.vs_incs = []
@@ -610,8 +617,9 @@ if selected_platform in platform_list:
editor_module_list = ["regex"]
if env["tools"] and not env.module_check_dependencies("tools", editor_module_list):
print(
- "Build option 'module_" + x + "_enabled=no' cannot be used with 'tools=yes' (editor), "
- "only with 'tools=no' (export template)."
+ "Build option 'module_"
+ + x
+ + "_enabled=no' cannot be used with 'tools=yes' (editor), only with 'tools=no' (export template)."
)
Exit(255)
@@ -641,9 +649,6 @@ if selected_platform in platform_list:
}
)
- # enable test framework globally and inform it of configuration method
- env.Append(CPPDEFINES=["DOCTEST_CONFIG_IMPLEMENT"])
-
scons_cache_path = os.environ.get("SCONS_CACHE")
if scons_cache_path != None:
CacheDir(scons_cache_path)
@@ -651,8 +656,7 @@ if selected_platform in platform_list:
Export("env")
- # build subdirs, the build order is dependent on link order.
-
+ # Build subdirs, the build order is dependent on link order.
SConscript("core/SCsub")
SConscript("servers/SCsub")
SConscript("scene/SCsub")
@@ -661,9 +665,11 @@ if selected_platform in platform_list:
SConscript("platform/SCsub")
SConscript("modules/SCsub")
+ if env["tests"]:
+ SConscript("tests/SCsub")
SConscript("main/SCsub")
- SConscript("platform/" + selected_platform + "/SCsub") # build selected platform
+ SConscript("platform/" + selected_platform + "/SCsub") # Build selected platform.
# Microsoft Visual Studio Project Generation
if env["vsproj"]:
diff --git a/core/bind/core_bind.cpp b/core/bind/core_bind.cpp
index 2f8b11652b..045d7d5872 100644
--- a/core/bind/core_bind.cpp
+++ b/core/bind/core_bind.cpp
@@ -781,6 +781,7 @@ void _OS::_bind_methods() {
// Those default values need to be specified for the docs generator,
// to avoid using values from the documentation writer's own OS instance.
+ ADD_PROPERTY_DEFAULT("tablet_driver", "");
ADD_PROPERTY_DEFAULT("exit_code", 0);
ADD_PROPERTY_DEFAULT("low_processor_usage_mode", false);
ADD_PROPERTY_DEFAULT("low_processor_usage_mode_sleep_usec", 6900);
diff --git a/core/callable_method_pointer.h b/core/callable_method_pointer.h
index 22db7d1c82..1bb89e53e1 100644
--- a/core/callable_method_pointer.h
+++ b/core/callable_method_pointer.h
@@ -131,7 +131,7 @@ void call_with_variant_args_helper(T *p_instance, void (T::*p_method)(P...), con
#ifdef DEBUG_METHODS_ENABLED
(p_instance->*p_method)(VariantCasterAndValidate<P>::cast(p_args, Is, r_error)...);
#else
- (p_instance->*p_method)(VariantCaster<P>::cast(p_args[Is])...);
+ (p_instance->*p_method)(VariantCaster<P>::cast(*p_args[Is])...);
#endif
}
@@ -228,7 +228,7 @@ void call_with_variant_args_ret_helper(T *p_instance, R (T::*p_method)(P...), co
#ifdef DEBUG_METHODS_ENABLED
r_ret = (p_instance->*p_method)(VariantCasterAndValidate<P>::cast(p_args, Is, r_error)...);
#else
- (p_instance->*p_method)(VariantCaster<P>::cast(p_args[Is])...);
+ (p_instance->*p_method)(VariantCaster<P>::cast(*p_args[Is])...);
#endif
}
diff --git a/core/global_constants.cpp b/core/global_constants.cpp
index 6281e56395..b30685539a 100644
--- a/core/global_constants.cpp
+++ b/core/global_constants.cpp
@@ -38,6 +38,7 @@
struct _GlobalConstant {
#ifdef DEBUG_METHODS_ENABLED
StringName enum_name;
+ bool ignore_value_in_docs;
#endif
const char *name;
int value;
@@ -45,8 +46,9 @@ struct _GlobalConstant {
_GlobalConstant() {}
#ifdef DEBUG_METHODS_ENABLED
- _GlobalConstant(const StringName &p_enum_name, const char *p_name, int p_value) :
+ _GlobalConstant(const StringName &p_enum_name, const char *p_name, int p_value, bool p_ignore_value_in_docs = false) :
enum_name(p_enum_name),
+ ignore_value_in_docs(p_ignore_value_in_docs),
name(p_name),
value(p_value) {
}
@@ -71,6 +73,15 @@ static Vector<_GlobalConstant> _global_constants;
#define BIND_GLOBAL_ENUM_CONSTANT_CUSTOM(m_custom_name, m_constant) \
_global_constants.push_back(_GlobalConstant(__constant_get_enum_name(m_constant, #m_constant), m_custom_name, m_constant));
+#define BIND_GLOBAL_CONSTANT_NO_VAL(m_constant) \
+ _global_constants.push_back(_GlobalConstant(StringName(), #m_constant, m_constant, true));
+
+#define BIND_GLOBAL_ENUM_CONSTANT_NO_VAL(m_constant) \
+ _global_constants.push_back(_GlobalConstant(__constant_get_enum_name(m_constant, #m_constant), #m_constant, m_constant, true));
+
+#define BIND_GLOBAL_ENUM_CONSTANT_CUSTOM_NO_VAL(m_custom_name, m_constant) \
+ _global_constants.push_back(_GlobalConstant(__constant_get_enum_name(m_constant, #m_constant), m_custom_name, m_constant, true));
+
#else
#define BIND_GLOBAL_CONSTANT(m_constant) \
@@ -82,6 +93,15 @@ static Vector<_GlobalConstant> _global_constants;
#define BIND_GLOBAL_ENUM_CONSTANT_CUSTOM(m_custom_name, m_constant) \
_global_constants.push_back(_GlobalConstant(m_custom_name, m_constant));
+#define BIND_GLOBAL_CONSTANT_NO_VAL(m_constant) \
+ _global_constants.push_back(_GlobalConstant(#m_constant, m_constant));
+
+#define BIND_GLOBAL_ENUM_CONSTANT_NO_VAL(m_constant) \
+ _global_constants.push_back(_GlobalConstant(#m_constant, m_constant));
+
+#define BIND_GLOBAL_ENUM_CONSTANT_CUSTOM_NO_VAL(m_custom_name, m_constant) \
+ _global_constants.push_back(_GlobalConstant(m_custom_name, m_constant));
+
#endif
VARIANT_ENUM_CAST(KeyList);
@@ -368,7 +388,7 @@ void register_global_constants() {
BIND_GLOBAL_ENUM_CONSTANT(KEY_MASK_ALT);
BIND_GLOBAL_ENUM_CONSTANT(KEY_MASK_META);
BIND_GLOBAL_ENUM_CONSTANT(KEY_MASK_CTRL);
- BIND_GLOBAL_ENUM_CONSTANT(KEY_MASK_CMD);
+ BIND_GLOBAL_ENUM_CONSTANT_NO_VAL(KEY_MASK_CMD);
BIND_GLOBAL_ENUM_CONSTANT(KEY_MASK_KPAD);
BIND_GLOBAL_ENUM_CONSTANT(KEY_MASK_GROUP_SWITCH);
@@ -648,10 +668,18 @@ int GlobalConstants::get_global_constant_count() {
StringName GlobalConstants::get_global_constant_enum(int p_idx) {
return _global_constants[p_idx].enum_name;
}
+
+bool GlobalConstants::get_ignore_value_in_docs(int p_idx) {
+ return _global_constants[p_idx].ignore_value_in_docs;
+}
#else
StringName GlobalConstants::get_global_constant_enum(int p_idx) {
return StringName();
}
+
+bool GlobalConstants::get_ignore_value_in_docs(int p_idx) {
+ return false;
+}
#endif
const char *GlobalConstants::get_global_constant_name(int p_idx) {
diff --git a/core/global_constants.h b/core/global_constants.h
index a20b5ecd9a..989633a6fa 100644
--- a/core/global_constants.h
+++ b/core/global_constants.h
@@ -37,6 +37,7 @@ class GlobalConstants {
public:
static int get_global_constant_count();
static StringName get_global_constant_enum(int p_idx);
+ static bool get_ignore_value_in_docs(int p_idx);
static const char *get_global_constant_name(int p_idx);
static int get_global_constant_value(int p_idx);
};
diff --git a/core/math/math_funcs.h b/core/math/math_funcs.h
index 7a9fd60e23..9f8d4da5b3 100644
--- a/core/math/math_funcs.h
+++ b/core/math/math_funcs.h
@@ -231,19 +231,19 @@ public:
static _ALWAYS_INLINE_ double range_lerp(double p_value, double p_istart, double p_istop, double p_ostart, double p_ostop) { return Math::lerp(p_ostart, p_ostop, Math::inverse_lerp(p_istart, p_istop, p_value)); }
static _ALWAYS_INLINE_ float range_lerp(float p_value, float p_istart, float p_istop, float p_ostart, float p_ostop) { return Math::lerp(p_ostart, p_ostop, Math::inverse_lerp(p_istart, p_istop, p_value)); }
- static _ALWAYS_INLINE_ double smoothstep(double p_from, double p_to, double p_weight) {
+ static _ALWAYS_INLINE_ double smoothstep(double p_from, double p_to, double p_s) {
if (is_equal_approx(p_from, p_to)) {
return p_from;
}
- double x = CLAMP((p_weight - p_from) / (p_to - p_from), 0.0, 1.0);
- return x * x * (3.0 - 2.0 * x);
+ double s = CLAMP((p_s - p_from) / (p_to - p_from), 0.0, 1.0);
+ return s * s * (3.0 - 2.0 * s);
}
- static _ALWAYS_INLINE_ float smoothstep(float p_from, float p_to, float p_weight) {
+ static _ALWAYS_INLINE_ float smoothstep(float p_from, float p_to, float p_s) {
if (is_equal_approx(p_from, p_to)) {
return p_from;
}
- float x = CLAMP((p_weight - p_from) / (p_to - p_from), 0.0f, 1.0f);
- return x * x * (3.0f - 2.0f * x);
+ float s = CLAMP((p_s - p_from) / (p_to - p_from), 0.0f, 1.0f);
+ return s * s * (3.0f - 2.0f * s);
}
static _ALWAYS_INLINE_ double move_toward(double p_from, double p_to, double p_delta) { return abs(p_to - p_from) <= p_delta ? p_to : p_from + SGN(p_to - p_from) * p_delta; }
static _ALWAYS_INLINE_ float move_toward(float p_from, float p_to, float p_delta) { return abs(p_to - p_from) <= p_delta ? p_to : p_from + SGN(p_to - p_from) * p_delta; }
diff --git a/core/object.cpp b/core/object.cpp
index 8abea9ca7e..ff6d4a666f 100644
--- a/core/object.cpp
+++ b/core/object.cpp
@@ -675,89 +675,11 @@ Variant Object::_call_deferred_bind(const Variant **p_args, int p_argcount, Call
StringName method = *p_args[0];
- MessageQueue::get_singleton()->push_call(get_instance_id(), method, &p_args[1], p_argcount - 1);
+ MessageQueue::get_singleton()->push_call(get_instance_id(), method, &p_args[1], p_argcount - 1, true);
return Variant();
}
-#ifdef DEBUG_ENABLED
-static void _test_call_error(const StringName &p_func, const Callable::CallError &error) {
- switch (error.error) {
- case Callable::CallError::CALL_OK:
- case Callable::CallError::CALL_ERROR_INVALID_METHOD:
- break;
- case Callable::CallError::CALL_ERROR_INVALID_ARGUMENT: {
- ERR_FAIL_MSG("Error calling function: " + String(p_func) + " - Invalid type for argument " + itos(error.argument) + ", expected " + Variant::get_type_name(Variant::Type(error.expected)) + ".");
- break;
- }
- case Callable::CallError::CALL_ERROR_TOO_MANY_ARGUMENTS: {
- ERR_FAIL_MSG("Error calling function: " + String(p_func) + " - Too many arguments, expected " + itos(error.argument) + ".");
- break;
- }
- case Callable::CallError::CALL_ERROR_TOO_FEW_ARGUMENTS: {
- ERR_FAIL_MSG("Error calling function: " + String(p_func) + " - Too few arguments, expected " + itos(error.argument) + ".");
- break;
- }
- case Callable::CallError::CALL_ERROR_INSTANCE_IS_NULL:
- break;
- }
-}
-#else
-
-#define _test_call_error(m_str, m_err)
-
-#endif
-
-void Object::call_multilevel(const StringName &p_method, const Variant **p_args, int p_argcount) {
- if (p_method == CoreStringNames::get_singleton()->_free) {
-#ifdef DEBUG_ENABLED
- ERR_FAIL_COND_MSG(Object::cast_to<Reference>(this), "Can't 'free' a reference.");
-
- ERR_FAIL_COND_MSG(_lock_index.get() > 1, "Object is locked and can't be freed.");
-#endif
-
- //must be here, must be before everything,
- memdelete(this);
- return;
- }
-
- //Variant ret;
- OBJ_DEBUG_LOCK
-
- Callable::CallError error;
-
- if (script_instance) {
- script_instance->call_multilevel(p_method, p_args, p_argcount);
- //_test_call_error(p_method,error);
- }
-
- MethodBind *method = ClassDB::get_method(get_class_name(), p_method);
-
- if (method) {
- method->call(this, p_args, p_argcount, error);
- _test_call_error(p_method, error);
- }
-}
-
-void Object::call_multilevel_reversed(const StringName &p_method, const Variant **p_args, int p_argcount) {
- MethodBind *method = ClassDB::get_method(get_class_name(), p_method);
-
- Callable::CallError error;
- OBJ_DEBUG_LOCK
-
- if (method) {
- method->call(this, p_args, p_argcount, error);
- _test_call_error(p_method, error);
- }
-
- //Variant ret;
-
- if (script_instance) {
- script_instance->call_multilevel_reversed(p_method, p_args, p_argcount);
- //_test_call_error(p_method,error);
- }
-}
-
bool Object::has_method(const StringName &p_method) const {
if (p_method == CoreStringNames::get_singleton()->_free) {
return true;
@@ -820,21 +742,6 @@ Variant Object::call(const StringName &p_name, VARIANT_ARG_DECLARE) {
return ret;
}
-void Object::call_multilevel(const StringName &p_name, VARIANT_ARG_DECLARE) {
- VARIANT_ARGPTRS;
-
- int argc = 0;
- for (int i = 0; i < VARIANT_ARG_MAX; i++) {
- if (argptr[i]->get_type() == Variant::NIL) {
- break;
- }
- argc++;
- }
-
- //Callable::CallError error;
- call_multilevel(p_name, argptr, argc);
-}
-
Variant Object::call(const StringName &p_method, const Variant **p_args, int p_argcount, Callable::CallError &r_error) {
r_error.error = Callable::CallError::CALL_OK;
diff --git a/core/object.h b/core/object.h
index 954be5304c..d9847d10aa 100644
--- a/core/object.h
+++ b/core/object.h
@@ -655,10 +655,7 @@ public:
void get_method_list(List<MethodInfo> *p_list) const;
Variant callv(const StringName &p_method, const Array &p_args);
virtual Variant call(const StringName &p_method, const Variant **p_args, int p_argcount, Callable::CallError &r_error);
- virtual void call_multilevel(const StringName &p_method, const Variant **p_args, int p_argcount);
- virtual void call_multilevel_reversed(const StringName &p_method, const Variant **p_args, int p_argcount);
Variant call(const StringName &p_name, VARIANT_ARG_LIST); // C++ helper
- void call_multilevel(const StringName &p_name, VARIANT_ARG_LIST); // C++ helper
void notification(int p_notification, bool p_reversed = false);
String to_string();
diff --git a/core/project_settings.cpp b/core/project_settings.cpp
index 638987bb2f..e08a44d0c1 100644
--- a/core/project_settings.cpp
+++ b/core/project_settings.cpp
@@ -122,6 +122,22 @@ void ProjectSettings::set_restart_if_changed(const String &p_name, bool p_restar
props[p_name].restart_if_changed = p_restart;
}
+void ProjectSettings::set_ignore_value_in_docs(const String &p_name, bool p_ignore) {
+ ERR_FAIL_COND_MSG(!props.has(p_name), "Request for nonexistent project setting: " + p_name + ".");
+#ifdef DEBUG_METHODS_ENABLED
+ props[p_name].ignore_value_in_docs = p_ignore;
+#endif
+}
+
+bool ProjectSettings::get_ignore_value_in_docs(const String &p_name) const {
+ ERR_FAIL_COND_V_MSG(!props.has(p_name), false, "Request for nonexistent project setting: " + p_name + ".");
+#ifdef DEBUG_METHODS_ENABLED
+ return props[p_name].ignore_value_in_docs;
+#else
+ return false;
+#endif
+}
+
String ProjectSettings::globalize_path(const String &p_path) const {
if (p_path.begins_with("res://")) {
if (resource_path != "") {
@@ -876,7 +892,7 @@ Error ProjectSettings::save_custom(const String &p_path, const CustomMap &p_cust
}
}
-Variant _GLOBAL_DEF(const String &p_var, const Variant &p_default, bool p_restart_if_changed) {
+Variant _GLOBAL_DEF(const String &p_var, const Variant &p_default, bool p_restart_if_changed, bool p_ignore_value_in_docs) {
Variant ret;
if (!ProjectSettings::get_singleton()->has_setting(p_var)) {
ProjectSettings::get_singleton()->set(p_var, p_default);
@@ -886,6 +902,7 @@ Variant _GLOBAL_DEF(const String &p_var, const Variant &p_default, bool p_restar
ProjectSettings::get_singleton()->set_initial_value(p_var, p_default);
ProjectSettings::get_singleton()->set_builtin_order(p_var);
ProjectSettings::get_singleton()->set_restart_if_changed(p_var, p_restart_if_changed);
+ ProjectSettings::get_singleton()->set_ignore_value_in_docs(p_var, p_ignore_value_in_docs);
return ret;
}
diff --git a/core/project_settings.h b/core/project_settings.h
index 4aceafe3c0..659ee402b3 100644
--- a/core/project_settings.h
+++ b/core/project_settings.h
@@ -62,6 +62,9 @@ protected:
bool hide_from_editor = false;
bool overridden = false;
bool restart_if_changed = false;
+#ifdef DEBUG_METHODS_ENABLED
+ bool ignore_value_in_docs = false;
+#endif
VariantContainer() {}
@@ -125,6 +128,9 @@ public:
void set_initial_value(const String &p_name, const Variant &p_value);
void set_restart_if_changed(const String &p_name, bool p_restart);
+ void set_ignore_value_in_docs(const String &p_name, bool p_ignore);
+ bool get_ignore_value_in_docs(const String &p_name) const;
+
bool property_can_revert(const String &p_name);
Variant property_get_revert(const String &p_name);
@@ -167,9 +173,11 @@ public:
};
//not a macro any longer
-Variant _GLOBAL_DEF(const String &p_var, const Variant &p_default, bool p_restart_if_changed = false);
+Variant _GLOBAL_DEF(const String &p_var, const Variant &p_default, bool p_restart_if_changed = false, bool p_ignore_value_in_docs = false);
#define GLOBAL_DEF(m_var, m_value) _GLOBAL_DEF(m_var, m_value)
#define GLOBAL_DEF_RST(m_var, m_value) _GLOBAL_DEF(m_var, m_value, true)
+#define GLOBAL_DEF_NOVAL(m_var, m_value) _GLOBAL_DEF(m_var, m_value, false, true)
+#define GLOBAL_DEF_RST_NOVAL(m_var, m_value) _GLOBAL_DEF(m_var, m_value, true, true)
#define GLOBAL_GET(m_var) ProjectSettings::get_singleton()->get(m_var)
#endif // PROJECT_SETTINGS_H
diff --git a/core/script_language.cpp b/core/script_language.cpp
index 420a560782..b63aeb952c 100644
--- a/core/script_language.cpp
+++ b/core/script_language.cpp
@@ -308,16 +308,6 @@ Variant ScriptInstance::call(const StringName &p_method, VARIANT_ARG_DECLARE) {
return call(p_method, argptr, argc, error);
}
-void ScriptInstance::call_multilevel(const StringName &p_method, const Variant **p_args, int p_argcount) {
- Callable::CallError ce;
- call(p_method, p_args, p_argcount, ce); // script may not support multilevel calls
-}
-
-void ScriptInstance::call_multilevel_reversed(const StringName &p_method, const Variant **p_args, int p_argcount) {
- Callable::CallError ce;
- call(p_method, p_args, p_argcount, ce); // script may not support multilevel calls
-}
-
void ScriptInstance::property_set_fallback(const StringName &, const Variant &, bool *r_valid) {
if (r_valid) {
*r_valid = false;
@@ -331,19 +321,6 @@ Variant ScriptInstance::property_get_fallback(const StringName &, bool *r_valid)
return Variant();
}
-void ScriptInstance::call_multilevel(const StringName &p_method, VARIANT_ARG_DECLARE) {
- VARIANT_ARGPTRS;
- int argc = 0;
- for (int i = 0; i < VARIANT_ARG_MAX; i++) {
- if (argptr[i]->get_type() == Variant::NIL) {
- break;
- }
- argc++;
- }
-
- call_multilevel(p_method, argptr, argc);
-}
-
ScriptInstance::~ScriptInstance() {
}
diff --git a/core/script_language.h b/core/script_language.h
index 6ba38399a1..aa7014ed3e 100644
--- a/core/script_language.h
+++ b/core/script_language.h
@@ -199,9 +199,6 @@ public:
virtual bool has_method(const StringName &p_method) const = 0;
virtual Variant call(const StringName &p_method, VARIANT_ARG_LIST);
virtual Variant call(const StringName &p_method, const Variant **p_args, int p_argcount, Callable::CallError &r_error) = 0;
- virtual void call_multilevel(const StringName &p_method, VARIANT_ARG_LIST);
- virtual void call_multilevel(const StringName &p_method, const Variant **p_args, int p_argcount);
- virtual void call_multilevel_reversed(const StringName &p_method, const Variant **p_args, int p_argcount);
virtual void notification(int p_notification) = 0;
virtual String to_string(bool *r_valid) {
if (r_valid) {
@@ -428,8 +425,6 @@ public:
r_error.error = Callable::CallError::CALL_ERROR_INVALID_METHOD;
return Variant();
}
- //virtual void call_multilevel(const StringName& p_method,VARIANT_ARG_LIST) { return Variant(); }
- //virtual void call_multilevel(const StringName& p_method,const Variant** p_args,int p_argcount,Callable::CallError &r_error) { return Variant(); }
virtual void notification(int p_notification) {}
virtual Ref<Script> get_script() const { return script; }
diff --git a/core/ustring.cpp b/core/ustring.cpp
index 5d3cf5f1a4..572ad1af59 100644
--- a/core/ustring.cpp
+++ b/core/ustring.cpp
@@ -1025,6 +1025,9 @@ String String::chr(CharType p_char) {
}
String String::num(double p_num, int p_decimals) {
+ if (Math::is_nan(p_num)) {
+ return "nan";
+ }
#ifndef NO_USE_STDLIB
if (p_decimals > 16) {
@@ -1313,6 +1316,9 @@ String String::num_real(double p_num) {
}
String String::num_scientific(double p_num) {
+ if (Math::is_nan(p_num)) {
+ return "nan";
+ }
#ifndef NO_USE_STDLIB
char buf[256];
diff --git a/core/variant.cpp b/core/variant.cpp
index f6b7e2821a..afd01b3359 100644
--- a/core/variant.cpp
+++ b/core/variant.cpp
@@ -1454,7 +1454,7 @@ Variant::operator signed long() const {
case INT:
return _data._int;
case FLOAT:
- return _data._real;
+ return _data._float;
case STRING:
return operator String().to_int();
default: {
@@ -1474,7 +1474,7 @@ Variant::operator unsigned long() const {
case INT:
return _data._int;
case FLOAT:
- return _data._real;
+ return _data._float;
case STRING:
return operator String().to_int();
default: {
diff --git a/doc/classes/@GlobalScope.xml b/doc/classes/@GlobalScope.xml
index 9a28a0d085..7f7df33471 100644
--- a/doc/classes/@GlobalScope.xml
+++ b/doc/classes/@GlobalScope.xml
@@ -898,7 +898,7 @@
<constant name="KEY_MASK_CTRL" value="268435456" enum="KeyModifierMask">
Ctrl key mask.
</constant>
- <constant name="KEY_MASK_CMD" value="268435456" enum="KeyModifierMask">
+ <constant name="KEY_MASK_CMD" value="platform-dependent" enum="KeyModifierMask">
Command key mask. On macOS, this is equivalent to [constant KEY_MASK_META]. On other platforms, this is equivalent to [constant KEY_MASK_CTRL]. This mask should be preferred to [constant KEY_MASK_META] or [constant KEY_MASK_CTRL] for system shortcuts as it handles all platforms correctly.
</constant>
<constant name="KEY_MASK_KPAD" value="536870912" enum="KeyModifierMask">
diff --git a/doc/classes/DisplayServer.xml b/doc/classes/DisplayServer.xml
index 49af8d7de2..814c232668 100644
--- a/doc/classes/DisplayServer.xml
+++ b/doc/classes/DisplayServer.xml
@@ -627,12 +627,14 @@
<return type="int">
</return>
<description>
+ Returns the on-screen keyboard's height in pixels. Returns 0 if there is no keyboard or if it is currently hidden.
</description>
</method>
<method name="virtual_keyboard_hide">
<return type="void">
</return>
<description>
+ Hides the virtual keyboard if it is shown, does nothing otherwise.
</description>
</method>
<method name="virtual_keyboard_show">
@@ -642,13 +644,23 @@
</argument>
<argument index="1" name="position" type="Rect2" default="Rect2i( 0, 0, 0, 0 )">
</argument>
- <argument index="2" name="max_length" type="int" default="-1">
+ <argument index="2" name="multiline" type="bool" default="false">
</argument>
- <argument index="3" name="cursor_start" type="int" default="-1">
+ <argument index="3" name="max_length" type="int" default="-1">
</argument>
- <argument index="4" name="cursor_end" type="int" default="-1">
+ <argument index="4" name="cursor_start" type="int" default="-1">
+ </argument>
+ <argument index="5" name="cursor_end" type="int" default="-1">
</argument>
<description>
+ Shows the virtual keyboard if the platform has one.
+ [code]existing_text[/code] parameter is useful for implementing your own [LineEdit] or [TextEdit], as it tells the virtual keyboard what text has already been typed (the virtual keyboard uses it for auto-correct and predictions).
+ [code]position[/code] parameter is the screen space [Rect2] of the edited text.
+ [code]multiline[/code] parameter needs to be set to [code]true[/code] to be able to enter multiple lines of text, as in [TextEdit].
+ [code]max_length[/code] limits the number of characters that can be entered if different from [code]-1[/code].
+ [code]cursor_start[/code] can optionally define the current text cursor position if [code]cursor_end[/code] is not set.
+ [code]cursor_start[/code] and [code]cursor_end[/code] can optionally define the current text selection.
+ [b]Note:[/b] This method is implemented on Android, iOS and UWP.
</description>
</method>
<method name="vsync_is_enabled" qualifiers="const">
diff --git a/doc/classes/ProjectSettings.xml b/doc/classes/ProjectSettings.xml
index e255ce2e1a..733bb559cb 100644
--- a/doc/classes/ProjectSettings.xml
+++ b/doc/classes/ProjectSettings.xml
@@ -244,7 +244,7 @@
<member name="audio/default_bus_layout" type="String" setter="" getter="" default="&quot;res://default_bus_layout.tres&quot;">
Default [AudioBusLayout] resource file to use in the project, unless overridden by the scene.
</member>
- <member name="audio/driver" type="String" setter="" getter="" default="&quot;PulseAudio&quot;">
+ <member name="audio/driver" type="String" setter="" getter="">
Specifies the audio driver to use. This setting is platform-dependent as each platform supports different audio drivers. If left empty, the default audio driver will be used.
</member>
<member name="audio/enable_audio_input" type="bool" setter="" getter="" default="false">
@@ -453,7 +453,7 @@
<member name="display/window/size/width" type="int" setter="" getter="" default="1024">
Sets the game's main viewport width. On desktop platforms, this is the default window size. Stretch mode settings also use this as a reference when enabled.
</member>
- <member name="display/window/tablet_driver" type="String" setter="" getter="" default="&quot;&quot;">
+ <member name="display/window/tablet_driver" type="String" setter="" getter="">
Specifies the tablet driver to use. If left empty, the default driver will be used.
</member>
<member name="display/window/vsync/use_vsync" type="bool" setter="" getter="" default="true">
@@ -472,7 +472,7 @@
<member name="gui/common/default_scroll_deadzone" type="int" setter="" getter="" default="0">
Default value for [member ScrollContainer.scroll_deadzone], which will be used for all [ScrollContainer]s unless overridden.
</member>
- <member name="gui/common/swap_cancel_ok" type="bool" setter="" getter="" default="false">
+ <member name="gui/common/swap_cancel_ok" type="bool" setter="" getter="">
If [code]true[/code], swaps Cancel and OK buttons in dialogs on Windows and UWP to follow interface conventions.
</member>
<member name="gui/common/text_edit_undo_stack_max_size" type="int" setter="" getter="" default="1024">
diff --git a/doc/classes/TileSet.xml b/doc/classes/TileSet.xml
index 9a78e45d46..4991a1e58b 100644
--- a/doc/classes/TileSet.xml
+++ b/doc/classes/TileSet.xml
@@ -478,7 +478,17 @@
<argument index="0" name="id" type="int">
</argument>
<description>
- Returns an array of the tile's shapes.
+ Returns an array of dictionaries describing the tile's shapes.
+ [b]Dictionary structure in the array returned by this method:[/b]
+ [codeblock]
+ {
+ "autotile_coord": Vector2,
+ "one_way": bool,
+ "one_way_margin": int,
+ "shape": CollisionShape2D,
+ "shape_transform": Transform2D,
+ }
+ [/codeblock]
</description>
</method>
<method name="tile_get_texture" qualifiers="const">
diff --git a/drivers/vulkan/SCsub b/drivers/vulkan/SCsub
index 91d0e42f80..61d91711da 100644
--- a/drivers/vulkan/SCsub
+++ b/drivers/vulkan/SCsub
@@ -4,6 +4,7 @@ Import("env")
env.add_source_files(env.drivers_sources, "*.cpp")
+# FIXME: Refactor all this to reduce code duplication.
if env["platform"] == "android":
# Use NDK Vulkan headers
thirdparty_dir = env["ANDROID_NDK_ROOT"] + "/sources/third_party/vulkan/src"
@@ -22,6 +23,17 @@ if env["platform"] == "android":
thirdparty_dir = "#thirdparty/vulkan"
vma_sources = [thirdparty_dir + "/android/vk_mem_alloc.cpp"]
env_thirdparty.add_source_files(env.drivers_sources, vma_sources)
+elif env["platform"] == "iphone":
+ # Use bundled Vulkan headers
+ thirdparty_dir = "#thirdparty/vulkan"
+ env.Prepend(CPPPATH=[thirdparty_dir, thirdparty_dir + "/include", thirdparty_dir + "/loader"])
+
+ # Build Vulkan memory allocator
+ env_thirdparty = env.Clone()
+ env_thirdparty.disable_warnings()
+
+ vma_sources = [thirdparty_dir + "/vk_mem_alloc.cpp"]
+ env_thirdparty.add_source_files(env.drivers_sources, vma_sources)
elif env["builtin_vulkan"]:
# Use bundled Vulkan headers
thirdparty_dir = "#thirdparty/vulkan"
@@ -70,16 +82,6 @@ elif env["builtin_vulkan"]:
'FALLBACK_CONFIG_DIRS=\\"%s\\"' % "/etc/xdg",
]
)
- elif env["platform"] == "iphone":
- env_thirdparty.AppendUnique(
- CPPDEFINES=[
- "VK_USE_PLATFORM_IOS_MVK",
- "VULKAN_NON_CMAKE_BUILD",
- 'SYSCONFDIR=\\"%s\\"' % "/etc",
- 'FALLBACK_DATA_DIRS=\\"%s\\"' % "/usr/local/share:/usr/share",
- 'FALLBACK_CONFIG_DIRS=\\"%s\\"' % "/etc/xdg",
- ]
- )
elif env["platform"] == "linuxbsd":
env_thirdparty.AppendUnique(
CPPDEFINES=[
@@ -99,3 +101,13 @@ elif env["builtin_vulkan"]:
loader_sources = [thirdparty_dir + "/loader/" + file for file in loader_sources]
env_thirdparty.add_source_files(env.drivers_sources, loader_sources)
env_thirdparty.add_source_files(env.drivers_sources, vma_sources)
+else: # Always build VMA.
+ thirdparty_dir = "#thirdparty/vulkan"
+ env.Prepend(CPPPATH=[thirdparty_dir])
+
+ # Build Vulkan loader library
+ env_thirdparty = env.Clone()
+ env_thirdparty.disable_warnings()
+ vma_sources = [thirdparty_dir + "/vk_mem_alloc.cpp"]
+
+ env_thirdparty.add_source_files(env.drivers_sources, vma_sources)
diff --git a/editor/code_editor.cpp b/editor/code_editor.cpp
index 9a87d6d38c..37db3ba780 100644
--- a/editor/code_editor.cpp
+++ b/editor/code_editor.cpp
@@ -1456,8 +1456,6 @@ void CodeTextEditor::set_edit_state(const Variant &p_state) {
text_editor->set_line_as_bookmark(bookmarks[i], true);
}
}
-
- text_editor->grab_focus();
}
void CodeTextEditor::set_error(const String &p_error) {
diff --git a/editor/doc_data.cpp b/editor/doc_data.cpp
index 54acbe9559..75b16b4510 100644
--- a/editor/doc_data.cpp
+++ b/editor/doc_data.cpp
@@ -321,12 +321,13 @@ void DocData::generate(bool p_basic_types) {
continue;
}
if (E->get().usage & PROPERTY_USAGE_EDITOR) {
- default_value = ProjectSettings::get_singleton()->property_get_revert(E->get().name);
- default_value_valid = true;
+ if (!ProjectSettings::get_singleton()->get_ignore_value_in_docs(E->get().name)) {
+ default_value = ProjectSettings::get_singleton()->property_get_revert(E->get().name);
+ default_value_valid = true;
+ }
}
} else {
default_value = get_documentation_default_value(name, E->get().name, default_value_valid);
-
if (inherited) {
bool base_default_value_valid = false;
Variant base_default_value = get_documentation_default_value(ClassDB::get_parent_class(name), E->get().name, base_default_value_valid);
@@ -478,6 +479,7 @@ void DocData::generate(bool p_basic_types) {
ConstantDoc constant;
constant.name = E->get();
constant.value = itos(ClassDB::get_integer_constant(name, E->get()));
+ constant.is_value_valid = true;
constant.enumeration = ClassDB::get_integer_constant_enum(name, E->get());
c.constants.push_back(constant);
}
@@ -620,6 +622,7 @@ void DocData::generate(bool p_basic_types) {
constant.name = E->get();
Variant value = Variant::get_constant_value(Variant::Type(i), E->get());
constant.value = value.get_type() == Variant::INT ? itos(value) : value.get_construct_string();
+ constant.is_value_valid = true;
c.constants.push_back(constant);
}
}
@@ -635,7 +638,12 @@ void DocData::generate(bool p_basic_types) {
for (int i = 0; i < GlobalConstants::get_global_constant_count(); i++) {
ConstantDoc cd;
cd.name = GlobalConstants::get_global_constant_name(i);
- cd.value = itos(GlobalConstants::get_global_constant_value(i));
+ if (!GlobalConstants::get_ignore_value_in_docs(i)) {
+ cd.value = itos(GlobalConstants::get_global_constant_value(i));
+ cd.is_value_valid = true;
+ } else {
+ cd.is_value_valid = false;
+ }
cd.enumeration = GlobalConstants::get_global_constant_enum(i);
c.constants.push_back(cd);
}
@@ -715,6 +723,7 @@ void DocData::generate(bool p_basic_types) {
ConstantDoc cd;
cd.name = E->get().first;
cd.value = E->get().second;
+ cd.is_value_valid = true;
c.constants.push_back(cd);
}
@@ -989,6 +998,7 @@ Error DocData::_load(Ref<XMLParser> parser) {
constant2.name = parser->get_attribute_value("name");
ERR_FAIL_COND_V(!parser->has_attribute("value"), ERR_FILE_CORRUPT);
constant2.value = parser->get_attribute_value("value");
+ constant2.is_value_valid = true;
if (parser->has_attribute("enum")) {
constant2.enumeration = parser->get_attribute_value("enum");
}
@@ -1178,10 +1188,18 @@ Error DocData::save_classes(const String &p_default_path, const Map<String, Stri
for (int i = 0; i < c.constants.size(); i++) {
const ConstantDoc &k = c.constants[i];
- if (k.enumeration != String()) {
- _write_string(f, 2, "<constant name=\"" + k.name + "\" value=\"" + k.value + "\" enum=\"" + k.enumeration + "\">");
+ if (k.is_value_valid) {
+ if (k.enumeration != String()) {
+ _write_string(f, 2, "<constant name=\"" + k.name + "\" value=\"" + k.value + "\" enum=\"" + k.enumeration + "\">");
+ } else {
+ _write_string(f, 2, "<constant name=\"" + k.name + "\" value=\"" + k.value + "\">");
+ }
} else {
- _write_string(f, 2, "<constant name=\"" + k.name + "\" value=\"" + k.value + "\">");
+ if (k.enumeration != String()) {
+ _write_string(f, 2, "<constant name=\"" + k.name + "\" value=\"platform-dependent\" enum=\"" + k.enumeration + "\">");
+ } else {
+ _write_string(f, 2, "<constant name=\"" + k.name + "\" value=\"platform-dependent\">");
+ }
}
_write_string(f, 3, k.description.strip_edges().xml_escape());
_write_string(f, 2, "</constant>");
diff --git a/editor/doc_data.h b/editor/doc_data.h
index 1880be81ed..a35cfb59c7 100644
--- a/editor/doc_data.h
+++ b/editor/doc_data.h
@@ -62,6 +62,7 @@ public:
struct ConstantDoc {
String name;
String value;
+ bool is_value_valid;
String enumeration;
String description;
bool operator<(const ConstantDoc &p_const) const {
diff --git a/editor/editor_autoload_settings.cpp b/editor/editor_autoload_settings.cpp
index 94887fb848..5d101ff2c2 100644
--- a/editor/editor_autoload_settings.cpp
+++ b/editor/editor_autoload_settings.cpp
@@ -688,12 +688,12 @@ bool EditorAutoloadSettings::autoload_add(const String &p_name, const String &p_
const String &path = p_path;
if (!FileAccess::exists(path)) {
- EditorNode::get_singleton()->show_warning(TTR("Can't add autoload:") + "\n" + TTR(vformat("%s is an invalid path. File does not exist.", path)));
+ EditorNode::get_singleton()->show_warning(TTR("Can't add autoload:") + "\n" + vformat(TTR("%s is an invalid path. File does not exist."), path));
return false;
}
if (!path.begins_with("res://")) {
- EditorNode::get_singleton()->show_warning(TTR("Can't add autoload:") + "\n" + TTR(vformat("%s is an invalid path. Not in resource path (res://).", path)));
+ EditorNode::get_singleton()->show_warning(TTR("Can't add autoload:") + "\n" + vformat(TTR("%s is an invalid path. Not in resource path (res://)."), path));
return false;
}
diff --git a/editor/input_map_editor.cpp b/editor/input_map_editor.cpp
index 70c354e55a..52cf9c1869 100644
--- a/editor/input_map_editor.cpp
+++ b/editor/input_map_editor.cpp
@@ -36,44 +36,44 @@
#include "editor/editor_scale.h"
static const char *_button_descriptions[JOY_SDL_BUTTONS] = {
- "Face Bottom, DualShock Cross, Xbox A, Nintendo B",
- "Face Right, DualShock Circle, Xbox B, Nintendo A",
- "Face Left, DualShock Square, Xbox X, Nintendo Y",
- "Face Top, DualShock Triangle, Xbox Y, Nintendo X",
- "DualShock Select, Xbox Back, Nintendo -",
- "Home, DualShock PS, Guide",
- "Start, Nintendo +",
- "Left Stick, DualShock L3, Xbox L/LS",
- "Right Stick, DualShock R3, Xbox R/RS",
- "Left Shoulder, DualShock L1, Xbox LB",
- "Right Shoulder, DualShock R1, Xbox RB",
- "D-Pad Up",
- "D-Pad Down",
- "D-Pad Left",
- "D-Pad Right"
+ TTRC("Face Bottom, DualShock Cross, Xbox A, Nintendo B"),
+ TTRC("Face Right, DualShock Circle, Xbox B, Nintendo A"),
+ TTRC("Face Left, DualShock Square, Xbox X, Nintendo Y"),
+ TTRC("Face Top, DualShock Triangle, Xbox Y, Nintendo X"),
+ TTRC("DualShock Select, Xbox Back, Nintendo -"),
+ TTRC("Home, DualShock PS, Guide"),
+ TTRC("Start, Nintendo +"),
+ TTRC("Left Stick, DualShock L3, Xbox L/LS"),
+ TTRC("Right Stick, DualShock R3, Xbox R/RS"),
+ TTRC("Left Shoulder, DualShock L1, Xbox LB"),
+ TTRC("Right Shoulder, DualShock R1, Xbox RB"),
+ TTRC("D-Pad Up"),
+ TTRC("D-Pad Down"),
+ TTRC("D-Pad Left"),
+ TTRC("D-Pad Right")
};
static const char *_axis_descriptions[JOY_AXIS_MAX * 2] = {
- "Left Stick Left",
- "Left Stick Right",
- "Left Stick Up",
- "Left Stick Down",
- "Right Stick Left",
- "Right Stick Right",
- "Right Stick Up",
- "Right Stick Down",
- "Joystick 2 Left",
- "Joystick 2 Right, Left Trigger, L2, LT",
- "Joystick 2 Up",
- "Joystick 2 Down, Right Trigger, R2, RT",
- "Joystick 3 Left",
- "Joystick 3 Right",
- "Joystick 3 Up",
- "Joystick 3 Down",
- "Joystick 4 Left",
- "Joystick 4 Right",
- "Joystick 4 Up",
- "Joystick 4 Down",
+ TTRC("Left Stick Left"),
+ TTRC("Left Stick Right"),
+ TTRC("Left Stick Up"),
+ TTRC("Left Stick Down"),
+ TTRC("Right Stick Left"),
+ TTRC("Right Stick Right"),
+ TTRC("Right Stick Up"),
+ TTRC("Right Stick Down"),
+ TTRC("Joystick 2 Left"),
+ TTRC("Joystick 2 Right, Left Trigger, L2, LT"),
+ TTRC("Joystick 2 Up"),
+ TTRC("Joystick 2 Down, Right Trigger, R2, RT"),
+ TTRC("Joystick 3 Left"),
+ TTRC("Joystick 3 Right"),
+ TTRC("Joystick 3 Up"),
+ TTRC("Joystick 3 Down"),
+ TTRC("Joystick 4 Left"),
+ TTRC("Joystick 4 Right"),
+ TTRC("Joystick 4 Up"),
+ TTRC("Joystick 4 Down"),
};
void InputMapEditor::_notification(int p_what) {
diff --git a/editor/plugins/animation_player_editor_plugin.cpp b/editor/plugins/animation_player_editor_plugin.cpp
index e2f35e29d8..af1d266832 100644
--- a/editor/plugins/animation_player_editor_plugin.cpp
+++ b/editor/plugins/animation_player_editor_plugin.cpp
@@ -411,7 +411,7 @@ void AnimationPlayerEditor::_animation_remove() {
String current = animation->get_item_text(animation->get_selected());
- delete_dialog->set_text(TTR("Delete Animation '" + current + "'?"));
+ delete_dialog->set_text(vformat(TTR("Delete Animation '%s'?"), current));
delete_dialog->popup_centered();
}
diff --git a/editor/plugins/node_3d_editor_plugin.cpp b/editor/plugins/node_3d_editor_plugin.cpp
index ced0b9f984..6f209c512e 100644
--- a/editor/plugins/node_3d_editor_plugin.cpp
+++ b/editor/plugins/node_3d_editor_plugin.cpp
@@ -5407,7 +5407,7 @@ void Node3DEditor::_update_gizmos_menu() {
}
String plugin_name = gizmo_plugins_by_name[i]->get_name();
const int plugin_state = gizmo_plugins_by_name[i]->get_state();
- gizmos_menu->add_multistate_item(TTR(plugin_name), 3, plugin_state, i);
+ gizmos_menu->add_multistate_item(plugin_name, 3, plugin_state, i);
const int idx = gizmos_menu->get_item_index(i);
gizmos_menu->set_item_tooltip(
idx,
diff --git a/editor/plugins/script_editor_plugin.cpp b/editor/plugins/script_editor_plugin.cpp
index 71830d0464..713d57ee95 100644
--- a/editor/plugins/script_editor_plugin.cpp
+++ b/editor/plugins/script_editor_plugin.cpp
@@ -696,18 +696,21 @@ void ScriptEditor::_close_tab(int p_idx, bool p_save, bool p_history_back) {
Node *tselected = tab_container->get_child(selected);
- ScriptEditorBase *current = Object::cast_to<ScriptEditorBase>(tab_container->get_child(selected));
+ ScriptEditorBase *current = Object::cast_to<ScriptEditorBase>(tselected);
if (current) {
Ref<Script> script = current->get_edited_resource();
- if (p_save) {
- // Do not try to save internal scripts
- if (!script.is_valid() || !(script->get_path() == "" || script->get_path().find("local://") != -1 || script->get_path().find("::") != -1)) {
+ if (p_save && script.is_valid()) {
+ // Do not try to save internal scripts, but prompt to save in-memory
+ // scripts which are not saved to disk yet (have empty path).
+ if (script->get_path().find("local://") == -1 && script->get_path().find("::") == -1) {
_menu_option(FILE_SAVE);
}
}
-
- if (script != nullptr) {
- previous_scripts.push_back(script->get_path());
+ if (script.is_valid()) {
+ if (!script->get_path().empty()) {
+ // Only saved scripts can be restored.
+ previous_scripts.push_back(script->get_path());
+ }
notify_script_close(script);
}
}
@@ -779,8 +782,10 @@ void ScriptEditor::_close_docs_tab() {
void ScriptEditor::_copy_script_path() {
ScriptEditorBase *se = _get_current_editor();
- RES script = se->get_edited_resource();
- DisplayServer::get_singleton()->clipboard_set(script->get_path());
+ if (se) {
+ RES script = se->get_edited_resource();
+ DisplayServer::get_singleton()->clipboard_set(script->get_path());
+ }
}
void ScriptEditor::_close_other_tabs() {
@@ -1038,17 +1043,19 @@ void ScriptEditor::_file_dialog_action(String p_file) {
} break;
case FILE_SAVE_AS: {
ScriptEditorBase *current = _get_current_editor();
+ if (current) {
+ RES resource = current->get_edited_resource();
+ String path = ProjectSettings::get_singleton()->localize_path(p_file);
+ Error err = _save_text_file(resource, path);
- String path = ProjectSettings::get_singleton()->localize_path(p_file);
- Error err = _save_text_file(current->get_edited_resource(), path);
+ if (err != OK) {
+ editor->show_accept(TTR("Error saving file!"), TTR("OK"));
+ return;
+ }
- if (err != OK) {
- editor->show_accept(TTR("Error saving file!"), TTR("OK"));
- return;
+ resource->set_path(path);
+ _update_script_names();
}
-
- ((Resource *)current->get_edited_resource().ptr())->set_path(path);
- _update_script_names();
} break;
case THEME_SAVE_AS: {
if (!EditorSettings::get_singleton()->save_text_editor_theme_as(p_file)) {
@@ -1240,13 +1247,14 @@ void ScriptEditor::_menu_option(int p_option) {
}
}
- Ref<TextFile> text_file = current->get_edited_resource();
+ RES resource = current->get_edited_resource();
+ Ref<TextFile> text_file = resource;
if (text_file != nullptr) {
current->apply_code();
_save_text_file(text_file, text_file->get_path());
break;
}
- editor->save_resource(current->get_edited_resource());
+ editor->save_resource(resource);
} break;
case FILE_SAVE_AS: {
@@ -1264,7 +1272,8 @@ void ScriptEditor::_menu_option(int p_option) {
}
}
- Ref<TextFile> text_file = current->get_edited_resource();
+ RES resource = current->get_edited_resource();
+ Ref<TextFile> text_file = resource;
if (text_file != nullptr) {
file_dialog->set_file_mode(EditorFileDialog::FILE_MODE_SAVE_FILE);
file_dialog->set_access(EditorFileDialog::ACCESS_FILESYSTEM);
@@ -1280,8 +1289,8 @@ void ScriptEditor::_menu_option(int p_option) {
break;
}
- editor->push_item(Object::cast_to<Object>(current->get_edited_resource().ptr()));
- editor->save_resource_as(current->get_edited_resource());
+ editor->push_item(resource.ptr());
+ editor->save_resource_as(resource);
} break;
@@ -1465,6 +1474,7 @@ void ScriptEditor::_notification(int p_what) {
editor->connect("stop_pressed", callable_mp(this, &ScriptEditor::_editor_stop));
editor->connect("script_add_function_request", callable_mp(this, &ScriptEditor::_add_callback));
editor->connect("resource_saved", callable_mp(this, &ScriptEditor::_res_saved_callback));
+ editor->get_filesystem_dock()->connect("file_removed", callable_mp(this, &ScriptEditor::_file_removed));
script_list->connect("item_selected", callable_mp(this, &ScriptEditor::_script_selected));
members_overview->connect("item_selected", callable_mp(this, &ScriptEditor::_members_overview_selected));
@@ -1472,6 +1482,7 @@ void ScriptEditor::_notification(int p_what) {
script_split->connect("dragged", callable_mp(this, &ScriptEditor::_script_split_dragged));
EditorSettings::get_singleton()->connect("settings_changed", callable_mp(this, &ScriptEditor::_editor_settings_changed));
+ EditorFileSystem::get_singleton()->connect("filesystem_changed", callable_mp(this, &ScriptEditor::_filesystem_changed));
[[fallthrough]];
}
case NOTIFICATION_THEME_CHANGED: {
@@ -1577,7 +1588,9 @@ void ScriptEditor::get_breakpoints(List<String> *p_breakpoints) {
List<int> bpoints;
se->get_breakpoints(&bpoints);
String base = script->get_path();
- ERR_CONTINUE(base.begins_with("local://") || base == "");
+ if (base.begins_with("local://") || base == "") {
+ continue;
+ }
for (List<int>::Element *E = bpoints.front(); E; E = E->next()) {
p_breakpoints->push_back(base + ":" + itos(E->get() + 1));
@@ -1630,6 +1643,8 @@ void ScriptEditor::ensure_select_current() {
if (tab_container->get_child_count() && tab_container->get_current_tab() >= 0) {
ScriptEditorBase *se = _get_current_editor();
if (se) {
+ se->enable_editor();
+
if (!grab_focus_block && is_visible_in_tree()) {
se->ensure_focus();
}
@@ -1840,6 +1855,12 @@ void ScriptEditor::_update_script_names() {
if (se) {
Ref<Texture2D> icon = se->get_theme_icon();
String path = se->get_edited_resource()->get_path();
+ bool saved = !path.empty();
+ if (saved) {
+ // The script might be deleted, moved, or renamed, so make sure
+ // to update original path to previously edited resource.
+ se->set_meta("_edit_res_path", path);
+ }
bool built_in = !path.is_resource_file();
String name;
@@ -1858,7 +1879,7 @@ void ScriptEditor::_update_script_names() {
_ScriptEditorItemData sd;
sd.icon = icon;
sd.name = name;
- sd.tooltip = path;
+ sd.tooltip = saved ? path : TTR("Unsaved file.");
sd.index = i;
sd.used = used.has(se->get_edited_resource());
sd.category = 0;
@@ -1891,6 +1912,9 @@ void ScriptEditor::_update_script_names() {
sd.name = path;
} break;
}
+ if (!saved) {
+ sd.name = se->get_name();
+ }
sedata.push_back(sd);
}
@@ -1974,6 +1998,11 @@ void ScriptEditor::_update_script_names() {
script_list->select(index);
script_name_label->set_text(sedata_filtered[i].name);
script_icon->set_texture(sedata_filtered[i].icon);
+ ScriptEditorBase *se = _get_current_editor();
+ if (se) {
+ se->enable_editor();
+ _update_selected_editor_menu();
+ }
}
}
@@ -2062,13 +2091,11 @@ bool ScriptEditor::edit(const RES &p_resource, int p_line, int p_col, bool p_gra
Ref<Script> script = p_resource;
- // refuse to open built-in if scene is not loaded
-
- // see if already has it
-
- bool open_dominant = EditorSettings::get_singleton()->get("text_editor/files/open_dominant_script_on_scene_change");
+ // Don't open dominant script if using an external editor.
+ const bool use_external_editor = EditorSettings::get_singleton()->get("text_editor/external/use_external_editor");
+ const bool open_dominant = EditorSettings::get_singleton()->get("text_editor/files/open_dominant_script_on_scene_change");
- const bool should_open = open_dominant || !EditorNode::get_singleton()->is_changing_scene();
+ const bool should_open = (open_dominant && !use_external_editor) || !EditorNode::get_singleton()->is_changing_scene();
if (script != nullptr && script->get_language()->overrides_external_editor()) {
if (should_open) {
@@ -2080,10 +2107,10 @@ bool ScriptEditor::edit(const RES &p_resource, int p_line, int p_col, bool p_gra
return false;
}
- if ((EditorDebuggerNode::get_singleton()->get_dump_stack_script() != p_resource || EditorDebuggerNode::get_singleton()->get_debug_with_external_editor()) &&
+ if (use_external_editor &&
+ (EditorDebuggerNode::get_singleton()->get_dump_stack_script() != p_resource || EditorDebuggerNode::get_singleton()->get_debug_with_external_editor()) &&
p_resource->get_path().is_resource_file() &&
- p_resource->get_class_name() != StringName("VisualScript") &&
- bool(EditorSettings::get_singleton()->get("text_editor/external/use_external_editor"))) {
+ p_resource->get_class_name() != StringName("VisualScript")) {
String path = EditorSettings::get_singleton()->get("text_editor/external/exec_path");
String flags = EditorSettings::get_singleton()->get("text_editor/external/exec_flags");
@@ -2148,6 +2175,8 @@ bool ScriptEditor::edit(const RES &p_resource, int p_line, int p_col, bool p_gra
if ((script != nullptr && se->get_edited_resource() == p_resource) || se->get_edited_resource()->get_path() == p_resource->get_path()) {
if (should_open) {
+ se->enable_editor();
+
if (tab_container->get_current_tab() != i) {
_go_to_tab(i);
_update_script_names();
@@ -2178,6 +2207,8 @@ bool ScriptEditor::edit(const RES &p_resource, int p_line, int p_col, bool p_gra
}
ERR_FAIL_COND_V(!se, false);
+ se->set_edited_resource(p_resource);
+
if (p_resource->get_class_name() != StringName("VisualScript")) {
bool highlighter_set = false;
for (int i = 0; i < syntax_highlighters.size(); i++) {
@@ -2198,7 +2229,14 @@ bool ScriptEditor::edit(const RES &p_resource, int p_line, int p_col, bool p_gra
}
tab_container->add_child(se);
- se->set_edited_resource(p_resource);
+
+ if (p_grab_focus) {
+ se->enable_editor();
+ }
+
+ // If we delete a script within the filesystem, the original resource path
+ // is lost, so keep it as metadata to figure out the exact tab to delete.
+ se->set_meta("_edit_res_path", p_resource->get_path());
se->set_tooltip_request_func("_get_debug_tooltip", this);
if (se->get_edit_menu()) {
se->get_edit_menu()->hide();
@@ -2208,6 +2246,7 @@ bool ScriptEditor::edit(const RES &p_resource, int p_line, int p_col, bool p_gra
if (p_grab_focus) {
_go_to_tab(tab_container->get_tab_count() - 1);
+ _add_recent_script(p_resource->get_path());
}
_sort_list_on_update = true;
@@ -2232,7 +2271,6 @@ bool ScriptEditor::edit(const RES &p_resource, int p_line, int p_col, bool p_gra
}
notify_script_changed(p_resource);
- _add_recent_script(p_resource->get_path());
return true;
}
@@ -2373,6 +2411,23 @@ void ScriptEditor::_editor_settings_changed() {
ScriptServer::set_reload_scripts_on_save(EDITOR_DEF("text_editor/files/auto_reload_and_parse_scripts_on_save", true));
}
+void ScriptEditor::_filesystem_changed() {
+ _update_script_names();
+}
+
+void ScriptEditor::_file_removed(const String &p_removed_file) {
+ for (int i = 0; i < tab_container->get_child_count(); i++) {
+ ScriptEditorBase *se = Object::cast_to<ScriptEditorBase>(tab_container->get_child(i));
+ if (!se) {
+ continue;
+ }
+ if (se->get_meta("_edit_res_path") == p_removed_file) {
+ // The script is deleted with no undo, so just close the tab.
+ _close_tab(i, false, false);
+ }
+ }
+}
+
void ScriptEditor::_autosave_scripts() {
save_all_scripts();
}
@@ -2704,7 +2759,7 @@ void ScriptEditor::set_window_layout(Ref<ConfigFile> p_layout) {
if (!scr.is_valid()) {
continue;
}
- if (!edit(scr)) {
+ if (!edit(scr, false)) {
continue;
}
} else {
@@ -2713,7 +2768,7 @@ void ScriptEditor::set_window_layout(Ref<ConfigFile> p_layout) {
if (error != OK || !text_file.is_valid()) {
continue;
}
- if (!edit(text_file)) {
+ if (!edit(text_file, false)) {
continue;
}
}
@@ -2945,13 +3000,11 @@ Array ScriptEditor::_get_open_script_editors() const {
}
void ScriptEditor::set_scene_root_script(Ref<Script> p_script) {
- bool open_dominant = EditorSettings::get_singleton()->get("text_editor/files/open_dominant_script_on_scene_change");
-
- if (bool(EditorSettings::get_singleton()->get("text_editor/external/use_external_editor"))) {
- return;
- }
+ // Don't open dominant script if using an external editor.
+ const bool use_external_editor = EditorSettings::get_singleton()->get("text_editor/external/use_external_editor");
+ const bool open_dominant = EditorSettings::get_singleton()->get("text_editor/files/open_dominant_script_on_scene_change");
- if (open_dominant && p_script.is_valid()) {
+ if (open_dominant && !use_external_editor && p_script.is_valid()) {
edit(p_script);
}
}
diff --git a/editor/plugins/script_editor_plugin.h b/editor/plugins/script_editor_plugin.h
index 3891af4091..1234ebd267 100644
--- a/editor/plugins/script_editor_plugin.h
+++ b/editor/plugins/script_editor_plugin.h
@@ -134,6 +134,7 @@ public:
virtual RES get_edited_resource() const = 0;
virtual Vector<String> get_functions() = 0;
virtual void set_edited_resource(const RES &p_res) = 0;
+ virtual void enable_editor() = 0;
virtual void reload_text() = 0;
virtual String get_name() = 0;
virtual Ref<Texture2D> get_theme_icon() = 0;
@@ -370,6 +371,8 @@ class ScriptEditor : public PanelContainer {
void _save_layout();
void _editor_settings_changed();
+ void _filesystem_changed();
+ void _file_removed(const String &p_file);
void _autosave_scripts();
void _update_autosave_timer();
diff --git a/editor/plugins/script_text_editor.cpp b/editor/plugins/script_text_editor.cpp
index f4fdf8ccb0..1a88562c13 100644
--- a/editor/plugins/script_text_editor.cpp
+++ b/editor/plugins/script_text_editor.cpp
@@ -140,10 +140,10 @@ RES ScriptTextEditor::get_edited_resource() const {
}
void ScriptTextEditor::set_edited_resource(const RES &p_res) {
- ERR_FAIL_COND(!script.is_null());
+ ERR_FAIL_COND(script.is_valid());
+ ERR_FAIL_COND(p_res.is_null());
script = p_res;
- _set_theme_for_script();
code_editor->get_text_edit()->set_text(script->get_source_code());
code_editor->get_text_edit()->clear_undo_history();
@@ -151,6 +151,17 @@ void ScriptTextEditor::set_edited_resource(const RES &p_res) {
emit_signal("name_changed");
code_editor->update_line_and_column();
+}
+
+void ScriptTextEditor::enable_editor() {
+ if (editor_enabled) {
+ return;
+ }
+
+ editor_enabled = true;
+
+ _enable_code_editor();
+ _set_theme_for_script();
_validate_script();
}
@@ -301,14 +312,6 @@ void ScriptTextEditor::reload_text() {
code_editor->update_line_and_column();
}
-void ScriptTextEditor::_notification(int p_what) {
- switch (p_what) {
- case NOTIFICATION_READY:
- _load_theme_settings();
- break;
- }
-}
-
void ScriptTextEditor::add_callback(const String &p_function, PackedStringArray p_args) {
String code = code_editor->get_text_edit()->get_text();
int pos = script->get_language()->find_function(p_function, code);
@@ -335,7 +338,10 @@ void ScriptTextEditor::update_settings() {
}
bool ScriptTextEditor::is_unsaved() {
- return code_editor->get_text_edit()->get_version() != code_editor->get_text_edit()->get_saved_version();
+ const bool unsaved =
+ code_editor->get_text_edit()->get_version() != code_editor->get_text_edit()->get_saved_version() ||
+ script->get_path().empty(); // In memory.
+ return unsaved;
}
Variant ScriptTextEditor::get_edit_state() {
@@ -352,6 +358,10 @@ void ScriptTextEditor::set_edit_state(const Variant &p_state) {
_change_syntax_highlighter(idx);
}
}
+
+ if (editor_enabled) {
+ ensure_focus();
+ }
}
void ScriptTextEditor::_convert_case(CodeTextEditor::CaseStyle p_case) {
@@ -408,6 +418,9 @@ String ScriptTextEditor::get_name() {
if (script->get_path().find("local://") == -1 && script->get_path().find("::") == -1) {
name = script->get_path().get_file();
if (is_unsaved()) {
+ if (script->get_path().empty()) {
+ name = TTR("[unsaved]");
+ }
name += "(*)";
}
} else if (script->get_name() != "") {
@@ -1274,23 +1287,28 @@ void ScriptTextEditor::_edit_option_toggle_inline_comment() {
}
void ScriptTextEditor::add_syntax_highlighter(Ref<EditorSyntaxHighlighter> p_highlighter) {
+ ERR_FAIL_COND(p_highlighter.is_null());
+
highlighters[p_highlighter->_get_name()] = p_highlighter;
highlighter_menu->add_radio_check_item(p_highlighter->_get_name());
}
void ScriptTextEditor::set_syntax_highlighter(Ref<EditorSyntaxHighlighter> p_highlighter) {
+ ERR_FAIL_COND(p_highlighter.is_null());
+
+ Map<String, Ref<EditorSyntaxHighlighter>>::Element *el = highlighters.front();
+ while (el != nullptr) {
+ int highlighter_index = highlighter_menu->get_item_idx_from_text(el->key());
+ highlighter_menu->set_item_checked(highlighter_index, el->value() == p_highlighter);
+ el = el->next();
+ }
+
TextEdit *te = code_editor->get_text_edit();
p_highlighter->_set_edited_resource(script);
te->set_syntax_highlighter(p_highlighter);
- highlighter_menu->set_item_checked(highlighter_menu->get_item_idx_from_text(p_highlighter->_get_name()), true);
}
void ScriptTextEditor::_change_syntax_highlighter(int p_idx) {
- Map<String, Ref<EditorSyntaxHighlighter>>::Element *el = highlighters.front();
- while (el != nullptr) {
- highlighter_menu->set_item_checked(highlighter_menu->get_item_idx_from_text(el->key()), false);
- el = el->next();
- }
set_syntax_highlighter(highlighters[highlighter_menu->get_item_text(p_idx)]);
}
@@ -1606,64 +1624,40 @@ void ScriptTextEditor::_make_context_menu(bool p_selection, bool p_color, bool p
context_menu->popup();
}
-ScriptTextEditor::ScriptTextEditor() {
- theme_loaded = false;
- script_is_valid = false;
+void ScriptTextEditor::_enable_code_editor() {
+ ERR_FAIL_COND(code_editor->get_parent());
VSplitContainer *editor_box = memnew(VSplitContainer);
add_child(editor_box);
editor_box->set_anchors_and_margins_preset(Control::PRESET_WIDE);
editor_box->set_v_size_flags(SIZE_EXPAND_FILL);
- code_editor = memnew(CodeTextEditor);
editor_box->add_child(code_editor);
- code_editor->add_theme_constant_override("separation", 2);
- code_editor->set_anchors_and_margins_preset(Control::PRESET_WIDE);
+ code_editor->connect("show_warnings_panel", callable_mp(this, &ScriptTextEditor::_show_warnings_panel));
code_editor->connect("validate_script", callable_mp(this, &ScriptTextEditor::_validate_script));
code_editor->connect("load_theme_settings", callable_mp(this, &ScriptTextEditor::_load_theme_settings));
- code_editor->set_code_complete_func(_code_complete_scripts, this);
code_editor->get_text_edit()->connect("breakpoint_toggled", callable_mp(this, &ScriptTextEditor::_breakpoint_toggled));
code_editor->get_text_edit()->connect("symbol_lookup", callable_mp(this, &ScriptTextEditor::_lookup_symbol));
code_editor->get_text_edit()->connect("symbol_validate", callable_mp(this, &ScriptTextEditor::_validate_symbol));
- code_editor->get_text_edit()->connect("info_clicked", callable_mp(this, &ScriptTextEditor::_lookup_connections));
- code_editor->set_v_size_flags(SIZE_EXPAND_FILL);
+ code_editor->get_text_edit()->connect("gui_input", callable_mp(this, &ScriptTextEditor::_text_edit_gui_input));
code_editor->show_toggle_scripts_button();
- warnings_panel = memnew(RichTextLabel);
editor_box->add_child(warnings_panel);
warnings_panel->add_theme_font_override(
"normal_font", EditorNode::get_singleton()->get_gui_base()->get_theme_font("main", "EditorFonts"));
- warnings_panel->set_custom_minimum_size(Size2(0, 100 * EDSCALE));
- warnings_panel->set_h_size_flags(SIZE_EXPAND_FILL);
- warnings_panel->set_meta_underline(true);
- warnings_panel->set_selection_enabled(true);
- warnings_panel->set_focus_mode(FOCUS_CLICK);
- warnings_panel->hide();
-
- code_editor->connect("show_warnings_panel", callable_mp(this, &ScriptTextEditor::_show_warnings_panel));
warnings_panel->connect("meta_clicked", callable_mp(this, &ScriptTextEditor::_warning_clicked));
- update_settings();
-
- code_editor->get_text_edit()->set_callhint_settings(
- EditorSettings::get_singleton()->get("text_editor/completion/put_callhint_tooltip_below_current_line"),
- EditorSettings::get_singleton()->get("text_editor/completion/callhint_tooltip_offset"));
-
- code_editor->get_text_edit()->set_select_identifiers_on_hover(true);
- code_editor->get_text_edit()->set_context_menu_enabled(false);
- code_editor->get_text_edit()->connect("gui_input", callable_mp(this, &ScriptTextEditor::_text_edit_gui_input));
-
- context_menu = memnew(PopupMenu);
add_child(context_menu);
context_menu->connect("id_pressed", callable_mp(this, &ScriptTextEditor::_edit_option));
- color_panel = memnew(PopupPanel);
add_child(color_panel);
+
color_picker = memnew(ColorPicker);
color_picker->set_deferred_mode(true);
- color_panel->add_child(color_picker);
color_picker->connect("color_changed", callable_mp(this, &ScriptTextEditor::_color_changed));
+ color_panel->add_child(color_picker);
+
// get default color picker mode from editor settings
int default_color_mode = EDITOR_GET("interface/inspector/default_color_picker_mode");
if (default_color_mode == 1) {
@@ -1672,12 +1666,27 @@ ScriptTextEditor::ScriptTextEditor() {
color_picker->set_raw_mode(true);
}
- edit_hb = memnew(HBoxContainer);
+ quick_open = memnew(ScriptEditorQuickOpen);
+ quick_open->connect("goto_line", callable_mp(this, &ScriptTextEditor::_goto_line));
+ add_child(quick_open);
- edit_menu = memnew(MenuButton);
- edit_menu->set_text(TTR("Edit"));
- edit_menu->set_switch_on_hover(true);
+ goto_line_dialog = memnew(GotoLineDialog);
+ add_child(goto_line_dialog);
+
+ add_child(connection_info_dialog);
+ edit_hb->add_child(search_menu);
+ search_menu->get_popup()->add_shortcut(ED_GET_SHORTCUT("script_text_editor/find"), SEARCH_FIND);
+ search_menu->get_popup()->add_shortcut(ED_GET_SHORTCUT("script_text_editor/find_next"), SEARCH_FIND_NEXT);
+ search_menu->get_popup()->add_shortcut(ED_GET_SHORTCUT("script_text_editor/find_previous"), SEARCH_FIND_PREV);
+ search_menu->get_popup()->add_shortcut(ED_GET_SHORTCUT("script_text_editor/replace"), SEARCH_REPLACE);
+ search_menu->get_popup()->add_separator();
+ search_menu->get_popup()->add_shortcut(ED_GET_SHORTCUT("script_text_editor/find_in_files"), SEARCH_IN_FILES);
+ search_menu->get_popup()->add_separator();
+ search_menu->get_popup()->add_shortcut(ED_GET_SHORTCUT("script_text_editor/contextual_help"), HELP_CONTEXTUAL);
+ search_menu->get_popup()->connect("id_pressed", callable_mp(this, &ScriptTextEditor::_edit_option));
+
+ edit_hb->add_child(edit_menu);
edit_menu->get_popup()->add_shortcut(ED_GET_SHORTCUT("script_text_editor/undo"), EDIT_UNDO);
edit_menu->get_popup()->add_shortcut(ED_GET_SHORTCUT("script_text_editor/redo"), EDIT_REDO);
edit_menu->get_popup()->add_separator();
@@ -1707,8 +1716,6 @@ ScriptTextEditor::ScriptTextEditor() {
edit_menu->get_popup()->connect("id_pressed", callable_mp(this, &ScriptTextEditor::_edit_option));
edit_menu->get_popup()->add_separator();
- PopupMenu *convert_case = memnew(PopupMenu);
- convert_case->set_name("convert_case");
edit_menu->get_popup()->add_child(convert_case);
edit_menu->get_popup()->add_submenu_item(TTR("Convert Case"), "convert_case");
convert_case->add_shortcut(ED_SHORTCUT("script_text_editor/convert_to_uppercase", TTR("Uppercase"), KEY_MASK_SHIFT | KEY_F4), EDIT_TO_UPPERCASE);
@@ -1716,12 +1723,73 @@ ScriptTextEditor::ScriptTextEditor() {
convert_case->add_shortcut(ED_SHORTCUT("script_text_editor/capitalize", TTR("Capitalize"), KEY_MASK_SHIFT | KEY_F6), EDIT_CAPITALIZE);
convert_case->connect("id_pressed", callable_mp(this, &ScriptTextEditor::_edit_option));
- highlighter_menu = memnew(PopupMenu);
- highlighter_menu->set_name("highlighter_menu");
edit_menu->get_popup()->add_child(highlighter_menu);
edit_menu->get_popup()->add_submenu_item(TTR("Syntax Highlighter"), "highlighter_menu");
highlighter_menu->connect("id_pressed", callable_mp(this, &ScriptTextEditor::_change_syntax_highlighter));
+ _load_theme_settings();
+
+ search_menu->get_popup()->add_shortcut(ED_GET_SHORTCUT("script_text_editor/replace_in_files"), REPLACE_IN_FILES);
+ edit_hb->add_child(goto_menu);
+ goto_menu->get_popup()->add_shortcut(ED_GET_SHORTCUT("script_text_editor/goto_function"), SEARCH_LOCATE_FUNCTION);
+ goto_menu->get_popup()->add_shortcut(ED_GET_SHORTCUT("script_text_editor/goto_line"), SEARCH_GOTO_LINE);
+ goto_menu->get_popup()->add_separator();
+
+ goto_menu->get_popup()->add_child(bookmarks_menu);
+ goto_menu->get_popup()->add_submenu_item(TTR("Bookmarks"), "Bookmarks");
+ _update_bookmark_list();
+ bookmarks_menu->connect("about_to_popup", callable_mp(this, &ScriptTextEditor::_update_bookmark_list));
+ bookmarks_menu->connect("index_pressed", callable_mp(this, &ScriptTextEditor::_bookmark_item_pressed));
+
+ goto_menu->get_popup()->add_child(breakpoints_menu);
+ goto_menu->get_popup()->add_submenu_item(TTR("Breakpoints"), "Breakpoints");
+ _update_breakpoint_list();
+ breakpoints_menu->connect("about_to_popup", callable_mp(this, &ScriptTextEditor::_update_breakpoint_list));
+ breakpoints_menu->connect("index_pressed", callable_mp(this, &ScriptTextEditor::_breakpoint_item_pressed));
+
+ goto_menu->get_popup()->connect("id_pressed", callable_mp(this, &ScriptTextEditor::_edit_option));
+}
+
+ScriptTextEditor::ScriptTextEditor() {
+ code_editor = memnew(CodeTextEditor);
+ code_editor->add_theme_constant_override("separation", 2);
+ code_editor->set_anchors_and_margins_preset(Control::PRESET_WIDE);
+ code_editor->set_code_complete_func(_code_complete_scripts, this);
+ code_editor->set_v_size_flags(SIZE_EXPAND_FILL);
+
+ warnings_panel = memnew(RichTextLabel);
+ warnings_panel->set_custom_minimum_size(Size2(0, 100 * EDSCALE));
+ warnings_panel->set_h_size_flags(SIZE_EXPAND_FILL);
+ warnings_panel->set_meta_underline(true);
+ warnings_panel->set_selection_enabled(true);
+ warnings_panel->set_focus_mode(FOCUS_CLICK);
+ warnings_panel->hide();
+
+ update_settings();
+
+ code_editor->get_text_edit()->set_callhint_settings(
+ EditorSettings::get_singleton()->get("text_editor/completion/put_callhint_tooltip_below_current_line"),
+ EditorSettings::get_singleton()->get("text_editor/completion/callhint_tooltip_offset"));
+
+ code_editor->get_text_edit()->set_select_identifiers_on_hover(true);
+ code_editor->get_text_edit()->set_context_menu_enabled(false);
+
+ context_menu = memnew(PopupMenu);
+
+ color_panel = memnew(PopupPanel);
+
+ edit_hb = memnew(HBoxContainer);
+
+ edit_menu = memnew(MenuButton);
+ edit_menu->set_text(TTR("Edit"));
+ edit_menu->set_switch_on_hover(true);
+
+ convert_case = memnew(PopupMenu);
+ convert_case->set_name("convert_case");
+
+ highlighter_menu = memnew(PopupMenu);
+ highlighter_menu->set_name("highlighter_menu");
+
Ref<EditorPlainTextSyntaxHighlighter> plain_highlighter;
plain_highlighter.instance();
add_syntax_highlighter(plain_highlighter);
@@ -1732,64 +1800,42 @@ ScriptTextEditor::ScriptTextEditor() {
set_syntax_highlighter(highlighter);
search_menu = memnew(MenuButton);
- edit_hb->add_child(search_menu);
search_menu->set_text(TTR("Search"));
search_menu->set_switch_on_hover(true);
- search_menu->get_popup()->add_shortcut(ED_GET_SHORTCUT("script_text_editor/find"), SEARCH_FIND);
- search_menu->get_popup()->add_shortcut(ED_GET_SHORTCUT("script_text_editor/find_next"), SEARCH_FIND_NEXT);
- search_menu->get_popup()->add_shortcut(ED_GET_SHORTCUT("script_text_editor/find_previous"), SEARCH_FIND_PREV);
- search_menu->get_popup()->add_shortcut(ED_GET_SHORTCUT("script_text_editor/replace"), SEARCH_REPLACE);
- search_menu->get_popup()->add_separator();
- search_menu->get_popup()->add_shortcut(ED_GET_SHORTCUT("script_text_editor/find_in_files"), SEARCH_IN_FILES);
- search_menu->get_popup()->add_shortcut(ED_GET_SHORTCUT("script_text_editor/replace_in_files"), REPLACE_IN_FILES);
- search_menu->get_popup()->add_separator();
- search_menu->get_popup()->add_shortcut(ED_GET_SHORTCUT("script_text_editor/contextual_help"), HELP_CONTEXTUAL);
- search_menu->get_popup()->connect("id_pressed", callable_mp(this, &ScriptTextEditor::_edit_option));
-
- edit_hb->add_child(edit_menu);
-
- MenuButton *goto_menu = memnew(MenuButton);
- edit_hb->add_child(goto_menu);
+ goto_menu = memnew(MenuButton);
goto_menu->set_text(TTR("Go To"));
goto_menu->set_switch_on_hover(true);
- goto_menu->get_popup()->connect("id_pressed", callable_mp(this, &ScriptTextEditor::_edit_option));
-
- goto_menu->get_popup()->add_shortcut(ED_GET_SHORTCUT("script_text_editor/goto_function"), SEARCH_LOCATE_FUNCTION);
- goto_menu->get_popup()->add_shortcut(ED_GET_SHORTCUT("script_text_editor/goto_line"), SEARCH_GOTO_LINE);
- goto_menu->get_popup()->add_separator();
bookmarks_menu = memnew(PopupMenu);
bookmarks_menu->set_name("Bookmarks");
- goto_menu->get_popup()->add_child(bookmarks_menu);
- goto_menu->get_popup()->add_submenu_item(TTR("Bookmarks"), "Bookmarks");
- _update_bookmark_list();
- bookmarks_menu->connect("about_to_popup", callable_mp(this, &ScriptTextEditor::_update_bookmark_list));
- bookmarks_menu->connect("index_pressed", callable_mp(this, &ScriptTextEditor::_bookmark_item_pressed));
breakpoints_menu = memnew(PopupMenu);
breakpoints_menu->set_name("Breakpoints");
- goto_menu->get_popup()->add_child(breakpoints_menu);
- goto_menu->get_popup()->add_submenu_item(TTR("Breakpoints"), "Breakpoints");
- _update_breakpoint_list();
- breakpoints_menu->connect("about_to_popup", callable_mp(this, &ScriptTextEditor::_update_breakpoint_list));
- breakpoints_menu->connect("index_pressed", callable_mp(this, &ScriptTextEditor::_breakpoint_item_pressed));
-
- quick_open = memnew(ScriptEditorQuickOpen);
- add_child(quick_open);
- quick_open->connect("goto_line", callable_mp(this, &ScriptTextEditor::_goto_line));
-
- goto_line_dialog = memnew(GotoLineDialog);
- add_child(goto_line_dialog);
connection_info_dialog = memnew(ConnectionInfoDialog);
- add_child(connection_info_dialog);
code_editor->get_text_edit()->set_drag_forwarding(this);
}
ScriptTextEditor::~ScriptTextEditor() {
highlighters.clear();
+
+ if (!editor_enabled) {
+ memdelete(code_editor);
+ memdelete(warnings_panel);
+ memdelete(context_menu);
+ memdelete(color_panel);
+ memdelete(edit_hb);
+ memdelete(edit_menu);
+ memdelete(convert_case);
+ memdelete(highlighter_menu);
+ memdelete(search_menu);
+ memdelete(goto_menu);
+ memdelete(bookmarks_menu);
+ memdelete(breakpoints_menu);
+ memdelete(connection_info_dialog);
+ }
}
static ScriptEditorBase *create_editor(const RES &p_resource) {
diff --git a/editor/plugins/script_text_editor.h b/editor/plugins/script_text_editor.h
index 6d7f84d746..e931c9fdc6 100644
--- a/editor/plugins/script_text_editor.h
+++ b/editor/plugins/script_text_editor.h
@@ -39,8 +39,8 @@
class ConnectionInfoDialog : public AcceptDialog {
GDCLASS(ConnectionInfoDialog, AcceptDialog);
- Label *method;
- Tree *tree;
+ Label *method = nullptr;
+ Tree *tree = nullptr;
virtual void ok_pressed() override;
@@ -53,11 +53,12 @@ public:
class ScriptTextEditor : public ScriptEditorBase {
GDCLASS(ScriptTextEditor, ScriptEditorBase);
- CodeTextEditor *code_editor;
- RichTextLabel *warnings_panel;
+ CodeTextEditor *code_editor = nullptr;
+ RichTextLabel *warnings_panel = nullptr;
Ref<Script> script;
- bool script_is_valid;
+ bool script_is_valid = false;
+ bool editor_enabled = false;
Vector<String> functions;
@@ -65,25 +66,27 @@ class ScriptTextEditor : public ScriptEditorBase {
Vector<String> member_keywords;
- HBoxContainer *edit_hb;
+ HBoxContainer *edit_hb = nullptr;
- MenuButton *edit_menu;
- MenuButton *search_menu;
- PopupMenu *bookmarks_menu;
- PopupMenu *breakpoints_menu;
- PopupMenu *highlighter_menu;
- PopupMenu *context_menu;
+ MenuButton *edit_menu = nullptr;
+ MenuButton *search_menu = nullptr;
+ MenuButton *goto_menu = nullptr;
+ PopupMenu *bookmarks_menu = nullptr;
+ PopupMenu *breakpoints_menu = nullptr;
+ PopupMenu *highlighter_menu = nullptr;
+ PopupMenu *context_menu = nullptr;
+ PopupMenu *convert_case = nullptr;
- GotoLineDialog *goto_line_dialog;
- ScriptEditorQuickOpen *quick_open;
- ConnectionInfoDialog *connection_info_dialog;
+ GotoLineDialog *goto_line_dialog = nullptr;
+ ScriptEditorQuickOpen *quick_open = nullptr;
+ ConnectionInfoDialog *connection_info_dialog = nullptr;
- PopupPanel *color_panel;
- ColorPicker *color_picker;
+ PopupPanel *color_panel = nullptr;
+ ColorPicker *color_picker = nullptr;
Vector2 color_position;
String color_args;
- bool theme_loaded;
+ bool theme_loaded = false;
enum {
EDIT_UNDO,
@@ -132,6 +135,8 @@ class ScriptTextEditor : public ScriptEditorBase {
LOOKUP_SYMBOL,
};
+ void _enable_code_editor();
+
protected:
void _update_breakpoint_list();
void _breakpoint_item_pressed(int p_idx);
@@ -149,7 +154,6 @@ protected:
void _show_warnings_panel(bool p_show);
void _warning_clicked(Variant p_line);
- void _notification(int p_what);
static void _bind_methods();
Map<String, Ref<EditorSyntaxHighlighter>> highlighters;
@@ -185,6 +189,7 @@ public:
virtual void apply_code() override;
virtual RES get_edited_resource() const override;
virtual void set_edited_resource(const RES &p_res) override;
+ virtual void enable_editor() override;
virtual Vector<String> get_functions() override;
virtual void reload_text() override;
virtual String get_name() override;
diff --git a/editor/plugins/shader_editor_plugin.cpp b/editor/plugins/shader_editor_plugin.cpp
index 60ba3802fb..dc2abe15ee 100644
--- a/editor/plugins/shader_editor_plugin.cpp
+++ b/editor/plugins/shader_editor_plugin.cpp
@@ -136,28 +136,39 @@ void ShaderTextEditor::_load_theme_settings() {
syntax_highlighter->set_function_color(EDITOR_GET("text_editor/highlighting/function_color"));
syntax_highlighter->set_member_variable_color(EDITOR_GET("text_editor/highlighting/member_variable_color"));
+ syntax_highlighter->clear_keyword_colors();
+
List<String> keywords;
ShaderLanguage::get_keyword_list(&keywords);
+ const Color keyword_color = EDITOR_GET("text_editor/highlighting/keyword_color");
+
+ for (List<String>::Element *E = keywords.front(); E; E = E->next()) {
+ syntax_highlighter->add_keyword_color(E->get(), keyword_color);
+ }
+ // Colorize built-ins like `COLOR` differently to make them easier
+ // to distinguish from keywords at a quick glance.
+
+ List<String> built_ins;
if (shader.is_valid()) {
for (const Map<StringName, ShaderLanguage::FunctionInfo>::Element *E = ShaderTypes::get_singleton()->get_functions(RenderingServer::ShaderMode(shader->get_mode())).front(); E; E = E->next()) {
for (const Map<StringName, ShaderLanguage::BuiltInInfo>::Element *F = E->get().built_ins.front(); F; F = F->next()) {
- keywords.push_back(F->key());
+ built_ins.push_back(F->key());
}
}
for (int i = 0; i < ShaderTypes::get_singleton()->get_modes(RenderingServer::ShaderMode(shader->get_mode())).size(); i++) {
- keywords.push_back(ShaderTypes::get_singleton()->get_modes(RenderingServer::ShaderMode(shader->get_mode()))[i]);
+ built_ins.push_back(ShaderTypes::get_singleton()->get_modes(RenderingServer::ShaderMode(shader->get_mode()))[i]);
}
}
- const Color keyword_color = EDITOR_GET("text_editor/highlighting/keyword_color");
- syntax_highlighter->clear_keyword_colors();
- for (List<String>::Element *E = keywords.front(); E; E = E->next()) {
- syntax_highlighter->add_keyword_color(E->get(), keyword_color);
+ const Color member_variable_color = EDITOR_GET("text_editor/highlighting/member_variable_color");
+
+ for (List<String>::Element *E = built_ins.front(); E; E = E->next()) {
+ syntax_highlighter->add_keyword_color(E->get(), member_variable_color);
}
- //colorize comments
+ // Colorize comments.
const Color comment_color = EDITOR_GET("text_editor/highlighting/comment_color");
syntax_highlighter->clear_color_regions();
syntax_highlighter->add_color_region("/*", "*/", comment_color, false);
diff --git a/editor/plugins/text_editor.cpp b/editor/plugins/text_editor.cpp
index d602d152fe..82e231e396 100644
--- a/editor/plugins/text_editor.cpp
+++ b/editor/plugins/text_editor.cpp
@@ -34,22 +34,27 @@
#include "editor/editor_node.h"
void TextEditor::add_syntax_highlighter(Ref<EditorSyntaxHighlighter> p_highlighter) {
+ ERR_FAIL_COND(p_highlighter.is_null());
+
highlighters[p_highlighter->_get_name()] = p_highlighter;
highlighter_menu->add_radio_check_item(p_highlighter->_get_name());
}
void TextEditor::set_syntax_highlighter(Ref<EditorSyntaxHighlighter> p_highlighter) {
- TextEdit *te = code_editor->get_text_edit();
- te->set_syntax_highlighter(p_highlighter);
- highlighter_menu->set_item_checked(highlighter_menu->get_item_idx_from_text(p_highlighter->_get_name()), true);
-}
+ ERR_FAIL_COND(p_highlighter.is_null());
-void TextEditor::_change_syntax_highlighter(int p_idx) {
Map<String, Ref<EditorSyntaxHighlighter>>::Element *el = highlighters.front();
while (el != nullptr) {
- highlighter_menu->set_item_checked(highlighter_menu->get_item_idx_from_text(el->key()), false);
+ int highlighter_index = highlighter_menu->get_item_idx_from_text(el->key());
+ highlighter_menu->set_item_checked(highlighter_index, el->value() == p_highlighter);
el = el->next();
}
+
+ TextEdit *te = code_editor->get_text_edit();
+ te->set_syntax_highlighter(p_highlighter);
+}
+
+void TextEditor::_change_syntax_highlighter(int p_idx) {
set_syntax_highlighter(highlighters[highlighter_menu->get_item_text(p_idx)]);
}
@@ -114,6 +119,9 @@ String TextEditor::get_name() {
if (text_file->get_path().find("local://") == -1 && text_file->get_path().find("::") == -1) {
name = text_file->get_path().get_file();
if (is_unsaved()) {
+ if (text_file->get_path().empty()) {
+ name = TTR("[unsaved]");
+ }
name += "(*)";
}
} else if (text_file->get_name() != "") {
@@ -126,7 +134,7 @@ String TextEditor::get_name() {
}
Ref<Texture2D> TextEditor::get_theme_icon() {
- return EditorNode::get_singleton()->get_object_icon(text_file.operator->(), "");
+ return EditorNode::get_singleton()->get_object_icon(text_file.ptr(), "");
}
RES TextEditor::get_edited_resource() const {
@@ -134,7 +142,8 @@ RES TextEditor::get_edited_resource() const {
}
void TextEditor::set_edited_resource(const RES &p_res) {
- ERR_FAIL_COND(!text_file.is_null());
+ ERR_FAIL_COND(text_file.is_valid());
+ ERR_FAIL_COND(p_res.is_null());
text_file = p_res;
@@ -146,6 +155,16 @@ void TextEditor::set_edited_resource(const RES &p_res) {
code_editor->update_line_and_column();
}
+void TextEditor::enable_editor() {
+ if (editor_enabled) {
+ return;
+ }
+
+ editor_enabled = true;
+
+ _load_theme_settings();
+}
+
void TextEditor::add_callback(const String &p_function, PackedStringArray p_args) {
}
@@ -220,7 +239,10 @@ void TextEditor::apply_code() {
}
bool TextEditor::is_unsaved() {
- return code_editor->get_text_edit()->get_version() != code_editor->get_text_edit()->get_saved_version();
+ const bool unsaved =
+ code_editor->get_text_edit()->get_version() != code_editor->get_text_edit()->get_saved_version() ||
+ text_file->get_path().empty(); // In memory.
+ return unsaved;
}
Variant TextEditor::get_edit_state() {
@@ -237,6 +259,8 @@ void TextEditor::set_edit_state(const Variant &p_state) {
_change_syntax_highlighter(idx);
}
}
+
+ ensure_focus();
}
void TextEditor::trim_trailing_whitespace() {
@@ -303,14 +327,6 @@ void TextEditor::clear_edit_menu() {
memdelete(edit_hb);
}
-void TextEditor::_notification(int p_what) {
- switch (p_what) {
- case NOTIFICATION_READY:
- _load_theme_settings();
- break;
- }
-}
-
void TextEditor::_edit_option(int p_op) {
TextEdit *tx = code_editor->get_text_edit();
diff --git a/editor/plugins/text_editor.h b/editor/plugins/text_editor.h
index 5299776b56..f3e9e599cf 100644
--- a/editor/plugins/text_editor.h
+++ b/editor/plugins/text_editor.h
@@ -37,18 +37,19 @@ class TextEditor : public ScriptEditorBase {
GDCLASS(TextEditor, ScriptEditorBase);
private:
- CodeTextEditor *code_editor;
+ CodeTextEditor *code_editor = nullptr;
Ref<TextFile> text_file;
+ bool editor_enabled = false;
- HBoxContainer *edit_hb;
- MenuButton *edit_menu;
- PopupMenu *highlighter_menu;
- MenuButton *search_menu;
- PopupMenu *bookmarks_menu;
- PopupMenu *context_menu;
+ HBoxContainer *edit_hb = nullptr;
+ MenuButton *edit_menu = nullptr;
+ PopupMenu *highlighter_menu = nullptr;
+ MenuButton *search_menu = nullptr;
+ PopupMenu *bookmarks_menu = nullptr;
+ PopupMenu *context_menu = nullptr;
- GotoLineDialog *goto_line_dialog;
+ GotoLineDialog *goto_line_dialog = nullptr;
enum {
EDIT_UNDO,
@@ -88,8 +89,6 @@ private:
protected:
static void _bind_methods();
- void _notification(int p_what);
-
void _edit_option(int p_op);
void _make_context_menu(bool p_selection, bool p_can_fold, bool p_is_folded, Vector2 p_position);
void _text_edit_gui_input(const Ref<InputEvent> &ev);
@@ -113,7 +112,7 @@ public:
virtual Ref<Texture2D> get_theme_icon() override;
virtual RES get_edited_resource() const override;
virtual void set_edited_resource(const RES &p_res) override;
- void set_edited_file(const Ref<TextFile> &p_file);
+ virtual void enable_editor() override;
virtual void reload_text() override;
virtual void apply_code() override;
virtual bool is_unsaved() override;
diff --git a/editor/plugins/tile_set_editor_plugin.cpp b/editor/plugins/tile_set_editor_plugin.cpp
index 7fb751e3ed..a613174ed9 100644
--- a/editor/plugins/tile_set_editor_plugin.cpp
+++ b/editor/plugins/tile_set_editor_plugin.cpp
@@ -698,7 +698,7 @@ void TileSetEditor::_on_tileset_toolbar_confirm() {
List<int> ids;
tileset->get_tile_list(&ids);
- undo_redo->create_action(TTR(option == TOOL_TILESET_MERGE_SCENE ? "Merge Tileset from Scene" : "Create Tileset from Scene"));
+ undo_redo->create_action(option == TOOL_TILESET_MERGE_SCENE ? TTR("Merge Tileset from Scene") : TTR("Create Tileset from Scene"));
undo_redo->add_do_method(this, "_undo_redo_import_scene", scene, option == TOOL_TILESET_MERGE_SCENE);
undo_redo->add_undo_method(tileset.ptr(), "clear");
for (List<int>::Element *E = ids.front(); E; E = E->next()) {
diff --git a/editor/scene_tree_dock.cpp b/editor/scene_tree_dock.cpp
index 108e44f294..ce869feddd 100644
--- a/editor/scene_tree_dock.cpp
+++ b/editor/scene_tree_dock.cpp
@@ -2718,7 +2718,7 @@ void SceneTreeDock::_update_create_root_dialog() {
if (l != String()) {
Button *button = memnew(Button);
favorite_nodes->add_child(button);
- button->set_text(TTR(l));
+ button->set_text(l);
String name = l.get_slicec(' ', 0);
if (ScriptServer::is_global_class(name)) {
name = ScriptServer::get_global_class_native_base(name);
diff --git a/editor/script_create_dialog.cpp b/editor/script_create_dialog.cpp
index ffdf8208b8..628475bbc0 100644
--- a/editor/script_create_dialog.cpp
+++ b/editor/script_create_dialog.cpp
@@ -603,7 +603,7 @@ void ScriptCreateDialog::_path_entered(const String &p_path) {
}
void ScriptCreateDialog::_msg_script_valid(bool valid, const String &p_msg) {
- error_label->set_text("- " + TTR(p_msg));
+ error_label->set_text("- " + p_msg);
if (valid) {
error_label->add_theme_color_override("font_color", gc->get_theme_color("success_color", "Editor"));
} else {
@@ -612,7 +612,7 @@ void ScriptCreateDialog::_msg_script_valid(bool valid, const String &p_msg) {
}
void ScriptCreateDialog::_msg_path_valid(bool valid, const String &p_msg) {
- path_error_label->set_text("- " + TTR(p_msg));
+ path_error_label->set_text("- " + p_msg);
if (valid) {
path_error_label->add_theme_color_override("font_color", gc->get_theme_color("success_color", "Editor"));
} else {
diff --git a/gles_builders.py b/gles_builders.py
index 85d0112c9a..eca42236ab 100644
--- a/gles_builders.py
+++ b/gles_builders.py
@@ -230,59 +230,76 @@ def build_legacygl_header(filename, include, class_suffix, output_attribs, gles2
fd.write("\t_FORCE_INLINE_ int get_uniform(Uniforms p_uniform) const { return _get_uniform(p_uniform); }\n\n")
if header_data.conditionals:
fd.write(
- "\t_FORCE_INLINE_ void set_conditional(Conditionals p_conditional,bool p_enable) { _set_conditional(p_conditional,p_enable); }\n\n"
+ "\t_FORCE_INLINE_ void set_conditional(Conditionals p_conditional,bool p_enable) {"
+ " _set_conditional(p_conditional,p_enable); }\n\n"
)
fd.write("\t#ifdef DEBUG_ENABLED\n ")
fd.write(
- "\t#define _FU if (get_uniform(p_uniform)<0) return; if (!is_version_valid()) return; ERR_FAIL_COND( get_active()!=this ); \n\n "
+ "\t#define _FU if (get_uniform(p_uniform)<0) return; if (!is_version_valid()) return; ERR_FAIL_COND("
+ " get_active()!=this ); \n\n "
)
fd.write("\t#else\n ")
fd.write("\t#define _FU if (get_uniform(p_uniform)<0) return; \n\n ")
fd.write("\t#endif\n")
fd.write(
- "\t_FORCE_INLINE_ void set_uniform(Uniforms p_uniform, float p_value) { _FU glUniform1f(get_uniform(p_uniform),p_value); }\n\n"
+ "\t_FORCE_INLINE_ void set_uniform(Uniforms p_uniform, float p_value) { _FU"
+ " glUniform1f(get_uniform(p_uniform),p_value); }\n\n"
)
fd.write(
- "\t_FORCE_INLINE_ void set_uniform(Uniforms p_uniform, double p_value) { _FU glUniform1f(get_uniform(p_uniform),p_value); }\n\n"
+ "\t_FORCE_INLINE_ void set_uniform(Uniforms p_uniform, double p_value) { _FU"
+ " glUniform1f(get_uniform(p_uniform),p_value); }\n\n"
)
fd.write(
- "\t_FORCE_INLINE_ void set_uniform(Uniforms p_uniform, uint8_t p_value) { _FU glUniform1i(get_uniform(p_uniform),p_value); }\n\n"
+ "\t_FORCE_INLINE_ void set_uniform(Uniforms p_uniform, uint8_t p_value) { _FU"
+ " glUniform1i(get_uniform(p_uniform),p_value); }\n\n"
)
fd.write(
- "\t_FORCE_INLINE_ void set_uniform(Uniforms p_uniform, int8_t p_value) { _FU glUniform1i(get_uniform(p_uniform),p_value); }\n\n"
+ "\t_FORCE_INLINE_ void set_uniform(Uniforms p_uniform, int8_t p_value) { _FU"
+ " glUniform1i(get_uniform(p_uniform),p_value); }\n\n"
)
fd.write(
- "\t_FORCE_INLINE_ void set_uniform(Uniforms p_uniform, uint16_t p_value) { _FU glUniform1i(get_uniform(p_uniform),p_value); }\n\n"
+ "\t_FORCE_INLINE_ void set_uniform(Uniforms p_uniform, uint16_t p_value) { _FU"
+ " glUniform1i(get_uniform(p_uniform),p_value); }\n\n"
)
fd.write(
- "\t_FORCE_INLINE_ void set_uniform(Uniforms p_uniform, int16_t p_value) { _FU glUniform1i(get_uniform(p_uniform),p_value); }\n\n"
+ "\t_FORCE_INLINE_ void set_uniform(Uniforms p_uniform, int16_t p_value) { _FU"
+ " glUniform1i(get_uniform(p_uniform),p_value); }\n\n"
)
fd.write(
- "\t_FORCE_INLINE_ void set_uniform(Uniforms p_uniform, uint32_t p_value) { _FU glUniform1i(get_uniform(p_uniform),p_value); }\n\n"
+ "\t_FORCE_INLINE_ void set_uniform(Uniforms p_uniform, uint32_t p_value) { _FU"
+ " glUniform1i(get_uniform(p_uniform),p_value); }\n\n"
)
fd.write(
- "\t_FORCE_INLINE_ void set_uniform(Uniforms p_uniform, int32_t p_value) { _FU glUniform1i(get_uniform(p_uniform),p_value); }\n\n"
+ "\t_FORCE_INLINE_ void set_uniform(Uniforms p_uniform, int32_t p_value) { _FU"
+ " glUniform1i(get_uniform(p_uniform),p_value); }\n\n"
)
fd.write(
- "\t_FORCE_INLINE_ void set_uniform(Uniforms p_uniform, const Color& p_color) { _FU GLfloat col[4]={p_color.r,p_color.g,p_color.b,p_color.a}; glUniform4fv(get_uniform(p_uniform),1,col); }\n\n"
+ "\t_FORCE_INLINE_ void set_uniform(Uniforms p_uniform, const Color& p_color) { _FU GLfloat"
+ " col[4]={p_color.r,p_color.g,p_color.b,p_color.a}; glUniform4fv(get_uniform(p_uniform),1,col); }\n\n"
)
fd.write(
- "\t_FORCE_INLINE_ void set_uniform(Uniforms p_uniform, const Vector2& p_vec2) { _FU GLfloat vec2[2]={p_vec2.x,p_vec2.y}; glUniform2fv(get_uniform(p_uniform),1,vec2); }\n\n"
+ "\t_FORCE_INLINE_ void set_uniform(Uniforms p_uniform, const Vector2& p_vec2) { _FU GLfloat"
+ " vec2[2]={p_vec2.x,p_vec2.y}; glUniform2fv(get_uniform(p_uniform),1,vec2); }\n\n"
)
fd.write(
- "\t_FORCE_INLINE_ void set_uniform(Uniforms p_uniform, const Size2i& p_vec2) { _FU GLint vec2[2]={p_vec2.x,p_vec2.y}; glUniform2iv(get_uniform(p_uniform),1,vec2); }\n\n"
+ "\t_FORCE_INLINE_ void set_uniform(Uniforms p_uniform, const Size2i& p_vec2) { _FU GLint"
+ " vec2[2]={p_vec2.x,p_vec2.y}; glUniform2iv(get_uniform(p_uniform),1,vec2); }\n\n"
)
fd.write(
- "\t_FORCE_INLINE_ void set_uniform(Uniforms p_uniform, const Vector3& p_vec3) { _FU GLfloat vec3[3]={p_vec3.x,p_vec3.y,p_vec3.z}; glUniform3fv(get_uniform(p_uniform),1,vec3); }\n\n"
+ "\t_FORCE_INLINE_ void set_uniform(Uniforms p_uniform, const Vector3& p_vec3) { _FU GLfloat"
+ " vec3[3]={p_vec3.x,p_vec3.y,p_vec3.z}; glUniform3fv(get_uniform(p_uniform),1,vec3); }\n\n"
)
fd.write(
- "\t_FORCE_INLINE_ void set_uniform(Uniforms p_uniform, float p_a, float p_b) { _FU glUniform2f(get_uniform(p_uniform),p_a,p_b); }\n\n"
+ "\t_FORCE_INLINE_ void set_uniform(Uniforms p_uniform, float p_a, float p_b) { _FU"
+ " glUniform2f(get_uniform(p_uniform),p_a,p_b); }\n\n"
)
fd.write(
- "\t_FORCE_INLINE_ void set_uniform(Uniforms p_uniform, float p_a, float p_b, float p_c) { _FU glUniform3f(get_uniform(p_uniform),p_a,p_b,p_c); }\n\n"
+ "\t_FORCE_INLINE_ void set_uniform(Uniforms p_uniform, float p_a, float p_b, float p_c) { _FU"
+ " glUniform3f(get_uniform(p_uniform),p_a,p_b,p_c); }\n\n"
)
fd.write(
- "\t_FORCE_INLINE_ void set_uniform(Uniforms p_uniform, float p_a, float p_b, float p_c, float p_d) { _FU glUniform4f(get_uniform(p_uniform),p_a,p_b,p_c,p_d); }\n\n"
+ "\t_FORCE_INLINE_ void set_uniform(Uniforms p_uniform, float p_a, float p_b, float p_c, float p_d) { _FU"
+ " glUniform4f(get_uniform(p_uniform),p_a,p_b,p_c,p_d); }\n\n"
)
fd.write(
diff --git a/main/SCsub b/main/SCsub
index bf188d7328..97f77840f1 100644
--- a/main/SCsub
+++ b/main/SCsub
@@ -7,21 +7,23 @@ import main_builders
env.main_sources = []
-env.add_source_files(env.main_sources, "*.cpp")
+env_main = env.Clone()
-env.Depends("#main/splash.gen.h", "#main/splash.png")
-env.CommandNoCache("#main/splash.gen.h", "#main/splash.png", run_in_subprocess(main_builders.make_splash))
+env_main.add_source_files(env.main_sources, "*.cpp")
-env.Depends("#main/splash_editor.gen.h", "#main/splash_editor.png")
-env.CommandNoCache(
+if env["tests"]:
+ env_main.Append(CPPDEFINES=["TESTS_ENABLED"])
+
+env_main.Depends("#main/splash.gen.h", "#main/splash.png")
+env_main.CommandNoCache("#main/splash.gen.h", "#main/splash.png", run_in_subprocess(main_builders.make_splash))
+
+env_main.Depends("#main/splash_editor.gen.h", "#main/splash_editor.png")
+env_main.CommandNoCache(
"#main/splash_editor.gen.h", "#main/splash_editor.png", run_in_subprocess(main_builders.make_splash_editor)
)
-env.Depends("#main/app_icon.gen.h", "#main/app_icon.png")
-env.CommandNoCache("#main/app_icon.gen.h", "#main/app_icon.png", run_in_subprocess(main_builders.make_app_icon))
-
-if env["tools"]:
- SConscript("tests/SCsub")
+env_main.Depends("#main/app_icon.gen.h", "#main/app_icon.png")
+env_main.CommandNoCache("#main/app_icon.gen.h", "#main/app_icon.png", run_in_subprocess(main_builders.make_app_icon))
-lib = env.add_library("main", env.main_sources)
+lib = env_main.add_library("main", env.main_sources)
env.Prepend(LIBS=[lib])
diff --git a/main/main.cpp b/main/main.cpp
index 35aa99c720..6965e5415a 100644
--- a/main/main.cpp
+++ b/main/main.cpp
@@ -55,7 +55,6 @@
#include "main/performance.h"
#include "main/splash.gen.h"
#include "main/splash_editor.gen.h"
-#include "main/tests/test_main.h"
#include "modules/modules_enabled.gen.h"
#include "modules/register_module_types.h"
#include "platform/register_platform_apis.h"
@@ -75,6 +74,10 @@
#include "servers/rendering/rendering_server_wrap_mt.h"
#include "servers/xr_server.h"
+#ifdef TESTS_ENABLED
+#include "tests/test_main.h"
+#endif
+
#ifdef TOOLS_ENABLED
#include "editor/doc_data.h"
@@ -383,6 +386,7 @@ void Main::print_help(const char *p_binary) {
OS::get_singleton()->print(
" --gdnative-generate-json-api Generate JSON dump of the Godot API for GDNative bindings.\n");
#endif
+#ifdef TESTS_ENABLED
OS::get_singleton()->print(" --test <test> Run a unit test [");
const char **test_names = tests_get_names();
const char *comma = "";
@@ -393,10 +397,12 @@ void Main::print_help(const char *p_binary) {
}
OS::get_singleton()->print("].\n");
#endif
+ OS::get_singleton()->print("\n");
+#endif
}
int Main::test_entrypoint(int argc, char *argv[], bool &tests_need_run) {
-#ifdef TOOLS_ENABLED // templates can't run unit test tool
+#ifdef TESTS_ENABLED
for (int x = 0; x < argc; x++) {
if (strncmp(argv[x], "--test", 6) == 0) {
tests_need_run = true;
@@ -1191,7 +1197,7 @@ Error Main::setup(const char *execpath, int argc, char *argv[], bool p_second_ph
OS::get_singleton()->_vsync_via_compositor = window_vsync_via_compositor;
if (tablet_driver == "") { // specified in project.godot
- tablet_driver = GLOBAL_DEF_RST("display/window/tablet_driver", OS::get_singleton()->get_tablet_driver_name(0));
+ tablet_driver = GLOBAL_DEF_RST_NOVAL("display/window/tablet_driver", OS::get_singleton()->get_tablet_driver_name(0));
}
for (int i = 0; i < OS::get_singleton()->get_tablet_driver_count(); i++) {
@@ -1245,7 +1251,7 @@ Error Main::setup(const char *execpath, int argc, char *argv[], bool p_second_ph
}
if (audio_driver == "") { // specified in project.godot
- audio_driver = GLOBAL_DEF_RST("audio/driver", AudioDriverManager::get_driver(0)->get_name());
+ audio_driver = GLOBAL_DEF_RST_NOVAL("audio/driver", AudioDriverManager::get_driver(0)->get_name());
}
for (int i = 0; i < AudioDriverManager::get_driver_count(); i++) {
diff --git a/main/tests/SCsub b/main/tests/SCsub
deleted file mode 100644
index cb1d35b12f..0000000000
--- a/main/tests/SCsub
+++ /dev/null
@@ -1,9 +0,0 @@
-#!/usr/bin/python
-
-Import("env")
-
-env.tests_sources = []
-env.add_source_files(env.tests_sources, "*.cpp")
-
-lib = env.add_library("tests", env.tests_sources)
-env.Prepend(LIBS=[lib])
diff --git a/methods.py b/methods.py
index 65a460f63d..fe93db4797 100644
--- a/methods.py
+++ b/methods.py
@@ -549,15 +549,18 @@ def generate_vs_project(env, num_jobs):
# in a backslash, so we need to remove this, lest it escape the
# last double quote off, confusing MSBuild
env["MSVSBUILDCOM"] = build_commandline(
- "scons --directory=\"$(ProjectDir.TrimEnd('\\'))\" platform=windows progress=no target=$(Configuration) tools=!tools! -j"
+ "scons --directory=\"$(ProjectDir.TrimEnd('\\'))\" platform=windows progress=no target=$(Configuration)"
+ " tools=!tools! -j"
+ str(num_jobs)
)
env["MSVSREBUILDCOM"] = build_commandline(
- "scons --directory=\"$(ProjectDir.TrimEnd('\\'))\" platform=windows progress=no target=$(Configuration) tools=!tools! vsproj=yes -j"
+ "scons --directory=\"$(ProjectDir.TrimEnd('\\'))\" platform=windows progress=no target=$(Configuration)"
+ " tools=!tools! vsproj=yes -j"
+ str(num_jobs)
)
env["MSVSCLEANCOM"] = build_commandline(
- "scons --directory=\"$(ProjectDir.TrimEnd('\\'))\" --clean platform=windows progress=no target=$(Configuration) tools=!tools! -j"
+ "scons --directory=\"$(ProjectDir.TrimEnd('\\'))\" --clean platform=windows progress=no"
+ " target=$(Configuration) tools=!tools! -j"
+ str(num_jobs)
)
diff --git a/misc/dist/ios_xcode/godot_ios.xcodeproj/project.pbxproj b/misc/dist/ios_xcode/godot_ios.xcodeproj/project.pbxproj
index 54dda2563d..9a1143a21a 100644
--- a/misc/dist/ios_xcode/godot_ios.xcodeproj/project.pbxproj
+++ b/misc/dist/ios_xcode/godot_ios.xcodeproj/project.pbxproj
@@ -12,6 +12,8 @@
$modules_buildfile
1FF8DBB11FBA9DE1009DE660 /* dummy.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 1FF8DBB01FBA9DE1009DE660 /* dummy.cpp */; };
D07CD44E1C5D589C00B7FB28 /* Images.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = D07CD44D1C5D589C00B7FB28 /* Images.xcassets */; };
+ 9039D3BE24C093AC0020482C /* MoltenVK.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 9039D3BD24C093AC0020482C /* MoltenVK.a */; };
+ 9073252C24BF980B0063BCD4 /* vulkan in Resources */ = {isa = PBXBuildFile; fileRef = 905036DC24BF932E00301046 /* vulkan */; };
D0BCFE4618AEBDA2004A7AAE /* InfoPlist.strings in Resources */ = {isa = PBXBuildFile; fileRef = D0BCFE4418AEBDA2004A7AAE /* InfoPlist.strings */; };
D0BCFE7818AEBFEB004A7AAE /* $binary.pck in Resources */ = {isa = PBXBuildFile; fileRef = D0BCFE7718AEBFEB004A7AAE /* $binary.pck */; };
$pbx_launch_screen_build_reference
@@ -37,6 +39,8 @@
$modules_fileref
1FF4C1881F584E6300A41E41 /* $binary.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = "$binary.entitlements"; sourceTree = "<group>"; };
1FF8DBB01FBA9DE1009DE660 /* dummy.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = dummy.cpp; sourceTree = "<group>"; };
+ 9039D3BD24C093AC0020482C /* MoltenVK.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; name = MoltenVK; path = MoltenVK.a; sourceTree = "<group>"; };
+ 905036DC24BF932E00301046 /* vulkan */ = {isa = PBXFileReference; lastKnownFileType = folder; name = vulkan; path = "$binary/vulkan"; sourceTree = "<group>"; };
D07CD44D1C5D589C00B7FB28 /* Images.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Images.xcassets; sourceTree = "<group>"; };
D0BCFE3418AEBDA2004A7AAE /* $binary.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = "$binary.app"; sourceTree = BUILT_PRODUCTS_DIR; };
D0BCFE4318AEBDA2004A7AAE /* $binary-Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = "$binary-Info.plist"; sourceTree = "<group>"; };
@@ -52,6 +56,7 @@
isa = PBXFrameworksBuildPhase;
buildActionMask = 2147483647;
files = (
+ 9039D3BE24C093AC0020482C /* MoltenVK.a in Frameworks */,
DEADBEEF2F582BE20003B888 /* $binary.a */,
$modules_buildphase
$additional_pbx_frameworks_build
@@ -64,6 +69,7 @@
D0BCFE2B18AEBDA2004A7AAE = {
isa = PBXGroup;
children = (
+ 905036DC24BF932E00301046 /* vulkan */,
1F1575711F582BE20003B888 /* dylibs */,
D0BCFE7718AEBFEB004A7AAE /* $binary.pck */,
D0BCFE4118AEBDA2004A7AAE /* $binary */,
@@ -84,6 +90,7 @@
D0BCFE3618AEBDA2004A7AAE /* Frameworks */ = {
isa = PBXGroup;
children = (
+ 9039D3BD24C093AC0020482C /* MoltenVK.a */,
DEADBEEF1F582BE20003B888 /* $binary.a */,
$modules_buildgrp
$additional_pbx_frameworks_refs
@@ -248,6 +255,7 @@
isa = PBXResourcesBuildPhase;
buildActionMask = 2147483647;
files = (
+ 9073252C24BF980B0063BCD4 /* vulkan in Resources */,
D07CD44E1C5D589C00B7FB28 /* Images.xcassets in Resources */,
D0BCFE7818AEBFEB004A7AAE /* $binary.pck in Resources */,
$pbx_launch_screen_build_phase
@@ -319,7 +327,7 @@
GCC_WARN_UNINITIALIZED_AUTOS = YES;
GCC_WARN_UNUSED_FUNCTION = YES;
GCC_WARN_UNUSED_VARIABLE = YES;
- IPHONEOS_DEPLOYMENT_TARGET = 9.0;
+ IPHONEOS_DEPLOYMENT_TARGET = 11.0;
OTHER_LDFLAGS = "$linker_flags";
SDKROOT = iphoneos;
TARGETED_DEVICE_FAMILY = "1,2";
@@ -358,7 +366,7 @@
GCC_WARN_UNINITIALIZED_AUTOS = YES;
GCC_WARN_UNUSED_FUNCTION = YES;
GCC_WARN_UNUSED_VARIABLE = YES;
- IPHONEOS_DEPLOYMENT_TARGET = 9.0;
+ IPHONEOS_DEPLOYMENT_TARGET = 11.0;
OTHER_LDFLAGS = "$linker_flags";
SDKROOT = iphoneos;
TARGETED_DEVICE_FAMILY = "1,2";
diff --git a/misc/dist/ios_xcode/godot_ios/vulkan/icd.d/MoltenVK_icd.json b/misc/dist/ios_xcode/godot_ios/vulkan/icd.d/MoltenVK_icd.json
new file mode 100644
index 0000000000..7501cb548c
--- /dev/null
+++ b/misc/dist/ios_xcode/godot_ios/vulkan/icd.d/MoltenVK_icd.json
@@ -0,0 +1,7 @@
+{
+ "file_format_version" : "1.0.0",
+ "ICD": {
+ "library_path": "./../../Frameworks/MoltenVK.framework/MoltenVK",
+ "api_version" : "1.0.0"
+ }
+}
diff --git a/modules/SCsub b/modules/SCsub
index 9155a53eaf..2d774306e4 100644
--- a/modules/SCsub
+++ b/modules/SCsub
@@ -1,10 +1,10 @@
#!/usr/bin/env python
-Import("env")
-
import modules_builders
import os
+Import("env")
+
env_modules = env.Clone()
Export("env_modules")
@@ -12,6 +12,15 @@ Export("env_modules")
# Header with MODULE_*_ENABLED defines.
env.CommandNoCache("modules_enabled.gen.h", Value(env.module_list), modules_builders.generate_modules_enabled)
+# Header to be included in `tests/test_main.cpp` to run module-specific tests.
+if env["tests"]:
+ env.CommandNoCache(
+ "modules_tests.gen.h",
+ Value(env.module_list),
+ Action(modules_builders.generate_modules_tests, "Generating modules tests header."),
+ )
+ env.AlwaysBuild("modules_tests.gen.h")
+
vs_sources = []
# libmodule_<name>.a for each active module.
for name, path in env.module_list.items():
diff --git a/modules/arkit/arkit_interface.h b/modules/arkit/arkit_interface.h
index 1044f3cf6f..5a2c50e213 100644
--- a/modules/arkit/arkit_interface.h
+++ b/modules/arkit/arkit_interface.h
@@ -60,8 +60,8 @@ private:
float eye_height, z_near, z_far;
Ref<CameraFeed> feed;
- int image_width[2];
- int image_height[2];
+ size_t image_width[2];
+ size_t image_height[2];
Vector<uint8_t> img_data[2];
struct anchor_map {
@@ -84,9 +84,9 @@ public:
void start_session();
void stop_session();
- bool get_anchor_detection_is_enabled() const;
- void set_anchor_detection_is_enabled(bool p_enable);
- virtual int get_camera_feed_id();
+ bool get_anchor_detection_is_enabled() const override;
+ void set_anchor_detection_is_enabled(bool p_enable) override;
+ virtual int get_camera_feed_id() override;
bool get_light_estimation_is_enabled() const;
void set_light_estimation_is_enabled(bool p_enable);
@@ -97,22 +97,22 @@ public:
/* while Godot has its own raycast logic this takes ARKits camera into account and hits on any ARAnchor */
Array raycast(Vector2 p_screen_coord);
- void notification(int p_what);
+ virtual void notification(int p_what) override;
- virtual StringName get_name() const;
- virtual int get_capabilities() const;
+ virtual StringName get_name() const override;
+ virtual int get_capabilities() const override;
- virtual bool is_initialized() const;
- virtual bool initialize();
- virtual void uninitialize();
+ virtual bool is_initialized() const override;
+ virtual bool initialize() override;
+ virtual void uninitialize() override;
- virtual Size2 get_render_targetsize();
- virtual bool is_stereo();
- virtual Transform get_transform_for_eye(XRInterface::Eyes p_eye, const Transform &p_cam_transform);
- virtual CameraMatrix get_projection_for_eye(XRInterface::Eyes p_eye, real_t p_aspect, real_t p_z_near, real_t p_z_far);
- virtual void commit_for_eye(XRInterface::Eyes p_eye, RID p_render_target, const Rect2 &p_screen_rect);
+ virtual Size2 get_render_targetsize() override;
+ virtual bool is_stereo() override;
+ virtual Transform get_transform_for_eye(XRInterface::Eyes p_eye, const Transform &p_cam_transform) override;
+ virtual CameraMatrix get_projection_for_eye(XRInterface::Eyes p_eye, real_t p_aspect, real_t p_z_near, real_t p_z_far) override;
+ virtual void commit_for_eye(XRInterface::Eyes p_eye, RID p_render_target, const Rect2 &p_screen_rect) override;
- virtual void process();
+ virtual void process() override;
// called by delegate (void * because C++ and Obj-C don't always mix, should really change all platform/iphone/*.cpp files to .mm)
void _add_or_update_anchor(void *p_anchor);
diff --git a/modules/arkit/arkit_interface.mm b/modules/arkit/arkit_interface.mm
index 7de824815a..3fb2cc933d 100644
--- a/modules/arkit/arkit_interface.mm
+++ b/modules/arkit/arkit_interface.mm
@@ -42,7 +42,9 @@
#include "arkit_session_delegate.h"
// just a dirty workaround for now, declare these as globals. I'll probably encapsulate ARSession and associated logic into an mm object and change ARKitInterface to a normal cpp object that consumes it.
+API_AVAILABLE(ios(11.0))
ARSession *ar_session;
+
ARKitSessionDelegate *ar_delegate;
NSTimeInterval last_timestamp;
@@ -55,22 +57,28 @@ void ARKitInterface::start_session() {
if (initialized) {
print_line("Starting ARKit session");
- Class ARWorldTrackingConfigurationClass = NSClassFromString(@"ARWorldTrackingConfiguration");
- ARWorldTrackingConfiguration *configuration = [ARWorldTrackingConfigurationClass new];
+ if (@available(iOS 11, *)) {
+ Class ARWorldTrackingConfigurationClass = NSClassFromString(@"ARWorldTrackingConfiguration");
+ ARWorldTrackingConfiguration *configuration = [ARWorldTrackingConfigurationClass new];
- configuration.lightEstimationEnabled = light_estimation_is_enabled;
- if (plane_detection_is_enabled) {
- configuration.planeDetection = ARPlaneDetectionVertical | ARPlaneDetectionHorizontal;
- } else {
- configuration.planeDetection = 0;
- }
+ configuration.lightEstimationEnabled = light_estimation_is_enabled;
+ if (plane_detection_is_enabled) {
+ if (@available(iOS 11.3, *)) {
+ configuration.planeDetection = ARPlaneDetectionVertical | ARPlaneDetectionHorizontal;
+ } else {
+ configuration.planeDetection = ARPlaneDetectionHorizontal;
+ }
+ } else {
+ configuration.planeDetection = 0;
+ }
- // make sure our camera is on
- if (feed.is_valid()) {
- feed->set_active(true);
- }
+ // make sure our camera is on
+ if (feed.is_valid()) {
+ feed->set_active(true);
+ }
- [ar_session runWithConfiguration:configuration];
+ [ar_session runWithConfiguration:configuration];
+ }
}
}
@@ -84,7 +92,9 @@ void ARKitInterface::stop_session() {
feed->set_active(false);
}
- [ar_session pause];
+ if (@available(iOS 11.0, *)) {
+ [ar_session pause];
+ }
}
}
@@ -92,12 +102,12 @@ void ARKitInterface::notification(int p_what) {
// TODO, this is not being called, need to find out why, possibly because this is not a node.
// in that case we need to find a way to get these notifications!
switch (p_what) {
- case MainLoop::NOTIFICATION_WM_FOCUS_IN: {
+ case DisplayServer::WINDOW_EVENT_FOCUS_IN: {
print_line("Focus in");
start_session();
}; break;
- case MainLoop::NOTIFICATION_WM_FOCUS_OUT: {
+ case DisplayServer::WINDOW_EVENT_FOCUS_OUT: {
print_line("Focus out");
stop_session();
@@ -162,37 +172,42 @@ int ARKitInterface::get_capabilities() const {
}
Array ARKitInterface::raycast(Vector2 p_screen_coord) {
- Array arr;
- Size2 screen_size = OS::get_singleton()->get_window_size();
- CGPoint point;
- point.x = p_screen_coord.x / screen_size.x;
- point.y = p_screen_coord.y / screen_size.y;
-
- ///@TODO maybe give more options here, for now we're taking just ARAchors into account that were found during plane detection keeping their size into account
- NSArray<ARHitTestResult *> *results = [ar_session.currentFrame hittest:point types:ARHitTestResultTypeExistingPlaneUsingExtent];
-
- for (ARHitTestResult *result in results) {
- Transform transform;
-
- matrix_float4x4 m44 = result.worldTransform;
- transform.basis.elements[0].x = m44.columns[0][0];
- transform.basis.elements[1].x = m44.columns[0][1];
- transform.basis.elements[2].x = m44.columns[0][2];
- transform.basis.elements[0].y = m44.columns[1][0];
- transform.basis.elements[1].y = m44.columns[1][1];
- transform.basis.elements[2].y = m44.columns[1][2];
- transform.basis.elements[0].z = m44.columns[2][0];
- transform.basis.elements[1].z = m44.columns[2][1];
- transform.basis.elements[2].z = m44.columns[2][2];
- transform.origin.x = m44.columns[3][0];
- transform.origin.y = m44.columns[3][1];
- transform.origin.z = m44.columns[3][2];
-
- /* important, NOT scaled to world_scale !! */
- arr.push_back(transform);
- }
+ if (@available(iOS 11, *)) {
+ Array arr;
+ Size2 screen_size = DisplayServer::get_singleton()->screen_get_size();
+ CGPoint point;
+ point.x = p_screen_coord.x / screen_size.x;
+ point.y = p_screen_coord.y / screen_size.y;
+
+ ///@TODO maybe give more options here, for now we're taking just ARAchors into account that were found during plane detection keeping their size into account
+
+ NSArray<ARHitTestResult *> *results = [ar_session.currentFrame hitTest:point types:ARHitTestResultTypeExistingPlaneUsingExtent];
+
+ for (ARHitTestResult *result in results) {
+ Transform transform;
+
+ matrix_float4x4 m44 = result.worldTransform;
+ transform.basis.elements[0].x = m44.columns[0][0];
+ transform.basis.elements[1].x = m44.columns[0][1];
+ transform.basis.elements[2].x = m44.columns[0][2];
+ transform.basis.elements[0].y = m44.columns[1][0];
+ transform.basis.elements[1].y = m44.columns[1][1];
+ transform.basis.elements[2].y = m44.columns[1][2];
+ transform.basis.elements[0].z = m44.columns[2][0];
+ transform.basis.elements[1].z = m44.columns[2][1];
+ transform.basis.elements[2].z = m44.columns[2][2];
+ transform.origin.x = m44.columns[3][0];
+ transform.origin.y = m44.columns[3][1];
+ transform.origin.z = m44.columns[3][2];
+
+ /* important, NOT scaled to world_scale !! */
+ arr.push_back(transform);
+ }
- return arr;
+ return arr;
+ } else {
+ return Array();
+ }
}
void ARKitInterface::_bind_methods() {
@@ -221,51 +236,55 @@ bool ARKitInterface::initialize() {
XRServer *xr_server = XRServer::get_singleton();
ERR_FAIL_NULL_V(xr_server, false);
- if (!initialized) {
- print_line("initializing ARKit");
+ if (@available(iOS 11, *)) {
+ if (!initialized) {
+ print_line("initializing ARKit");
- // create our ar session and delegate
- Class ARSessionClass = NSClassFromString(@"ARSession");
- if (ARSessionClass == Nil) {
- void *arkit_handle = dlopen("/System/Library/Frameworks/ARKit.framework/ARKit", RTLD_NOW);
- if (arkit_handle) {
- ARSessionClass = NSClassFromString(@"ARSession");
- } else {
- print_line("ARKit init failed");
- return false;
+ // create our ar session and delegate
+ Class ARSessionClass = NSClassFromString(@"ARSession");
+ if (ARSessionClass == Nil) {
+ void *arkit_handle = dlopen("/System/Library/Frameworks/ARKit.framework/ARKit", RTLD_NOW);
+ if (arkit_handle) {
+ ARSessionClass = NSClassFromString(@"ARSession");
+ } else {
+ print_line("ARKit init failed");
+ return false;
+ }
}
- }
- ar_session = [ARSessionClass new];
- ar_delegate = [ARKitSessionDelegate new];
- ar_delegate.arkit_interface = this;
- ar_session.delegate = ar_delegate;
+ ar_session = [ARSessionClass new];
+ ar_delegate = [ARKitSessionDelegate new];
+ ar_delegate.arkit_interface = this;
+ ar_session.delegate = ar_delegate;
- // reset our transform
- transform = Transform();
+ // reset our transform
+ transform = Transform();
- // make this our primary interface
- xr_server->set_primary_interface(this);
+ // make this our primary interface
+ xr_server->set_primary_interface(this);
- // make sure we have our feed setup
- if (feed.is_null()) {
- feed.instance();
- feed->set_name("ARKit");
+ // make sure we have our feed setup
+ if (feed.is_null()) {
+ feed.instance();
+ feed->set_name("ARKit");
- CameraServer *cs = CameraServer::get_singleton();
- if (cs != NULL) {
- cs->add_feed(feed);
+ CameraServer *cs = CameraServer::get_singleton();
+ if (cs != NULL) {
+ cs->add_feed(feed);
+ }
}
- }
- feed->set_active(true);
+ feed->set_active(true);
- // yeah!
- initialized = true;
+ // yeah!
+ initialized = true;
- // Start our session...
- start_session();
- }
+ // Start our session...
+ start_session();
+ }
- return true;
+ return true;
+ } else {
+ return false;
+ }
}
void ARKitInterface::uninitialize() {
@@ -286,9 +305,12 @@ void ARKitInterface::uninitialize() {
remove_all_anchors();
- [ar_session release];
+ if (@available(iOS 11.0, *)) {
+ [ar_session release];
+ ar_session = NULL;
+ }
[ar_delegate release];
- ar_session = NULL;
+
ar_delegate = NULL;
initialized = false;
session_was_started = false;
@@ -296,15 +318,15 @@ void ARKitInterface::uninitialize() {
}
Size2 ARKitInterface::get_render_targetsize() {
- _THREAD_SAFE_METHOD_
+ // _THREAD_SAFE_METHOD_
- Size2 target_size = OS::get_singleton()->get_window_size();
+ Size2 target_size = DisplayServer::get_singleton()->screen_get_size();
return target_size;
}
Transform ARKitInterface::get_transform_for_eye(XRInterface::Eyes p_eye, const Transform &p_cam_transform) {
- _THREAD_SAFE_METHOD_
+ // _THREAD_SAFE_METHOD_
Transform transform_for_eye;
@@ -336,7 +358,7 @@ CameraMatrix ARKitInterface::get_projection_for_eye(XRInterface::Eyes p_eye, rea
}
void ARKitInterface::commit_for_eye(XRInterface::Eyes p_eye, RID p_render_target, const Rect2 &p_screen_rect) {
- _THREAD_SAFE_METHOD_
+ // _THREAD_SAFE_METHOD_
// We must have a valid render target
ERR_FAIL_COND(!p_render_target.is_valid());
@@ -345,15 +367,15 @@ void ARKitInterface::commit_for_eye(XRInterface::Eyes p_eye, RID p_render_target
ERR_FAIL_COND(p_screen_rect == Rect2());
// get the size of our screen
- Rect2 screen_rect = p_screen_rect;
+ // Rect2 screen_rect = p_screen_rect;
// screen_rect.position.x += screen_rect.size.x;
// screen_rect.size.x = -screen_rect.size.x;
// screen_rect.position.y += screen_rect.size.y;
// screen_rect.size.y = -screen_rect.size.y;
- VSG::rasterizer->set_current_render_target(RID());
- VSG::rasterizer->blit_render_target_to_screen(p_render_target, screen_rect, 0);
+ // VSG::rasterizer->set_current_render_target(RID());
+ // VSG::rasterizer->blit_render_target_to_screen(p_render_target, screen_rect, 0);
}
XRPositionalTracker *ARKitInterface::get_anchor_for_uuid(const unsigned char *p_uuid) {
@@ -432,7 +454,7 @@ void ARKitInterface::remove_all_anchors() {
}
void ARKitInterface::process() {
- _THREAD_SAFE_METHOD_
+ // _THREAD_SAFE_METHOD_
if (@available(iOS 11.0, *)) {
if (initialized) {
@@ -443,8 +465,16 @@ void ARKitInterface::process() {
last_timestamp = current_frame.timestamp;
// get some info about our screen and orientation
- Size2 screen_size = OS::get_singleton()->get_window_size();
- UIInterfaceOrientation orientation = [[UIApplication sharedApplication] statusBarOrientation];
+ Size2 screen_size = DisplayServer::get_singleton()->screen_get_size();
+ UIInterfaceOrientation orientation = UIInterfaceOrientationUnknown;
+
+ if (@available(iOS 13, *)) {
+ orientation = [UIApplication sharedApplication].delegate.window.windowScene.interfaceOrientation;
+#if !defined(TARGET_OS_SIMULATOR) || !TARGET_OS_SIMULATOR
+ } else {
+ orientation = [[UIApplication sharedApplication] statusBarOrientation];
+#endif
+ }
// Grab our camera image for our backbuffer
CVPixelBufferRef pixelBuffer = current_frame.capturedImage;
@@ -475,27 +505,27 @@ void ARKitInterface::process() {
{
// do Y
- int new_width = CVPixelBufferGetWidthOfPlane(pixelBuffer, 0);
- int new_height = CVPixelBufferGetHeightOfPlane(pixelBuffer, 0);
- int bytes_per_row = CVPixelBufferGetBytesPerRowOfPlane(pixelBuffer, 0);
+ size_t new_width = CVPixelBufferGetWidthOfPlane(pixelBuffer, 0);
+ size_t new_height = CVPixelBufferGetHeightOfPlane(pixelBuffer, 0);
+ size_t bytes_per_row = CVPixelBufferGetBytesPerRowOfPlane(pixelBuffer, 0);
if ((image_width[0] != new_width) || (image_height[0] != new_height)) {
printf("- Camera padding l:%lu r:%lu t:%lu b:%lu\n", extraLeft, extraRight, extraTop, extraBottom);
- printf("- Camera Y plane size: %i, %i - %i\n", new_width, new_height, bytes_per_row);
+ printf("- Camera Y plane size: %lu, %lu - %lu\n", new_width, new_height, bytes_per_row);
image_width[0] = new_width;
image_height[0] = new_height;
img_data[0].resize(new_width * new_height);
}
- uint8_t *w = img_data[0].write();
+ uint8_t *w = img_data[0].ptrw();
if (new_width == bytes_per_row) {
- memcpy(w.ptr(), dataY, new_width * new_height);
+ memcpy(w, dataY, new_width * new_height);
} else {
- int offset_a = 0;
- int offset_b = extraLeft + (extraTop * bytes_per_row);
- for (int r = 0; r < new_height; r++) {
- memcpy(w.ptr() + offset_a, dataY + offset_b, new_width);
+ size_t offset_a = 0;
+ size_t offset_b = extraLeft + (extraTop * bytes_per_row);
+ for (size_t r = 0; r < new_height; r++) {
+ memcpy(w + offset_a, dataY + offset_b, new_width);
offset_a += new_width;
offset_b += bytes_per_row;
}
@@ -507,26 +537,26 @@ void ARKitInterface::process() {
{
// do CbCr
- int new_width = CVPixelBufferGetWidthOfPlane(pixelBuffer, 1);
- int new_height = CVPixelBufferGetHeightOfPlane(pixelBuffer, 1);
- int bytes_per_row = CVPixelBufferGetBytesPerRowOfPlane(pixelBuffer, 0);
+ size_t new_width = CVPixelBufferGetWidthOfPlane(pixelBuffer, 1);
+ size_t new_height = CVPixelBufferGetHeightOfPlane(pixelBuffer, 1);
+ size_t bytes_per_row = CVPixelBufferGetBytesPerRowOfPlane(pixelBuffer, 0);
if ((image_width[1] != new_width) || (image_height[1] != new_height)) {
- printf("- Camera CbCr plane size: %i, %i - %i\n", new_width, new_height, bytes_per_row);
+ printf("- Camera CbCr plane size: %lu, %lu - %lu\n", new_width, new_height, bytes_per_row);
image_width[1] = new_width;
image_height[1] = new_height;
img_data[1].resize(2 * new_width * new_height);
}
- uint8_t *w = img_data[1].write();
+ uint8_t *w = img_data[1].ptrw();
if ((2 * new_width) == bytes_per_row) {
- memcpy(w.ptr(), dataCbCr, 2 * new_width * new_height);
+ memcpy(w, dataCbCr, 2 * new_width * new_height);
} else {
- int offset_a = 0;
- int offset_b = extraLeft + (extraTop * bytes_per_row);
- for (int r = 0; r < new_height; r++) {
- memcpy(w.ptr() + offset_a, dataCbCr + offset_b, 2 * new_width);
+ size_t offset_a = 0;
+ size_t offset_b = extraLeft + (extraTop * bytes_per_row);
+ for (size_t r = 0; r < new_height; r++) {
+ memcpy(w + offset_a, dataCbCr + offset_b, 2 * new_width);
offset_a += 2 * new_width;
offset_b += bytes_per_row;
}
@@ -658,69 +688,78 @@ void ARKitInterface::process() {
}
void ARKitInterface::_add_or_update_anchor(void *p_anchor) {
- _THREAD_SAFE_METHOD_
-
- ARAnchor *anchor = (ARAnchor *)p_anchor;
-
- unsigned char uuid[16];
- [anchor.identifier getUUIDBytes:uuid];
-
- XRPositionalTracker *tracker = get_anchor_for_uuid(uuid);
- if (tracker != NULL) {
- // lets update our mesh! (using Arjens code as is for now)
- // we should also probably limit how often we do this...
+ // _THREAD_SAFE_METHOD_
- // can we safely cast this?
- ARPlaneAnchor *planeAnchor = (ARPlaneAnchor *)anchor;
-
- if (planeAnchor.geometry.triangleCount > 0) {
- Ref<SurfaceTool> surftool;
- surftool.instance();
- surftool->begin(Mesh::PRIMITIVE_TRIANGLES);
+ if (@available(iOS 11.0, *)) {
+ ARAnchor *anchor = (ARAnchor *)p_anchor;
+
+ unsigned char uuid[16];
+ [anchor.identifier getUUIDBytes:uuid];
+
+ XRPositionalTracker *tracker = get_anchor_for_uuid(uuid);
+ if (tracker != NULL) {
+ // lets update our mesh! (using Arjens code as is for now)
+ // we should also probably limit how often we do this...
+
+ // can we safely cast this?
+ ARPlaneAnchor *planeAnchor = (ARPlaneAnchor *)anchor;
+
+ if (@available(iOS 11.3, *)) {
+ if (planeAnchor.geometry.triangleCount > 0) {
+ Ref<SurfaceTool> surftool;
+ surftool.instance();
+ surftool->begin(Mesh::PRIMITIVE_TRIANGLES);
+
+ for (int j = planeAnchor.geometry.triangleCount * 3 - 1; j >= 0; j--) {
+ int16_t index = planeAnchor.geometry.triangleIndices[j];
+ simd_float3 vrtx = planeAnchor.geometry.vertices[index];
+ simd_float2 textcoord = planeAnchor.geometry.textureCoordinates[index];
+ surftool->add_uv(Vector2(textcoord[0], textcoord[1]));
+ surftool->add_color(Color(0.8, 0.8, 0.8));
+ surftool->add_vertex(Vector3(vrtx[0], vrtx[1], vrtx[2]));
+ }
- for (int j = planeAnchor.geometry.triangleCount * 3 - 1; j >= 0; j--) {
- int16_t index = planeAnchor.geometry.triangleIndices[j];
- simd_float3 vrtx = planeAnchor.geometry.vertices[index];
- simd_float2 textcoord = planeAnchor.geometry.textureCoordinates[index];
- surftool->add_uv(Vector2(textcoord[0], textcoord[1]));
- surftool->add_color(Color(0.8, 0.8, 0.8));
- surftool->add_vertex(Vector3(vrtx[0], vrtx[1], vrtx[2]));
+ surftool->generate_normals();
+ tracker->set_mesh(surftool->commit());
+ } else {
+ Ref<Mesh> nomesh;
+ tracker->set_mesh(nomesh);
+ }
+ } else {
+ Ref<Mesh> nomesh;
+ tracker->set_mesh(nomesh);
}
- surftool->generate_normals();
- tracker->set_mesh(surftool->commit());
- } else {
- Ref<Mesh> nomesh;
- tracker->set_mesh(nomesh);
+ // Note, this also contains a scale factor which gives us an idea of the size of the anchor
+ // We may extract that in our XRAnchor class
+ Basis b;
+ matrix_float4x4 m44 = anchor.transform;
+ b.elements[0].x = m44.columns[0][0];
+ b.elements[1].x = m44.columns[0][1];
+ b.elements[2].x = m44.columns[0][2];
+ b.elements[0].y = m44.columns[1][0];
+ b.elements[1].y = m44.columns[1][1];
+ b.elements[2].y = m44.columns[1][2];
+ b.elements[0].z = m44.columns[2][0];
+ b.elements[1].z = m44.columns[2][1];
+ b.elements[2].z = m44.columns[2][2];
+ tracker->set_orientation(b);
+ tracker->set_rw_position(Vector3(m44.columns[3][0], m44.columns[3][1], m44.columns[3][2]));
}
-
- // Note, this also contains a scale factor which gives us an idea of the size of the anchor
- // We may extract that in our XRAnchor class
- Basis b;
- matrix_float4x4 m44 = anchor.transform;
- b.elements[0].x = m44.columns[0][0];
- b.elements[1].x = m44.columns[0][1];
- b.elements[2].x = m44.columns[0][2];
- b.elements[0].y = m44.columns[1][0];
- b.elements[1].y = m44.columns[1][1];
- b.elements[2].y = m44.columns[1][2];
- b.elements[0].z = m44.columns[2][0];
- b.elements[1].z = m44.columns[2][1];
- b.elements[2].z = m44.columns[2][2];
- tracker->set_orientation(b);
- tracker->set_rw_position(Vector3(m44.columns[3][0], m44.columns[3][1], m44.columns[3][2]));
}
}
void ARKitInterface::_remove_anchor(void *p_anchor) {
- _THREAD_SAFE_METHOD_
+ // _THREAD_SAFE_METHOD_
- ARAnchor *anchor = (ARAnchor *)p_anchor;
+ if (@available(iOS 11.0, *)) {
+ ARAnchor *anchor = (ARAnchor *)p_anchor;
- unsigned char uuid[16];
- [anchor.identifier getUUIDBytes:uuid];
+ unsigned char uuid[16];
+ [anchor.identifier getUUIDBytes:uuid];
- remove_anchor_for_uuid(uuid);
+ remove_anchor_for_uuid(uuid);
+ }
}
ARKitInterface::ARKitInterface() {
@@ -728,7 +767,9 @@ ARKitInterface::ARKitInterface() {
session_was_started = false;
plane_detection_is_enabled = false;
light_estimation_is_enabled = false;
- ar_session = NULL;
+ if (@available(iOS 11.0, *)) {
+ ar_session = NULL;
+ }
z_near = 0.01;
z_far = 1000.0;
projection.set_perspective(60.0, 1.0, z_near, z_far, false);
diff --git a/modules/arkit/arkit_session_delegate.h b/modules/arkit/arkit_session_delegate.h
index 158b80a60a..df98bf506e 100644
--- a/modules/arkit/arkit_session_delegate.h
+++ b/modules/arkit/arkit_session_delegate.h
@@ -42,9 +42,9 @@ class ARKitInterface;
@property(nonatomic) ARKitInterface *arkit_interface;
-- (void)session:(ARSession *)session didAddAnchors:(NSArray<ARAnchor *> *)anchors;
-- (void)session:(ARSession *)session didRemoveAnchors:(NSArray<ARAnchor *> *)anchors;
-- (void)session:(ARSession *)session didUpdateAnchors:(NSArray<ARAnchor *> *)anchors;
+- (void)session:(ARSession *)session didAddAnchors:(NSArray<ARAnchor *> *)anchors API_AVAILABLE(ios(11.0));
+- (void)session:(ARSession *)session didRemoveAnchors:(NSArray<ARAnchor *> *)anchors API_AVAILABLE(ios(11.0));
+- (void)session:(ARSession *)session didUpdateAnchors:(NSArray<ARAnchor *> *)anchors API_AVAILABLE(ios(11.0));
@end
#endif /* !ARKIT_SESSION_DELEGATE_H */
diff --git a/modules/bullet/shape_bullet.cpp b/modules/bullet/shape_bullet.cpp
index f4550c2024..274493ed17 100644
--- a/modules/bullet/shape_bullet.cpp
+++ b/modules/bullet/shape_bullet.cpp
@@ -504,6 +504,9 @@ void HeightMapShapeBullet::set_data(const Variant &p_data) {
int l_width = d["width"];
int l_depth = d["depth"];
+ ERR_FAIL_COND_MSG(l_width < 2, "Map width must be at least 2.");
+ ERR_FAIL_COND_MSG(l_depth < 2, "Map depth must be at least 2.");
+
// TODO This code will need adjustments if real_t is set to `double`,
// because that precision is unnecessary for a heightmap and Bullet doesn't support it...
diff --git a/modules/camera/camera_ios.mm b/modules/camera/camera_ios.mm
index f01135f251..c10b13b2af 100644
--- a/modules/camera/camera_ios.mm
+++ b/modules/camera/camera_ios.mm
@@ -158,25 +158,31 @@
} else if (dataCbCr == NULL) {
print_line("Couldn't access CbCr pixel buffer data");
} else {
- UIInterfaceOrientation orientation = [[UIApplication sharedApplication] statusBarOrientation];
+ UIInterfaceOrientation orientation = UIInterfaceOrientationUnknown;
+
+ if (@available(iOS 13, *)) {
+ orientation = [UIApplication sharedApplication].delegate.window.windowScene.interfaceOrientation;
+#if !defined(TARGET_OS_SIMULATOR) || !TARGET_OS_SIMULATOR
+ } else {
+ orientation = [[UIApplication sharedApplication] statusBarOrientation];
+#endif
+ }
+
Ref<Image> img[2];
{
// do Y
- int new_width = CVPixelBufferGetWidthOfPlane(pixelBuffer, 0);
- int new_height = CVPixelBufferGetHeightOfPlane(pixelBuffer, 0);
- int _bytes_per_row = CVPixelBufferGetBytesPerRowOfPlane(pixelBuffer, 0);
+ size_t new_width = CVPixelBufferGetWidthOfPlane(pixelBuffer, 0);
+ size_t new_height = CVPixelBufferGetHeightOfPlane(pixelBuffer, 0);
if ((width[0] != new_width) || (height[0] != new_height)) {
- // printf("Camera Y plane %i, %i - %i\n", new_width, new_height, bytes_per_row);
-
width[0] = new_width;
height[0] = new_height;
img_data[0].resize(new_width * new_height);
}
uint8_t *w = img_data[0].ptrw();
- memcpy(w.ptr(), dataY, new_width * new_height);
+ memcpy(w, dataY, new_width * new_height);
img[0].instance();
img[0]->create(new_width, new_height, 0, Image::FORMAT_R8, img_data[0]);
@@ -184,20 +190,17 @@
{
// do CbCr
- int new_width = CVPixelBufferGetWidthOfPlane(pixelBuffer, 1);
- int new_height = CVPixelBufferGetHeightOfPlane(pixelBuffer, 1);
- int bytes_per_row = CVPixelBufferGetBytesPerRowOfPlane(pixelBuffer, 1);
+ size_t new_width = CVPixelBufferGetWidthOfPlane(pixelBuffer, 1);
+ size_t new_height = CVPixelBufferGetHeightOfPlane(pixelBuffer, 1);
if ((width[1] != new_width) || (height[1] != new_height)) {
- // printf("Camera CbCr plane %i, %i - %i\n", new_width, new_height, bytes_per_row);
-
width[1] = new_width;
height[1] = new_height;
img_data[1].resize(2 * new_width * new_height);
}
uint8_t *w = img_data[1].ptrw();
- memcpy(w.ptr(), dataCbCr, 2 * new_width * new_height);
+ memcpy(w, dataCbCr, 2 * new_width * new_height);
///TODO GLES2 doesn't support FORMAT_RG8, need to do some form of conversion
img[1].instance();
@@ -359,41 +362,59 @@ void CameraIOS::update_feeds() {
// this way of doing things is deprecated but still works,
// rewrite to using AVCaptureDeviceDiscoverySession
- AVCaptureDeviceDiscoverySession *session = [AVCaptureDeviceDiscoverySession discoverySessionWithDeviceTypes:[NSArray arrayWithObjects:AVCaptureDeviceTypeBuiltInTelephotoCamera, AVCaptureDeviceTypeBuiltInDualCamera, AVCaptureDeviceTypeBuiltInTrueDepthCamera, AVCaptureDeviceTypeBuiltInWideAngleCamera, nil] mediaType:AVMediaTypeVideo position:AVCaptureDevicePositionUnspecified];
+ NSMutableArray *deviceTypes = [NSMutableArray array];
- // remove devices that are gone..
- for (int i = feeds.size() - 1; i >= 0; i--) {
- Ref<CameraFeedIOS> feed(feeds[i]);
+ if (@available(iOS 10, *)) {
+ [deviceTypes addObject:AVCaptureDeviceTypeBuiltInWideAngleCamera];
+ [deviceTypes addObject:AVCaptureDeviceTypeBuiltInTelephotoCamera];
- if (feed.is_null()) {
- // feed not managed by us
- } else if (![session.devices containsObject:feed->get_device()]) {
- // remove it from our array, this will also destroy it ;)
- remove_feed(feed);
- };
- };
+ if (@available(iOS 10.2, *)) {
+ [deviceTypes addObject:AVCaptureDeviceTypeBuiltInDualCamera];
+ }
- // add new devices..
- for (AVCaptureDevice *device in session.devices) {
- bool found = false;
+ if (@available(iOS 11.1, *)) {
+ [deviceTypes addObject:AVCaptureDeviceTypeBuiltInTrueDepthCamera];
+ }
+
+ AVCaptureDeviceDiscoverySession *session = [AVCaptureDeviceDiscoverySession
+ discoverySessionWithDeviceTypes:deviceTypes
+ mediaType:AVMediaTypeVideo
+ position:AVCaptureDevicePositionUnspecified];
- for (int i = 0; i < feeds.size() && !found; i++) {
+ // remove devices that are gone..
+ for (int i = feeds.size() - 1; i >= 0; i--) {
Ref<CameraFeedIOS> feed(feeds[i]);
if (feed.is_null()) {
// feed not managed by us
- } else if (feed->get_device() == device) {
- found = true;
+ } else if (![session.devices containsObject:feed->get_device()]) {
+ // remove it from our array, this will also destroy it ;)
+ remove_feed(feed);
};
};
- if (!found) {
- Ref<CameraFeedIOS> newfeed;
- newfeed.instance();
- newfeed->set_device(device);
- add_feed(newfeed);
+ // add new devices..
+ for (AVCaptureDevice *device in session.devices) {
+ bool found = false;
+
+ for (int i = 0; i < feeds.size() && !found; i++) {
+ Ref<CameraFeedIOS> feed(feeds[i]);
+
+ if (feed.is_null()) {
+ // feed not managed by us
+ } else if (feed->get_device() == device) {
+ found = true;
+ };
+ };
+
+ if (!found) {
+ Ref<CameraFeedIOS> newfeed;
+ newfeed.instance();
+ newfeed->set_device(device);
+ add_feed(newfeed);
+ };
};
- };
+ }
};
CameraIOS::CameraIOS() {
diff --git a/modules/gdnative/gdnative_builders.py b/modules/gdnative/gdnative_builders.py
index a6f8afb85b..28e4957b2f 100644
--- a/modules/gdnative/gdnative_builders.py
+++ b/modules/gdnative/gdnative_builders.py
@@ -74,7 +74,7 @@ def _build_gdnative_api_struct_header(api):
ret_val += [
"typedef struct godot_gdnative_core_"
- + ("{0}_{1}".format(core["version"]["major"], core["version"]["minor"]))
+ + "{0}_{1}".format(core["version"]["major"], core["version"]["minor"])
+ "_api_struct {",
"\tunsigned int type;",
"\tgodot_gdnative_api_version version;",
@@ -185,7 +185,7 @@ def _build_gdnative_api_struct_source(api):
ret_val += [
"extern const godot_gdnative_core_"
- + ("{0}_{1}_api_struct api_{0}_{1}".format(core["version"]["major"], core["version"]["minor"]))
+ + "{0}_{1}_api_struct api_{0}_{1}".format(core["version"]["major"], core["version"]["minor"])
+ " = {",
"\tGDNATIVE_" + core["type"] + ",",
"\t{" + str(core["version"]["major"]) + ", " + str(core["version"]["minor"]) + "},",
diff --git a/modules/gdnative/nativescript/nativescript.cpp b/modules/gdnative/nativescript/nativescript.cpp
index 94aa2125c2..632f4e5fee 100644
--- a/modules/gdnative/nativescript/nativescript.cpp
+++ b/modules/gdnative/nativescript/nativescript.cpp
@@ -991,7 +991,8 @@ void NativeScriptInstance::notification(int p_notification) {
Variant value = p_notification;
const Variant *args[1] = { &value };
- call_multilevel("_notification", args, 1);
+ Callable::CallError error;
+ call("_notification", args, 1, error);
}
String NativeScriptInstance::to_string(bool *r_valid) {
@@ -1087,31 +1088,6 @@ ScriptLanguage *NativeScriptInstance::get_language() {
return NativeScriptLanguage::get_singleton();
}
-void NativeScriptInstance::call_multilevel(const StringName &p_method, const Variant **p_args, int p_argcount) {
- NativeScriptDesc *script_data = GET_SCRIPT_DESC();
-
- while (script_data) {
- Map<StringName, NativeScriptDesc::Method>::Element *E = script_data->methods.find(p_method);
- if (E) {
- godot_variant res = E->get().method.method((godot_object *)owner,
- E->get().method.method_data,
- userdata,
- p_argcount,
- (godot_variant **)p_args);
- godot_variant_destroy(&res);
- }
- script_data = script_data->base_data;
- }
-}
-
-void NativeScriptInstance::call_multilevel_reversed(const StringName &p_method, const Variant **p_args, int p_argcount) {
- NativeScriptDesc *script_data = GET_SCRIPT_DESC();
-
- if (script_data) {
- _ml_call_reversed(script_data, p_method, p_args, p_argcount);
- }
-}
-
NativeScriptInstance::~NativeScriptInstance() {
NativeScriptDesc *script_data = GET_SCRIPT_DESC();
diff --git a/modules/gdnative/nativescript/nativescript.h b/modules/gdnative/nativescript/nativescript.h
index e709ce2337..145bf7dcb6 100644
--- a/modules/gdnative/nativescript/nativescript.h
+++ b/modules/gdnative/nativescript/nativescript.h
@@ -231,9 +231,6 @@ public:
virtual ScriptLanguage *get_language();
- virtual void call_multilevel(const StringName &p_method, const Variant **p_args, int p_argcount);
- virtual void call_multilevel_reversed(const StringName &p_method, const Variant **p_args, int p_argcount);
-
virtual void refcount_incremented();
virtual bool refcount_decremented();
diff --git a/modules/gdnative/pluginscript/pluginscript_instance.h b/modules/gdnative/pluginscript/pluginscript_instance.h
index 6309b6fde3..690d1a0432 100644
--- a/modules/gdnative/pluginscript/pluginscript_instance.h
+++ b/modules/gdnative/pluginscript/pluginscript_instance.h
@@ -62,12 +62,6 @@ public:
virtual Variant call(const StringName &p_method, const Variant **p_args, int p_argcount, Callable::CallError &r_error);
- // Rely on default implementations provided by ScriptInstance for the moment.
- // Note that multilevel call could be removed in 3.0 release, so stay tuned
- // (see https://godotengine.org/qa/9244/can-override-the-_ready-and-_process-functions-child-classes)
- //virtual void call_multilevel(const StringName& p_method,const Variant** p_args,int p_argcount);
- //virtual void call_multilevel_reversed(const StringName& p_method,const Variant** p_args,int p_argcount);
-
virtual void notification(int p_notification);
virtual Ref<Script> get_script() const;
diff --git a/modules/gdscript/doc_classes/@GDScript.xml b/modules/gdscript/doc_classes/@GDScript.xml
index 36de66ea52..d8825ecc9a 100644
--- a/modules/gdscript/doc_classes/@GDScript.xml
+++ b/modules/gdscript/doc_classes/@GDScript.xml
@@ -318,7 +318,7 @@
</argument>
<description>
The natural exponential function. It raises the mathematical constant [b]e[/b] to the power of [code]s[/code] and returns it.
- [b]e[/b] has an approximate value of 2.71828.
+ [b]e[/b] has an approximate value of 2.71828, and can be obtained with [code]exp(1)[/code].
For exponents to other bases use the method [method pow].
[codeblock]
a = exp(2) # Approximately 7.39
@@ -505,6 +505,8 @@
</argument>
<description>
Returns [code]true[/code] if [code]a[/code] and [code]b[/code] are approximately equal to each other.
+ Here, approximately equal means that [code]a[/code] and [code]b[/code] are within a small internal epsilon of each other, which scales with the magnitude of the numbers.
+ Infinity values of the same sign are considered equal.
</description>
</method>
<method name="is_inf">
@@ -641,6 +643,7 @@
[codeblock]
log(10) # Returns 2.302585
[/codeblock]
+ [b]Note:[/b] The logarithm of [code]0[/code] returns [code]-inf[/code], while negative values return [code]-nan[/code].
</description>
</method>
<method name="max">
@@ -686,7 +689,9 @@
Moves [code]from[/code] toward [code]to[/code] by the [code]delta[/code] value.
Use a negative [code]delta[/code] value to move away.
[codeblock]
+ move_toward(5, 10, 4) # Returns 9
move_toward(10, 5, 4) # Returns 6
+ move_toward(10, 5, -1.5) # Returns 11.5
[/codeblock]
</description>
</method>
@@ -696,12 +701,17 @@
<argument index="0" name="value" type="int">
</argument>
<description>
- Returns the nearest larger power of 2 for integer [code]value[/code].
+ Returns the nearest equal or larger power of 2 for integer [code]value[/code].
+ In other words, returns the smallest value [code]a[/code] where [code]a = pow(2, n)[/code] such that [code]value &lt;= a[/code] for some non-negative integer [code]n[/code].
[codeblock]
nearest_po2(3) # Returns 4
nearest_po2(4) # Returns 4
nearest_po2(5) # Returns 8
+
+ nearest_po2(0) # Returns 0 (this may not be what you expect)
+ nearest_po2(-1) # Returns 0 (this may not be what you expect)
[/codeblock]
+ [b]WARNING:[/b] Due to the way it is implemented, this function returns [code]0[/code] rather than [code]1[/code] for non-positive values of [code]value[/code] (in reality, 1 is the smallest integer power of 2).
</description>
</method>
<method name="ord">
@@ -1093,12 +1103,15 @@
</argument>
<argument index="1" name="to" type="float">
</argument>
- <argument index="2" name="weight" type="float">
+ <argument index="2" name="s" type="float">
</argument>
<description>
- Returns a number smoothly interpolated between the [code]from[/code] and [code]to[/code], based on the [code]weight[/code]. Similar to [method lerp], but interpolates faster at the beginning and slower at the end.
+ Returns the result of smoothly interpolating the value of [code]s[/code] between [code]0[/code] and [code]1[/code], based on the where [code]s[/code] lies with respect to the edges [code]from[/code] and [code]to[/code].
+ The return value is [code]0[/code] if [code]s &lt;= from[/code], and [code]1[/code] if [code]s &gt;= to[/code]. If [code]s[/code] lies between [code]from[/code] and [code]to[/code], the returned value follows an S-shaped curve that maps [code]s[/code] between [code]0[/code] and [code]1[/code].
+ This S-shaped curve is the cubic Hermite interpolator, given by [code]f(s) = 3*s^2 - 2*s^3[/code].
[codeblock]
- smoothstep(0, 2, 0.5) # Returns 0.15
+ smoothstep(0, 2, -5.0) # Returns 0.0
+ smoothstep(0, 2, 0.5) # Returns 0.15625
smoothstep(0, 2, 1.0) # Returns 0.5
smoothstep(0, 2, 2.0) # Returns 1.0
[/codeblock]
@@ -1114,7 +1127,7 @@
[codeblock]
sqrt(9) # Returns 3
[/codeblock]
- If you need negative inputs, use [code]System.Numerics.Complex[/code] in C#.
+ [b]Note:[/b]Negative values of [code]s[/code] return NaN. If you need negative inputs, use [code]System.Numerics.Complex[/code] in C#.
</description>
</method>
<method name="step_decimals">
diff --git a/modules/gdscript/gdscript.cpp b/modules/gdscript/gdscript.cpp
index 40ef0aeec6..9170255c02 100644
--- a/modules/gdscript/gdscript.cpp
+++ b/modules/gdscript/gdscript.cpp
@@ -1309,39 +1309,6 @@ Variant GDScriptInstance::call(const StringName &p_method, const Variant **p_arg
return Variant();
}
-void GDScriptInstance::call_multilevel(const StringName &p_method, const Variant **p_args, int p_argcount) {
- GDScript *sptr = script.ptr();
- Callable::CallError ce;
-
- while (sptr) {
- Map<StringName, GDScriptFunction *>::Element *E = sptr->member_functions.find(p_method);
- if (E) {
- E->get()->call(this, p_args, p_argcount, ce);
- return;
- }
- sptr = sptr->_base;
- }
-}
-
-void GDScriptInstance::_ml_call_reversed(GDScript *sptr, const StringName &p_method, const Variant **p_args, int p_argcount) {
- if (sptr->_base) {
- _ml_call_reversed(sptr->_base, p_method, p_args, p_argcount);
- }
-
- Callable::CallError ce;
-
- Map<StringName, GDScriptFunction *>::Element *E = sptr->member_functions.find(p_method);
- if (E) {
- E->get()->call(this, p_args, p_argcount, ce);
- }
-}
-
-void GDScriptInstance::call_multilevel_reversed(const StringName &p_method, const Variant **p_args, int p_argcount) {
- if (script.ptr()) {
- _ml_call_reversed(script.ptr(), p_method, p_args, p_argcount);
- }
-}
-
void GDScriptInstance::notification(int p_notification) {
//notification is not virtual, it gets called at ALL levels just like in C.
Variant value = p_notification;
diff --git a/modules/gdscript/gdscript.h b/modules/gdscript/gdscript.h
index 8236464f15..9906b4014d 100644
--- a/modules/gdscript/gdscript.h
+++ b/modules/gdscript/gdscript.h
@@ -146,7 +146,6 @@ protected:
void _get_property_list(List<PropertyInfo> *p_properties) const;
Variant call(const StringName &p_method, const Variant **p_args, int p_argcount, Callable::CallError &r_error) override;
- //void call_multilevel(const StringName& p_method,const Variant** p_args,int p_argcount);
static void _bind_methods();
@@ -258,8 +257,6 @@ class GDScriptInstance : public ScriptInstance {
SelfList<GDScriptFunctionState>::List pending_func_states;
- void _ml_call_reversed(GDScript *sptr, const StringName &p_method, const Variant **p_args, int p_argcount);
-
public:
virtual Object *get_owner() { return owner; }
@@ -271,8 +268,6 @@ public:
virtual void get_method_list(List<MethodInfo> *p_list) const;
virtual bool has_method(const StringName &p_method) const;
virtual Variant call(const StringName &p_method, const Variant **p_args, int p_argcount, Callable::CallError &r_error);
- virtual void call_multilevel(const StringName &p_method, const Variant **p_args, int p_argcount);
- virtual void call_multilevel_reversed(const StringName &p_method, const Variant **p_args, int p_argcount);
Variant debug_get_member_by_index(int p_idx) const { return members[p_idx]; }
diff --git a/modules/gdscript/gdscript_compiler.cpp b/modules/gdscript/gdscript_compiler.cpp
index 3d37c7f803..e34d87f5cc 100644
--- a/modules/gdscript/gdscript_compiler.cpp
+++ b/modules/gdscript/gdscript_compiler.cpp
@@ -736,7 +736,7 @@ int GDScriptCompiler::_parse_expression(CodeGen &codegen, const GDScriptParser::
} else {
if (callee->type == GDScriptParser::Node::IDENTIFIER) {
// Self function call.
- if (codegen.function_node && codegen.function_node->is_static) {
+ if ((codegen.function_node && codegen.function_node->is_static) || call->function_name == "new") {
ret = (GDScriptFunction::ADDR_TYPE_CLASS << GDScriptFunction::ADDR_BITS);
} else {
ret = (GDScriptFunction::ADDR_TYPE_SELF << GDScriptFunction::ADDR_BITS);
diff --git a/modules/gdscript/gdscript_functions.cpp b/modules/gdscript/gdscript_functions.cpp
index 7f2a62a8e9..fefbf906f0 100644
--- a/modules/gdscript/gdscript_functions.cpp
+++ b/modules/gdscript/gdscript_functions.cpp
@@ -1636,7 +1636,7 @@ MethodInfo GDScriptFunctions::get_info(Function p_func) {
return mi;
} break;
case MATH_SMOOTHSTEP: {
- MethodInfo mi("smoothstep", PropertyInfo(Variant::FLOAT, "from"), PropertyInfo(Variant::FLOAT, "to"), PropertyInfo(Variant::FLOAT, "weight"));
+ MethodInfo mi("smoothstep", PropertyInfo(Variant::FLOAT, "from"), PropertyInfo(Variant::FLOAT, "to"), PropertyInfo(Variant::FLOAT, "s"));
mi.return_val.type = Variant::FLOAT;
return mi;
} break;
diff --git a/modules/modules_builders.py b/modules/modules_builders.py
index e7be6380d1..2243162555 100644
--- a/modules/modules_builders.py
+++ b/modules/modules_builders.py
@@ -12,5 +12,16 @@ def generate_modules_enabled(target, source, env):
f.write("#define %s\n" % ("MODULE_" + module.upper() + "_ENABLED"))
+def generate_modules_tests(target, source, env):
+ import os
+ import glob
+
+ with open(target[0].path, "w") as f:
+ for name, path in env.module_list.items():
+ headers = glob.glob(os.path.join(path, "tests", "*.h"))
+ for h in headers:
+ f.write('#include "%s"\n' % (os.path.normpath(h)))
+
+
if __name__ == "__main__":
subprocess_main(globals())
diff --git a/modules/mono/build_scripts/mono_configure.py b/modules/mono/build_scripts/mono_configure.py
index 80e3b59325..3e771e06f0 100644
--- a/modules/mono/build_scripts/mono_configure.py
+++ b/modules/mono/build_scripts/mono_configure.py
@@ -125,7 +125,8 @@ def configure(env, env_mono):
if not mono_prefix and (os.getenv("MONO32_PREFIX") or os.getenv("MONO64_PREFIX")):
print(
- "WARNING: The environment variables 'MONO32_PREFIX' and 'MONO64_PREFIX' are deprecated; use the 'mono_prefix' SCons parameter instead"
+ "WARNING: The environment variables 'MONO32_PREFIX' and 'MONO64_PREFIX' are deprecated; use the"
+ " 'mono_prefix' SCons parameter instead"
)
# Although we don't support building with tools for any platform where we currently use static AOT,
diff --git a/modules/mono/csharp_script.cpp b/modules/mono/csharp_script.cpp
index 7d3ae31588..bbdec224f0 100644
--- a/modules/mono/csharp_script.cpp
+++ b/modules/mono/csharp_script.cpp
@@ -44,7 +44,6 @@
#ifdef TOOLS_ENABLED
#include "editor/bindings_generator.h"
-#include "editor/csharp_project.h"
#include "editor/editor_node.h"
#include "editor/node_dock.h"
#endif
@@ -897,7 +896,7 @@ void CSharpLanguage::reload_assemblies(bool p_soft_reload) {
// Call OnBeforeSerialize
if (csi->script->script_class->implements_interface(CACHED_CLASS(ISerializationListener))) {
- obj->get_script_instance()->call_multilevel(string_names.on_before_serialize);
+ obj->get_script_instance()->call(string_names.on_before_serialize);
}
// Save instance info
@@ -1133,7 +1132,7 @@ void CSharpLanguage::reload_assemblies(bool p_soft_reload) {
// Call OnAfterDeserialization
if (csi->script->script_class->implements_interface(CACHED_CLASS(ISerializationListener))) {
- obj->get_script_instance()->call_multilevel(string_names.on_after_deserialize);
+ obj->get_script_instance()->call(string_names.on_after_deserialize);
}
}
}
@@ -1866,41 +1865,6 @@ Variant CSharpInstance::call(const StringName &p_method, const Variant **p_args,
return Variant();
}
-void CSharpInstance::call_multilevel(const StringName &p_method, const Variant **p_args, int p_argcount) {
- GD_MONO_SCOPE_THREAD_ATTACH;
-
- if (script.is_valid()) {
- MonoObject *mono_object = get_mono_object();
-
- ERR_FAIL_NULL(mono_object);
-
- _call_multilevel(mono_object, p_method, p_args, p_argcount);
- }
-}
-
-void CSharpInstance::_call_multilevel(MonoObject *p_mono_object, const StringName &p_method, const Variant **p_args, int p_argcount) {
- GD_MONO_ASSERT_THREAD_ATTACHED;
-
- GDMonoClass *top = script->script_class;
-
- while (top && top != script->native) {
- GDMonoMethod *method = top->get_method(p_method, p_argcount);
-
- if (method) {
- method->invoke(p_mono_object, p_args);
- return;
- }
-
- top = top->get_parent_class();
- }
-}
-
-void CSharpInstance::call_multilevel_reversed(const StringName &p_method, const Variant **p_args, int p_argcount) {
- // Sorry, the method is the one that controls the call order
-
- call_multilevel(p_method, p_args, p_argcount);
-}
-
bool CSharpInstance::_reference_owner_unsafe() {
#ifdef DEBUG_ENABLED
CRASH_COND(!base_ref);
@@ -3759,13 +3723,9 @@ Error ResourceFormatSaverCSharpScript::save(const String &p_path, const RES &p_r
#ifdef TOOLS_ENABLED
if (!FileAccess::exists(p_path)) {
- // The file does not yet exists, let's assume the user just created this script
-
- if (_create_project_solution_if_needed()) {
- CSharpProject::add_item(GodotSharpDirs::get_project_csproj_path(),
- "Compile",
- ProjectSettings::get_singleton()->globalize_path(p_path));
- } else {
+ // The file does not yet exist, let's assume the user just created this script. In such
+ // cases we need to check whether the solution and csproj were already created or not.
+ if (!_create_project_solution_if_needed()) {
ERR_PRINT("C# project could not be created; cannot add file: '" + p_path + "'.");
}
}
diff --git a/modules/mono/csharp_script.h b/modules/mono/csharp_script.h
index c2370364f9..f0b43a40f9 100644
--- a/modules/mono/csharp_script.h
+++ b/modules/mono/csharp_script.h
@@ -265,8 +265,6 @@ class CSharpInstance : public ScriptInstance {
friend void GDMonoInternals::tie_managed_to_unmanaged(MonoObject *, Object *);
static CSharpInstance *create_for_managed_type(Object *p_owner, CSharpScript *p_script, const MonoGCHandleData &p_gchandle);
- void _call_multilevel(MonoObject *p_mono_object, const StringName &p_method, const Variant **p_args, int p_argcount);
-
void get_properties_state_for_reloading(List<Pair<StringName, Variant>> &r_state);
void get_event_signals_state_for_reloading(List<Pair<StringName, Array>> &r_state);
@@ -285,8 +283,6 @@ public:
/* TODO */ void get_method_list(List<MethodInfo> *p_list) const override {}
bool has_method(const StringName &p_method) const override;
Variant call(const StringName &p_method, const Variant **p_args, int p_argcount, Callable::CallError &r_error) override;
- void call_multilevel(const StringName &p_method, const Variant **p_args, int p_argcount) override;
- void call_multilevel_reversed(const StringName &p_method, const Variant **p_args, int p_argcount) override;
void mono_object_disposed(MonoObject *p_obj);
diff --git a/modules/mono/editor/Godot.NET.Sdk/Godot.NET.Sdk.sln b/modules/mono/editor/Godot.NET.Sdk/Godot.NET.Sdk.sln
new file mode 100644
index 0000000000..56c0cb7703
--- /dev/null
+++ b/modules/mono/editor/Godot.NET.Sdk/Godot.NET.Sdk.sln
@@ -0,0 +1,16 @@
+
+Microsoft Visual Studio Solution File, Format Version 12.00
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Godot.NET.Sdk", "Godot.NET.Sdk\Godot.NET.Sdk.csproj", "{31B00BFA-DEA1-42FA-A472-9E54A92A8A5F}"
+EndProject
+Global
+ GlobalSection(SolutionConfigurationPlatforms) = preSolution
+ Debug|Any CPU = Debug|Any CPU
+ Release|Any CPU = Release|Any CPU
+ EndGlobalSection
+ GlobalSection(ProjectConfigurationPlatforms) = postSolution
+ {31B00BFA-DEA1-42FA-A472-9E54A92A8A5F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {31B00BFA-DEA1-42FA-A472-9E54A92A8A5F}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {31B00BFA-DEA1-42FA-A472-9E54A92A8A5F}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {31B00BFA-DEA1-42FA-A472-9E54A92A8A5F}.Release|Any CPU.Build.0 = Release|Any CPU
+ EndGlobalSection
+EndGlobal
diff --git a/modules/mono/editor/Godot.NET.Sdk/Godot.NET.Sdk/Godot.NET.Sdk.csproj b/modules/mono/editor/Godot.NET.Sdk/Godot.NET.Sdk/Godot.NET.Sdk.csproj
new file mode 100644
index 0000000000..86a0a4393e
--- /dev/null
+++ b/modules/mono/editor/Godot.NET.Sdk/Godot.NET.Sdk/Godot.NET.Sdk.csproj
@@ -0,0 +1,35 @@
+<Project Sdk="Microsoft.Build.NoTargets/2.0.1">
+ <PropertyGroup>
+ <TargetFramework>netstandard2.0</TargetFramework>
+
+ <Description>MSBuild .NET Sdk for Godot projects.</Description>
+ <Authors>Godot Engine contributors</Authors>
+
+ <PackageId>Godot.NET.Sdk</PackageId>
+ <Version>4.0.0</Version>
+ <PackageVersion>4.0.0-dev2</PackageVersion>
+ <PackageProjectUrl>https://github.com/godotengine/godot/tree/master/modules/mono/editor/Godot.NET.Sdk</PackageProjectUrl>
+ <PackageType>MSBuildSdk</PackageType>
+ <PackageTags>MSBuildSdk</PackageTags>
+ <GeneratePackageOnBuild>true</GeneratePackageOnBuild>
+ </PropertyGroup>
+
+ <PropertyGroup>
+ <NuspecFile>Godot.NET.Sdk.nuspec</NuspecFile>
+ <GenerateNuspecDependsOn>$(GenerateNuspecDependsOn);SetNuSpecProperties</GenerateNuspecDependsOn>
+ </PropertyGroup>
+
+ <Target Name="SetNuSpecProperties" Condition=" Exists('$(NuspecFile)') ">
+ <PropertyGroup>
+ <NuspecProperties>
+ id=$(PackageId);
+ description=$(Description);
+ authors=$(Authors);
+ version=$(PackageVersion);
+ packagetype=$(PackageType);
+ tags=$(PackageTags);
+ projecturl=$(PackageProjectUrl)
+ </NuspecProperties>
+ </PropertyGroup>
+ </Target>
+</Project>
diff --git a/modules/mono/editor/Godot.NET.Sdk/Godot.NET.Sdk/Godot.NET.Sdk.nuspec b/modules/mono/editor/Godot.NET.Sdk/Godot.NET.Sdk/Godot.NET.Sdk.nuspec
new file mode 100644
index 0000000000..5b5cefe80e
--- /dev/null
+++ b/modules/mono/editor/Godot.NET.Sdk/Godot.NET.Sdk/Godot.NET.Sdk.nuspec
@@ -0,0 +1,22 @@
+<?xml version="1.0" encoding="utf-8" ?>
+<package xmlns="http://schemas.microsoft.com/packaging/2011/10/nuspec.xsd">
+ <metadata>
+ <id>$id$</id>
+ <version>$version$</version>
+ <description>$description$</description>
+ <authors>$authors$</authors>
+ <owners>$authors$</owners>
+ <projectUrl>$projecturl$</projectUrl>
+ <requireLicenseAcceptance>false</requireLicenseAcceptance>
+ <license type="expression">MIT</license>
+ <licenseUrl>https://licenses.nuget.org/MIT</licenseUrl>
+ <tags>$tags$</tags>
+ <packageTypes>
+ <packageType name="$packagetype$" />
+ </packageTypes>
+ <repository url="$projecturl$" />
+ </metadata>
+ <files>
+ <file src="Sdk\**" target="Sdk" />\
+ </files>
+</package>
diff --git a/modules/mono/editor/Godot.NET.Sdk/Godot.NET.Sdk/Sdk/Sdk.props b/modules/mono/editor/Godot.NET.Sdk/Godot.NET.Sdk/Sdk/Sdk.props
new file mode 100644
index 0000000000..dfc59e6ccb
--- /dev/null
+++ b/modules/mono/editor/Godot.NET.Sdk/Godot.NET.Sdk/Sdk/Sdk.props
@@ -0,0 +1,112 @@
+<Project>
+ <PropertyGroup>
+ <!-- Determines if we should import Microsoft.NET.Sdk, if it wasn't already imported. -->
+ <GodotSdkImportsMicrosoftNetSdk Condition=" '$(UsingMicrosoftNETSdk)' != 'true' ">true</GodotSdkImportsMicrosoftNetSdk>
+
+ <GodotProjectTypeGuid>{8F3E2DF0-C35C-4265-82FC-BEA011F4A7ED}</GodotProjectTypeGuid>
+ </PropertyGroup>
+
+ <PropertyGroup>
+ <Configurations>Debug;ExportDebug;ExportRelease</Configurations>
+ <Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
+
+ <GodotProjectDir Condition=" '$(SolutionDir)' != '' ">$(SolutionDir)</GodotProjectDir>
+ <GodotProjectDir Condition=" '$(SolutionDir)' == '' ">$(MSBuildProjectDirectory)</GodotProjectDir>
+ <GodotProjectDir>$([MSBuild]::EnsureTrailingSlash('$(GodotProjectDir)'))</GodotProjectDir>
+
+ <!-- Custom output paths for Godot projects. In brief, 'bin\' and 'obj\' are moved to '$(GodotProjectDir)\.mono\temp\'. -->
+ <BaseOutputPath>$(GodotProjectDir).mono\temp\bin\</BaseOutputPath>
+ <OutputPath>$(GodotProjectDir).mono\temp\bin\$(Configuration)\</OutputPath>
+ <!--
+ Use custom IntermediateOutputPath and BaseIntermediateOutputPath only if it wasn't already set.
+ Otherwise the old values may have already been changed by MSBuild which can cause problems with NuGet.
+ -->
+ <IntermediateOutputPath Condition=" '$(IntermediateOutputPath)' == '' ">$(GodotProjectDir).mono\temp\obj\$(Configuration)\</IntermediateOutputPath>
+ <BaseIntermediateOutputPath Condition=" '$(BaseIntermediateOutputPath)' == '' ">$(GodotProjectDir).mono\temp\obj\</BaseIntermediateOutputPath>
+
+ <!-- Do not append the target framework name to the output path. -->
+ <AppendTargetFrameworkToOutputPath>false</AppendTargetFrameworkToOutputPath>
+ </PropertyGroup>
+
+ <Import Sdk="Microsoft.NET.Sdk" Project="Sdk.props" Condition=" '$(GodotSdkImportsMicrosoftNetSdk)' == 'true' " />
+
+ <PropertyGroup>
+ <EnableDefaultNoneItems>false</EnableDefaultNoneItems>
+ </PropertyGroup>
+
+ <!--
+ The Microsoft.NET.Sdk only understands of the Debug and Release configurations.
+ We need to set the following properties manually for ExportDebug and ExportRelease.
+ -->
+ <PropertyGroup Condition=" '$(Configuration)' == 'Debug' or '$(Configuration)' == 'ExportDebug' ">
+ <DebugSymbols Condition=" '$(DebugSymbols)' == '' ">true</DebugSymbols>
+ <Optimize Condition=" '$(Optimize)' == '' ">false</Optimize>
+ </PropertyGroup>
+ <PropertyGroup Condition=" '$(Configuration)' == 'ExportRelease' ">
+ <Optimize Condition=" '$(Optimize)' == '' ">true</Optimize>
+ </PropertyGroup>
+
+ <PropertyGroup>
+ <GodotApiConfiguration Condition=" '$(Configuration)' != 'ExportRelease' ">Debug</GodotApiConfiguration>
+ <GodotApiConfiguration Condition=" '$(Configuration)' == 'ExportRelease' ">Release</GodotApiConfiguration>
+ </PropertyGroup>
+
+ <!-- Auto-detect the target Godot platform if it was not specified. -->
+ <PropertyGroup Condition=" '$(GodotTargetPlatform)' == '' ">
+ <GodotTargetPlatform Condition=" '$([MSBuild]::IsOsPlatform(Linux))' ">linuxbsd</GodotTargetPlatform>
+ <GodotTargetPlatform Condition=" '$([MSBuild]::IsOsPlatform(FreeBSD))' ">linuxbsd</GodotTargetPlatform>
+ <GodotTargetPlatform Condition=" '$([MSBuild]::IsOsPlatform(OSX))' ">osx</GodotTargetPlatform>
+ <GodotTargetPlatform Condition=" '$([MSBuild]::IsOsPlatform(Windows))' ">windows</GodotTargetPlatform>
+ </PropertyGroup>
+
+ <PropertyGroup>
+ <GodotRealTIsDouble Condition=" '$(GodotRealTIsDouble)' == '' ">false</GodotRealTIsDouble>
+ </PropertyGroup>
+
+ <!-- Godot DefineConstants. -->
+ <PropertyGroup>
+ <!-- Define constant to identify Godot builds. -->
+ <GodotDefineConstants>GODOT</GodotDefineConstants>
+
+ <!--
+ Define constant to determine the target Godot platform. This includes the
+ recognized platform names and the platform category (PC, MOBILE or WEB).
+ -->
+ <GodotPlatformConstants Condition=" '$(GodotTargetPlatform)' == 'windows' ">GODOT_WINDOWS;GODOT_PC</GodotPlatformConstants>
+ <GodotPlatformConstants Condition=" '$(GodotTargetPlatform)' == 'linuxbsd' ">GODOT_LINUXBSD;GODOT_PC</GodotPlatformConstants>
+ <GodotPlatformConstants Condition=" '$(GodotTargetPlatform)' == 'osx' ">GODOT_OSX;GODOT_MACOS;GODOT_PC</GodotPlatformConstants>
+ <GodotPlatformConstants Condition=" '$(GodotTargetPlatform)' == 'server' ">GODOT_SERVER;GODOT_PC</GodotPlatformConstants>
+ <GodotPlatformConstants Condition=" '$(GodotTargetPlatform)' == 'uwp' ">GODOT_UWP;GODOT_PC</GodotPlatformConstants>
+ <GodotPlatformConstants Condition=" '$(GodotTargetPlatform)' == 'haiku' ">GODOT_HAIKU;GODOT_PC</GodotPlatformConstants>
+ <GodotPlatformConstants Condition=" '$(GodotTargetPlatform)' == 'android' ">GODOT_ANDROID;GODOT_MOBILE</GodotPlatformConstants>
+ <GodotPlatformConstants Condition=" '$(GodotTargetPlatform)' == 'iphone' ">GODOT_IPHONE;GODOT_IOS;GODOT_MOBILE</GodotPlatformConstants>
+ <GodotPlatformConstants Condition=" '$(GodotTargetPlatform)' == 'javascript' ">GODOT_JAVASCRIPT;GODOT_HTML5;GODOT_WASM;GODOT_WEB</GodotPlatformConstants>
+
+ <GodotDefineConstants>$(GodotDefineConstants);$(GodotPlatformConstants)</GodotDefineConstants>
+ </PropertyGroup>
+
+ <PropertyGroup>
+ <!-- ExportDebug also defines DEBUG like Debug does. -->
+ <DefineConstants Condition=" '$(Configuration)' == 'ExportDebug' ">$(DefineConstants);DEBUG</DefineConstants>
+ <!-- Debug defines TOOLS to differenciate between Debug and ExportDebug configurations. -->
+ <DefineConstants Condition=" '$(Configuration)' == 'Debug' ">$(DefineConstants);TOOLS</DefineConstants>
+
+ <DefineConstants>$(GodotDefineConstants);$(DefineConstants)</DefineConstants>
+ </PropertyGroup>
+
+ <ItemGroup>
+ <!--
+ TODO:
+ We should consider a nuget package for reference assemblies. This is difficult because the
+ Godot scripting API is continuaslly breaking backwards compatibility even in patch releases.
+ -->
+ <Reference Include="GodotSharp">
+ <Private>false</Private>
+ <HintPath>$(GodotProjectDir).mono\assemblies\$(GodotApiConfiguration)\GodotSharp.dll</HintPath>
+ </Reference>
+ <Reference Include="GodotSharpEditor" Condition=" '$(Configuration)' == 'Debug' ">
+ <Private>false</Private>
+ <HintPath>$(GodotProjectDir).mono\assemblies\$(GodotApiConfiguration)\GodotSharpEditor.dll</HintPath>
+ </Reference>
+ </ItemGroup>
+</Project>
diff --git a/modules/mono/editor/Godot.NET.Sdk/Godot.NET.Sdk/Sdk/Sdk.targets b/modules/mono/editor/Godot.NET.Sdk/Godot.NET.Sdk/Sdk/Sdk.targets
new file mode 100644
index 0000000000..f5afd75505
--- /dev/null
+++ b/modules/mono/editor/Godot.NET.Sdk/Godot.NET.Sdk/Sdk/Sdk.targets
@@ -0,0 +1,17 @@
+<Project>
+ <Import Sdk="Microsoft.NET.Sdk" Project="Sdk.targets" Condition=" '$(GodotSdkImportsMicrosoftNetSdk)' == 'true' " />
+
+ <PropertyGroup>
+ <EnableGodotProjectTypeGuid Condition=" '$(EnableGodotProjectTypeGuid)' == '' ">true</EnableGodotProjectTypeGuid>
+ <ProjectTypeGuids Condition=" '$(EnableGodotProjectTypeGuid)' == 'true' ">$(GodotProjectTypeGuid);$(DefaultProjectTypeGuid)</ProjectTypeGuids>
+ </PropertyGroup>
+
+ <PropertyGroup>
+ <!--
+ Define constant to determine whether the real_t type in Godot is double precision or not.
+ By default this is false, like the official Godot builds. If someone is using a custom
+ Godot build where real_t is double, they can override the GodotRealTIsDouble property.
+ -->
+ <DefineConstants Condition=" '$(GodotRealTIsDouble)' == 'true' ">GODOT_REAL_T_IS_DOUBLE;$(DefineConstants)</DefineConstants>
+ </PropertyGroup>
+</Project>
diff --git a/modules/mono/editor/GodotTools/GodotTools.Core/FileUtils.cs b/modules/mono/editor/GodotTools/GodotTools.Core/FileUtils.cs
index 85760a3705..e1ccf0454a 100644
--- a/modules/mono/editor/GodotTools/GodotTools.Core/FileUtils.cs
+++ b/modules/mono/editor/GodotTools/GodotTools.Core/FileUtils.cs
@@ -19,7 +19,10 @@ namespace GodotTools.Core
}
if (attempt > maxAttempts + 1)
- return;
+ {
+ // Overwrite the oldest one
+ backupPath = backupPathBase;
+ }
File.Copy(filePath, backupPath, overwrite: true);
}
diff --git a/modules/mono/editor/GodotTools/GodotTools.ProjectEditor/IdentifierUtils.cs b/modules/mono/editor/GodotTools/GodotTools.ProjectEditor/IdentifierUtils.cs
index f93eb9a1fa..ed77076df3 100644
--- a/modules/mono/editor/GodotTools/GodotTools.ProjectEditor/IdentifierUtils.cs
+++ b/modules/mono/editor/GodotTools/GodotTools.ProjectEditor/IdentifierUtils.cs
@@ -22,6 +22,37 @@ namespace GodotTools.ProjectEditor
return string.Join(".", identifiers);
}
+ /// <summary>
+ /// Skips invalid identifier characters including decimal digit numbers at the start of the identifier.
+ /// </summary>
+ private static void SkipInvalidCharacters(string source, int startIndex, StringBuilder outputBuilder)
+ {
+ for (int i = startIndex; i < source.Length; i++)
+ {
+ char @char = source[i];
+
+ switch (char.GetUnicodeCategory(@char))
+ {
+ case UnicodeCategory.UppercaseLetter:
+ case UnicodeCategory.LowercaseLetter:
+ case UnicodeCategory.TitlecaseLetter:
+ case UnicodeCategory.ModifierLetter:
+ case UnicodeCategory.LetterNumber:
+ case UnicodeCategory.OtherLetter:
+ outputBuilder.Append(@char);
+ break;
+ case UnicodeCategory.NonSpacingMark:
+ case UnicodeCategory.SpacingCombiningMark:
+ case UnicodeCategory.ConnectorPunctuation:
+ case UnicodeCategory.DecimalDigitNumber:
+ // Identifiers may start with underscore
+ if (outputBuilder.Length > startIndex || @char == '_')
+ outputBuilder.Append(@char);
+ break;
+ }
+ }
+ }
+
public static string SanitizeIdentifier(string identifier, bool allowEmpty)
{
if (string.IsNullOrEmpty(identifier))
@@ -44,30 +75,7 @@ namespace GodotTools.ProjectEditor
startIndex += 1;
}
- for (int i = startIndex; i < identifier.Length; i++)
- {
- char @char = identifier[i];
-
- switch (Char.GetUnicodeCategory(@char))
- {
- case UnicodeCategory.UppercaseLetter:
- case UnicodeCategory.LowercaseLetter:
- case UnicodeCategory.TitlecaseLetter:
- case UnicodeCategory.ModifierLetter:
- case UnicodeCategory.LetterNumber:
- case UnicodeCategory.OtherLetter:
- identifierBuilder.Append(@char);
- break;
- case UnicodeCategory.NonSpacingMark:
- case UnicodeCategory.SpacingCombiningMark:
- case UnicodeCategory.ConnectorPunctuation:
- case UnicodeCategory.DecimalDigitNumber:
- // Identifiers may start with underscore
- if (identifierBuilder.Length > startIndex || @char == '_')
- identifierBuilder.Append(@char);
- break;
- }
- }
+ SkipInvalidCharacters(identifier, startIndex, identifierBuilder);
if (identifierBuilder.Length == startIndex)
{
diff --git a/modules/mono/editor/GodotTools/GodotTools.ProjectEditor/ProjectExtensions.cs b/modules/mono/editor/GodotTools/GodotTools.ProjectEditor/ProjectExtensions.cs
deleted file mode 100644
index 704f2ec194..0000000000
--- a/modules/mono/editor/GodotTools/GodotTools.ProjectEditor/ProjectExtensions.cs
+++ /dev/null
@@ -1,118 +0,0 @@
-using GodotTools.Core;
-using System;
-using System.Collections.Generic;
-using System.IO;
-using Microsoft.Build.Construction;
-using Microsoft.Build.Globbing;
-
-namespace GodotTools.ProjectEditor
-{
- public static class ProjectExtensions
- {
- public static ProjectItemElement FindItemOrNull(this ProjectRootElement root, string itemType, string include, bool noCondition = false)
- {
- string normalizedInclude = include.NormalizePath();
-
- foreach (var itemGroup in root.ItemGroups)
- {
- if (noCondition && itemGroup.Condition.Length != 0)
- continue;
-
- foreach (var item in itemGroup.Items)
- {
- if (item.ItemType != itemType)
- continue;
-
- //var glob = Glob.Parse(item.Include.NormalizePath(), globOptions);
- var glob = MSBuildGlob.Parse(item.Include.NormalizePath());
-
- if (glob.IsMatch(normalizedInclude))
- return item;
- }
- }
-
- return null;
- }
- public static ProjectItemElement FindItemOrNullAbs(this ProjectRootElement root, string itemType, string include, bool noCondition = false)
- {
- string normalizedInclude = Path.GetFullPath(include).NormalizePath();
-
- foreach (var itemGroup in root.ItemGroups)
- {
- if (noCondition && itemGroup.Condition.Length != 0)
- continue;
-
- foreach (var item in itemGroup.Items)
- {
- if (item.ItemType != itemType)
- continue;
-
- var glob = MSBuildGlob.Parse(Path.GetFullPath(item.Include).NormalizePath());
-
- if (glob.IsMatch(normalizedInclude))
- return item;
- }
- }
-
- return null;
- }
-
- public static IEnumerable<ProjectItemElement> FindAllItemsInFolder(this ProjectRootElement root, string itemType, string folder)
- {
- string absFolderNormalizedWithSep = Path.GetFullPath(folder).NormalizePath() + Path.DirectorySeparatorChar;
-
- foreach (var itemGroup in root.ItemGroups)
- {
- foreach (var item in itemGroup.Items)
- {
- if (item.ItemType != itemType)
- continue;
-
- string absPathNormalized = Path.GetFullPath(item.Include).NormalizePath();
-
- if (absPathNormalized.StartsWith(absFolderNormalizedWithSep))
- yield return item;
- }
- }
- }
-
- public static bool HasItem(this ProjectRootElement root, string itemType, string include, bool noCondition = false)
- {
- return root.FindItemOrNull(itemType, include, noCondition) != null;
- }
-
- public static bool AddItemChecked(this ProjectRootElement root, string itemType, string include)
- {
- if (!root.HasItem(itemType, include, noCondition: true))
- {
- root.AddItem(itemType, include);
- return true;
- }
-
- return false;
- }
-
- public static bool RemoveItemChecked(this ProjectRootElement root, string itemType, string include)
- {
- var item = root.FindItemOrNullAbs(itemType, include);
- if (item != null)
- {
- item.Parent.RemoveChild(item);
- return true;
- }
-
- return false;
- }
-
- public static Guid GetGuid(this ProjectRootElement root)
- {
- foreach (var property in root.Properties)
- {
- if (property.Name == "ProjectGuid")
- return Guid.Parse(property.Value);
- }
-
- return Guid.Empty;
- }
- }
-}
diff --git a/modules/mono/editor/GodotTools/GodotTools.ProjectEditor/ProjectGenerator.cs b/modules/mono/editor/GodotTools/GodotTools.ProjectEditor/ProjectGenerator.cs
index 679d5bb444..5541876f9e 100644
--- a/modules/mono/editor/GodotTools/GodotTools.ProjectEditor/ProjectGenerator.cs
+++ b/modules/mono/editor/GodotTools/GodotTools.ProjectEditor/ProjectGenerator.cs
@@ -1,174 +1,49 @@
-using GodotTools.Core;
using System;
-using System.Collections.Generic;
using System.IO;
-using System.Reflection;
using Microsoft.Build.Construction;
+using Microsoft.Build.Evaluation;
namespace GodotTools.ProjectEditor
{
public static class ProjectGenerator
{
- private const string CoreApiProjectName = "GodotSharp";
- private const string EditorApiProjectName = "GodotSharpEditor";
+ public const string GodotSdkVersionToUse = "4.0.0-dev2";
- public const string CSharpProjectTypeGuid = "{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}";
- public const string GodotProjectTypeGuid = "{8F3E2DF0-C35C-4265-82FC-BEA011F4A7ED}";
+ public static string GodotSdkAttrValue => $"Godot.NET.Sdk/{GodotSdkVersionToUse}";
- public static readonly string GodotDefaultProjectTypeGuids = $"{GodotProjectTypeGuid};{CSharpProjectTypeGuid}";
-
- public static string GenGameProject(string dir, string name, IEnumerable<string> compileItems)
- {
- string path = Path.Combine(dir, name + ".csproj");
-
- ProjectPropertyGroupElement mainGroup;
- var root = CreateLibraryProject(name, "Debug", out mainGroup);
-
- mainGroup.SetProperty("ProjectTypeGuids", GodotDefaultProjectTypeGuids);
- mainGroup.SetProperty("OutputPath", Path.Combine(".mono", "temp", "bin", "$(Configuration)"));
- mainGroup.SetProperty("BaseIntermediateOutputPath", Path.Combine(".mono", "temp", "obj"));
- mainGroup.SetProperty("IntermediateOutputPath", Path.Combine("$(BaseIntermediateOutputPath)", "$(Configuration)"));
- mainGroup.SetProperty("ApiConfiguration", "Debug").Condition = " '$(Configuration)' != 'ExportRelease' ";
- mainGroup.SetProperty("ApiConfiguration", "Release").Condition = " '$(Configuration)' == 'ExportRelease' ";
-
- var debugGroup = root.AddPropertyGroup();
- debugGroup.Condition = " '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ";
- debugGroup.AddProperty("DebugSymbols", "true");
- debugGroup.AddProperty("DebugType", "portable");
- debugGroup.AddProperty("Optimize", "false");
- debugGroup.AddProperty("DefineConstants", "$(GodotDefineConstants);GODOT;DEBUG;TOOLS;");
- debugGroup.AddProperty("ErrorReport", "prompt");
- debugGroup.AddProperty("WarningLevel", "4");
- debugGroup.AddProperty("ConsolePause", "false");
-
- var coreApiRef = root.AddItem("Reference", CoreApiProjectName);
- coreApiRef.AddMetadata("HintPath", Path.Combine("$(ProjectDir)", ".mono", "assemblies", "$(ApiConfiguration)", CoreApiProjectName + ".dll"));
- coreApiRef.AddMetadata("Private", "False");
-
- var editorApiRef = root.AddItem("Reference", EditorApiProjectName);
- editorApiRef.Condition = " '$(Configuration)' == 'Debug' ";
- editorApiRef.AddMetadata("HintPath", Path.Combine("$(ProjectDir)", ".mono", "assemblies", "$(ApiConfiguration)", EditorApiProjectName + ".dll"));
- editorApiRef.AddMetadata("Private", "False");
-
- GenAssemblyInfoFile(root, dir, name);
-
- foreach (var item in compileItems)
- {
- root.AddItem("Compile", item.RelativeToPath(dir).Replace("/", "\\"));
- }
-
- root.Save(path);
-
- return root.GetGuid().ToString().ToUpper();
- }
-
- private static void GenAssemblyInfoFile(ProjectRootElement root, string dir, string name, string[] assemblyLines = null, string[] usingDirectives = null)
+ public static ProjectRootElement GenGameProject(string name)
{
- string propertiesDir = Path.Combine(dir, "Properties");
- if (!Directory.Exists(propertiesDir))
- Directory.CreateDirectory(propertiesDir);
-
- string usingDirectivesText = string.Empty;
+ if (name.Length == 0)
+ throw new ArgumentException("Project name is empty", nameof(name));
- if (usingDirectives != null)
- {
- foreach (var usingDirective in usingDirectives)
- usingDirectivesText += "\nusing " + usingDirective + ";";
- }
+ var root = ProjectRootElement.Create(NewProjectFileOptions.None);
- string assemblyLinesText = string.Empty;
+ root.Sdk = GodotSdkAttrValue;
- if (assemblyLines != null)
- assemblyLinesText += string.Join("\n", assemblyLines) + "\n";
+ var mainGroup = root.AddPropertyGroup();
+ mainGroup.AddProperty("TargetFramework", "netstandard2.1");
- string content = string.Format(AssemblyInfoTemplate, usingDirectivesText, name, assemblyLinesText);
+ string sanitizedName = IdentifierUtils.SanitizeQualifiedIdentifier(name, allowEmptyIdentifiers: true);
- string assemblyInfoFile = Path.Combine(propertiesDir, "AssemblyInfo.cs");
+ // If the name is not a valid namespace, manually set RootNamespace to a sanitized one.
+ if (sanitizedName != name)
+ mainGroup.AddProperty("RootNamespace", sanitizedName);
- File.WriteAllText(assemblyInfoFile, content);
-
- root.AddItem("Compile", assemblyInfoFile.RelativeToPath(dir).Replace("/", "\\"));
+ return root;
}
- public static ProjectRootElement CreateLibraryProject(string name, string defaultConfig, out ProjectPropertyGroupElement mainGroup)
+ public static string GenAndSaveGameProject(string dir, string name)
{
- if (string.IsNullOrEmpty(name))
- throw new ArgumentException($"{nameof(name)} cannot be empty", nameof(name));
-
- var root = ProjectRootElement.Create();
- root.DefaultTargets = "Build";
-
- mainGroup = root.AddPropertyGroup();
- mainGroup.AddProperty("Configuration", defaultConfig).Condition = " '$(Configuration)' == '' ";
- mainGroup.AddProperty("Platform", "AnyCPU").Condition = " '$(Platform)' == '' ";
- mainGroup.AddProperty("ProjectGuid", "{" + Guid.NewGuid().ToString().ToUpper() + "}");
- mainGroup.AddProperty("OutputType", "Library");
- mainGroup.AddProperty("OutputPath", Path.Combine("bin", "$(Configuration)"));
- mainGroup.AddProperty("RootNamespace", IdentifierUtils.SanitizeQualifiedIdentifier(name, allowEmptyIdentifiers: true));
- mainGroup.AddProperty("AssemblyName", name);
- mainGroup.AddProperty("TargetFrameworkVersion", "v4.7");
- mainGroup.AddProperty("GodotProjectGeneratorVersion", Assembly.GetExecutingAssembly().GetName().Version.ToString());
+ if (name.Length == 0)
+ throw new ArgumentException("Project name is empty", nameof(name));
- var exportDebugGroup = root.AddPropertyGroup();
- exportDebugGroup.Condition = " '$(Configuration)|$(Platform)' == 'ExportDebug|AnyCPU' ";
- exportDebugGroup.AddProperty("DebugSymbols", "true");
- exportDebugGroup.AddProperty("DebugType", "portable");
- exportDebugGroup.AddProperty("Optimize", "false");
- exportDebugGroup.AddProperty("DefineConstants", "$(GodotDefineConstants);GODOT;DEBUG;");
- exportDebugGroup.AddProperty("ErrorReport", "prompt");
- exportDebugGroup.AddProperty("WarningLevel", "4");
- exportDebugGroup.AddProperty("ConsolePause", "false");
-
- var exportReleaseGroup = root.AddPropertyGroup();
- exportReleaseGroup.Condition = " '$(Configuration)|$(Platform)' == 'ExportRelease|AnyCPU' ";
- exportReleaseGroup.AddProperty("DebugType", "portable");
- exportReleaseGroup.AddProperty("Optimize", "true");
- exportReleaseGroup.AddProperty("DefineConstants", "$(GodotDefineConstants);GODOT;");
- exportReleaseGroup.AddProperty("ErrorReport", "prompt");
- exportReleaseGroup.AddProperty("WarningLevel", "4");
- exportReleaseGroup.AddProperty("ConsolePause", "false");
-
- // References
- var referenceGroup = root.AddItemGroup();
- referenceGroup.AddItem("Reference", "System");
- var frameworkRefAssembliesItem = referenceGroup.AddItem("PackageReference", "Microsoft.NETFramework.ReferenceAssemblies");
+ string path = Path.Combine(dir, name + ".csproj");
- // Use metadata (child nodes) instead of attributes for the PackageReference.
- // This is for compatibility with 3.2, where GodotTools uses an old Microsoft.Build.
- frameworkRefAssembliesItem.AddMetadata("Version", "1.0.0");
- frameworkRefAssembliesItem.AddMetadata("PrivateAssets", "All");
+ var root = GenGameProject(name);
- root.AddImport(Path.Combine("$(MSBuildBinPath)", "Microsoft.CSharp.targets").Replace("/", "\\"));
+ root.Save(path);
- return root;
+ return Guid.NewGuid().ToString().ToUpper();
}
-
- private const string AssemblyInfoTemplate =
- @"using System.Reflection;{0}
-
-// Information about this assembly is defined by the following attributes.
-// Change them to the values specific to your project.
-
-[assembly: AssemblyTitle(""{1}"")]
-[assembly: AssemblyDescription("""")]
-[assembly: AssemblyConfiguration("""")]
-[assembly: AssemblyCompany("""")]
-[assembly: AssemblyProduct("""")]
-[assembly: AssemblyCopyright("""")]
-[assembly: AssemblyTrademark("""")]
-[assembly: AssemblyCulture("""")]
-
-// The assembly version has the format ""{{Major}}.{{Minor}}.{{Build}}.{{Revision}}"".
-// The form ""{{Major}}.{{Minor}}.*"" will automatically update the build and revision,
-// and ""{{Major}}.{{Minor}}.{{Build}}.*"" will update just the revision.
-
-[assembly: AssemblyVersion(""1.0.*"")]
-
-// The following attributes are used to specify the signing key for the assembly,
-// if desired. See the Mono documentation for more information about signing.
-
-//[assembly: AssemblyDelaySign(false)]
-//[assembly: AssemblyKeyFile("""")]
-{2}";
}
}
diff --git a/modules/mono/editor/GodotTools/GodotTools.ProjectEditor/ProjectUtils.cs b/modules/mono/editor/GodotTools/GodotTools.ProjectEditor/ProjectUtils.cs
index 8774b4ee31..4041c56597 100644
--- a/modules/mono/editor/GodotTools/GodotTools.ProjectEditor/ProjectUtils.cs
+++ b/modules/mono/editor/GodotTools/GodotTools.ProjectEditor/ProjectUtils.cs
@@ -1,9 +1,9 @@
+using System;
using GodotTools.Core;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Linq;
-using System.Reflection;
using Microsoft.Build.Construction;
using Microsoft.Build.Globbing;
@@ -11,7 +11,7 @@ namespace GodotTools.ProjectEditor
{
public sealed class MSBuildProject
{
- public ProjectRootElement Root { get; }
+ internal ProjectRootElement Root { get; set; }
public bool HasUnsavedChanges { get; set; }
@@ -31,91 +31,7 @@ namespace GodotTools.ProjectEditor
return root != null ? new MSBuildProject(root) : null;
}
- public static void AddItemToProjectChecked(string projectPath, string itemType, string include)
- {
- var dir = Directory.GetParent(projectPath).FullName;
- var root = ProjectRootElement.Open(projectPath);
- Debug.Assert(root != null);
-
- var normalizedInclude = include.RelativeToPath(dir).Replace("/", "\\");
-
- if (root.AddItemChecked(itemType, normalizedInclude))
- root.Save();
- }
-
- public static void RenameItemInProjectChecked(string projectPath, string itemType, string oldInclude, string newInclude)
- {
- var dir = Directory.GetParent(projectPath).FullName;
- var root = ProjectRootElement.Open(projectPath);
- Debug.Assert(root != null);
-
- var normalizedOldInclude = oldInclude.NormalizePath();
- var normalizedNewInclude = newInclude.NormalizePath();
-
- var item = root.FindItemOrNullAbs(itemType, normalizedOldInclude);
-
- if (item == null)
- return;
-
- item.Include = normalizedNewInclude.RelativeToPath(dir).Replace("/", "\\");
- root.Save();
- }
-
- public static void RemoveItemFromProjectChecked(string projectPath, string itemType, string include)
- {
- var root = ProjectRootElement.Open(projectPath);
- Debug.Assert(root != null);
-
- var normalizedInclude = include.NormalizePath();
-
- if (root.RemoveItemChecked(itemType, normalizedInclude))
- root.Save();
- }
-
- public static void RenameItemsToNewFolderInProjectChecked(string projectPath, string itemType, string oldFolder, string newFolder)
- {
- var dir = Directory.GetParent(projectPath).FullName;
- var root = ProjectRootElement.Open(projectPath);
- Debug.Assert(root != null);
-
- bool dirty = false;
-
- var oldFolderNormalized = oldFolder.NormalizePath();
- var newFolderNormalized = newFolder.NormalizePath();
- string absOldFolderNormalized = Path.GetFullPath(oldFolderNormalized).NormalizePath();
- string absNewFolderNormalized = Path.GetFullPath(newFolderNormalized).NormalizePath();
-
- foreach (var item in root.FindAllItemsInFolder(itemType, oldFolderNormalized))
- {
- string absPathNormalized = Path.GetFullPath(item.Include).NormalizePath();
- string absNewIncludeNormalized = absNewFolderNormalized + absPathNormalized.Substring(absOldFolderNormalized.Length);
- item.Include = absNewIncludeNormalized.RelativeToPath(dir).Replace("/", "\\");
- dirty = true;
- }
-
- if (dirty)
- root.Save();
- }
-
- public static void RemoveItemsInFolderFromProjectChecked(string projectPath, string itemType, string folder)
- {
- var root = ProjectRootElement.Open(projectPath);
- Debug.Assert(root != null);
-
- var folderNormalized = folder.NormalizePath();
-
- var itemsToRemove = root.FindAllItemsInFolder(itemType, folderNormalized).ToList();
-
- if (itemsToRemove.Count > 0)
- {
- foreach (var item in itemsToRemove)
- item.Parent.RemoveChild(item);
-
- root.Save();
- }
- }
-
- private static string[] GetAllFilesRecursive(string rootDirectory, string mask)
+ private static List<string> GetAllFilesRecursive(string rootDirectory, string mask)
{
string[] files = Directory.GetFiles(rootDirectory, mask, SearchOption.AllDirectories);
@@ -125,262 +41,59 @@ namespace GodotTools.ProjectEditor
files[i] = files[i].RelativeToPath(rootDirectory);
}
- return files;
+ return new List<string>(files);
}
- public static string[] GetIncludeFiles(string projectPath, string itemType)
+ // NOTE: Assumes auto-including items. Only used by the scripts metadata generator, which will be replaced with source generators in the future.
+ public static IEnumerable<string> GetIncludeFiles(string projectPath, string itemType)
{
- var result = new List<string>();
- var existingFiles = GetAllFilesRecursive(Path.GetDirectoryName(projectPath), "*.cs");
+ var excluded = new List<string>();
+ var includedFiles = GetAllFilesRecursive(Path.GetDirectoryName(projectPath), "*.cs");
var root = ProjectRootElement.Open(projectPath);
Debug.Assert(root != null);
- foreach (var itemGroup in root.ItemGroups)
+ foreach (var item in root.Items)
{
- if (itemGroup.Condition.Length != 0)
+ if (string.IsNullOrEmpty(item.Condition))
continue;
- foreach (var item in itemGroup.Items)
- {
- if (item.ItemType != itemType)
- continue;
-
- string normalizedInclude = item.Include.NormalizePath();
+ if (item.ItemType != itemType)
+ continue;
- var glob = MSBuildGlob.Parse(normalizedInclude);
+ string normalizedExclude = item.Exclude.NormalizePath();
- // TODO Check somehow if path has no blob to avoid the following loop...
+ var glob = MSBuildGlob.Parse(normalizedExclude);
- foreach (var existingFile in existingFiles)
- {
- if (glob.IsMatch(existingFile))
- {
- result.Add(existingFile);
- }
- }
- }
+ excluded.AddRange(includedFiles.Where(includedFile => glob.IsMatch(includedFile)));
}
- return result.ToArray();
+ includedFiles.RemoveAll(f => excluded.Contains(f));
+
+ return includedFiles;
}
- public static void EnsureHasProjectTypeGuids(MSBuildProject project)
+ public static void MigrateToProjectSdksStyle(MSBuildProject project, string projectName)
{
- var root = project.Root;
-
- bool found = root.PropertyGroups.Any(pg =>
- string.IsNullOrEmpty(pg.Condition) && pg.Properties.Any(p => p.Name == "ProjectTypeGuids"));
+ var origRoot = project.Root;
- if (found)
+ if (!string.IsNullOrEmpty(origRoot.Sdk))
return;
- root.AddProperty("ProjectTypeGuids", ProjectGenerator.GodotDefaultProjectTypeGuids);
-
+ project.Root = ProjectGenerator.GenGameProject(projectName);
+ project.Root.FullPath = origRoot.FullPath;
project.HasUnsavedChanges = true;
}
- /// Simple function to make sure the Api assembly references are configured correctly
- public static void FixApiHintPath(MSBuildProject project)
- {
- var root = project.Root;
-
- void AddPropertyIfNotPresent(string name, string condition, string value)
- {
- if (root.PropertyGroups
- .Any(g => (string.IsNullOrEmpty(g.Condition) || g.Condition.Trim() == condition) &&
- g.Properties
- .Any(p => p.Name == name &&
- p.Value == value &&
- (p.Condition.Trim() == condition || g.Condition.Trim() == condition))))
- {
- return;
- }
-
- root.AddProperty(name, value).Condition = " " + condition + " ";
- project.HasUnsavedChanges = true;
- }
-
- AddPropertyIfNotPresent(name: "ApiConfiguration",
- condition: "'$(Configuration)' != 'ExportRelease'",
- value: "Debug");
- AddPropertyIfNotPresent(name: "ApiConfiguration",
- condition: "'$(Configuration)' == 'ExportRelease'",
- value: "Release");
-
- void SetReferenceHintPath(string referenceName, string condition, string hintPath)
- {
- foreach (var itemGroup in root.ItemGroups.Where(g =>
- g.Condition.Trim() == string.Empty || g.Condition.Trim() == condition))
- {
- var references = itemGroup.Items.Where(item =>
- item.ItemType == "Reference" &&
- item.Include == referenceName &&
- (item.Condition.Trim() == condition || itemGroup.Condition.Trim() == condition));
-
- var referencesWithHintPath = references.Where(reference =>
- reference.Metadata.Any(m => m.Name == "HintPath"));
-
- if (referencesWithHintPath.Any(reference => reference.Metadata
- .Any(m => m.Name == "HintPath" && m.Value == hintPath)))
- {
- // Found a Reference item with the right HintPath
- return;
- }
-
- var referenceWithHintPath = referencesWithHintPath.FirstOrDefault();
- if (referenceWithHintPath != null)
- {
- // Found a Reference item with a wrong HintPath
- foreach (var metadata in referenceWithHintPath.Metadata.ToList()
- .Where(m => m.Name == "HintPath"))
- {
- // Safe to remove as we duplicate with ToList() to loop
- referenceWithHintPath.RemoveChild(metadata);
- }
-
- referenceWithHintPath.AddMetadata("HintPath", hintPath);
- project.HasUnsavedChanges = true;
- return;
- }
-
- var referenceWithoutHintPath = references.FirstOrDefault();
- if (referenceWithoutHintPath != null)
- {
- // Found a Reference item without a HintPath
- referenceWithoutHintPath.AddMetadata("HintPath", hintPath);
- project.HasUnsavedChanges = true;
- return;
- }
- }
-
- // Found no Reference item at all. Add it.
- root.AddItem("Reference", referenceName).Condition = " " + condition + " ";
- project.HasUnsavedChanges = true;
- }
-
- const string coreProjectName = "GodotSharp";
- const string editorProjectName = "GodotSharpEditor";
-
- const string coreCondition = "";
- const string editorCondition = "'$(Configuration)' == 'Debug'";
-
- var coreHintPath = $"$(ProjectDir)/.mono/assemblies/$(ApiConfiguration)/{coreProjectName}.dll";
- var editorHintPath = $"$(ProjectDir)/.mono/assemblies/$(ApiConfiguration)/{editorProjectName}.dll";
-
- SetReferenceHintPath(coreProjectName, coreCondition, coreHintPath);
- SetReferenceHintPath(editorProjectName, editorCondition, editorHintPath);
- }
-
- public static void MigrateFromOldConfigNames(MSBuildProject project)
- {
- var root = project.Root;
-
- bool hasGodotProjectGeneratorVersion = false;
- bool foundOldConfiguration = false;
-
- foreach (var propertyGroup in root.PropertyGroups.Where(g => string.IsNullOrEmpty(g.Condition)))
- {
- if (!hasGodotProjectGeneratorVersion && propertyGroup.Properties.Any(p => p.Name == "GodotProjectGeneratorVersion"))
- hasGodotProjectGeneratorVersion = true;
-
- foreach (var configItem in propertyGroup.Properties
- .Where(p => p.Condition.Trim() == "'$(Configuration)' == ''" && p.Value == "Tools"))
- {
- configItem.Value = "Debug";
- foundOldConfiguration = true;
- project.HasUnsavedChanges = true;
- }
- }
-
- if (!hasGodotProjectGeneratorVersion)
- {
- root.PropertyGroups.First(g => string.IsNullOrEmpty(g.Condition))?
- .AddProperty("GodotProjectGeneratorVersion", Assembly.GetExecutingAssembly().GetName().Version.ToString());
- project.HasUnsavedChanges = true;
- }
-
- if (!foundOldConfiguration)
- {
- var toolsConditions = new[]
- {
- "'$(Configuration)|$(Platform)' == 'Tools|AnyCPU'",
- "'$(Configuration)|$(Platform)' != 'Tools|AnyCPU'",
- "'$(Configuration)' == 'Tools'",
- "'$(Configuration)' != 'Tools'"
- };
-
- foundOldConfiguration = root.PropertyGroups
- .Any(g => toolsConditions.Any(c => c == g.Condition.Trim()));
- }
-
- if (foundOldConfiguration)
- {
- void MigrateConfigurationConditions(string oldConfiguration, string newConfiguration)
- {
- void MigrateConditions(string oldCondition, string newCondition)
- {
- foreach (var propertyGroup in root.PropertyGroups.Where(g => g.Condition.Trim() == oldCondition))
- {
- propertyGroup.Condition = " " + newCondition + " ";
- project.HasUnsavedChanges = true;
- }
-
- foreach (var propertyGroup in root.PropertyGroups)
- {
- foreach (var prop in propertyGroup.Properties.Where(p => p.Condition.Trim() == oldCondition))
- {
- prop.Condition = " " + newCondition + " ";
- project.HasUnsavedChanges = true;
- }
- }
-
- foreach (var itemGroup in root.ItemGroups.Where(g => g.Condition.Trim() == oldCondition))
- {
- itemGroup.Condition = " " + newCondition + " ";
- project.HasUnsavedChanges = true;
- }
-
- foreach (var itemGroup in root.ItemGroups)
- {
- foreach (var item in itemGroup.Items.Where(item => item.Condition.Trim() == oldCondition))
- {
- item.Condition = " " + newCondition + " ";
- project.HasUnsavedChanges = true;
- }
- }
- }
-
- foreach (var op in new[] {"==", "!="})
- {
- MigrateConditions($"'$(Configuration)|$(Platform)' {op} '{oldConfiguration}|AnyCPU'", $"'$(Configuration)|$(Platform)' {op} '{newConfiguration}|AnyCPU'");
- MigrateConditions($"'$(Configuration)' {op} '{oldConfiguration}'", $"'$(Configuration)' {op} '{newConfiguration}'");
- }
- }
-
- MigrateConfigurationConditions("Debug", "ExportDebug");
- MigrateConfigurationConditions("Release", "ExportRelease");
- MigrateConfigurationConditions("Tools", "Debug"); // Must be last
- }
- }
-
- public static void EnsureHasNugetNetFrameworkRefAssemblies(MSBuildProject project)
+ public static void EnsureGodotSdkIsUpToDate(MSBuildProject project)
{
var root = project.Root;
+ string godotSdkAttrValue = ProjectGenerator.GodotSdkAttrValue;
- bool found = root.ItemGroups.Any(g => string.IsNullOrEmpty(g.Condition) && g.Items.Any(
- item => item.ItemType == "PackageReference" && item.Include == "Microsoft.NETFramework.ReferenceAssemblies"));
-
- if (found)
+ if (!string.IsNullOrEmpty(root.Sdk) && root.Sdk.Trim().Equals(godotSdkAttrValue, StringComparison.OrdinalIgnoreCase))
return;
- var frameworkRefAssembliesItem = root.AddItem("PackageReference", "Microsoft.NETFramework.ReferenceAssemblies");
-
- // Use metadata (child nodes) instead of attributes for the PackageReference.
- // This is for compatibility with 3.2, where GodotTools uses an old Microsoft.Build.
- frameworkRefAssembliesItem.AddMetadata("Version", "1.0.0");
- frameworkRefAssembliesItem.AddMetadata("PrivateAssets", "All");
-
+ root.Sdk = godotSdkAttrValue;
project.HasUnsavedChanges = true;
}
}
diff --git a/modules/mono/editor/GodotTools/GodotTools/BottomPanel.cs b/modules/mono/editor/GodotTools/GodotTools/BottomPanel.cs
index 3de3d8d318..3ab669a9f3 100644
--- a/modules/mono/editor/GodotTools/GodotTools/BottomPanel.cs
+++ b/modules/mono/editor/GodotTools/GodotTools/BottomPanel.cs
@@ -24,48 +24,50 @@ namespace GodotTools
private Button errorsBtn;
private Button viewLogBtn;
- private void _UpdateBuildTabsList()
+ private void _UpdateBuildTab(int index, int? currentTab)
{
- buildTabsList.Clear();
+ var tab = (BuildTab)buildTabs.GetChild(index);
- int currentTab = buildTabs.CurrentTab;
+ string itemName = Path.GetFileNameWithoutExtension(tab.BuildInfo.Solution);
+ itemName += " [" + tab.BuildInfo.Configuration + "]";
- bool noCurrentTab = currentTab < 0 || currentTab >= buildTabs.GetTabCount();
+ buildTabsList.AddItem(itemName, tab.IconTexture);
- for (int i = 0; i < buildTabs.GetChildCount(); i++)
- {
- var tab = (BuildTab)buildTabs.GetChild(i);
+ string itemTooltip = "Solution: " + tab.BuildInfo.Solution;
+ itemTooltip += "\nConfiguration: " + tab.BuildInfo.Configuration;
+ itemTooltip += "\nStatus: ";
- if (tab == null)
- continue;
+ if (tab.BuildExited)
+ itemTooltip += tab.BuildResult == BuildTab.BuildResults.Success ? "Succeeded" : "Errored";
+ else
+ itemTooltip += "Running";
- string itemName = Path.GetFileNameWithoutExtension(tab.BuildInfo.Solution);
- itemName += " [" + tab.BuildInfo.Configuration + "]";
+ if (!tab.BuildExited || tab.BuildResult == BuildTab.BuildResults.Error)
+ itemTooltip += $"\nErrors: {tab.ErrorCount}";
- buildTabsList.AddItem(itemName, tab.IconTexture);
+ itemTooltip += $"\nWarnings: {tab.WarningCount}";
- string itemTooltip = "Solution: " + tab.BuildInfo.Solution;
- itemTooltip += "\nConfiguration: " + tab.BuildInfo.Configuration;
- itemTooltip += "\nStatus: ";
+ buildTabsList.SetItemTooltip(index, itemTooltip);
- if (tab.BuildExited)
- itemTooltip += tab.BuildResult == BuildTab.BuildResults.Success ? "Succeeded" : "Errored";
- else
- itemTooltip += "Running";
+ // If this tab was already selected before the changes or if no tab was selected
+ if (currentTab == null || currentTab == index)
+ {
+ buildTabsList.Select(index);
+ _BuildTabsItemSelected(index);
+ }
+ }
- if (!tab.BuildExited || tab.BuildResult == BuildTab.BuildResults.Error)
- itemTooltip += $"\nErrors: {tab.ErrorCount}";
+ private void _UpdateBuildTabsList()
+ {
+ buildTabsList.Clear();
- itemTooltip += $"\nWarnings: {tab.WarningCount}";
+ int? currentTab = buildTabs.CurrentTab;
- buildTabsList.SetItemTooltip(i, itemTooltip);
+ if (currentTab < 0 || currentTab >= buildTabs.GetTabCount())
+ currentTab = null;
- if (noCurrentTab || currentTab == i)
- {
- buildTabsList.Select(i);
- _BuildTabsItemSelected(i);
- }
- }
+ for (int i = 0; i < buildTabs.GetChildCount(); i++)
+ _UpdateBuildTab(i, currentTab);
}
public BuildTab GetBuildTabFor(BuildInfo buildInfo)
@@ -160,13 +162,7 @@ namespace GodotTools
}
}
- var godotDefines = new[]
- {
- OS.GetName(),
- Internal.GodotIs32Bits() ? "32" : "64"
- };
-
- bool buildSuccess = BuildManager.BuildProjectBlocking("Debug", godotDefines);
+ bool buildSuccess = BuildManager.BuildProjectBlocking("Debug");
if (!buildSuccess)
return;
@@ -272,7 +268,7 @@ namespace GodotTools
};
panelTabs.AddChild(panelBuildsTab);
- var toolBarHBox = new HBoxContainer { SizeFlagsHorizontal = (int)SizeFlags.ExpandFill };
+ var toolBarHBox = new HBoxContainer {SizeFlagsHorizontal = (int)SizeFlags.ExpandFill};
panelBuildsTab.AddChild(toolBarHBox);
var buildProjectBtn = new Button
@@ -325,7 +321,7 @@ namespace GodotTools
};
panelBuildsTab.AddChild(hsc);
- buildTabsList = new ItemList { SizeFlagsHorizontal = (int)SizeFlags.ExpandFill };
+ buildTabsList = new ItemList {SizeFlagsHorizontal = (int)SizeFlags.ExpandFill};
buildTabsList.ItemSelected += _BuildTabsItemSelected;
buildTabsList.NothingSelected += _BuildTabsNothingSelected;
hsc.AddChild(buildTabsList);
diff --git a/modules/mono/editor/GodotTools/GodotTools/BuildManager.cs b/modules/mono/editor/GodotTools/GodotTools/BuildManager.cs
index 0974d23176..6399991b84 100644
--- a/modules/mono/editor/GodotTools/GodotTools/BuildManager.cs
+++ b/modules/mono/editor/GodotTools/GodotTools/BuildManager.cs
@@ -6,6 +6,7 @@ using GodotTools.Build;
using GodotTools.Ides.Rider;
using GodotTools.Internals;
using GodotTools.Utils;
+using JetBrains.Annotations;
using static GodotTools.Internals.Globals;
using File = GodotTools.Utils.File;
@@ -152,7 +153,7 @@ namespace GodotTools
}
}
- public static bool BuildProjectBlocking(string config, IEnumerable<string> godotDefines)
+ public static bool BuildProjectBlocking(string config, [CanBeNull] string platform = null)
{
if (!File.Exists(GodotSharpDirs.ProjectSlnPath))
return true; // No solution to build
@@ -168,29 +169,18 @@ namespace GodotTools
return false;
}
- var editorSettings = GodotSharpEditor.Instance.GetEditorInterface().GetEditorSettings();
- var buildTool = (BuildTool)editorSettings.GetSetting("mono/builds/build_tool");
-
using (var pr = new EditorProgress("mono_project_debug_build", "Building project solution...", 1))
{
pr.Step("Building project solution", 0);
var buildInfo = new BuildInfo(GodotSharpDirs.ProjectSlnPath, targets: new[] {"Build"}, config, restore: true);
- bool escapeNeedsDoubleBackslash = buildTool == BuildTool.MsBuildMono || buildTool == BuildTool.DotnetCli;
-
- // Add Godot defines
- string constants = !escapeNeedsDoubleBackslash ? "GodotDefineConstants=\"" : "GodotDefineConstants=\\\"";
-
- foreach (var godotDefine in godotDefines)
- constants += $"GODOT_{godotDefine.ToUpper().Replace("-", "_").Replace(" ", "_").Replace(";", "_")};";
+ // If a platform was not specified, try determining the current one. If that fails, let MSBuild auto-detect it.
+ if (platform != null || OS.PlatformNameMap.TryGetValue(Godot.OS.GetName(), out platform))
+ buildInfo.CustomProperties.Add($"GodotTargetPlatform={platform}");
if (Internal.GodotIsRealTDouble())
- constants += "GODOT_REAL_T_IS_DOUBLE;";
-
- constants += !escapeNeedsDoubleBackslash ? "\"" : "\\\"";
-
- buildInfo.CustomProperties.Add(constants);
+ buildInfo.CustomProperties.Add("GodotRealTIsDouble=true");
if (!Build(buildInfo))
{
@@ -233,13 +223,7 @@ namespace GodotTools
return true; // Requested play from an external editor/IDE which already built the project
}
- var godotDefines = new[]
- {
- Godot.OS.GetName(),
- Internal.GodotIs32Bits() ? "32" : "64"
- };
-
- return BuildProjectBlocking("Debug", godotDefines);
+ return BuildProjectBlocking("Debug");
}
public static void Initialize()
diff --git a/modules/mono/editor/GodotTools/GodotTools/CsProjOperations.cs b/modules/mono/editor/GodotTools/GodotTools/CsProjOperations.cs
index 421729cc11..a8afb38728 100644
--- a/modules/mono/editor/GodotTools/GodotTools/CsProjOperations.cs
+++ b/modules/mono/editor/GodotTools/GodotTools/CsProjOperations.cs
@@ -1,9 +1,9 @@
using Godot;
using System;
+using System.Linq;
using Godot.Collections;
using GodotTools.Internals;
using GodotTools.ProjectEditor;
-using static GodotTools.Internals.Globals;
using File = GodotTools.Utils.File;
using Directory = GodotTools.Utils.Directory;
@@ -15,7 +15,7 @@ namespace GodotTools
{
try
{
- return ProjectGenerator.GenGameProject(dir, name, compileItems: new string[] { });
+ return ProjectGenerator.GenAndSaveGameProject(dir, name);
}
catch (Exception e)
{
@@ -24,14 +24,6 @@ namespace GodotTools
}
}
- public static void AddItem(string projectPath, string itemType, string include)
- {
- if (!(bool)GlobalDef("mono/project/auto_update_project", true))
- return;
-
- ProjectUtils.AddItemToProjectChecked(projectPath, itemType, include);
- }
-
private static readonly DateTime Epoch = new DateTime(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc);
private static ulong ConvertToTimestamp(this DateTime value)
@@ -40,81 +32,77 @@ namespace GodotTools
return (ulong)elapsedTime.TotalSeconds;
}
- public static void GenerateScriptsMetadata(string projectPath, string outputPath)
+ private static bool TryParseFileMetadata(string includeFile, ulong modifiedTime, out Dictionary fileMetadata)
{
- if (File.Exists(outputPath))
- File.Delete(outputPath);
+ fileMetadata = null;
- var oldDict = Internal.GetScriptsMetadataOrNothing();
- var newDict = new Godot.Collections.Dictionary<string, object>();
+ var parseError = ScriptClassParser.ParseFile(includeFile, out var classes, out string errorStr);
- foreach (var includeFile in ProjectUtils.GetIncludeFiles(projectPath, "Compile"))
+ if (parseError != Error.Ok)
{
- string projectIncludeFile = ("res://" + includeFile).SimplifyGodotPath();
+ GD.PushError($"Failed to determine namespace and class for script: {includeFile}. Parse error: {errorStr ?? parseError.ToString()}");
+ return false;
+ }
- ulong modifiedTime = File.GetLastWriteTime(projectIncludeFile).ConvertToTimestamp();
+ string searchName = System.IO.Path.GetFileNameWithoutExtension(includeFile);
- if (oldDict.TryGetValue(projectIncludeFile, out var oldFileVar))
- {
- var oldFileDict = (Dictionary)oldFileVar;
-
- if (ulong.TryParse(oldFileDict["modified_time"] as string, out ulong storedModifiedTime))
- {
- if (storedModifiedTime == modifiedTime)
- {
- // No changes so no need to parse again
- newDict[projectIncludeFile] = oldFileDict;
- continue;
- }
- }
- }
+ var firstMatch = classes.FirstOrDefault(classDecl =>
+ classDecl.BaseCount != 0 && // If it doesn't inherit anything, it can't be a Godot.Object.
+ classDecl.SearchName != searchName // Filter by the name we're looking for
+ );
+
+ if (firstMatch == null)
+ return false; // Not found
- Error parseError = ScriptClassParser.ParseFile(projectIncludeFile, out var classes, out string errorStr);
- if (parseError != Error.Ok)
+ fileMetadata = new Dictionary
+ {
+ ["modified_time"] = $"{modifiedTime}",
+ ["class"] = new Dictionary
{
- GD.PushError($"Failed to determine namespace and class for script: {projectIncludeFile}. Parse error: {errorStr ?? parseError.ToString()}");
- continue;
+ ["namespace"] = firstMatch.Namespace,
+ ["class_name"] = firstMatch.Name,
+ ["nested"] = firstMatch.Nested
}
+ };
- string searchName = System.IO.Path.GetFileNameWithoutExtension(projectIncludeFile);
-
- var classDict = new Dictionary();
+ return true;
+ }
- foreach (var classDecl in classes)
- {
- if (classDecl.BaseCount == 0)
- continue; // Does not inherit nor implement anything, so it can't be a script class
+ public static void GenerateScriptsMetadata(string projectPath, string outputPath)
+ {
+ var metadataDict = Internal.GetScriptsMetadataOrNothing().Duplicate();
- string classCmp = classDecl.Nested ?
- classDecl.Name.Substring(classDecl.Name.LastIndexOf(".", StringComparison.Ordinal) + 1) :
- classDecl.Name;
+ bool IsUpToDate(string includeFile, ulong modifiedTime)
+ {
+ return metadataDict.TryGetValue(includeFile, out var oldFileVar) &&
+ ulong.TryParse(((Dictionary)oldFileVar)["modified_time"] as string,
+ out ulong storedModifiedTime) && storedModifiedTime == modifiedTime;
+ }
- if (classCmp != searchName)
- continue;
+ var outdatedFiles = ProjectUtils.GetIncludeFiles(projectPath, "Compile")
+ .Select(path => ("res://" + path).SimplifyGodotPath())
+ .ToDictionary(path => path, path => File.GetLastWriteTime(path).ConvertToTimestamp())
+ .Where(pair => !IsUpToDate(includeFile: pair.Key, modifiedTime: pair.Value))
+ .ToArray();
- classDict["namespace"] = classDecl.Namespace;
- classDict["class_name"] = classDecl.Name;
- classDict["nested"] = classDecl.Nested;
- break;
- }
+ foreach (var pair in outdatedFiles)
+ {
+ metadataDict.Remove(pair.Key);
- if (classDict.Count == 0)
- continue; // Not found
+ string includeFile = pair.Key;
- newDict[projectIncludeFile] = new Dictionary { ["modified_time"] = $"{modifiedTime}", ["class"] = classDict };
+ if (TryParseFileMetadata(includeFile, modifiedTime: pair.Value, out var fileMetadata))
+ metadataDict[includeFile] = fileMetadata;
}
- if (newDict.Count > 0)
- {
- string json = JSON.Print(newDict);
+ string json = metadataDict.Count <= 0 ? "{}" : JSON.Print(metadataDict);
- string baseDir = outputPath.GetBaseDir();
+ string baseDir = outputPath.GetBaseDir();
- if (!Directory.Exists(baseDir))
- Directory.CreateDirectory(baseDir);
+ if (!Directory.Exists(baseDir))
+ Directory.CreateDirectory(baseDir);
- File.WriteAllText(outputPath, json);
- }
+ File.WriteAllText(outputPath, json);
}
}
}
diff --git a/modules/mono/editor/GodotTools/GodotTools/Export/ExportPlugin.cs b/modules/mono/editor/GodotTools/GodotTools/Export/ExportPlugin.cs
index 6bfbc62f3b..554763eecb 100644
--- a/modules/mono/editor/GodotTools/GodotTools/Export/ExportPlugin.cs
+++ b/modules/mono/editor/GodotTools/GodotTools/Export/ExportPlugin.cs
@@ -7,6 +7,7 @@ using System.Linq;
using System.Runtime.CompilerServices;
using GodotTools.Core;
using GodotTools.Internals;
+using JetBrains.Annotations;
using static GodotTools.Internals.Globals;
using Directory = GodotTools.Utils.Directory;
using File = GodotTools.Utils.File;
@@ -145,9 +146,7 @@ namespace GodotTools.Export
if (!File.Exists(GodotSharpDirs.ProjectSlnPath))
return;
- string platform = DeterminePlatformFromFeatures(features);
-
- if (platform == null)
+ if (!DeterminePlatformFromFeatures(features, out string platform))
throw new NotSupportedException("Target platform not supported");
string outputDir = new FileInfo(path).Directory?.FullName ??
@@ -160,10 +159,7 @@ namespace GodotTools.Export
AddFile(scriptsMetadataPath, scriptsMetadataPath);
- // Turn export features into defines
- var godotDefines = features;
-
- if (!BuildManager.BuildProjectBlocking(buildConfig, godotDefines))
+ if (!BuildManager.BuildProjectBlocking(buildConfig, platform))
throw new Exception("Failed to build project");
// Add dependency assemblies
@@ -289,6 +285,7 @@ namespace GodotTools.Export
}
}
+ [NotNull]
private static string ExportDataDirectory(string[] features, string platform, bool isDebug, string outputDir)
{
string target = isDebug ? "release_debug" : "release";
@@ -343,18 +340,19 @@ namespace GodotTools.Export
private static bool PlatformHasTemplateDir(string platform)
{
// OSX export templates are contained in a zip, so we place our custom template inside it and let Godot do the rest.
- return !new[] { OS.Platforms.OSX, OS.Platforms.Android, OS.Platforms.iOS, OS.Platforms.HTML5 }.Contains(platform);
+ return !new[] {OS.Platforms.OSX, OS.Platforms.Android, OS.Platforms.iOS, OS.Platforms.HTML5}.Contains(platform);
}
- private static string DeterminePlatformFromFeatures(IEnumerable<string> features)
+ private static bool DeterminePlatformFromFeatures(IEnumerable<string> features, out string platform)
{
foreach (var feature in features)
{
- if (OS.PlatformNameMap.TryGetValue(feature, out string platform))
- return platform;
+ if (OS.PlatformNameMap.TryGetValue(feature, out platform))
+ return true;
}
- return null;
+ platform = null;
+ return false;
}
private static string GetBclProfileDir(string profile)
@@ -391,7 +389,7 @@ namespace GodotTools.Export
/// </summary>
private static bool PlatformRequiresCustomBcl(string platform)
{
- if (new[] { OS.Platforms.Android, OS.Platforms.iOS, OS.Platforms.HTML5 }.Contains(platform))
+ if (new[] {OS.Platforms.Android, OS.Platforms.iOS, OS.Platforms.HTML5}.Contains(platform))
return true;
// The 'net_4_x' BCL is not compatible between Windows and the other platforms.
diff --git a/modules/mono/editor/GodotTools/GodotTools/GodotSharpEditor.cs b/modules/mono/editor/GodotTools/GodotTools/GodotSharpEditor.cs
index f330f9ed2c..a363ecc920 100644
--- a/modules/mono/editor/GodotTools/GodotTools/GodotSharpEditor.cs
+++ b/modules/mono/editor/GodotTools/GodotTools/GodotSharpEditor.cs
@@ -175,36 +175,6 @@ namespace GodotTools
// Once shown a first time, it can be seen again via the Mono menu - it doesn't have to be exclusive from that time on.
aboutDialog.Exclusive = false;
}
-
- var fileSystemDock = GetEditorInterface().GetFileSystemDock();
-
- fileSystemDock.FilesMoved += (file, newFile) =>
- {
- if (Path.GetExtension(file) == Internal.CSharpLanguageExtension)
- {
- ProjectUtils.RenameItemInProjectChecked(GodotSharpDirs.ProjectCsProjPath, "Compile",
- ProjectSettings.GlobalizePath(file), ProjectSettings.GlobalizePath(newFile));
- }
- };
-
- fileSystemDock.FileRemoved += file =>
- {
- if (Path.GetExtension(file) == Internal.CSharpLanguageExtension)
- ProjectUtils.RemoveItemFromProjectChecked(GodotSharpDirs.ProjectCsProjPath, "Compile",
- ProjectSettings.GlobalizePath(file));
- };
-
- fileSystemDock.FolderMoved += (oldFolder, newFolder) =>
- {
- ProjectUtils.RenameItemsToNewFolderInProjectChecked(GodotSharpDirs.ProjectCsProjPath, "Compile",
- ProjectSettings.GlobalizePath(oldFolder), ProjectSettings.GlobalizePath(newFolder));
- };
-
- fileSystemDock.FolderRemoved += oldFolder =>
- {
- ProjectUtils.RemoveItemsInFolderFromProjectChecked(GodotSharpDirs.ProjectCsProjPath, "Compile",
- ProjectSettings.GlobalizePath(oldFolder));
- };
}
}
@@ -389,6 +359,37 @@ namespace GodotTools
return BuildManager.EditorBuildCallback();
}
+ private void ApplyNecessaryChangesToSolution()
+ {
+ try
+ {
+ // Migrate solution from old configuration names to: Debug, ExportDebug and ExportRelease
+ DotNetSolution.MigrateFromOldConfigNames(GodotSharpDirs.ProjectSlnPath);
+
+ var msbuildProject = ProjectUtils.Open(GodotSharpDirs.ProjectCsProjPath)
+ ?? throw new Exception("Cannot open C# project");
+
+ // NOTE: The order in which changes are made to the project is important
+
+ // Migrate to MSBuild project Sdks style if using the old style
+ ProjectUtils.MigrateToProjectSdksStyle(msbuildProject, ProjectAssemblyName);
+
+ ProjectUtils.EnsureGodotSdkIsUpToDate(msbuildProject);
+
+ if (msbuildProject.HasUnsavedChanges)
+ {
+ // Save a copy of the project before replacing it
+ FileUtils.SaveBackupCopy(GodotSharpDirs.ProjectCsProjPath);
+
+ msbuildProject.Save();
+ }
+ }
+ catch (Exception e)
+ {
+ GD.PushError(e.ToString());
+ }
+ }
+
public override void EnablePlugin()
{
base.EnablePlugin();
@@ -468,42 +469,7 @@ namespace GodotTools
if (File.Exists(GodotSharpDirs.ProjectSlnPath) && File.Exists(GodotSharpDirs.ProjectCsProjPath))
{
- try
- {
- // Migrate solution from old configuration names to: Debug, ExportDebug and ExportRelease
- DotNetSolution.MigrateFromOldConfigNames(GodotSharpDirs.ProjectSlnPath);
-
- var msbuildProject = ProjectUtils.Open(GodotSharpDirs.ProjectCsProjPath)
- ?? throw new Exception("Cannot open C# project");
-
- // NOTE: The order in which changes are made to the project is important
-
- // Migrate csproj from old configuration names to: Debug, ExportDebug and ExportRelease
- ProjectUtils.MigrateFromOldConfigNames(msbuildProject);
-
- // Apply the other fixes only after configurations have been migrated
-
- // Make sure the existing project has the ProjectTypeGuids property (for VisualStudio)
- ProjectUtils.EnsureHasProjectTypeGuids(msbuildProject);
-
- // Make sure the existing project has Api assembly references configured correctly
- ProjectUtils.FixApiHintPath(msbuildProject);
-
- // Make sure the existing project references the Microsoft.NETFramework.ReferenceAssemblies nuget package
- ProjectUtils.EnsureHasNugetNetFrameworkRefAssemblies(msbuildProject);
-
- if (msbuildProject.HasUnsavedChanges)
- {
- // Save a copy of the project before replacing it
- FileUtils.SaveBackupCopy(GodotSharpDirs.ProjectCsProjPath);
-
- msbuildProject.Save();
- }
- }
- catch (Exception e)
- {
- GD.PushError(e.ToString());
- }
+ ApplyNecessaryChangesToSolution();
}
else
{
diff --git a/modules/mono/editor/GodotTools/GodotTools/Internals/ScriptClassParser.cs b/modules/mono/editor/GodotTools/GodotTools/Internals/ScriptClassParser.cs
index 569f27649f..c72a84c513 100644
--- a/modules/mono/editor/GodotTools/GodotTools/Internals/ScriptClassParser.cs
+++ b/modules/mono/editor/GodotTools/GodotTools/Internals/ScriptClassParser.cs
@@ -15,6 +15,10 @@ namespace GodotTools.Internals
public bool Nested { get; }
public long BaseCount { get; }
+ public string SearchName => Nested ?
+ Name.Substring(Name.LastIndexOf(".", StringComparison.Ordinal) + 1) :
+ Name;
+
public ClassDecl(string name, string @namespace, bool nested, long baseCount)
{
Name = name;
diff --git a/modules/mono/editor/bindings_generator.cpp b/modules/mono/editor/bindings_generator.cpp
index 79e4b7c794..a17c371117 100644
--- a/modules/mono/editor/bindings_generator.cpp
+++ b/modules/mono/editor/bindings_generator.cpp
@@ -45,7 +45,6 @@
#include "../mono_gd/gd_mono_marshal.h"
#include "../utils/path_utils.h"
#include "../utils/string_utils.h"
-#include "csharp_project.h"
#define CS_INDENT " " // 4 whitespaces
diff --git a/modules/mono/editor/csharp_project.cpp b/modules/mono/editor/csharp_project.cpp
deleted file mode 100644
index 6f54eb09a2..0000000000
--- a/modules/mono/editor/csharp_project.cpp
+++ /dev/null
@@ -1,69 +0,0 @@
-/*************************************************************************/
-/* csharp_project.cpp */
-/*************************************************************************/
-/* This file is part of: */
-/* GODOT ENGINE */
-/* https://godotengine.org */
-/*************************************************************************/
-/* Copyright (c) 2007-2020 Juan Linietsky, Ariel Manzur. */
-/* Copyright (c) 2014-2020 Godot Engine contributors (cf. AUTHORS.md). */
-/* */
-/* 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 "csharp_project.h"
-
-#include "core/io/json.h"
-#include "core/os/dir_access.h"
-#include "core/os/file_access.h"
-#include "core/os/os.h"
-#include "core/project_settings.h"
-
-#include "../csharp_script.h"
-#include "../mono_gd/gd_mono_class.h"
-#include "../mono_gd/gd_mono_marshal.h"
-#include "../utils/string_utils.h"
-#include "script_class_parser.h"
-
-namespace CSharpProject {
-
-void add_item(const String &p_project_path, const String &p_item_type, const String &p_include) {
- if (!GLOBAL_DEF("mono/project/auto_update_project", true)) {
- return;
- }
-
- GDMonoAssembly *tools_project_editor_assembly = GDMono::get_singleton()->get_tools_project_editor_assembly();
-
- GDMonoClass *klass = tools_project_editor_assembly->get_class("GodotTools.ProjectEditor", "ProjectUtils");
-
- Variant project_path = p_project_path;
- Variant item_type = p_item_type;
- Variant include = p_include;
- const Variant *args[3] = { &project_path, &item_type, &include };
- MonoException *exc = nullptr;
- klass->get_method("AddItemToProjectChecked", 3)->invoke(nullptr, args, &exc);
-
- if (exc) {
- GDMonoUtils::debug_print_unhandled_exception(exc);
- ERR_FAIL();
- }
-}
-
-} // namespace CSharpProject
diff --git a/modules/mono/glue/GodotSharp/GodotSharp/GodotSharp.csproj b/modules/mono/glue/GodotSharp/GodotSharp/GodotSharp.csproj
index 06ec2483c8..86a16c17f1 100644
--- a/modules/mono/glue/GodotSharp/GodotSharp/GodotSharp.csproj
+++ b/modules/mono/glue/GodotSharp/GodotSharp/GodotSharp.csproj
@@ -1,39 +1,17 @@
-<?xml version="1.0" encoding="utf-8"?>
-<Project ToolsVersion="4.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
+<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
- <Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
- <Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
<ProjectGuid>{AEBF0036-DA76-4341-B651-A3F2856AB2FA}</ProjectGuid>
- <OutputType>Library</OutputType>
<OutputPath>bin/$(Configuration)</OutputPath>
+ <AppendTargetFrameworkToOutputPath>false</AppendTargetFrameworkToOutputPath>
<RootNamespace>Godot</RootNamespace>
- <AssemblyName>GodotSharp</AssemblyName>
- <TargetFrameworkVersion>v4.5</TargetFrameworkVersion>
+ <TargetFramework>netstandard2.1</TargetFramework>
<DocumentationFile>$(OutputPath)/$(AssemblyName).xml</DocumentationFile>
- <BaseIntermediateOutputPath>obj</BaseIntermediateOutputPath>
+ <EnableDefaultItems>false</EnableDefaultItems>
</PropertyGroup>
- <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
- <DebugSymbols>true</DebugSymbols>
- <DebugType>portable</DebugType>
- <Optimize>false</Optimize>
- <DefineConstants>$(GodotDefineConstants);GODOT;DEBUG;</DefineConstants>
- <ErrorReport>prompt</ErrorReport>
- <WarningLevel>4</WarningLevel>
- <ConsolePause>false</ConsolePause>
- </PropertyGroup>
- <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
- <DebugType>portable</DebugType>
- <Optimize>true</Optimize>
- <DefineConstants>$(GodotDefineConstants);GODOT;</DefineConstants>
- <ErrorReport>prompt</ErrorReport>
- <WarningLevel>4</WarningLevel>
- <ConsolePause>false</ConsolePause>
+ <PropertyGroup>
+ <DefineConstants>$(DefineConstants);GODOT</DefineConstants>
</PropertyGroup>
<ItemGroup>
- <PackageReference Include="Microsoft.NETFramework.ReferenceAssemblies" Version="1.0.0" PrivateAssets="All" />
- <Reference Include="System" />
- </ItemGroup>
- <ItemGroup>
<Compile Include="Core\AABB.cs" />
<Compile Include="Core\Array.cs" />
<Compile Include="Core\Attributes\ExportAttribute.cs" />
@@ -90,5 +68,4 @@
Fortunately code completion, go to definition and such still work.
-->
<Import Project="Generated\GeneratedIncludes.props" />
- <Import Project="$(MSBuildBinPath)\Microsoft.CSharp.targets" />
</Project>
diff --git a/modules/mono/glue/GodotSharp/GodotSharp/Properties/AssemblyInfo.cs b/modules/mono/glue/GodotSharp/GodotSharp/Properties/AssemblyInfo.cs
index f84e0183f6..da6f293871 100644
--- a/modules/mono/glue/GodotSharp/GodotSharp/Properties/AssemblyInfo.cs
+++ b/modules/mono/glue/GodotSharp/GodotSharp/Properties/AssemblyInfo.cs
@@ -1,27 +1,3 @@
-using System.Reflection;
using System.Runtime.CompilerServices;
-// Information about this assembly is defined by the following attributes.
-// Change them to the values specific to your project.
-
-[assembly: AssemblyTitle("GodotSharp")]
-[assembly: AssemblyDescription("")]
-[assembly: AssemblyConfiguration("")]
-[assembly: AssemblyCompany("")]
-[assembly: AssemblyProduct("")]
-[assembly: AssemblyCopyright("")]
-[assembly: AssemblyTrademark("")]
-[assembly: AssemblyCulture("")]
-
-// The assembly version has the format "{Major}.{Minor}.{Build}.{Revision}".
-// The form "{Major}.{Minor}.*" will automatically update the build and revision,
-// and "{Major}.{Minor}.{Build}.*" will update just the revision.
-
-[assembly: AssemblyVersion("1.0.*")]
-
-// The following attributes are used to specify the signing key for the assembly,
-// if desired. See the Mono documentation for more information about signing.
-
-//[assembly: AssemblyDelaySign(false)]
-//[assembly: AssemblyKeyFile("")]
[assembly: InternalsVisibleTo("GodotSharpEditor")]
diff --git a/modules/mono/glue/GodotSharp/GodotSharpEditor/GodotSharpEditor.csproj b/modules/mono/glue/GodotSharp/GodotSharpEditor/GodotSharpEditor.csproj
index 8785931312..a8c4ba96b5 100644
--- a/modules/mono/glue/GodotSharp/GodotSharpEditor/GodotSharpEditor.csproj
+++ b/modules/mono/glue/GodotSharp/GodotSharpEditor/GodotSharpEditor.csproj
@@ -1,46 +1,26 @@
-<?xml version="1.0" encoding="utf-8"?>
-<Project ToolsVersion="4.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
+<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
- <Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
- <Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
<ProjectGuid>{8FBEC238-D944-4074-8548-B3B524305905}</ProjectGuid>
- <OutputType>Library</OutputType>
<OutputPath>bin/$(Configuration)</OutputPath>
+ <AppendTargetFrameworkToOutputPath>false</AppendTargetFrameworkToOutputPath>
<RootNamespace>Godot</RootNamespace>
- <AssemblyName>GodotSharpEditor</AssemblyName>
- <TargetFrameworkVersion>v4.5</TargetFrameworkVersion>
+ <TargetFramework>netstandard2.1</TargetFramework>
<DocumentationFile>$(OutputPath)/$(AssemblyName).xml</DocumentationFile>
- <BaseIntermediateOutputPath>obj</BaseIntermediateOutputPath>
+ <EnableDefaultItems>false</EnableDefaultItems>
</PropertyGroup>
- <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
- <DebugSymbols>true</DebugSymbols>
- <DebugType>portable</DebugType>
- <Optimize>false</Optimize>
- <DefineConstants>$(GodotDefineConstants);GODOT;DEBUG;</DefineConstants>
- <ErrorReport>prompt</ErrorReport>
- <WarningLevel>4</WarningLevel>
- <ConsolePause>false</ConsolePause>
- </PropertyGroup>
- <PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
- <DebugType>portable</DebugType>
- <Optimize>true</Optimize>
- <DefineConstants>$(GodotDefineConstants);GODOT;</DefineConstants>
- <ErrorReport>prompt</ErrorReport>
- <WarningLevel>4</WarningLevel>
- <ConsolePause>false</ConsolePause>
+ <PropertyGroup>
+ <DefineConstants>$(DefineConstants);GODOT</DefineConstants>
</PropertyGroup>
<ItemGroup>
- <PackageReference Include="Microsoft.NETFramework.ReferenceAssemblies" Version="1.0.0" PrivateAssets="All" />
- <Reference Include="System" />
- </ItemGroup>
- <ItemGroup>
- <Compile Include="Properties\AssemblyInfo.cs" />
- </ItemGroup>
- <Import Project="Generated\GeneratedIncludes.props" />
- <ItemGroup>
<ProjectReference Include="..\GodotSharp\GodotSharp.csproj">
- <Private>False</Private>
+ <Private>false</Private>
</ProjectReference>
</ItemGroup>
- <Import Project="$(MSBuildBinPath)\Microsoft.CSharp.targets" />
+ <!--
+ We import a props file with auto-generated includes. This works well with Rider.
+ However, Visual Studio and MonoDevelop won't list them in the solution explorer.
+ We can't use wildcards as there may be undesired old files still hanging around.
+ Fortunately code completion, go to definition and such still work.
+ -->
+ <Import Project="Generated\GeneratedIncludes.props" />
</Project>
diff --git a/modules/mono/glue/GodotSharp/GodotSharpEditor/Properties/AssemblyInfo.cs b/modules/mono/glue/GodotSharp/GodotSharpEditor/Properties/AssemblyInfo.cs
deleted file mode 100644
index 3684b7a3cb..0000000000
--- a/modules/mono/glue/GodotSharp/GodotSharpEditor/Properties/AssemblyInfo.cs
+++ /dev/null
@@ -1,25 +0,0 @@
-using System.Reflection;
-
-// Information about this assembly is defined by the following attributes.
-// Change them to the values specific to your project.
-
-[assembly: AssemblyTitle("GodotSharpEditor")]
-[assembly: AssemblyDescription("")]
-[assembly: AssemblyConfiguration("")]
-[assembly: AssemblyCompany("")]
-[assembly: AssemblyProduct("")]
-[assembly: AssemblyCopyright("")]
-[assembly: AssemblyTrademark("")]
-[assembly: AssemblyCulture("")]
-
-// The assembly version has the format "{Major}.{Minor}.{Build}.{Revision}".
-// The form "{Major}.{Minor}.*" will automatically update the build and revision,
-// and "{Major}.{Minor}.{Build}.*" will update just the revision.
-
-[assembly: AssemblyVersion("1.0.*")]
-
-// The following attributes are used to specify the signing key for the assembly,
-// if desired. See the Mono documentation for more information about signing.
-
-//[assembly: AssemblyDelaySign(false)]
-//[assembly: AssemblyKeyFile("")]
diff --git a/modules/visual_script/visual_script_editor.cpp b/modules/visual_script/visual_script_editor.cpp
index 2fcd9332a1..5581ea9318 100644
--- a/modules/visual_script/visual_script_editor.cpp
+++ b/modules/visual_script/visual_script_editor.cpp
@@ -2513,6 +2513,8 @@ RES VisualScriptEditor::get_edited_resource() const {
}
void VisualScriptEditor::set_edited_resource(const RES &p_res) {
+ ERR_FAIL_COND(script.is_valid());
+ ERR_FAIL_COND(p_res.is_null());
script = p_res;
signal_editor->script = script;
signal_editor->undo_redo = undo_redo;
@@ -2533,6 +2535,9 @@ void VisualScriptEditor::set_edited_resource(const RES &p_res) {
_update_members();
}
+void VisualScriptEditor::enable_editor() {
+}
+
Vector<String> VisualScriptEditor::get_functions() {
return Vector<String>();
}
@@ -2546,6 +2551,9 @@ String VisualScriptEditor::get_name() {
if (script->get_path().find("local://") == -1 && script->get_path().find("::") == -1) {
name = script->get_path().get_file();
if (is_unsaved()) {
+ if (script->get_path().empty()) {
+ name = TTR("[unsaved]");
+ }
name += "(*)";
}
} else if (script->get_name() != "") {
@@ -2562,7 +2570,11 @@ Ref<Texture2D> VisualScriptEditor::get_theme_icon() {
}
bool VisualScriptEditor::is_unsaved() {
- return script->is_edited() || script->are_subnodes_edited();
+ bool unsaved =
+ script->is_edited() ||
+ script->are_subnodes_edited() ||
+ script->get_path().empty(); // In memory.
+ return unsaved;
}
Variant VisualScriptEditor::get_edit_state() {
diff --git a/modules/visual_script/visual_script_editor.h b/modules/visual_script/visual_script_editor.h
index e59618e120..0c5665cee8 100644
--- a/modules/visual_script/visual_script_editor.h
+++ b/modules/visual_script/visual_script_editor.h
@@ -294,6 +294,7 @@ public:
virtual void apply_code() override;
virtual RES get_edited_resource() const override;
virtual void set_edited_resource(const RES &p_res) override;
+ virtual void enable_editor() override;
virtual Vector<String> get_functions() override;
virtual void reload_text() override;
virtual String get_name() override;
diff --git a/platform/android/detect.py b/platform/android/detect.py
index a4ac87f723..0accacb679 100644
--- a/platform/android/detect.py
+++ b/platform/android/detect.py
@@ -115,7 +115,8 @@ def configure(env):
if env["android_arch"] == "x86_64":
if get_platform(env["ndk_platform"]) < 21:
print(
- "WARNING: android_arch=x86_64 is not supported by ndk_platform lower than android-21; setting ndk_platform=android-21"
+ "WARNING: android_arch=x86_64 is not supported by ndk_platform lower than android-21; setting"
+ " ndk_platform=android-21"
)
env["ndk_platform"] = "android-21"
env["ARCH"] = "arch-x86_64"
@@ -136,7 +137,8 @@ def configure(env):
elif env["android_arch"] == "arm64v8":
if get_platform(env["ndk_platform"]) < 21:
print(
- "WARNING: android_arch=arm64v8 is not supported by ndk_platform lower than android-21; setting ndk_platform=android-21"
+ "WARNING: android_arch=arm64v8 is not supported by ndk_platform lower than android-21; setting"
+ " ndk_platform=android-21"
)
env["ndk_platform"] = "android-21"
env["ARCH"] = "arch-arm64"
@@ -231,7 +233,10 @@ def configure(env):
env.Append(CPPDEFINES=[("__ANDROID_API__", str(get_platform(env["ndk_platform"])))])
env.Append(
- CCFLAGS="-fpic -ffunction-sections -funwind-tables -fstack-protector-strong -fvisibility=hidden -fno-strict-aliasing".split()
+ CCFLAGS=(
+ "-fpic -ffunction-sections -funwind-tables -fstack-protector-strong -fvisibility=hidden"
+ " -fno-strict-aliasing".split()
+ )
)
env.Append(CPPDEFINES=["NO_STATVFS", "GLES_ENABLED"])
diff --git a/platform/android/display_server_android.cpp b/platform/android/display_server_android.cpp
index 7193519a52..235c9ff665 100644
--- a/platform/android/display_server_android.cpp
+++ b/platform/android/display_server_android.cpp
@@ -155,12 +155,12 @@ bool DisplayServerAndroid::screen_is_touchscreen(int p_screen) const {
return true;
}
-void DisplayServerAndroid::virtual_keyboard_show(const String &p_existing_text, const Rect2 &p_screen_rect, int p_max_length, int p_cursor_start, int p_cursor_end) {
+void DisplayServerAndroid::virtual_keyboard_show(const String &p_existing_text, const Rect2 &p_screen_rect, bool p_multiline, int p_max_length, int p_cursor_start, int p_cursor_end) {
GodotIOJavaWrapper *godot_io_java = OS_Android::get_singleton()->get_godot_io_java();
ERR_FAIL_COND(!godot_io_java);
if (godot_io_java->has_vk()) {
- godot_io_java->show_vk(p_existing_text, p_max_length, p_cursor_start, p_cursor_end);
+ godot_io_java->show_vk(p_existing_text, p_multiline, p_max_length, p_cursor_start, p_cursor_end);
} else {
ERR_PRINT("Virtual keyboard not available");
}
diff --git a/platform/android/display_server_android.h b/platform/android/display_server_android.h
index 4cae52fa76..5cdc69ee83 100644
--- a/platform/android/display_server_android.h
+++ b/platform/android/display_server_android.h
@@ -113,7 +113,7 @@ public:
virtual int screen_get_dpi(int p_screen = SCREEN_OF_MAIN_WINDOW) const;
virtual bool screen_is_touchscreen(int p_screen = SCREEN_OF_MAIN_WINDOW) const;
- virtual void virtual_keyboard_show(const String &p_existing_text, const Rect2 &p_screen_rect = Rect2(), int p_max_length = -1, int p_cursor_start = -1, int p_cursor_end = -1);
+ virtual void virtual_keyboard_show(const String &p_existing_text, const Rect2 &p_screen_rect = Rect2(), bool p_multiline = false, int p_max_length = -1, int p_cursor_start = -1, int p_cursor_end = -1);
virtual void virtual_keyboard_hide();
virtual int virtual_keyboard_get_height() const;
diff --git a/platform/android/export/export.cpp b/platform/android/export/export.cpp
index 0213094c60..8d3257a365 100644
--- a/platform/android/export/export.cpp
+++ b/platform/android/export/export.cpp
@@ -768,6 +768,30 @@ class EditorExportPlatformAndroid : public EditorExportPlatform {
}
}
+ void _write_tmp_manifest(const Ref<EditorExportPreset> &p_preset, bool p_give_internet, bool p_debug) {
+ String manifest_text =
+ "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n"
+ "<manifest xmlns:android=\"http://schemas.android.com/apk/res/android\"\n"
+ " xmlns:tools=\"http://schemas.android.com/tools\">\n";
+
+ manifest_text += _get_screen_sizes_tag(p_preset);
+ manifest_text += _get_gles_tag();
+
+ Vector<String> perms;
+ _get_permissions(p_preset, p_give_internet, perms);
+ for (int i = 0; i < perms.size(); i++) {
+ manifest_text += vformat(" <uses-permission android:name=\"%s\" />\n", perms.get(i));
+ }
+
+ manifest_text += _get_xr_features_tag(p_preset);
+ manifest_text += _get_instrumentation_tag(p_preset);
+ String plugins_names = get_plugins_names(get_enabled_plugins(p_preset));
+ manifest_text += _get_application_tag(p_preset, plugins_names);
+ manifest_text += "</manifest>\n";
+ String manifest_path = vformat("res://android/build/src/%s/AndroidManifest.xml", (p_debug ? "debug" : "release"));
+ store_string_at_path(manifest_path, manifest_text);
+ }
+
void _fix_manifest(const Ref<EditorExportPreset> &p_preset, Vector<uint8_t> &p_manifest, bool p_give_internet) {
// Leaving the unused types commented because looking these constants up
// again later would be annoying
@@ -2064,6 +2088,7 @@ public:
EditorProgress ep("export", "Exporting for Android", 105, true);
bool use_custom_build = bool(p_preset->get("custom_template/use_custom_build"));
+ bool p_give_internet = p_flags & (DEBUG_FLAG_DUMB_CLIENT | DEBUG_FLAG_REMOTE_DEBUG);
Ref<Image> main_image;
Ref<Image> foreground;
@@ -2093,9 +2118,10 @@ public:
if (err != OK) {
EditorNode::add_io_error("Unable to overwrite res://android/build/res/*.xml files with project name");
}
- // Copies the project icon files into the appropriate Gradle project directory
+ // Copies the project icon files into the appropriate Gradle project directory.
_copy_icons_to_gradle_project(p_preset, main_image, foreground, background);
-
+ // Write an AndroidManifest.xml file into the Gradle project directory.
+ _write_tmp_manifest(p_preset, p_give_internet, p_debug);
//build project if custom build is enabled
String sdk_path = EDITOR_GET("export/android/custom_build_sdk_path");
@@ -2115,6 +2141,8 @@ public:
build_command = build_path.plus_file(build_command);
String package_name = get_package_name(p_preset->get("package/unique_name"));
+ String version_code = itos(p_preset->get("version/code"));
+ String version_name = p_preset->get("version/name");
Vector<PluginConfig> enabled_plugins = get_enabled_plugins(p_preset);
String local_plugins_binaries = get_plugins_binaries(BINARY_TYPE_LOCAL, enabled_plugins);
@@ -2128,6 +2156,8 @@ public:
}
cmdline.push_back("build");
cmdline.push_back("-Pexport_package_name=" + package_name); // argument to specify the package name.
+ cmdline.push_back("-Pexport_version_code=" + version_code); // argument to specify the version code.
+ cmdline.push_back("-Pexport_version_name=" + version_name); // argument to specify the version name.
cmdline.push_back("-Pplugins_local_binaries=" + local_plugins_binaries); // argument to specify the list of plugins local dependencies.
cmdline.push_back("-Pplugins_remote_binaries=" + remote_plugins_binaries); // argument to specify the list of plugins remote dependencies.
cmdline.push_back("-Pplugins_maven_repos=" + custom_maven_repos); // argument to specify the list of custom maven repos for the plugins dependencies.
@@ -2248,11 +2278,10 @@ public:
//write
- if (file == "AndroidManifest.xml") {
- _fix_manifest(p_preset, data, p_flags & (DEBUG_FLAG_DUMB_CLIENT | DEBUG_FLAG_REMOTE_DEBUG));
- }
-
if (!use_custom_build) {
+ if (file == "AndroidManifest.xml") {
+ _fix_manifest(p_preset, data, p_give_internet);
+ }
if (file == "resources.arsc") {
_fix_resources(p_preset, data);
}
diff --git a/platform/android/export/gradle_export_util.h b/platform/android/export/gradle_export_util.h
index 622860c307..209a664f8f 100644
--- a/platform/android/export/gradle_export_util.h
+++ b/platform/android/export/gradle_export_util.h
@@ -142,4 +142,104 @@ Error _create_project_name_strings_files(const Ref<EditorExportPreset> &p_preset
return OK;
}
+String bool_to_string(bool v) {
+ return v ? "true" : "false";
+}
+
+String _get_gles_tag() {
+ bool min_gles3 = ProjectSettings::get_singleton()->get("rendering/quality/driver/driver_name") == "GLES3" &&
+ !ProjectSettings::get_singleton()->get("rendering/quality/driver/fallback_to_gles2");
+ return min_gles3 ? " <uses-feature android:glEsVersion=\"0x00030000\" android:required=\"true\" />\n" : "";
+}
+
+String _get_screen_sizes_tag(const Ref<EditorExportPreset> &p_preset) {
+ String manifest_screen_sizes = " <supports-screens \n tools:node=\"replace\"";
+ String sizes[] = { "small", "normal", "large", "xlarge" };
+ size_t num_sizes = sizeof(sizes) / sizeof(sizes[0]);
+ for (size_t i = 0; i < num_sizes; i++) {
+ String feature_name = vformat("screen/support_%s", sizes[i]);
+ String feature_support = bool_to_string(p_preset->get(feature_name));
+ String xml_entry = vformat("\n android:%sScreens=\"%s\"", sizes[i], feature_support);
+ manifest_screen_sizes += xml_entry;
+ }
+ manifest_screen_sizes += " />\n";
+ return manifest_screen_sizes;
+}
+
+String _get_xr_features_tag(const Ref<EditorExportPreset> &p_preset) {
+ String manifest_xr_features;
+ bool uses_xr = (int)(p_preset->get("xr_features/xr_mode")) == 1;
+ if (uses_xr) {
+ int dof_index = p_preset->get("xr_features/degrees_of_freedom"); // 0: none, 1: 3dof and 6dof, 2: 6dof
+ if (dof_index == 1) {
+ manifest_xr_features += " <uses-feature tools:node=\"replace\" android:name=\"android.hardware.vr.headtracking\" android:required=\"false\" android:version=\"1\" />\n";
+ } else if (dof_index == 2) {
+ manifest_xr_features += " <uses-feature tools:node=\"replace\" android:name=\"android.hardware.vr.headtracking\" android:required=\"true\" android:version=\"1\" />\n";
+ }
+ int hand_tracking_index = p_preset->get("xr_features/hand_tracking"); // 0: none, 1: optional, 2: required
+ if (hand_tracking_index == 1) {
+ manifest_xr_features += " <uses-feature tools:node=\"replace\" android:name=\"oculus.software.handtracking\" android:required=\"false\" />\n";
+ } else if (hand_tracking_index == 2) {
+ manifest_xr_features += " <uses-feature tools:node=\"replace\" android:name=\"oculus.software.handtracking\" android:required=\"true\" />\n";
+ }
+ }
+ return manifest_xr_features;
+}
+
+String _get_instrumentation_tag(const Ref<EditorExportPreset> &p_preset) {
+ String package_name = p_preset->get("package/unique_name");
+ String manifest_instrumentation_text = vformat(
+ " <instrumentation\n"
+ " tools:node=\"replace\"\n"
+ " android:name=\".GodotInstrumentation\"\n"
+ " android:icon=\"@mipmap/icon\"\n"
+ " android:label=\"@string/godot_project_name_string\"\n"
+ " android:targetPackage=\"%s\" />\n",
+ package_name);
+ return manifest_instrumentation_text;
+}
+
+String _get_plugins_tag(const String &plugins_names) {
+ if (!plugins_names.empty()) {
+ return vformat(" <meta-data tools:node=\"replace\" android:name=\"plugins\" android:value=\"%s\" />\n", plugins_names);
+ } else {
+ return " <meta-data tools:node=\"remove\" android:name=\"plugins\" />\n";
+ }
+}
+
+String _get_activity_tag(const Ref<EditorExportPreset> &p_preset) {
+ bool uses_xr = (int)(p_preset->get("xr_features/xr_mode")) == 1;
+ String orientation = (int)(p_preset->get("screen/orientation")) == 1 ? "portrait" : "landscape";
+ String manifest_activity_text = vformat(
+ " <activity android:name=\"com.godot.game.GodotApp\" "
+ "tools:replace=\"android:screenOrientation\" "
+ "android:screenOrientation=\"%s\">\n",
+ orientation);
+ if (uses_xr) {
+ String focus_awareness = bool_to_string(p_preset->get("xr_features/focus_awareness"));
+ manifest_activity_text += vformat(" <meta-data tools:node=\"replace\" android:name=\"com.oculus.vr.focusaware\" android:value=\"%s\" />\n", focus_awareness);
+ } else {
+ manifest_activity_text += " <meta-data tools:node=\"remove\" android:name=\"com.oculus.vr.focusaware\" />\n";
+ }
+ manifest_activity_text += " </activity>\n";
+ return manifest_activity_text;
+}
+
+String _get_application_tag(const Ref<EditorExportPreset> &p_preset, const String &plugins_names) {
+ bool uses_xr = (int)(p_preset->get("xr_features/xr_mode")) == 1;
+ String manifest_application_text =
+ " <application android:label=\"@string/godot_project_name_string\"\n"
+ " android:allowBackup=\"false\" tools:ignore=\"GoogleAppIndexingWarning\"\n"
+ " android:icon=\"@mipmap/icon\">)\n\n"
+ " <meta-data tools:node=\"remove\" android:name=\"xr_mode_metadata_name\" />\n";
+
+ manifest_application_text += _get_plugins_tag(plugins_names);
+ if (uses_xr) {
+ manifest_application_text += " <meta-data tools:node=\"replace\" android:name=\"com.samsung.android.vr.application.mode\" android:value=\"vr_only\" />\n";
+ }
+ manifest_application_text += _get_activity_tag(p_preset);
+ manifest_application_text += " </application>\n";
+ return manifest_application_text;
+}
+
#endif //GODOT_GRADLE_EXPORT_UTIL_H
diff --git a/platform/android/java/app/build.gradle b/platform/android/java/app/build.gradle
index 19202d2310..3f8d138e8f 100644
--- a/platform/android/java/app/build.gradle
+++ b/platform/android/java/app/build.gradle
@@ -82,6 +82,8 @@ android {
// Feel free to modify the application id to your own.
applicationId getExportPackageName()
+ versionCode getExportVersionCode()
+ versionName getExportVersionName()
minSdkVersion versions.minSdk
targetSdkVersion versions.targetSdk
}
diff --git a/platform/android/java/app/config.gradle b/platform/android/java/app/config.gradle
index acfdef531e..7d5d238100 100644
--- a/platform/android/java/app/config.gradle
+++ b/platform/android/java/app/config.gradle
@@ -28,6 +28,22 @@ ext.getExportPackageName = { ->
return appId
}
+ext.getExportVersionCode = { ->
+ String versionCode = project.hasProperty("export_version_code") ? project.property("export_version_code") : ""
+ if (versionCode == null || versionCode.isEmpty()) {
+ versionCode = "1"
+ }
+ return Integer.parseInt(versionCode)
+}
+
+ext.getExportVersionName = { ->
+ String versionName = project.hasProperty("export_version_name") ? project.property("export_version_name") : ""
+ if (versionName == null || versionName.isEmpty()) {
+ versionName = "1.0"
+ }
+ return versionName
+}
+
final String PLUGIN_VALUE_SEPARATOR_REGEX = "\\|"
/**
diff --git a/platform/android/java/lib/src/org/godotengine/godot/GodotIO.java b/platform/android/java/lib/src/org/godotengine/godot/GodotIO.java
index 4dd228e53b..c2f3c88416 100644
--- a/platform/android/java/lib/src/org/godotengine/godot/GodotIO.java
+++ b/platform/android/java/lib/src/org/godotengine/godot/GodotIO.java
@@ -461,9 +461,9 @@ public class GodotIO {
return (int)(metrics.density * 160f);
}
- public void showKeyboard(String p_existing_text, int p_max_input_length, int p_cursor_start, int p_cursor_end) {
+ public void showKeyboard(String p_existing_text, boolean p_multiline, int p_max_input_length, int p_cursor_start, int p_cursor_end) {
if (edit != null)
- edit.showKeyboard(p_existing_text, p_max_input_length, p_cursor_start, p_cursor_end);
+ edit.showKeyboard(p_existing_text, p_multiline, p_max_input_length, p_cursor_start, p_cursor_end);
//InputMethodManager inputMgr = (InputMethodManager)activity.getSystemService(Context.INPUT_METHOD_SERVICE);
//inputMgr.toggleSoftInput(InputMethodManager.SHOW_FORCED, 0);
diff --git a/platform/android/java/lib/src/org/godotengine/godot/input/GodotEditText.java b/platform/android/java/lib/src/org/godotengine/godot/input/GodotEditText.java
index c0defd008e..c95339c583 100644
--- a/platform/android/java/lib/src/org/godotengine/godot/input/GodotEditText.java
+++ b/platform/android/java/lib/src/org/godotengine/godot/input/GodotEditText.java
@@ -36,6 +36,7 @@ import android.content.Context;
import android.os.Handler;
import android.os.Message;
import android.text.InputFilter;
+import android.text.InputType;
import android.util.AttributeSet;
import android.view.KeyEvent;
import android.view.inputmethod.EditorInfo;
@@ -58,7 +59,8 @@ public class GodotEditText extends EditText {
private GodotTextInputWrapper mInputWrapper;
private EditHandler sHandler = new EditHandler(this);
private String mOriginText;
- private int mMaxInputLength;
+ private int mMaxInputLength = Integer.MAX_VALUE;
+ private boolean mMultiline = false;
private static class EditHandler extends Handler {
private final WeakReference<GodotEditText> mEdit;
@@ -95,7 +97,11 @@ public class GodotEditText extends EditText {
protected void initView() {
setPadding(0, 0, 0, 0);
- setImeOptions(EditorInfo.IME_FLAG_NO_EXTRACT_UI);
+ setImeOptions(EditorInfo.IME_FLAG_NO_EXTRACT_UI | EditorInfo.IME_ACTION_DONE);
+ }
+
+ public boolean isMultiline() {
+ return mMultiline;
}
private void handleMessage(final Message msg) {
@@ -115,6 +121,12 @@ public class GodotEditText extends EditText {
edit.mInputWrapper.setSelection(false);
}
+ int inputType = InputType.TYPE_CLASS_TEXT;
+ if (edit.isMultiline()) {
+ inputType |= InputType.TYPE_TEXT_FLAG_MULTI_LINE;
+ }
+ edit.setInputType(inputType);
+
edit.mInputWrapper.setOriginText(text);
edit.addTextChangedListener(edit.mInputWrapper);
final InputMethodManager imm = (InputMethodManager)mRenderView.getView().getContext().getSystemService(Context.INPUT_METHOD_SERVICE);
@@ -189,7 +201,7 @@ public class GodotEditText extends EditText {
// ===========================================================
// Methods
// ===========================================================
- public void showKeyboard(String p_existing_text, int p_max_input_length, int p_cursor_start, int p_cursor_end) {
+ public void showKeyboard(String p_existing_text, boolean p_multiline, int p_max_input_length, int p_cursor_start, int p_cursor_end) {
int maxInputLength = (p_max_input_length <= 0) ? Integer.MAX_VALUE : p_max_input_length;
if (p_cursor_start == -1) { // cursor position not given
this.mOriginText = p_existing_text;
@@ -202,6 +214,8 @@ public class GodotEditText extends EditText {
this.mMaxInputLength = maxInputLength - (p_existing_text.length() - p_cursor_end);
}
+ this.mMultiline = p_multiline;
+
final Message msg = new Message();
msg.what = HANDLER_OPEN_IME_KEYBOARD;
msg.obj = this;
diff --git a/platform/android/java/lib/src/org/godotengine/godot/input/GodotTextInputWrapper.java b/platform/android/java/lib/src/org/godotengine/godot/input/GodotTextInputWrapper.java
index 9c7cf9f341..4dd1054738 100644
--- a/platform/android/java/lib/src/org/godotengine/godot/input/GodotTextInputWrapper.java
+++ b/platform/android/java/lib/src/org/godotengine/godot/input/GodotTextInputWrapper.java
@@ -123,7 +123,7 @@ public class GodotTextInputWrapper implements TextWatcher, OnEditorActionListene
public void run() {
for (int i = 0; i < count; ++i) {
int key = newChars[i];
- if (key == '\n') {
+ if ((key == '\n') && !mEdit.isMultiline()) {
// Return keys are handled through action events
continue;
}
@@ -151,7 +151,7 @@ public class GodotTextInputWrapper implements TextWatcher, OnEditorActionListene
});
}
- if (pActionID == EditorInfo.IME_NULL) {
+ if (pActionID == EditorInfo.IME_ACTION_DONE) {
// Enter key has been pressed
GodotLib.key(KeyEvent.KEYCODE_ENTER, KeyEvent.KEYCODE_ENTER, 0, true);
GodotLib.key(KeyEvent.KEYCODE_ENTER, KeyEvent.KEYCODE_ENTER, 0, false);
diff --git a/platform/android/java_godot_io_wrapper.cpp b/platform/android/java_godot_io_wrapper.cpp
index 0a42adeaf2..4ccbc6b97e 100644
--- a/platform/android/java_godot_io_wrapper.cpp
+++ b/platform/android/java_godot_io_wrapper.cpp
@@ -53,7 +53,7 @@ GodotIOJavaWrapper::GodotIOJavaWrapper(JNIEnv *p_env, jobject p_godot_io_instanc
_get_model = p_env->GetMethodID(cls, "getModel", "()Ljava/lang/String;");
_get_screen_DPI = p_env->GetMethodID(cls, "getScreenDPI", "()I");
_get_unique_id = p_env->GetMethodID(cls, "getUniqueID", "()Ljava/lang/String;");
- _show_keyboard = p_env->GetMethodID(cls, "showKeyboard", "(Ljava/lang/String;III)V");
+ _show_keyboard = p_env->GetMethodID(cls, "showKeyboard", "(Ljava/lang/String;ZIII)V");
_hide_keyboard = p_env->GetMethodID(cls, "hideKeyboard", "()V");
_set_screen_orientation = p_env->GetMethodID(cls, "setScreenOrientation", "(I)V");
_get_screen_orientation = p_env->GetMethodID(cls, "getScreenOrientation", "()I");
@@ -132,11 +132,11 @@ bool GodotIOJavaWrapper::has_vk() {
return (_show_keyboard != 0) && (_hide_keyboard != 0);
}
-void GodotIOJavaWrapper::show_vk(const String &p_existing, int p_max_input_length, int p_cursor_start, int p_cursor_end) {
+void GodotIOJavaWrapper::show_vk(const String &p_existing, bool p_multiline, int p_max_input_length, int p_cursor_start, int p_cursor_end) {
if (_show_keyboard) {
JNIEnv *env = ThreadAndroid::get_env();
jstring jStr = env->NewStringUTF(p_existing.utf8().get_data());
- env->CallVoidMethod(godot_io_instance, _show_keyboard, jStr, p_max_input_length, p_cursor_start, p_cursor_end);
+ env->CallVoidMethod(godot_io_instance, _show_keyboard, jStr, p_multiline, p_max_input_length, p_cursor_start, p_cursor_end);
}
}
diff --git a/platform/android/java_godot_io_wrapper.h b/platform/android/java_godot_io_wrapper.h
index 1742021379..6465ded985 100644
--- a/platform/android/java_godot_io_wrapper.h
+++ b/platform/android/java_godot_io_wrapper.h
@@ -70,7 +70,7 @@ public:
int get_screen_dpi();
String get_unique_id();
bool has_vk();
- void show_vk(const String &p_existing, int p_max_input_length, int p_cursor_start, int p_cursor_end);
+ void show_vk(const String &p_existing, bool p_multiline, int p_max_input_length, int p_cursor_start, int p_cursor_end);
void hide_vk();
int get_vk_height();
void set_vk_height(int p_height);
diff --git a/platform/iphone/SCsub b/platform/iphone/SCsub
index b72d29149c..49c77468ed 100644
--- a/platform/iphone/SCsub
+++ b/platform/iphone/SCsub
@@ -3,10 +3,8 @@
Import("env")
iphone_lib = [
- "godot_iphone.cpp",
- "os_iphone.cpp",
- "semaphore_iphone.cpp",
- "gl_view.mm",
+ "godot_iphone.mm",
+ "os_iphone.mm",
"main.m",
"app_delegate.mm",
"view_controller.mm",
@@ -15,6 +13,12 @@ iphone_lib = [
"icloud.mm",
"ios.mm",
"vulkan_context_iphone.mm",
+ "display_server_iphone.mm",
+ "joypad_iphone.mm",
+ "godot_view.mm",
+ "display_layer.mm",
+ "godot_view_renderer.mm",
+ "godot_view_gesture_recognizer.m",
]
env_ios = env.Clone()
diff --git a/platform/iphone/app_delegate.h b/platform/iphone/app_delegate.h
index 27552d781a..2f082f1e07 100644
--- a/platform/iphone/app_delegate.h
+++ b/platform/iphone/app_delegate.h
@@ -28,29 +28,20 @@
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
/*************************************************************************/
-#if defined(OPENGL_ENABLED)
-#import "gl_view.h"
-#endif
-#import "view_controller.h"
#import <UIKit/UIKit.h>
-#import <CoreMotion/CoreMotion.h>
+@class ViewController;
// FIXME: Add support for both GLES2 and Vulkan when GLES2 is implemented again,
// so it can't be done with compilation time branching.
//#if defined(OPENGL_ENABLED)
//@interface AppDelegate : NSObject <UIApplicationDelegate, GLViewDelegate> {
//#endif
-#if defined(VULKAN_ENABLED)
-@interface AppDelegate : NSObject <UIApplicationDelegate> {
-#endif
- //@property (strong, nonatomic) UIWindow *window;
- ViewController *view_controller;
- bool is_focus_out;
-};
+//#if defined(VULKAN_ENABLED)
+@interface AppDelegate : NSObject <UIApplicationDelegate>
+//#endif
@property(strong, nonatomic) UIWindow *window;
-
-+ (ViewController *)getViewController;
+@property(strong, class, readonly, nonatomic) ViewController *viewController;
@end
diff --git a/platform/iphone/app_delegate.mm b/platform/iphone/app_delegate.mm
index c4ef185bf1..7edbcc4667 100644
--- a/platform/iphone/app_delegate.mm
+++ b/platform/iphone/app_delegate.mm
@@ -29,644 +29,60 @@
/*************************************************************************/
#import "app_delegate.h"
-
#include "core/project_settings.h"
#include "drivers/coreaudio/audio_driver_coreaudio.h"
-#if defined(OPENGL_ENABLED)
-#import "gl_view.h"
-#endif
+#import "godot_view.h"
#include "main/main.h"
#include "os_iphone.h"
+#import "view_controller.h"
-#import "GameController/GameController.h"
#import <AudioToolbox/AudioServices.h>
-#define kFilteringFactor 0.1
#define kRenderingFrequency 60
-#define kAccelerometerFrequency 100.0 // Hz
-
-Error _shell_open(String);
-void _set_keep_screen_on(bool p_enabled);
-
-Error _shell_open(String p_uri) {
- NSString *url = [[NSString alloc] initWithUTF8String:p_uri.utf8().get_data()];
-
- if (![[UIApplication sharedApplication] canOpenURL:[NSURL URLWithString:url]])
- return ERR_CANT_OPEN;
-
- printf("opening url %ls\n", p_uri.c_str());
- [[UIApplication sharedApplication] openURL:[NSURL URLWithString:url]];
- [url release];
- return OK;
-};
-
-void _set_keep_screen_on(bool p_enabled) {
- [[UIApplication sharedApplication] setIdleTimerDisabled:(BOOL)p_enabled];
-};
-
-void _vibrate() {
- AudioServicesPlaySystemSound(kSystemSoundID_Vibrate);
-};
-
-@implementation AppDelegate
-
-@synthesize window;
extern int gargc;
extern char **gargv;
-extern int iphone_main(int, int, int, char **, String);
+
+extern int iphone_main(int, char **, String);
extern void iphone_finish();
-CMMotionManager *motionManager;
-bool motionInitialised;
+@implementation AppDelegate
static ViewController *mainViewController = nil;
-+ (ViewController *)getViewController {
- return mainViewController;
-}
-
-NSMutableDictionary *ios_joysticks = nil;
-NSMutableArray *pending_ios_joysticks = nil;
-
-- (GCControllerPlayerIndex)getFreePlayerIndex {
- bool have_player_1 = false;
- bool have_player_2 = false;
- bool have_player_3 = false;
- bool have_player_4 = false;
-
- if (ios_joysticks == nil) {
- NSArray *keys = [ios_joysticks allKeys];
- for (NSNumber *key in keys) {
- GCController *controller = [ios_joysticks objectForKey:key];
- if (controller.playerIndex == GCControllerPlayerIndex1) {
- have_player_1 = true;
- } else if (controller.playerIndex == GCControllerPlayerIndex2) {
- have_player_2 = true;
- } else if (controller.playerIndex == GCControllerPlayerIndex3) {
- have_player_3 = true;
- } else if (controller.playerIndex == GCControllerPlayerIndex4) {
- have_player_4 = true;
- };
- };
- };
-
- if (!have_player_1) {
- return GCControllerPlayerIndex1;
- } else if (!have_player_2) {
- return GCControllerPlayerIndex2;
- } else if (!have_player_3) {
- return GCControllerPlayerIndex3;
- } else if (!have_player_4) {
- return GCControllerPlayerIndex4;
- } else {
- return GCControllerPlayerIndexUnset;
- };
-};
-
-void _ios_add_joystick(GCController *controller, AppDelegate *delegate) {
- // get a new id for our controller
- int joy_id = OSIPhone::get_singleton()->get_unused_joy_id();
- if (joy_id != -1) {
- // assign our player index
- if (controller.playerIndex == GCControllerPlayerIndexUnset) {
- controller.playerIndex = [delegate getFreePlayerIndex];
- };
-
- // tell Godot about our new controller
- OSIPhone::get_singleton()->joy_connection_changed(
- joy_id, true, [controller.vendorName UTF8String]);
-
- // add it to our dictionary, this will retain our controllers
- [ios_joysticks setObject:controller
- forKey:[NSNumber numberWithInt:joy_id]];
-
- // set our input handler
- [delegate setControllerInputHandler:controller];
- } else {
- printf("Couldn't retrieve new joy id\n");
- };
-}
-
-static void on_focus_out(ViewController *view_controller, bool *is_focus_out) {
- if (!*is_focus_out) {
- *is_focus_out = true;
- if (OS::get_singleton()->get_main_loop())
- OS::get_singleton()->get_main_loop()->notification(
- MainLoop::NOTIFICATION_WM_FOCUS_OUT);
-
- [view_controller.view stopAnimation];
- if (OS::get_singleton()->native_video_is_playing()) {
- OSIPhone::get_singleton()->native_video_focus_out();
- }
- AudioDriverCoreAudio *audio = dynamic_cast<AudioDriverCoreAudio *>(AudioDriverCoreAudio::get_singleton());
- if (audio)
- audio->stop();
- }
-}
-
-static void on_focus_in(ViewController *view_controller, bool *is_focus_out) {
- if (*is_focus_out) {
- *is_focus_out = false;
- if (OS::get_singleton()->get_main_loop())
- OS::get_singleton()->get_main_loop()->notification(
- MainLoop::NOTIFICATION_WM_FOCUS_IN);
-
- [view_controller.view startAnimation];
- if (OSIPhone::get_singleton()->native_video_is_playing()) {
- OSIPhone::get_singleton()->native_video_unpause();
- }
-
- AudioDriverCoreAudio *audio = dynamic_cast<AudioDriverCoreAudio *>(AudioDriverCoreAudio::get_singleton());
- if (audio)
- audio->start();
- }
++ (ViewController *)viewController {
+ return mainViewController;
}
-- (void)controllerWasConnected:(NSNotification *)notification {
- // create our dictionary if we don't have one yet
- if (ios_joysticks == nil) {
- ios_joysticks = [[NSMutableDictionary alloc] init];
- };
-
- // get our controller
- GCController *controller = (GCController *)notification.object;
- if (controller == nil) {
- printf("Couldn't retrieve new controller\n");
- } else if ([[ios_joysticks allKeysForObject:controller] count] != 0) {
- printf("Controller is already registered\n");
- } else if (frame_count > 1) {
- _ios_add_joystick(controller, self);
- } else {
- if (pending_ios_joysticks == nil)
- pending_ios_joysticks = [[NSMutableArray alloc] init];
- [pending_ios_joysticks addObject:controller];
- };
-};
-
-- (void)controllerWasDisconnected:(NSNotification *)notification {
- if (ios_joysticks != nil) {
- // find our joystick, there should be only one in our dictionary
- GCController *controller = (GCController *)notification.object;
- NSArray *keys = [ios_joysticks allKeysForObject:controller];
- for (NSNumber *key in keys) {
- // tell Godot this joystick is no longer there
- int joy_id = [key intValue];
- OSIPhone::get_singleton()->joy_connection_changed(joy_id, false, "");
-
- // and remove it from our dictionary
- [ios_joysticks removeObjectForKey:key];
- };
- };
-};
-
-- (int)getJoyIdForController:(GCController *)controller {
- if (ios_joysticks != nil) {
- // find our joystick, there should be only one in our dictionary
- NSArray *keys = [ios_joysticks allKeysForObject:controller];
- for (NSNumber *key in keys) {
- int joy_id = [key intValue];
- return joy_id;
- };
- };
-
- return -1;
-};
-
-- (void)setControllerInputHandler:(GCController *)controller {
- // Hook in the callback handler for the correct gamepad profile.
- // This is a bit of a weird design choice on Apples part.
- // You need to select the most capable gamepad profile for the
- // gamepad attached.
- if (controller.extendedGamepad != nil) {
- // The extended gamepad profile has all the input you could possibly find on
- // a gamepad but will only be active if your gamepad actually has all of
- // these...
- controller.extendedGamepad.valueChangedHandler = ^(
- GCExtendedGamepad *gamepad, GCControllerElement *element) {
- int joy_id = [self getJoyIdForController:controller];
-
- if (element == gamepad.buttonA) {
- OSIPhone::get_singleton()->joy_button(joy_id, JOY_BUTTON_A,
- gamepad.buttonA.isPressed);
- } else if (element == gamepad.buttonB) {
- OSIPhone::get_singleton()->joy_button(joy_id, JOY_BUTTON_B,
- gamepad.buttonB.isPressed);
- } else if (element == gamepad.buttonX) {
- OSIPhone::get_singleton()->joy_button(joy_id, JOY_BUTTON_X,
- gamepad.buttonX.isPressed);
- } else if (element == gamepad.buttonY) {
- OSIPhone::get_singleton()->joy_button(joy_id, JOY_BUTTON_Y,
- gamepad.buttonY.isPressed);
- } else if (element == gamepad.leftShoulder) {
- OSIPhone::get_singleton()->joy_button(joy_id, JOY_BUTTON_LEFT_SHOULDER,
- gamepad.leftShoulder.isPressed);
- } else if (element == gamepad.rightShoulder) {
- OSIPhone::get_singleton()->joy_button(joy_id, JOY_BUTTON_RIGHT_SHOULDER,
- gamepad.rightShoulder.isPressed);
- } else if (element == gamepad.dpad) {
- OSIPhone::get_singleton()->joy_button(joy_id, JOY_BUTTON_DPAD_UP,
- gamepad.dpad.up.isPressed);
- OSIPhone::get_singleton()->joy_button(joy_id, JOY_BUTTON_DPAD_DOWN,
- gamepad.dpad.down.isPressed);
- OSIPhone::get_singleton()->joy_button(joy_id, JOY_BUTTON_DPAD_LEFT,
- gamepad.dpad.left.isPressed);
- OSIPhone::get_singleton()->joy_button(joy_id, JOY_BUTTON_DPAD_RIGHT,
- gamepad.dpad.right.isPressed);
- };
-
- InputDefault::JoyAxis jx;
- jx.min = -1;
- if (element == gamepad.leftThumbstick) {
- jx.value = gamepad.leftThumbstick.xAxis.value;
- OSIPhone::get_singleton()->joy_axis(joy_id, JOY_AXIS_LEFT_X, jx);
- jx.value = -gamepad.leftThumbstick.yAxis.value;
- OSIPhone::get_singleton()->joy_axis(joy_id, JOY_AXIS_LEFT_Y, jx);
- } else if (element == gamepad.rightThumbstick) {
- jx.value = gamepad.rightThumbstick.xAxis.value;
- OSIPhone::get_singleton()->joy_axis(joy_id, JOY_AXIS_RIGHT_X, jx);
- jx.value = -gamepad.rightThumbstick.yAxis.value;
- OSIPhone::get_singleton()->joy_axis(joy_id, JOY_AXIS_RIGHT_Y, jx);
- } else if (element == gamepad.leftTrigger) {
- jx.value = gamepad.leftTrigger.value;
- OSIPhone::get_singleton()->joy_axis(joy_id, JOY_AXIS_TRIGGER_LEFT, jx);
- } else if (element == gamepad.rightTrigger) {
- jx.value = gamepad.rightTrigger.value;
- OSIPhone::get_singleton()->joy_axis(joy_id, JOY_AXIS_TRIGGER_RIGHT, jx);
- };
- };
- } else if (controller.gamepad != nil) {
- // gamepad is the standard profile with 4 buttons, shoulder buttons and a
- // D-pad
- controller.gamepad.valueChangedHandler = ^(GCGamepad *gamepad,
- GCControllerElement *element) {
- int joy_id = [self getJoyIdForController:controller];
-
- if (element == gamepad.buttonA) {
- OSIPhone::get_singleton()->joy_button(joy_id, JOY_BUTTON_A,
- gamepad.buttonA.isPressed);
- } else if (element == gamepad.buttonB) {
- OSIPhone::get_singleton()->joy_button(joy_id, JOY_BUTTON_B,
- gamepad.buttonB.isPressed);
- } else if (element == gamepad.buttonX) {
- OSIPhone::get_singleton()->joy_button(joy_id, JOY_BUTTON_X,
- gamepad.buttonX.isPressed);
- } else if (element == gamepad.buttonY) {
- OSIPhone::get_singleton()->joy_button(joy_id, JOY_BUTTON_Y,
- gamepad.buttonY.isPressed);
- } else if (element == gamepad.leftShoulder) {
- OSIPhone::get_singleton()->joy_button(joy_id, JOY_BUTTON_LEFT_SHOULDER,
- gamepad.leftShoulder.isPressed);
- } else if (element == gamepad.rightShoulder) {
- OSIPhone::get_singleton()->joy_button(joy_id, JOY_BUTTON_RIGHT_SHOULDER,
- gamepad.rightShoulder.isPressed);
- } else if (element == gamepad.dpad) {
- OSIPhone::get_singleton()->joy_button(joy_id, JOY_BUTTON_DPAD_UP,
- gamepad.dpad.up.isPressed);
- OSIPhone::get_singleton()->joy_button(joy_id, JOY_BUTTON_DPAD_DOWN,
- gamepad.dpad.down.isPressed);
- OSIPhone::get_singleton()->joy_button(joy_id, JOY_BUTTON_DPAD_LEFT,
- gamepad.dpad.left.isPressed);
- OSIPhone::get_singleton()->joy_button(joy_id, JOY_BUTTON_DPAD_RIGHT,
- gamepad.dpad.right.isPressed);
- };
- };
-#ifdef ADD_MICRO_GAMEPAD // disabling this for now, only available on iOS 9+,
- // while we are setting that as the minimum, seems our
- // build environment doesn't like it
- } else if (controller.microGamepad != nil) {
- // micro gamepads were added in OS 9 and feature just 2 buttons and a d-pad
- controller.microGamepad.valueChangedHandler =
- ^(GCMicroGamepad *gamepad, GCControllerElement *element) {
- int joy_id = [self getJoyIdForController:controller];
-
- if (element == gamepad.buttonA) {
- OSIPhone::get_singleton()->joy_button(joy_id, JOY_BUTTON_A,
- gamepad.buttonA.isPressed);
- } else if (element == gamepad.buttonX) {
- OSIPhone::get_singleton()->joy_button(joy_id, JOY_BUTTON_X,
- gamepad.buttonX.isPressed);
- } else if (element == gamepad.dpad) {
- OSIPhone::get_singleton()->joy_button(joy_id, JOY_BUTTON_DPAD_UP,
- gamepad.dpad.up.isPressed);
- OSIPhone::get_singleton()->joy_button(joy_id, JOY_BUTTON_DPAD_DOWN,
- gamepad.dpad.down.isPressed);
- OSIPhone::get_singleton()->joy_button(joy_id, JOY_BUTTON_DPAD_LEFT,
- gamepad.dpad.left.isPressed);
- OSIPhone::get_singleton()->joy_button(joy_id, JOY_BUTTON_DPAD_RIGHT,
- gamepad.dpad.right.isPressed);
- };
- };
-#endif
- };
-
- ///@TODO need to add support for controller.motion which gives us access to
- /// the orientation of the device (if supported)
-
- ///@TODO need to add support for controllerPausedHandler which should be a
- /// toggle
-};
-
-- (void)initGameControllers {
- // get told when controllers connect, this will be called right away for
- // already connected controllers
- [[NSNotificationCenter defaultCenter]
- addObserver:self
- selector:@selector(controllerWasConnected:)
- name:GCControllerDidConnectNotification
- object:nil];
-
- // get told when controllers disconnect
- [[NSNotificationCenter defaultCenter]
- addObserver:self
- selector:@selector(controllerWasDisconnected:)
- name:GCControllerDidDisconnectNotification
- object:nil];
-};
-
-- (void)deinitGameControllers {
- [[NSNotificationCenter defaultCenter]
- removeObserver:self
- name:GCControllerDidConnectNotification
- object:nil];
- [[NSNotificationCenter defaultCenter]
- removeObserver:self
- name:GCControllerDidDisconnectNotification
- object:nil];
-
- if (ios_joysticks != nil) {
- [ios_joysticks dealloc];
- ios_joysticks = nil;
- };
-
- if (pending_ios_joysticks != nil) {
- [pending_ios_joysticks dealloc];
- pending_ios_joysticks = nil;
- };
-};
-
-OS::VideoMode _get_video_mode() {
- int backingWidth;
- int backingHeight;
-#if defined(OPENGL_ENABLED)
- glGetRenderbufferParameterivOES(GL_RENDERBUFFER_OES,
- GL_RENDERBUFFER_WIDTH_OES, &backingWidth);
- glGetRenderbufferParameterivOES(GL_RENDERBUFFER_OES,
- GL_RENDERBUFFER_HEIGHT_OES, &backingHeight);
-#endif
-
- OS::VideoMode vm;
- vm.fullscreen = true;
- vm.width = backingWidth;
- vm.height = backingHeight;
- vm.resizable = false;
- return vm;
-};
-
-static int frame_count = 0;
-- (void)drawView:(UIView *)view;
-{
- switch (frame_count) {
- case 0: {
- OS::get_singleton()->set_video_mode(_get_video_mode());
-
- if (!OS::get_singleton()) {
- exit(0);
- };
- ++frame_count;
-
- NSString *locale_code = [[NSLocale currentLocale] localeIdentifier];
- OSIPhone::get_singleton()->set_locale(
- String::utf8([locale_code UTF8String]));
-
- NSString *uuid;
- if ([[UIDevice currentDevice]
- respondsToSelector:@selector(identifierForVendor)]) {
- uuid = [UIDevice currentDevice].identifierForVendor.UUIDString;
- } else {
- // before iOS 6, so just generate an identifier and store it
- uuid = [[NSUserDefaults standardUserDefaults]
- objectForKey:@"identiferForVendor"];
- if (!uuid) {
- CFUUIDRef cfuuid = CFUUIDCreate(NULL);
- uuid = (__bridge_transfer NSString *)CFUUIDCreateString(NULL, cfuuid);
- CFRelease(cfuuid);
- [[NSUserDefaults standardUserDefaults]
- setObject:uuid
- forKey:@"identifierForVendor"];
- }
- }
-
- OSIPhone::get_singleton()->set_unique_id(String::utf8([uuid UTF8String]));
-
- }; break;
-
- case 1: {
- Main::setup2();
- ++frame_count;
-
- if (pending_ios_joysticks != nil) {
- for (GCController *controller in pending_ios_joysticks) {
- _ios_add_joystick(controller, self);
- }
- [pending_ios_joysticks dealloc];
- pending_ios_joysticks = nil;
- }
-
- // this might be necessary before here
- NSDictionary *dict = [[NSBundle mainBundle] infoDictionary];
- for (NSString *key in dict) {
- NSObject *value = [dict objectForKey:key];
- String ukey = String::utf8([key UTF8String]);
-
- // we need a NSObject to Variant conversor
-
- if ([value isKindOfClass:[NSString class]]) {
- NSString *str = (NSString *)value;
- String uval = String::utf8([str UTF8String]);
-
- ProjectSettings::get_singleton()->set("Info.plist/" + ukey, uval);
-
- } else if ([value isKindOfClass:[NSNumber class]]) {
- NSNumber *n = (NSNumber *)value;
- double dval = [n doubleValue];
-
- ProjectSettings::get_singleton()->set("Info.plist/" + ukey, dval);
- };
- // do stuff
- }
-
- }; break;
-
- case 2: {
- Main::start();
- ++frame_count;
-
- }; break; // no fallthrough
-
- default: {
- if (OSIPhone::get_singleton()) {
- // OSIPhone::get_singleton()->update_accelerometer(accel[0], accel[1],
- // accel[2]);
- if (motionInitialised) {
- // Just using polling approach for now, we can set this up so it sends
- // data to us in intervals, might be better. See Apple reference pages
- // for more details:
- // https://developer.apple.com/reference/coremotion/cmmotionmanager?language=objc
-
- // Apple splits our accelerometer date into a gravity and user movement
- // component. We add them back together
- CMAcceleration gravity = motionManager.deviceMotion.gravity;
- CMAcceleration acceleration =
- motionManager.deviceMotion.userAcceleration;
-
- ///@TODO We don't seem to be getting data here, is my device broken or
- /// is this code incorrect?
- CMMagneticField magnetic =
- motionManager.deviceMotion.magneticField.field;
-
- ///@TODO we can access rotationRate as a CMRotationRate variable
- ///(processed date) or CMGyroData (raw data), have to see what works
- /// best
- CMRotationRate rotation = motionManager.deviceMotion.rotationRate;
-
- // Adjust for screen orientation.
- // [[UIDevice currentDevice] orientation] changes even if we've fixed
- // our orientation which is not a good thing when you're trying to get
- // your user to move the screen in all directions and want consistent
- // output
-
- ///@TODO Using [[UIApplication sharedApplication] statusBarOrientation]
- /// is a bit of a hack. Godot obviously knows the orientation so maybe
- /// we
- // can use that instead? (note that left and right seem swapped)
-
- switch ([[UIApplication sharedApplication] statusBarOrientation]) {
- case UIDeviceOrientationLandscapeLeft: {
- OSIPhone::get_singleton()->update_gravity(-gravity.y, gravity.x,
- gravity.z);
- OSIPhone::get_singleton()->update_accelerometer(
- -(acceleration.y + gravity.y), (acceleration.x + gravity.x),
- acceleration.z + gravity.z);
- OSIPhone::get_singleton()->update_magnetometer(
- -magnetic.y, magnetic.x, magnetic.z);
- OSIPhone::get_singleton()->update_gyroscope(-rotation.y, rotation.x,
- rotation.z);
- }; break;
- case UIDeviceOrientationLandscapeRight: {
- OSIPhone::get_singleton()->update_gravity(gravity.y, -gravity.x,
- gravity.z);
- OSIPhone::get_singleton()->update_accelerometer(
- (acceleration.y + gravity.y), -(acceleration.x + gravity.x),
- acceleration.z + gravity.z);
- OSIPhone::get_singleton()->update_magnetometer(
- magnetic.y, -magnetic.x, magnetic.z);
- OSIPhone::get_singleton()->update_gyroscope(rotation.y, -rotation.x,
- rotation.z);
- }; break;
- case UIDeviceOrientationPortraitUpsideDown: {
- OSIPhone::get_singleton()->update_gravity(-gravity.x, gravity.y,
- gravity.z);
- OSIPhone::get_singleton()->update_accelerometer(
- -(acceleration.x + gravity.x), (acceleration.y + gravity.y),
- acceleration.z + gravity.z);
- OSIPhone::get_singleton()->update_magnetometer(
- -magnetic.x, magnetic.y, magnetic.z);
- OSIPhone::get_singleton()->update_gyroscope(-rotation.x, rotation.y,
- rotation.z);
- }; break;
- default: { // assume portrait
- OSIPhone::get_singleton()->update_gravity(gravity.x, gravity.y,
- gravity.z);
- OSIPhone::get_singleton()->update_accelerometer(
- acceleration.x + gravity.x, acceleration.y + gravity.y,
- acceleration.z + gravity.z);
- OSIPhone::get_singleton()->update_magnetometer(magnetic.x, magnetic.y,
- magnetic.z);
- OSIPhone::get_singleton()->update_gyroscope(rotation.x, rotation.y,
- rotation.z);
- }; break;
- };
- }
-
- bool quit_request = OSIPhone::get_singleton()->iterate();
- };
-
- }; break;
- };
-};
-
-- (void)applicationDidReceiveMemoryWarning:(UIApplication *)application {
- if (OS::get_singleton()->get_main_loop()) {
- OS::get_singleton()->get_main_loop()->notification(
- MainLoop::NOTIFICATION_OS_MEMORY_WARNING);
- }
-};
-
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
- CGRect rect = [[UIScreen mainScreen] bounds];
+ // TODO: might be required to make an early return, so app wouldn't crash because of timeout.
+ // TODO: logo screen is not displayed while shaders are compiling
+ // DummyViewController(Splash/LoadingViewController) -> setup -> GodotViewController
- is_focus_out = false;
-
- [application setStatusBarHidden:YES withAnimation:UIStatusBarAnimationNone];
- // disable idle timer
- // application.idleTimerDisabled = YES;
+ CGRect windowBounds = [[UIScreen mainScreen] bounds];
// Create a full-screen window
- window = [[UIWindow alloc] initWithFrame:rect];
-
- OS::VideoMode vm = _get_video_mode();
+ self.window = [[[UIWindow alloc] initWithFrame:windowBounds] autorelease];
- NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory,
- NSUserDomainMask, YES);
+ NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES);
NSString *documentsDirectory = [paths objectAtIndex:0];
- int err = iphone_main(vm.width, vm.height, gargc, gargv, String::utf8([documentsDirectory UTF8String]));
+ int err = iphone_main(gargc, gargv, String::utf8([documentsDirectory UTF8String]));
+
if (err != 0) {
// bail, things did not go very well for us, should probably output a message on screen with our error code...
exit(0);
- return FALSE;
+ return NO;
};
-#if defined(OPENGL_ENABLED)
- // WARNING: We must *always* create the GLView after we have constructed the
- // OS with iphone_main. This allows the GLView to access project settings so
- // it can properly initialize the OpenGL context
- GLView *glView = [[GLView alloc] initWithFrame:rect];
- glView.delegate = self;
-
- view_controller = [[ViewController alloc] init];
- view_controller.view = glView;
-
- _set_keep_screen_on(bool(GLOBAL_DEF("display/window/energy_saving/keep_screen_on", true)) ? YES : NO);
- glView.useCADisplayLink =
- bool(GLOBAL_DEF("display.iOS/use_cadisplaylink", true)) ? YES : NO;
- printf("cadisaplylink: %d", glView.useCADisplayLink);
- glView.animationInterval = 1.0 / kRenderingFrequency;
- [glView startAnimation];
-#endif
-
-#if defined(VULKAN_ENABLED)
- view_controller = [[ViewController alloc] init];
-#endif
+ ViewController *viewController = [[ViewController alloc] init];
+ viewController.godotView.useCADisplayLink = bool(GLOBAL_DEF("display.iOS/use_cadisplaylink", true)) ? YES : NO;
+ viewController.godotView.renderingInterval = 1.0 / kRenderingFrequency;
- window.rootViewController = view_controller;
+ self.window.rootViewController = viewController;
// Show the window
- [window makeKeyAndVisible];
-
- // Configure and start accelerometer
- if (!motionInitialised) {
- motionManager = [[CMMotionManager alloc] init];
- if (motionManager.deviceMotionAvailable) {
- motionManager.deviceMotionUpdateInterval = 1.0 / 70.0;
- [motionManager startDeviceMotionUpdatesUsingReferenceFrame:
- CMAttitudeReferenceFrameXMagneticNorthZVertical];
- motionInitialised = YES;
- };
- };
-
- [self initGameControllers];
+ [self.window makeKeyAndVisible];
[[NSNotificationCenter defaultCenter]
addObserver:self
@@ -674,40 +90,33 @@ static int frame_count = 0;
name:AVAudioSessionInterruptionNotification
object:[AVAudioSession sharedInstance]];
- // OSIPhone::screen_width = rect.size.width - rect.origin.x;
- // OSIPhone::screen_height = rect.size.height - rect.origin.y;
-
- mainViewController = view_controller;
+ mainViewController = viewController;
// prevent to stop music in another background app
[[AVAudioSession sharedInstance] setCategory:AVAudioSessionCategoryAmbient error:nil];
- return TRUE;
+ return YES;
};
- (void)onAudioInterruption:(NSNotification *)notification {
if ([notification.name isEqualToString:AVAudioSessionInterruptionNotification]) {
if ([[notification.userInfo valueForKey:AVAudioSessionInterruptionTypeKey] isEqualToNumber:[NSNumber numberWithInt:AVAudioSessionInterruptionTypeBegan]]) {
NSLog(@"Audio interruption began");
- on_focus_out(view_controller, &is_focus_out);
+ OSIPhone::get_singleton()->on_focus_out();
} else if ([[notification.userInfo valueForKey:AVAudioSessionInterruptionTypeKey] isEqualToNumber:[NSNumber numberWithInt:AVAudioSessionInterruptionTypeEnded]]) {
NSLog(@"Audio interruption ended");
- on_focus_in(view_controller, &is_focus_out);
+ OSIPhone::get_singleton()->on_focus_in();
}
}
};
-- (void)applicationWillTerminate:(UIApplication *)application {
- [self deinitGameControllers];
-
- if (motionInitialised) {
- ///@TODO is this the right place to clean this up?
- [motionManager stopDeviceMotionUpdates];
- [motionManager release];
- motionManager = nil;
- motionInitialised = NO;
- };
+- (void)applicationDidReceiveMemoryWarning:(UIApplication *)application {
+ if (OS::get_singleton()->get_main_loop()) {
+ OS::get_singleton()->get_main_loop()->notification(MainLoop::NOTIFICATION_OS_MEMORY_WARNING);
+ }
+};
+- (void)applicationWillTerminate:(UIApplication *)application {
iphone_finish();
};
@@ -722,15 +131,15 @@ static int frame_count = 0;
// notification panel by swiping from the upper part of the screen.
- (void)applicationWillResignActive:(UIApplication *)application {
- on_focus_out(view_controller, &is_focus_out);
+ OSIPhone::get_singleton()->on_focus_out();
}
- (void)applicationDidBecomeActive:(UIApplication *)application {
- on_focus_in(view_controller, &is_focus_out);
+ OSIPhone::get_singleton()->on_focus_in();
}
- (void)dealloc {
- [window release];
+ self.window = nil;
[super dealloc];
}
diff --git a/platform/iphone/detect.py b/platform/iphone/detect.py
index c48eb7ff0e..f4ef40a0ba 100644
--- a/platform/iphone/detect.py
+++ b/platform/iphone/detect.py
@@ -31,7 +31,8 @@ def get_opts():
("IPHONESDK", "Path to the iPhone SDK", ""),
BoolVariable(
"use_static_mvk",
- "Link MoltenVK statically as Level-0 driver (better portability) or use Vulkan ICD loader (enables validation layers)",
+ "Link MoltenVK statically as Level-0 driver (better portability) or use Vulkan ICD loader (enables"
+ " validation layers)",
False,
),
BoolVariable("game_center", "Support for game center", True),
@@ -120,18 +121,31 @@ def configure(env):
CCFLAGS=(
"-arch "
+ arch_flag
- + " -fobjc-abi-version=2 -fobjc-legacy-dispatch -fmessage-length=0 -fpascal-strings -fblocks -fasm-blocks -isysroot $IPHONESDK -mios-simulator-version-min=10.0"
+ + " -fobjc-abi-version=2 -fobjc-legacy-dispatch -fmessage-length=0 -fpascal-strings -fblocks"
+ " -fasm-blocks -isysroot $IPHONESDK -mios-simulator-version-min=13.0"
).split()
)
elif env["arch"] == "arm":
detect_darwin_sdk_path("iphone", env)
env.Append(
- CCFLAGS='-fno-objc-arc -arch armv7 -fmessage-length=0 -fno-strict-aliasing -fdiagnostics-print-source-range-info -fdiagnostics-show-category=id -fdiagnostics-parseable-fixits -fpascal-strings -fblocks -isysroot $IPHONESDK -fvisibility=hidden -mthumb "-DIBOutlet=__attribute__((iboutlet))" "-DIBOutletCollection(ClassName)=__attribute__((iboutletcollection(ClassName)))" "-DIBAction=void)__attribute__((ibaction)" -miphoneos-version-min=10.0 -MMD -MT dependencies'.split()
+ CCFLAGS=(
+ "-fno-objc-arc -arch armv7 -fmessage-length=0 -fno-strict-aliasing"
+ " -fdiagnostics-print-source-range-info -fdiagnostics-show-category=id -fdiagnostics-parseable-fixits"
+ " -fpascal-strings -fblocks -isysroot $IPHONESDK -fvisibility=hidden -mthumb"
+ ' "-DIBOutlet=__attribute__((iboutlet))"'
+ ' "-DIBOutletCollection(ClassName)=__attribute__((iboutletcollection(ClassName)))"'
+ ' "-DIBAction=void)__attribute__((ibaction)" -miphoneos-version-min=11.0 -MMD -MT dependencies'.split()
+ )
)
elif env["arch"] == "arm64":
detect_darwin_sdk_path("iphone", env)
env.Append(
- CCFLAGS="-fno-objc-arc -arch arm64 -fmessage-length=0 -fno-strict-aliasing -fdiagnostics-print-source-range-info -fdiagnostics-show-category=id -fdiagnostics-parseable-fixits -fpascal-strings -fblocks -fvisibility=hidden -MMD -MT dependencies -miphoneos-version-min=10.0 -isysroot $IPHONESDK".split()
+ CCFLAGS=(
+ "-fno-objc-arc -arch arm64 -fmessage-length=0 -fno-strict-aliasing"
+ " -fdiagnostics-print-source-range-info -fdiagnostics-show-category=id -fdiagnostics-parseable-fixits"
+ " -fpascal-strings -fblocks -fvisibility=hidden -MMD -MT dependencies -miphoneos-version-min=11.0"
+ " -isysroot $IPHONESDK".split()
+ )
)
env.Append(CPPDEFINES=["NEED_LONG_INT"])
env.Append(CPPDEFINES=["LIBYUV_DISABLE_NEON"])
@@ -143,6 +157,9 @@ def configure(env):
else:
env.Append(CCFLAGS=["-fno-exceptions"])
+ # Temp fix for ABS/MAX/MIN macros in iPhone SDK blocking compilation
+ env.Append(CCFLAGS=["-Wno-ambiguous-macro"])
+
## Link flags
if env["arch"] == "x86" or env["arch"] == "x86_64":
@@ -151,7 +168,7 @@ def configure(env):
LINKFLAGS=[
"-arch",
arch_flag,
- "-mios-simulator-version-min=10.0",
+ "-mios-simulator-version-min=13.0",
"-isysroot",
"$IPHONESDK",
"-Xlinker",
@@ -162,9 +179,9 @@ def configure(env):
]
)
elif env["arch"] == "arm":
- env.Append(LINKFLAGS=["-arch", "armv7", "-Wl,-dead_strip", "-miphoneos-version-min=10.0"])
+ env.Append(LINKFLAGS=["-arch", "armv7", "-Wl,-dead_strip", "-miphoneos-version-min=11.0"])
if env["arch"] == "arm64":
- env.Append(LINKFLAGS=["-arch", "arm64", "-Wl,-dead_strip", "-miphoneos-version-min=10.0"])
+ env.Append(LINKFLAGS=["-arch", "arm64", "-Wl,-dead_strip", "-miphoneos-version-min=11.0"])
env.Append(
LINKFLAGS=[
@@ -228,8 +245,7 @@ def configure(env):
env.Append(CPPDEFINES=["VULKAN_ENABLED"])
env.Append(LINKFLAGS=["-framework", "IOSurface"])
- if env["use_static_mvk"]:
- env.Append(LINKFLAGS=["-framework", "MoltenVK"])
- env["builtin_vulkan"] = False
- elif not env["builtin_vulkan"]:
- env.Append(LIBS=["vulkan"])
+
+ # Use Static Vulkan for iOS. Dynamic Framework works fine too.
+ env.Append(LINKFLAGS=["-framework", "MoltenVK"])
+ env["builtin_vulkan"] = False
diff --git a/platform/iphone/display_layer.h b/platform/iphone/display_layer.h
new file mode 100644
index 0000000000..bfde8f96a3
--- /dev/null
+++ b/platform/iphone/display_layer.h
@@ -0,0 +1,58 @@
+/*************************************************************************/
+/* display_layer.h */
+/*************************************************************************/
+/* This file is part of: */
+/* GODOT ENGINE */
+/* https://godotengine.org */
+/*************************************************************************/
+/* Copyright (c) 2007-2020 Juan Linietsky, Ariel Manzur. */
+/* Copyright (c) 2014-2020 Godot Engine contributors (cf. AUTHORS.md). */
+/* */
+/* 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 <OpenGLES/EAGLDrawable.h>
+#import <QuartzCore/QuartzCore.h>
+
+@protocol DisplayLayer <NSObject>
+
+- (void)renderDisplayLayer;
+- (void)initializeDisplayLayer;
+- (void)layoutDisplayLayer;
+
+@end
+
+// An ugly workaround for iOS simulator
+#if defined(TARGET_OS_SIMULATOR) && TARGET_OS_SIMULATOR
+#if defined(__IPHONE_13_0)
+API_AVAILABLE(ios(13.0))
+@interface GodotMetalLayer : CAMetalLayer <DisplayLayer>
+#else
+@interface GodotMetalLayer : CALayer <DisplayLayer>
+#endif
+#else
+@interface GodotMetalLayer : CAMetalLayer <DisplayLayer>
+#endif
+@end
+
+API_DEPRECATED("OpenGLES is deprecated", ios(2.0, 12.0))
+@interface GodotOpenGLLayer : CAEAGLLayer <DisplayLayer>
+
+@end
diff --git a/platform/iphone/display_layer.mm b/platform/iphone/display_layer.mm
new file mode 100644
index 0000000000..5ec94fb471
--- /dev/null
+++ b/platform/iphone/display_layer.mm
@@ -0,0 +1,186 @@
+/*************************************************************************/
+/* display_layer.mm */
+/*************************************************************************/
+/* This file is part of: */
+/* GODOT ENGINE */
+/* https://godotengine.org */
+/*************************************************************************/
+/* Copyright (c) 2007-2020 Juan Linietsky, Ariel Manzur. */
+/* Copyright (c) 2014-2020 Godot Engine contributors (cf. AUTHORS.md). */
+/* */
+/* 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 "display_layer.h"
+#include "core/os/keyboard.h"
+#include "core/project_settings.h"
+#include "display_server_iphone.h"
+#include "main/main.h"
+#include "os_iphone.h"
+#include "servers/audio_server.h"
+
+#import <AudioToolbox/AudioServices.h>
+#import <GameController/GameController.h>
+#import <OpenGLES/EAGL.h>
+#import <OpenGLES/ES1/gl.h>
+#import <OpenGLES/ES1/glext.h>
+#import <QuartzCore/QuartzCore.h>
+#import <UIKit/UIKit.h>
+
+@implementation GodotMetalLayer
+
+- (void)initializeDisplayLayer {
+#if defined(TARGET_OS_SIMULATOR) && TARGET_OS_SIMULATOR
+ if (@available(iOS 13, *)) {
+ // Simulator supports Metal since iOS 13
+ } else {
+ NSLog(@"iOS Simulator prior to iOS 13 does not support Metal rendering.");
+ }
+#endif
+}
+
+- (void)layoutDisplayLayer {
+}
+
+- (void)renderDisplayLayer {
+}
+
+@end
+
+@implementation GodotOpenGLLayer {
+ // The pixel dimensions of the backbuffer
+ GLint backingWidth;
+ GLint backingHeight;
+
+ EAGLContext *context;
+ GLuint viewRenderbuffer, viewFramebuffer;
+ GLuint depthRenderbuffer;
+}
+
+- (void)initializeDisplayLayer {
+ // Get our backing layer
+
+ // Configure it so that it is opaque, does not retain the contents of the backbuffer when displayed, and uses RGBA8888 color.
+ self.opaque = YES;
+ self.drawableProperties = [NSDictionary
+ dictionaryWithObjectsAndKeys:[NSNumber numberWithBool:FALSE],
+ kEAGLDrawablePropertyRetainedBacking,
+ kEAGLColorFormatRGBA8,
+ kEAGLDrawablePropertyColorFormat,
+ nil];
+
+ // FIXME: Add Vulkan support via MoltenVK. Add fallback code back?
+
+ // Create GL ES 2 context
+ if (GLOBAL_GET("rendering/quality/driver/driver_name") == "GLES2") {
+ context = [[EAGLContext alloc] initWithAPI:kEAGLRenderingAPIOpenGLES2];
+ NSLog(@"Setting up an OpenGL ES 2.0 context.");
+ if (!context) {
+ NSLog(@"Failed to create OpenGL ES 2.0 context!");
+ return;
+ }
+ }
+
+ if (![EAGLContext setCurrentContext:context]) {
+ NSLog(@"Failed to set EAGLContext!");
+ return;
+ }
+ if (![self createFramebuffer]) {
+ NSLog(@"Failed to create frame buffer!");
+ return;
+ }
+}
+
+- (void)layoutDisplayLayer {
+ [EAGLContext setCurrentContext:context];
+ [self destroyFramebuffer];
+ [self createFramebuffer];
+}
+
+- (void)renderDisplayLayer {
+ [EAGLContext setCurrentContext:context];
+}
+
+- (void)dealloc {
+ if ([EAGLContext currentContext] == context) {
+ [EAGLContext setCurrentContext:nil];
+ }
+
+ if (context) {
+ [context release];
+ context = nil;
+ }
+
+ [super dealloc];
+}
+
+- (BOOL)createFramebuffer {
+ glGenFramebuffersOES(1, &viewFramebuffer);
+ glGenRenderbuffersOES(1, &viewRenderbuffer);
+
+ glBindFramebufferOES(GL_FRAMEBUFFER_OES, viewFramebuffer);
+ glBindRenderbufferOES(GL_RENDERBUFFER_OES, viewRenderbuffer);
+ // This call associates the storage for the current render buffer with the EAGLDrawable (our CAself)
+ // allowing us to draw into a buffer that will later be rendered to screen wherever the layer is (which corresponds with our view).
+ [context renderbufferStorage:GL_RENDERBUFFER_OES fromDrawable:(id<EAGLDrawable>)self];
+ glFramebufferRenderbufferOES(GL_FRAMEBUFFER_OES, GL_COLOR_ATTACHMENT0_OES, GL_RENDERBUFFER_OES, viewRenderbuffer);
+
+ glGetRenderbufferParameterivOES(GL_RENDERBUFFER_OES, GL_RENDERBUFFER_WIDTH_OES, &backingWidth);
+ glGetRenderbufferParameterivOES(GL_RENDERBUFFER_OES, GL_RENDERBUFFER_HEIGHT_OES, &backingHeight);
+
+ // For this sample, we also need a depth buffer, so we'll create and attach one via another renderbuffer.
+ glGenRenderbuffersOES(1, &depthRenderbuffer);
+ glBindRenderbufferOES(GL_RENDERBUFFER_OES, depthRenderbuffer);
+ glRenderbufferStorageOES(GL_RENDERBUFFER_OES, GL_DEPTH_COMPONENT16_OES, backingWidth, backingHeight);
+ glFramebufferRenderbufferOES(GL_FRAMEBUFFER_OES, GL_DEPTH_ATTACHMENT_OES, GL_RENDERBUFFER_OES, depthRenderbuffer);
+
+ if (glCheckFramebufferStatusOES(GL_FRAMEBUFFER_OES) != GL_FRAMEBUFFER_COMPLETE_OES) {
+ NSLog(@"failed to make complete framebuffer object %x", glCheckFramebufferStatusOES(GL_FRAMEBUFFER_OES));
+ return NO;
+ }
+
+ // if (OS::get_singleton()) {
+ // OS::VideoMode vm;
+ // vm.fullscreen = true;
+ // vm.width = backingWidth;
+ // vm.height = backingHeight;
+ // vm.resizable = false;
+ // OS::get_singleton()->set_video_mode(vm);
+ // OSIPhone::get_singleton()->set_base_framebuffer(viewFramebuffer);
+ // };
+ // gl_view_base_fb = viewFramebuffer;
+
+ return YES;
+}
+
+// Clean up any buffers we have allocated.
+- (void)destroyFramebuffer {
+ glDeleteFramebuffersOES(1, &viewFramebuffer);
+ viewFramebuffer = 0;
+ glDeleteRenderbuffersOES(1, &viewRenderbuffer);
+ viewRenderbuffer = 0;
+
+ if (depthRenderbuffer) {
+ glDeleteRenderbuffersOES(1, &depthRenderbuffer);
+ depthRenderbuffer = 0;
+ }
+}
+
+@end
diff --git a/platform/iphone/display_server_iphone.h b/platform/iphone/display_server_iphone.h
new file mode 100644
index 0000000000..229b1e80db
--- /dev/null
+++ b/platform/iphone/display_server_iphone.h
@@ -0,0 +1,202 @@
+/*************************************************************************/
+/* display_server_iphone.h */
+/*************************************************************************/
+/* This file is part of: */
+/* GODOT ENGINE */
+/* https://godotengine.org */
+/*************************************************************************/
+/* Copyright (c) 2007-2020 Juan Linietsky, Ariel Manzur. */
+/* Copyright (c) 2014-2020 Godot Engine contributors (cf. AUTHORS.md). */
+/* */
+/* Permission is hereby granted, free of charge, to any person obtaining */
+/* a copy of this software and associated documentation files (the */
+/* "Software"), to deal in the Software without restriction, including */
+/* without limitation the rights to use, copy, modify, merge, publish, */
+/* distribute, sublicense, and/or sell copies of the Software, and to */
+/* permit persons to whom the Software is furnished to do so, subject to */
+/* the following conditions: */
+/* */
+/* The above copyright notice and this permission notice shall be */
+/* included in all copies or substantial portions of the Software. */
+/* */
+/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
+/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
+/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/
+/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
+/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
+/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
+/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
+/*************************************************************************/
+
+#ifndef display_server_iphone_h
+#define display_server_iphone_h
+
+#include "core/input/input.h"
+#include "servers/display_server.h"
+
+#if defined(VULKAN_ENABLED)
+#include "drivers/vulkan/rendering_device_vulkan.h"
+#include "servers/rendering/rasterizer_rd/rasterizer_rd.h"
+
+#include "vulkan_context_iphone.h"
+
+#import <QuartzCore/CAMetalLayer.h>
+#include <vulkan/vulkan_metal.h>
+#endif
+
+class DisplayServerIPhone : public DisplayServer {
+ GDCLASS(DisplayServerIPhone, DisplayServer)
+
+ _THREAD_SAFE_CLASS_
+
+#if defined(VULKAN_ENABLED)
+ VulkanContextIPhone *context_vulkan;
+ RenderingDeviceVulkan *rendering_device_vulkan;
+#endif
+
+ DisplayServer::ScreenOrientation screen_orientation;
+
+ ObjectID window_attached_instance_id;
+
+ Callable window_event_callback;
+ Callable window_resize_callback;
+ Callable input_event_callback;
+ Callable input_text_callback;
+
+ int virtual_keyboard_height = 0;
+
+ void perform_event(const Ref<InputEvent> &p_event);
+
+ DisplayServerIPhone(const String &p_rendering_driver, DisplayServer::WindowMode p_mode, uint32_t p_flags, const Vector2i &p_resolution, Error &r_error);
+ ~DisplayServerIPhone();
+
+public:
+ String rendering_driver;
+
+ static DisplayServerIPhone *get_singleton();
+
+ static void register_iphone_driver();
+ static DisplayServer *create_func(const String &p_rendering_driver, WindowMode p_mode, uint32_t p_flags, const Vector2i &p_resolution, Error &r_error);
+ static Vector<String> get_rendering_drivers_func();
+
+ // MARK: - Events
+
+ virtual void process_events() override;
+
+ virtual void window_set_rect_changed_callback(const Callable &p_callable, WindowID p_window = MAIN_WINDOW_ID) override;
+ virtual void window_set_window_event_callback(const Callable &p_callable, WindowID p_window = MAIN_WINDOW_ID) override;
+ virtual void window_set_input_event_callback(const Callable &p_callable, WindowID p_window = MAIN_WINDOW_ID) override;
+ virtual void window_set_input_text_callback(const Callable &p_callable, WindowID p_window = MAIN_WINDOW_ID) override;
+ virtual void window_set_drop_files_callback(const Callable &p_callable, WindowID p_window = MAIN_WINDOW_ID) override;
+
+ static void _dispatch_input_events(const Ref<InputEvent> &p_event);
+ void send_input_event(const Ref<InputEvent> &p_event) const;
+ void send_input_text(const String &p_text) const;
+ void send_window_event(DisplayServer::WindowEvent p_event) const;
+ void _window_callback(const Callable &p_callable, const Variant &p_arg) const;
+
+ // MARK: - Input
+
+ // MARK: Touches
+
+ void touch_press(int p_idx, int p_x, int p_y, bool p_pressed, bool p_doubleclick);
+ void touch_drag(int p_idx, int p_prev_x, int p_prev_y, int p_x, int p_y);
+ void touches_cancelled(int p_idx);
+
+ // MARK: Keyboard
+
+ void key(uint32_t p_key, bool p_pressed);
+
+ // MARK: Motion
+
+ void update_gravity(float p_x, float p_y, float p_z);
+ void update_accelerometer(float p_x, float p_y, float p_z);
+ void update_magnetometer(float p_x, float p_y, float p_z);
+ void update_gyroscope(float p_x, float p_y, float p_z);
+
+ // MARK: -
+
+ virtual bool has_feature(Feature p_feature) const override;
+ virtual String get_name() const override;
+
+ virtual void alert(const String &p_alert, const String &p_title = "ALERT!") override;
+
+ virtual int get_screen_count() const override;
+ virtual Point2i screen_get_position(int p_screen = SCREEN_OF_MAIN_WINDOW) const override;
+ virtual Size2i screen_get_size(int p_screen = SCREEN_OF_MAIN_WINDOW) const override;
+ virtual Rect2i screen_get_usable_rect(int p_screen = SCREEN_OF_MAIN_WINDOW) const override;
+ virtual int screen_get_dpi(int p_screen = SCREEN_OF_MAIN_WINDOW) const override;
+ virtual float screen_get_scale(int p_screen = SCREEN_OF_MAIN_WINDOW) const override;
+
+ virtual Vector<DisplayServer::WindowID> get_window_list() const override;
+
+ virtual WindowID
+ get_window_at_screen_position(const Point2i &p_position) const override;
+
+ virtual void window_attach_instance_id(ObjectID p_instance, WindowID p_window = MAIN_WINDOW_ID) override;
+ virtual ObjectID window_get_attached_instance_id(WindowID p_window = MAIN_WINDOW_ID) const override;
+
+ virtual void window_set_title(const String &p_title, WindowID p_window = MAIN_WINDOW_ID) override;
+
+ virtual int window_get_current_screen(WindowID p_window = MAIN_WINDOW_ID) const override;
+ virtual void window_set_current_screen(int p_screen, WindowID p_window = MAIN_WINDOW_ID) override;
+
+ virtual Point2i window_get_position(WindowID p_window = MAIN_WINDOW_ID) const override;
+ virtual void window_set_position(const Point2i &p_position, WindowID p_window = MAIN_WINDOW_ID) override;
+
+ virtual void window_set_transient(WindowID p_window, WindowID p_parent) override;
+
+ virtual void window_set_max_size(const Size2i p_size, WindowID p_window = MAIN_WINDOW_ID) override;
+ virtual Size2i window_get_max_size(WindowID p_window = MAIN_WINDOW_ID) const override;
+
+ virtual void window_set_min_size(const Size2i p_size, WindowID p_window = MAIN_WINDOW_ID) override;
+ virtual Size2i window_get_min_size(WindowID p_window = MAIN_WINDOW_ID) const override;
+
+ virtual void window_set_size(const Size2i p_size, WindowID p_window = MAIN_WINDOW_ID) override;
+ virtual Size2i window_get_size(WindowID p_window = MAIN_WINDOW_ID) const override;
+ virtual Size2i window_get_real_size(WindowID p_window = MAIN_WINDOW_ID) const override;
+
+ virtual void window_set_mode(WindowMode p_mode, WindowID p_window = MAIN_WINDOW_ID) override;
+ virtual WindowMode window_get_mode(WindowID p_window = MAIN_WINDOW_ID) const override;
+
+ virtual bool window_is_maximize_allowed(WindowID p_window = MAIN_WINDOW_ID) const override;
+
+ virtual void window_set_flag(WindowFlags p_flag, bool p_enabled, WindowID p_window = MAIN_WINDOW_ID) override;
+ virtual bool window_get_flag(WindowFlags p_flag, WindowID p_window = MAIN_WINDOW_ID) const override;
+
+ virtual void window_request_attention(WindowID p_window = MAIN_WINDOW_ID) override;
+ virtual void window_move_to_foreground(WindowID p_window = MAIN_WINDOW_ID) override;
+
+ virtual float screen_get_max_scale() const override;
+
+ virtual void screen_set_orientation(DisplayServer::ScreenOrientation p_orientation, int p_screen) override;
+ virtual DisplayServer::ScreenOrientation screen_get_orientation(int p_screen) const override;
+
+ virtual bool window_can_draw(WindowID p_window = MAIN_WINDOW_ID) const override;
+
+ virtual bool can_any_window_draw() const override;
+
+ virtual bool screen_is_touchscreen(int p_screen) const override;
+
+ virtual void virtual_keyboard_show(const String &p_existing_text, const Rect2 &p_screen_rect, bool p_multiline, int p_max_length, int p_cursor_start, int p_cursor_end) override;
+ virtual void virtual_keyboard_hide() override;
+
+ void virtual_keyboard_set_height(int height);
+ virtual int virtual_keyboard_get_height() const override;
+
+ virtual void clipboard_set(const String &p_text) override;
+ virtual String clipboard_get() const override;
+
+ virtual void screen_set_keep_on(bool p_enable) override;
+ virtual bool screen_is_kept_on() const override;
+
+ virtual Error native_video_play(String p_path, float p_volume, String p_audio_track, String p_subtitle_track, int p_screen = SCREEN_OF_MAIN_WINDOW) override;
+ virtual bool native_video_is_playing() const override;
+ virtual void native_video_pause() override;
+ virtual void native_video_unpause() override;
+ virtual void native_video_stop() override;
+
+ void resize_window(CGSize size);
+};
+
+#endif /* display_server_iphone_h */
diff --git a/platform/iphone/display_server_iphone.mm b/platform/iphone/display_server_iphone.mm
new file mode 100644
index 0000000000..aafee49594
--- /dev/null
+++ b/platform/iphone/display_server_iphone.mm
@@ -0,0 +1,751 @@
+/*************************************************************************/
+/* display_server_iphone.mm */
+/*************************************************************************/
+/* This file is part of: */
+/* GODOT ENGINE */
+/* https://godotengine.org */
+/*************************************************************************/
+/* Copyright (c) 2007-2020 Juan Linietsky, Ariel Manzur. */
+/* Copyright (c) 2014-2020 Godot Engine contributors (cf. AUTHORS.md). */
+/* */
+/* 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 "display_server_iphone.h"
+#import "app_delegate.h"
+#include "core/io/file_access_pack.h"
+#include "core/project_settings.h"
+#import "godot_view.h"
+#include "ios.h"
+#include "os_iphone.h"
+#import "view_controller.h"
+
+#import <Foundation/Foundation.h>
+#import <sys/utsname.h>
+
+static const float kDisplayServerIPhoneAcceleration = 1;
+static NSDictionary *iOSModelToDPI = @{
+ @[
+ @"iPad1,1",
+ @"iPad2,1",
+ @"iPad2,2",
+ @"iPad2,3",
+ @"iPad2,4",
+ ] : @132,
+ @[
+ @"iPhone1,1",
+ @"iPhone1,2",
+ @"iPhone2,1",
+ @"iPad2,5",
+ @"iPad2,6",
+ @"iPad2,7",
+ @"iPod1,1",
+ @"iPod2,1",
+ @"iPod3,1",
+ ] : @163,
+ @[
+ @"iPad3,1",
+ @"iPad3,2",
+ @"iPad3,3",
+ @"iPad3,4",
+ @"iPad3,5",
+ @"iPad3,6",
+ @"iPad4,1",
+ @"iPad4,2",
+ @"iPad4,3",
+ @"iPad5,3",
+ @"iPad5,4",
+ @"iPad6,3",
+ @"iPad6,4",
+ @"iPad6,7",
+ @"iPad6,8",
+ @"iPad6,11",
+ @"iPad6,12",
+ @"iPad7,1",
+ @"iPad7,2",
+ @"iPad7,3",
+ @"iPad7,4",
+ @"iPad7,5",
+ @"iPad7,6",
+ @"iPad7,11",
+ @"iPad7,12",
+ @"iPad8,1",
+ @"iPad8,2",
+ @"iPad8,3",
+ @"iPad8,4",
+ @"iPad8,5",
+ @"iPad8,6",
+ @"iPad8,7",
+ @"iPad8,8",
+ @"iPad8,9",
+ @"iPad8,10",
+ @"iPad8,11",
+ @"iPad8,12",
+ @"iPad11,3",
+ @"iPad11,4",
+ ] : @264,
+ @[
+ @"iPhone3,1",
+ @"iPhone3,2",
+ @"iPhone3,3",
+ @"iPhone4,1",
+ @"iPhone5,1",
+ @"iPhone5,2",
+ @"iPhone5,3",
+ @"iPhone5,4",
+ @"iPhone6,1",
+ @"iPhone6,2",
+ @"iPhone7,2",
+ @"iPhone8,1",
+ @"iPhone8,4",
+ @"iPhone9,1",
+ @"iPhone9,3",
+ @"iPhone10,1",
+ @"iPhone10,4",
+ @"iPhone11,8",
+ @"iPhone12,1",
+ @"iPhone12,8",
+ @"iPad4,4",
+ @"iPad4,5",
+ @"iPad4,6",
+ @"iPad4,7",
+ @"iPad4,8",
+ @"iPad4,9",
+ @"iPad5,1",
+ @"iPad5,2",
+ @"iPad11,1",
+ @"iPad11,2",
+ @"iPod4,1",
+ @"iPod5,1",
+ @"iPod7,1",
+ @"iPod9,1",
+ ] : @326,
+ @[
+ @"iPhone7,1",
+ @"iPhone8,2",
+ @"iPhone9,2",
+ @"iPhone9,4",
+ @"iPhone10,2",
+ @"iPhone10,5",
+ ] : @401,
+ @[
+ @"iPhone10,3",
+ @"iPhone10,6",
+ @"iPhone11,2",
+ @"iPhone11,4",
+ @"iPhone11,6",
+ @"iPhone12,3",
+ @"iPhone12,5",
+ ] : @458,
+};
+
+DisplayServerIPhone *DisplayServerIPhone::get_singleton() {
+ return (DisplayServerIPhone *)DisplayServer::get_singleton();
+}
+
+DisplayServerIPhone::DisplayServerIPhone(const String &p_rendering_driver, DisplayServer::WindowMode p_mode, uint32_t p_flags, const Vector2i &p_resolution, Error &r_error) {
+ rendering_driver = p_rendering_driver;
+
+#if defined(OPENGL_ENABLED)
+ // FIXME: Add support for both GLES2 and Vulkan when GLES2 is implemented
+ // again,
+
+ if (rendering_driver == "opengl_es") {
+ bool gl_initialization_error = false;
+
+ // FIXME: Add Vulkan support via MoltenVK. Add fallback code back?
+
+ if (RasterizerGLES2::is_viable() == OK) {
+ RasterizerGLES2::register_config();
+ RasterizerGLES2::make_current();
+ } else {
+ gl_initialization_error = true;
+ }
+
+ if (gl_initialization_error) {
+ OS::get_singleton()->alert("Your device does not support any of the supported OpenGL versions.", "Unable to initialize video driver");
+ // return ERR_UNAVAILABLE;
+ }
+
+ // rendering_server = memnew(RenderingServerRaster);
+ // // FIXME: Reimplement threaded rendering
+ // if (get_render_thread_mode() != RENDER_THREAD_UNSAFE) {
+ // rendering_server = memnew(RenderingServerWrapMT(rendering_server,
+ // false));
+ // }
+ // rendering_server->init();
+ // rendering_server->cursor_set_visible(false, 0);
+
+ // reset this to what it should be, it will have been set to 0 after
+ // rendering_server->init() is called
+ // RasterizerStorageGLES2::system_fbo = gl_view_base_fb;
+ }
+#endif
+
+#if defined(VULKAN_ENABLED)
+ rendering_driver = "vulkan";
+
+ context_vulkan = nullptr;
+ rendering_device_vulkan = nullptr;
+
+ if (rendering_driver == "vulkan") {
+ context_vulkan = memnew(VulkanContextIPhone);
+ if (context_vulkan->initialize() != OK) {
+ memdelete(context_vulkan);
+ context_vulkan = nullptr;
+ ERR_FAIL_MSG("Failed to initialize Vulkan context");
+ }
+
+ CALayer *layer = [AppDelegate.viewController.godotView initializeRenderingForDriver:@"vulkan"];
+
+ if (!layer) {
+ ERR_FAIL_MSG("Failed to create iOS rendering layer.");
+ }
+
+ Size2i size = Size2i(layer.bounds.size.width, layer.bounds.size.height) * screen_get_max_scale();
+ if (context_vulkan->window_create(MAIN_WINDOW_ID, layer, size.width, size.height) != OK) {
+ memdelete(context_vulkan);
+ context_vulkan = nullptr;
+ ERR_FAIL_MSG("Failed to create Vulkan window.");
+ }
+
+ rendering_device_vulkan = memnew(RenderingDeviceVulkan);
+ rendering_device_vulkan->initialize(context_vulkan);
+
+ RasterizerRD::make_current();
+ }
+#endif
+
+ bool keep_screen_on = bool(GLOBAL_DEF("display/window/energy_saving/keep_screen_on", true));
+ screen_set_keep_on(keep_screen_on);
+
+ Input::get_singleton()->set_event_dispatch_function(_dispatch_input_events);
+
+ r_error = OK;
+}
+
+DisplayServerIPhone::~DisplayServerIPhone() {
+#if defined(VULKAN_ENABLED)
+ if (rendering_driver == "vulkan") {
+ if (rendering_device_vulkan) {
+ rendering_device_vulkan->finalize();
+ memdelete(rendering_device_vulkan);
+ rendering_device_vulkan = NULL;
+ }
+
+ if (context_vulkan) {
+ context_vulkan->window_destroy(MAIN_WINDOW_ID);
+ memdelete(context_vulkan);
+ context_vulkan = NULL;
+ }
+ }
+#endif
+}
+
+DisplayServer *DisplayServerIPhone::create_func(const String &p_rendering_driver, WindowMode p_mode, uint32_t p_flags, const Vector2i &p_resolution, Error &r_error) {
+ return memnew(DisplayServerIPhone(p_rendering_driver, p_mode, p_flags, p_resolution, r_error));
+}
+
+Vector<String> DisplayServerIPhone::get_rendering_drivers_func() {
+ Vector<String> drivers;
+
+#if defined(VULKAN_ENABLED)
+ drivers.push_back("vulkan");
+#endif
+#if defined(OPENGL_ENABLED)
+ drivers.push_back("opengl_es");
+#endif
+
+ return drivers;
+}
+
+void DisplayServerIPhone::register_iphone_driver() {
+ register_create_function("iphone", create_func, get_rendering_drivers_func);
+}
+
+// MARK: Events
+
+void DisplayServerIPhone::window_set_rect_changed_callback(const Callable &p_callable, WindowID p_window) {
+ window_resize_callback = p_callable;
+}
+
+void DisplayServerIPhone::window_set_window_event_callback(const Callable &p_callable, WindowID p_window) {
+ window_event_callback = p_callable;
+}
+void DisplayServerIPhone::window_set_input_event_callback(const Callable &p_callable, WindowID p_window) {
+ input_event_callback = p_callable;
+}
+
+void DisplayServerIPhone::window_set_input_text_callback(const Callable &p_callable, WindowID p_window) {
+ input_text_callback = p_callable;
+}
+
+void DisplayServerIPhone::window_set_drop_files_callback(const Callable &p_callable, WindowID p_window) {
+ // Probably not supported for iOS
+}
+
+void DisplayServerIPhone::process_events() {
+}
+
+void DisplayServerIPhone::_dispatch_input_events(const Ref<InputEvent> &p_event) {
+ DisplayServerIPhone::get_singleton()->send_input_event(p_event);
+}
+
+void DisplayServerIPhone::send_input_event(const Ref<InputEvent> &p_event) const {
+ _window_callback(input_event_callback, p_event);
+}
+
+void DisplayServerIPhone::send_input_text(const String &p_text) const {
+ _window_callback(input_text_callback, p_text);
+}
+
+void DisplayServerIPhone::send_window_event(DisplayServer::WindowEvent p_event) const {
+ _window_callback(window_event_callback, int(p_event));
+}
+
+void DisplayServerIPhone::_window_callback(const Callable &p_callable, const Variant &p_arg) const {
+ if (!p_callable.is_null()) {
+ const Variant *argp = &p_arg;
+ Variant ret;
+ Callable::CallError ce;
+ p_callable.call((const Variant **)&argp, 1, ret, ce);
+ }
+}
+
+// MARK: - Input
+
+// MARK: Touches
+
+void DisplayServerIPhone::touch_press(int p_idx, int p_x, int p_y, bool p_pressed, bool p_doubleclick) {
+ if (!GLOBAL_DEF("debug/disable_touch", false)) {
+ Ref<InputEventScreenTouch> ev;
+ ev.instance();
+
+ ev->set_index(p_idx);
+ ev->set_pressed(p_pressed);
+ ev->set_position(Vector2(p_x, p_y));
+ perform_event(ev);
+ }
+};
+
+void DisplayServerIPhone::touch_drag(int p_idx, int p_prev_x, int p_prev_y, int p_x, int p_y) {
+ if (!GLOBAL_DEF("debug/disable_touch", false)) {
+ Ref<InputEventScreenDrag> ev;
+ ev.instance();
+ ev->set_index(p_idx);
+ ev->set_position(Vector2(p_x, p_y));
+ ev->set_relative(Vector2(p_x - p_prev_x, p_y - p_prev_y));
+ perform_event(ev);
+ };
+};
+
+void DisplayServerIPhone::perform_event(const Ref<InputEvent> &p_event) {
+ Input::get_singleton()->parse_input_event(p_event);
+};
+
+void DisplayServerIPhone::touches_cancelled(int p_idx) {
+ touch_press(p_idx, -1, -1, false, false);
+};
+
+// MARK: Keyboard
+
+void DisplayServerIPhone::key(uint32_t p_key, bool p_pressed) {
+ Ref<InputEventKey> ev;
+ ev.instance();
+ ev->set_echo(false);
+ ev->set_pressed(p_pressed);
+ ev->set_keycode(p_key);
+ ev->set_physical_keycode(p_key);
+ ev->set_unicode(p_key);
+ perform_event(ev);
+};
+
+// MARK: Motion
+
+void DisplayServerIPhone::update_gravity(float p_x, float p_y, float p_z) {
+ Input::get_singleton()->set_gravity(Vector3(p_x, p_y, p_z));
+};
+
+void DisplayServerIPhone::update_accelerometer(float p_x, float p_y,
+ float p_z) {
+ // Found out the Z should not be negated! Pass as is!
+ Vector3 v_accelerometer = Vector3(
+ p_x / kDisplayServerIPhoneAcceleration,
+ p_y / kDisplayServerIPhoneAcceleration,
+ p_z / kDisplayServerIPhoneAcceleration);
+
+ Input::get_singleton()->set_accelerometer(v_accelerometer);
+
+ /*
+ if (p_x != last_accel.x) {
+ //printf("updating accel x %f\n", p_x);
+ InputEvent ev;
+ ev.type = InputEvent::JOYPAD_MOTION;
+ ev.device = 0;
+ ev.joy_motion.axis = JOY_ANALOG_0;
+ ev.joy_motion.axis_value = (p_x / (float)ACCEL_RANGE);
+ last_accel.x = p_x;
+ queue_event(ev);
+ };
+ if (p_y != last_accel.y) {
+ //printf("updating accel y %f\n", p_y);
+ InputEvent ev;
+ ev.type = InputEvent::JOYPAD_MOTION;
+ ev.device = 0;
+ ev.joy_motion.axis = JOY_ANALOG_1;
+ ev.joy_motion.axis_value = (p_y / (float)ACCEL_RANGE);
+ last_accel.y = p_y;
+ queue_event(ev);
+ };
+ if (p_z != last_accel.z) {
+ //printf("updating accel z %f\n", p_z);
+ InputEvent ev;
+ ev.type = InputEvent::JOYPAD_MOTION;
+ ev.device = 0;
+ ev.joy_motion.axis = JOY_ANALOG_2;
+ ev.joy_motion.axis_value = ( (1.0 - p_z) / (float)ACCEL_RANGE);
+ last_accel.z = p_z;
+ queue_event(ev);
+ };
+ */
+};
+
+void DisplayServerIPhone::update_magnetometer(float p_x, float p_y, float p_z) {
+ Input::get_singleton()->set_magnetometer(Vector3(p_x, p_y, p_z));
+};
+
+void DisplayServerIPhone::update_gyroscope(float p_x, float p_y, float p_z) {
+ Input::get_singleton()->set_gyroscope(Vector3(p_x, p_y, p_z));
+};
+
+// MARK: -
+
+bool DisplayServerIPhone::has_feature(Feature p_feature) const {
+ switch (p_feature) {
+ // case FEATURE_CONSOLE_WINDOW:
+ // case FEATURE_CURSOR_SHAPE:
+ // case FEATURE_CUSTOM_CURSOR_SHAPE:
+ // case FEATURE_GLOBAL_MENU:
+ // case FEATURE_HIDPI:
+ // case FEATURE_ICON:
+ // case FEATURE_IME:
+ // case FEATURE_MOUSE:
+ // case FEATURE_MOUSE_WARP:
+ // case FEATURE_NATIVE_DIALOG:
+ // case FEATURE_NATIVE_ICON:
+ // case FEATURE_NATIVE_VIDEO:
+ // case FEATURE_WINDOW_TRANSPARENCY:
+ case FEATURE_CLIPBOARD:
+ case FEATURE_KEEP_SCREEN_ON:
+ case FEATURE_ORIENTATION:
+ case FEATURE_TOUCHSCREEN:
+ case FEATURE_VIRTUAL_KEYBOARD:
+ return true;
+ default:
+ return false;
+ }
+}
+
+String DisplayServerIPhone::get_name() const {
+ return "iPhone";
+}
+
+void DisplayServerIPhone::alert(const String &p_alert, const String &p_title) {
+ const CharString utf8_alert = p_alert.utf8();
+ const CharString utf8_title = p_title.utf8();
+ iOS::alert(utf8_alert.get_data(), utf8_title.get_data());
+}
+
+int DisplayServerIPhone::get_screen_count() const {
+ return 1;
+}
+
+Point2i DisplayServerIPhone::screen_get_position(int p_screen) const {
+ return Size2i();
+}
+
+Size2i DisplayServerIPhone::screen_get_size(int p_screen) const {
+ CALayer *layer = AppDelegate.viewController.godotView.renderingLayer;
+
+ if (!layer) {
+ return Size2i();
+ }
+
+ return Size2i(layer.bounds.size.width, layer.bounds.size.height) * screen_get_scale(p_screen);
+}
+
+Rect2i DisplayServerIPhone::screen_get_usable_rect(int p_screen) const {
+ return Rect2i(screen_get_position(p_screen), screen_get_size(p_screen));
+}
+
+int DisplayServerIPhone::screen_get_dpi(int p_screen) const {
+ struct utsname systemInfo;
+ uname(&systemInfo);
+
+ NSString *string = [NSString stringWithCString:systemInfo.machine encoding:NSUTF8StringEncoding];
+
+ for (NSArray *keyArray in iOSModelToDPI) {
+ if ([keyArray containsObject:string]) {
+ NSNumber *value = iOSModelToDPI[keyArray];
+ return [value intValue];
+ }
+ }
+
+ return 163;
+}
+
+float DisplayServerIPhone::screen_get_scale(int p_screen) const {
+ return [UIScreen mainScreen].nativeScale;
+}
+
+Vector<DisplayServer::WindowID> DisplayServerIPhone::get_window_list() const {
+ Vector<DisplayServer::WindowID> list;
+ list.push_back(MAIN_WINDOW_ID);
+ return list;
+}
+
+DisplayServer::WindowID DisplayServerIPhone::get_window_at_screen_position(const Point2i &p_position) const {
+ return MAIN_WINDOW_ID;
+}
+
+void DisplayServerIPhone::window_attach_instance_id(ObjectID p_instance, WindowID p_window) {
+ window_attached_instance_id = p_instance;
+}
+
+ObjectID DisplayServerIPhone::window_get_attached_instance_id(WindowID p_window) const {
+ return window_attached_instance_id;
+}
+
+void DisplayServerIPhone::window_set_title(const String &p_title, WindowID p_window) {
+ // Probably not supported for iOS
+}
+
+int DisplayServerIPhone::window_get_current_screen(WindowID p_window) const {
+ return SCREEN_OF_MAIN_WINDOW;
+}
+
+void DisplayServerIPhone::window_set_current_screen(int p_screen, WindowID p_window) {
+ // Probably not supported for iOS
+}
+
+Point2i DisplayServerIPhone::window_get_position(WindowID p_window) const {
+ return Point2i();
+}
+
+void DisplayServerIPhone::window_set_position(const Point2i &p_position, WindowID p_window) {
+ // Probably not supported for single window iOS app
+}
+
+void DisplayServerIPhone::window_set_transient(WindowID p_window, WindowID p_parent) {
+ // Probably not supported for iOS
+}
+
+void DisplayServerIPhone::window_set_max_size(const Size2i p_size, WindowID p_window) {
+ // Probably not supported for iOS
+}
+
+Size2i DisplayServerIPhone::window_get_max_size(WindowID p_window) const {
+ return Size2i();
+}
+
+void DisplayServerIPhone::window_set_min_size(const Size2i p_size, WindowID p_window) {
+ // Probably not supported for iOS
+}
+
+Size2i DisplayServerIPhone::window_get_min_size(WindowID p_window) const {
+ return Size2i();
+}
+
+void DisplayServerIPhone::window_set_size(const Size2i p_size, WindowID p_window) {
+ // Probably not supported for iOS
+}
+
+Size2i DisplayServerIPhone::window_get_size(WindowID p_window) const {
+ CGRect screenBounds = [UIScreen mainScreen].bounds;
+ return Size2i(screenBounds.size.width, screenBounds.size.height) * screen_get_max_scale();
+}
+
+Size2i DisplayServerIPhone::window_get_real_size(WindowID p_window) const {
+ return window_get_size(p_window);
+}
+
+void DisplayServerIPhone::window_set_mode(WindowMode p_mode, WindowID p_window) {
+ // Probably not supported for iOS
+}
+
+DisplayServer::WindowMode DisplayServerIPhone::window_get_mode(WindowID p_window) const {
+ return WindowMode::WINDOW_MODE_FULLSCREEN;
+}
+
+bool DisplayServerIPhone::window_is_maximize_allowed(WindowID p_window) const {
+ return false;
+}
+
+void DisplayServerIPhone::window_set_flag(WindowFlags p_flag, bool p_enabled, WindowID p_window) {
+ // Probably not supported for iOS
+}
+
+bool DisplayServerIPhone::window_get_flag(WindowFlags p_flag, WindowID p_window) const {
+ return false;
+}
+
+void DisplayServerIPhone::window_request_attention(WindowID p_window) {
+ // Probably not supported for iOS
+}
+
+void DisplayServerIPhone::window_move_to_foreground(WindowID p_window) {
+ // Probably not supported for iOS
+}
+
+float DisplayServerIPhone::screen_get_max_scale() const {
+ return screen_get_scale(SCREEN_OF_MAIN_WINDOW);
+};
+
+void DisplayServerIPhone::screen_set_orientation(DisplayServer::ScreenOrientation p_orientation, int p_screen) {
+ screen_orientation = p_orientation;
+}
+
+DisplayServer::ScreenOrientation DisplayServerIPhone::screen_get_orientation(int p_screen) const {
+ return screen_orientation;
+}
+
+bool DisplayServerIPhone::window_can_draw(WindowID p_window) const {
+ return true;
+}
+
+bool DisplayServerIPhone::can_any_window_draw() const {
+ return true;
+}
+
+bool DisplayServerIPhone::screen_is_touchscreen(int p_screen) const {
+ return true;
+}
+
+void DisplayServerIPhone::virtual_keyboard_show(const String &p_existing_text, const Rect2 &p_screen_rect, bool p_multiline, int p_max_length, int p_cursor_start, int p_cursor_end) {
+ [AppDelegate.viewController.godotView becomeFirstResponderWithString:p_existing_text];
+}
+
+void DisplayServerIPhone::virtual_keyboard_hide() {
+ [AppDelegate.viewController.godotView resignFirstResponder];
+}
+
+void DisplayServerIPhone::virtual_keyboard_set_height(int height) {
+ virtual_keyboard_height = height * screen_get_max_scale();
+}
+
+int DisplayServerIPhone::virtual_keyboard_get_height() const {
+ return virtual_keyboard_height;
+}
+
+void DisplayServerIPhone::clipboard_set(const String &p_text) {
+ [UIPasteboard generalPasteboard].string = [NSString stringWithUTF8String:p_text.utf8()];
+}
+
+String DisplayServerIPhone::clipboard_get() const {
+ NSString *text = [UIPasteboard generalPasteboard].string;
+
+ return String::utf8([text UTF8String]);
+}
+
+void DisplayServerIPhone::screen_set_keep_on(bool p_enable) {
+ [UIApplication sharedApplication].idleTimerDisabled = p_enable;
+}
+
+bool DisplayServerIPhone::screen_is_kept_on() const {
+ return [UIApplication sharedApplication].idleTimerDisabled;
+}
+
+Error DisplayServerIPhone::native_video_play(String p_path, float p_volume, String p_audio_track, String p_subtitle_track, int p_screen) {
+ FileAccess *f = FileAccess::open(p_path, FileAccess::READ);
+ bool exists = f && f->is_open();
+
+ String user_data_dir = OSIPhone::get_singleton()->get_user_data_dir();
+
+ if (!exists) {
+ return FAILED;
+ }
+
+ String tempFile = OSIPhone::get_singleton()->get_user_data_dir();
+
+ if (p_path.begins_with("res://")) {
+ if (PackedData::get_singleton()->has_path(p_path)) {
+ printf("Unable to play %S using the native player as it resides in a .pck file\n", p_path.c_str());
+ return ERR_INVALID_PARAMETER;
+ } else {
+ p_path = p_path.replace("res:/", ProjectSettings::get_singleton()->get_resource_path());
+ }
+ } else if (p_path.begins_with("user://")) {
+ p_path = p_path.replace("user:/", user_data_dir);
+ }
+
+ memdelete(f);
+
+ printf("Playing video: %S\n", p_path.c_str());
+
+ String file_path = ProjectSettings::get_singleton()->globalize_path(p_path);
+
+ NSString *filePath = [[[NSString alloc] initWithUTF8String:file_path.utf8().get_data()] autorelease];
+ NSString *audioTrack = [NSString stringWithUTF8String:p_audio_track.utf8()];
+ NSString *subtitleTrack = [NSString stringWithUTF8String:p_subtitle_track.utf8()];
+
+ if (![AppDelegate.viewController playVideoAtPath:filePath
+ volume:p_volume
+ audio:audioTrack
+ subtitle:subtitleTrack]) {
+ return OK;
+ }
+
+ return FAILED;
+}
+
+bool DisplayServerIPhone::native_video_is_playing() const {
+ return [AppDelegate.viewController isVideoPlaying];
+}
+
+void DisplayServerIPhone::native_video_pause() {
+ if (native_video_is_playing()) {
+ [AppDelegate.viewController pauseVideo];
+ }
+}
+
+void DisplayServerIPhone::native_video_unpause() {
+ [AppDelegate.viewController unpauseVideo];
+};
+
+void DisplayServerIPhone::native_video_stop() {
+ if (native_video_is_playing()) {
+ [AppDelegate.viewController stopVideo];
+ }
+}
+
+void DisplayServerIPhone::resize_window(CGSize viewSize) {
+ Size2i size = Size2i(viewSize.width, viewSize.height) * screen_get_max_scale();
+
+#if defined(VULKAN_ENABLED)
+ if (rendering_driver == "vulkan") {
+ if (context_vulkan) {
+ context_vulkan->window_resize(MAIN_WINDOW_ID, size.x, size.y);
+ }
+ }
+#endif
+
+ Variant resize_rect = Rect2i(Point2i(), size);
+ _window_callback(window_resize_callback, resize_rect);
+}
diff --git a/platform/iphone/game_center.h b/platform/iphone/game_center.h
index 0d3ef5b696..1e9a68fe48 100644
--- a/platform/iphone/game_center.h
+++ b/platform/iphone/game_center.h
@@ -51,12 +51,12 @@ public:
void connect();
bool is_authenticated();
- Error post_score(Variant p_score);
- Error award_achievement(Variant p_params);
+ Error post_score(Dictionary p_score);
+ Error award_achievement(Dictionary p_params);
void reset_achievements();
void request_achievements();
void request_achievement_descriptions();
- Error show_game_center(Variant p_params);
+ Error show_game_center(Dictionary p_params);
Error request_identity_verification_signature();
void game_center_closed();
diff --git a/platform/iphone/game_center.mm b/platform/iphone/game_center.mm
index 8d470da1a8..4481775c32 100644
--- a/platform/iphone/game_center.mm
+++ b/platform/iphone/game_center.mm
@@ -47,13 +47,15 @@ extern "C" {
#import "app_delegate.h"
};
+#import "view_controller.h"
+
GameCenter *GameCenter::instance = NULL;
void GameCenter::_bind_methods() {
ClassDB::bind_method(D_METHOD("is_authenticated"), &GameCenter::is_authenticated);
ClassDB::bind_method(D_METHOD("post_score"), &GameCenter::post_score);
- ClassDB::bind_method(D_METHOD("award_achievement"), &GameCenter::award_achievement);
+ ClassDB::bind_method(D_METHOD("award_achievement", "achievement"), &GameCenter::award_achievement);
ClassDB::bind_method(D_METHOD("reset_achievements"), &GameCenter::reset_achievements);
ClassDB::bind_method(D_METHOD("request_achievements"), &GameCenter::request_achievements);
ClassDB::bind_method(D_METHOD("request_achievement_descriptions"), &GameCenter::request_achievement_descriptions);
@@ -105,7 +107,14 @@ void GameCenter::connect() {
ret["type"] = "authentication";
if (player.isAuthenticated) {
ret["result"] = "ok";
- ret["player_id"] = [player.playerID UTF8String];
+ if (@available(iOS 13, *)) {
+ ret["player_id"] = [player.teamPlayerID UTF8String];
+#if !defined(TARGET_OS_SIMULATOR) || !TARGET_OS_SIMULATOR
+ } else {
+ ret["player_id"] = [player.playerID UTF8String];
+#endif
+ }
+
GameCenter::get_singleton()->authenticated = true;
} else {
ret["result"] = "error";
@@ -123,11 +132,10 @@ bool GameCenter::is_authenticated() {
return authenticated;
};
-Error GameCenter::post_score(Variant p_score) {
- Dictionary params = p_score;
- ERR_FAIL_COND_V(!params.has("score") || !params.has("category"), ERR_INVALID_PARAMETER);
- float score = params["score"];
- String category = params["category"];
+Error GameCenter::post_score(Dictionary p_score) {
+ ERR_FAIL_COND_V(!p_score.has("score") || !p_score.has("category"), ERR_INVALID_PARAMETER);
+ float score = p_score["score"];
+ String category = p_score["category"];
NSString *cat_str = [[[NSString alloc] initWithUTF8String:category.utf8().get_data()] autorelease];
GKScore *reporter = [[[GKScore alloc] initWithLeaderboardIdentifier:cat_str] autorelease];
@@ -153,11 +161,10 @@ Error GameCenter::post_score(Variant p_score) {
return OK;
};
-Error GameCenter::award_achievement(Variant p_params) {
- Dictionary params = p_params;
- ERR_FAIL_COND_V(!params.has("name") || !params.has("progress"), ERR_INVALID_PARAMETER);
- String name = params["name"];
- float progress = params["progress"];
+Error GameCenter::award_achievement(Dictionary p_params) {
+ ERR_FAIL_COND_V(!p_params.has("name") || !p_params.has("progress"), ERR_INVALID_PARAMETER);
+ String name = p_params["name"];
+ float progress = p_params["progress"];
NSString *name_str = [[[NSString alloc] initWithUTF8String:name.utf8().get_data()] autorelease];
GKAchievement *achievement = [[[GKAchievement alloc] initWithIdentifier:name_str] autorelease];
@@ -167,8 +174,8 @@ Error GameCenter::award_achievement(Variant p_params) {
achievement.percentComplete = progress;
achievement.showsCompletionBanner = NO;
- if (params.has("show_completion_banner")) {
- achievement.showsCompletionBanner = params["show_completion_banner"] ? YES : NO;
+ if (p_params.has("show_completion_banner")) {
+ achievement.showsCompletionBanner = p_params["show_completion_banner"] ? YES : NO;
}
[GKAchievement reportAchievements:@[ achievement ]
@@ -202,7 +209,7 @@ void GameCenter::request_achievement_descriptions() {
Array hidden;
Array replayable;
- for (int i = 0; i < [descriptions count]; i++) {
+ for (NSUInteger i = 0; i < [descriptions count]; i++) {
GKAchievementDescription *description = [descriptions objectAtIndex:i];
const char *str = [description.identifier UTF8String];
@@ -250,7 +257,7 @@ void GameCenter::request_achievements() {
PackedStringArray names;
PackedFloat32Array percentages;
- for (int i = 0; i < [achievements count]; i++) {
+ for (NSUInteger i = 0; i < [achievements count]; i++) {
GKAchievement *achievement = [achievements objectAtIndex:i];
const char *str = [achievement.identifier UTF8String];
names.push_back(String::utf8(str != NULL ? str : ""));
@@ -285,14 +292,12 @@ void GameCenter::reset_achievements() {
}];
};
-Error GameCenter::show_game_center(Variant p_params) {
+Error GameCenter::show_game_center(Dictionary p_params) {
ERR_FAIL_COND_V(!NSProtocolFromString(@"GKGameCenterControllerDelegate"), FAILED);
- Dictionary params = p_params;
-
GKGameCenterViewControllerState view_state = GKGameCenterViewControllerStateDefault;
- if (params.has("view")) {
- String view_name = params["view"];
+ if (p_params.has("view")) {
+ String view_name = p_params["view"];
if (view_name == "default") {
view_state = GKGameCenterViewControllerStateDefault;
} else if (view_name == "leaderboards") {
@@ -306,7 +311,7 @@ Error GameCenter::show_game_center(Variant p_params) {
}
}
- GKGameCenterViewController *controller = [[GKGameCenterViewController alloc] init];
+ GKGameCenterViewController *controller = [[[GKGameCenterViewController alloc] init] autorelease];
ERR_FAIL_COND_V(!controller, FAILED);
ViewController *root_controller = (ViewController *)((AppDelegate *)[[UIApplication sharedApplication] delegate]).window.rootViewController;
@@ -316,8 +321,8 @@ Error GameCenter::show_game_center(Variant p_params) {
controller.viewState = view_state;
if (view_state == GKGameCenterViewControllerStateLeaderboards) {
controller.leaderboardIdentifier = nil;
- if (params.has("leaderboard_name")) {
- String name = params["leaderboard_name"];
+ if (p_params.has("leaderboard_name")) {
+ String name = p_params["leaderboard_name"];
NSString *name_str = [[[NSString alloc] initWithUTF8String:name.utf8().get_data()] autorelease];
controller.leaderboardIdentifier = name_str;
}
@@ -341,7 +346,13 @@ Error GameCenter::request_identity_verification_signature() {
ret["signature"] = [[signature base64EncodedStringWithOptions:0] UTF8String];
ret["salt"] = [[salt base64EncodedStringWithOptions:0] UTF8String];
ret["timestamp"] = timestamp;
- ret["player_id"] = [player.playerID UTF8String];
+ if (@available(iOS 13, *)) {
+ ret["player_id"] = [player.teamPlayerID UTF8String];
+#if !defined(TARGET_OS_SIMULATOR) || !TARGET_OS_SIMULATOR
+ } else {
+ ret["player_id"] = [player.playerID UTF8String];
+#endif
+ }
} else {
ret["result"] = "error";
ret["error_code"] = (int64_t)error.code;
diff --git a/platform/iphone/gl_view.h b/platform/iphone/gl_view.h
deleted file mode 100644
index 975aa4b70a..0000000000
--- a/platform/iphone/gl_view.h
+++ /dev/null
@@ -1,123 +0,0 @@
-/*************************************************************************/
-/* gl_view.h */
-/*************************************************************************/
-/* This file is part of: */
-/* GODOT ENGINE */
-/* https://godotengine.org */
-/*************************************************************************/
-/* Copyright (c) 2007-2020 Juan Linietsky, Ariel Manzur. */
-/* Copyright (c) 2014-2020 Godot Engine contributors (cf. AUTHORS.md). */
-/* */
-/* 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 <AVFoundation/AVFoundation.h>
-#import <MediaPlayer/MediaPlayer.h>
-#import <OpenGLES/EAGL.h>
-#import <OpenGLES/ES1/gl.h>
-#import <OpenGLES/ES1/glext.h>
-#import <UIKit/UIKit.h>
-
-@protocol GLViewDelegate;
-
-@interface GLView : UIView <UIKeyInput> {
-@private
- // The pixel dimensions of the backbuffer
- GLint backingWidth;
- GLint backingHeight;
-
- EAGLContext *context;
-
- // OpenGL names for the renderbuffer and framebuffers used to render to this view
- GLuint viewRenderbuffer, viewFramebuffer;
-
- // OpenGL name for the depth buffer that is attached to viewFramebuffer, if it exists (0 if it does not exist)
- GLuint depthRenderbuffer;
-
- BOOL useCADisplayLink;
- // CADisplayLink available on 3.1+ synchronizes the animation timer & drawing with the refresh rate of the display, only supports animation intervals of 1/60 1/30 & 1/15
- CADisplayLink *displayLink;
-
- // An animation timer that, when animation is started, will periodically call -drawView at the given rate.
- // Only used if CADisplayLink is not
- NSTimer *animationTimer;
-
- NSTimeInterval animationInterval;
-
- // Delegate to do our drawing, called by -drawView, which can be called manually or via the animation timer.
- id<GLViewDelegate> delegate;
-
- // Flag to denote that the -setupView method of a delegate has been called.
- // Resets to NO whenever the delegate changes.
- BOOL delegateSetup;
- BOOL active;
- float screen_scale;
-}
-
-@property(nonatomic, assign) id<GLViewDelegate> delegate;
-
-// AVPlayer-related properties
-@property(strong, nonatomic) AVAsset *avAsset;
-@property(strong, nonatomic) AVPlayerItem *avPlayerItem;
-@property(strong, nonatomic) AVPlayer *avPlayer;
-@property(strong, nonatomic) AVPlayerLayer *avPlayerLayer;
-
-@property(strong, nonatomic) UIWindow *backgroundWindow;
-
-@property(nonatomic) UITextAutocorrectionType autocorrectionType;
-
-- (void)startAnimation;
-- (void)stopAnimation;
-- (void)drawView;
-
-- (BOOL)canBecomeFirstResponder;
-
-- (void)open_keyboard;
-- (void)hide_keyboard;
-- (void)deleteBackward;
-- (BOOL)hasText;
-- (void)insertText:(NSString *)p_text;
-
-- (id)initGLES;
-- (BOOL)createFramebuffer;
-- (void)destroyFramebuffer;
-
-- (void)audioRouteChangeListenerCallback:(NSNotification *)notification;
-- (void)keyboardOnScreen:(NSNotification *)notification;
-- (void)keyboardHidden:(NSNotification *)notification;
-
-@property NSTimeInterval animationInterval;
-@property(nonatomic, assign) BOOL useCADisplayLink;
-
-@end
-
-@protocol GLViewDelegate <NSObject>
-
-@required
-
-// Draw with OpenGL ES
-- (void)drawView:(GLView *)view;
-
-@optional
-
-// Called whenever you need to do some initialization before rendering.
-- (void)setupView:(GLView *)view;
-
-@end
diff --git a/platform/iphone/gl_view.mm b/platform/iphone/gl_view.mm
deleted file mode 100644
index 1169ebc6b4..0000000000
--- a/platform/iphone/gl_view.mm
+++ /dev/null
@@ -1,702 +0,0 @@
-/*************************************************************************/
-/* gl_view.mm */
-/*************************************************************************/
-/* This file is part of: */
-/* GODOT ENGINE */
-/* https://godotengine.org */
-/*************************************************************************/
-/* Copyright (c) 2007-2020 Juan Linietsky, Ariel Manzur. */
-/* Copyright (c) 2014-2020 Godot Engine contributors (cf. AUTHORS.md). */
-/* */
-/* 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 "gl_view.h"
-
-#include "core/os/keyboard.h"
-#include "core/project_settings.h"
-#include "os_iphone.h"
-#include "servers/audio_server.h"
-
-#import <OpenGLES/EAGLDrawable.h>
-#import <QuartzCore/QuartzCore.h>
-
-/*
-@interface GLView (private)
-
-- (id)initGLES;
-- (BOOL)createFramebuffer;
-- (void)destroyFramebuffer;
-@end
-*/
-
-int gl_view_base_fb;
-static String keyboard_text;
-static GLView *_instance = NULL;
-
-static bool video_found_error = false;
-static bool video_playing = false;
-static CMTime video_current_time;
-
-void _show_keyboard(String);
-void _hide_keyboard();
-bool _play_video(String, float, String, String);
-bool _is_video_playing();
-void _pause_video();
-void _focus_out_video();
-void _unpause_video();
-void _stop_video();
-CGFloat _points_to_pixels(CGFloat);
-
-void _show_keyboard(String p_existing) {
- keyboard_text = p_existing;
- printf("instance on show is %p\n", _instance);
- [_instance open_keyboard];
-};
-
-void _hide_keyboard() {
- printf("instance on hide is %p\n", _instance);
- [_instance hide_keyboard];
- keyboard_text = "";
-};
-
-Rect2 _get_ios_window_safe_area(float p_window_width, float p_window_height) {
- UIEdgeInsets insets = UIEdgeInsetsMake(0, 0, 0, 0);
- if (_instance != nil && [_instance respondsToSelector:@selector(safeAreaInsets)]) {
- insets = [_instance safeAreaInsets];
- }
- ERR_FAIL_COND_V(insets.left < 0 || insets.top < 0 || insets.right < 0 || insets.bottom < 0,
- Rect2(0, 0, p_window_width, p_window_height));
- UIEdgeInsets window_insets = UIEdgeInsetsMake(_points_to_pixels(insets.top), _points_to_pixels(insets.left), _points_to_pixels(insets.bottom), _points_to_pixels(insets.right));
- return Rect2(window_insets.left, window_insets.top, p_window_width - window_insets.right - window_insets.left, p_window_height - window_insets.bottom - window_insets.top);
-}
-
-bool _play_video(String p_path, float p_volume, String p_audio_track, String p_subtitle_track) {
- p_path = ProjectSettings::get_singleton()->globalize_path(p_path);
-
- NSString *file_path = [[[NSString alloc] initWithUTF8String:p_path.utf8().get_data()] autorelease];
-
- _instance.avAsset = [AVAsset assetWithURL:[NSURL fileURLWithPath:file_path]];
-
- _instance.avPlayerItem = [[AVPlayerItem alloc] initWithAsset:_instance.avAsset];
- [_instance.avPlayerItem addObserver:_instance forKeyPath:@"status" options:0 context:nil];
-
- _instance.avPlayer = [[AVPlayer alloc] initWithPlayerItem:_instance.avPlayerItem];
- _instance.avPlayerLayer = [AVPlayerLayer playerLayerWithPlayer:_instance.avPlayer];
-
- [_instance.avPlayer addObserver:_instance forKeyPath:@"status" options:0 context:nil];
- [[NSNotificationCenter defaultCenter]
- addObserver:_instance
- selector:@selector(playerItemDidReachEnd:)
- name:AVPlayerItemDidPlayToEndTimeNotification
- object:[_instance.avPlayer currentItem]];
-
- [_instance.avPlayer addObserver:_instance forKeyPath:@"rate" options:NSKeyValueObservingOptionNew context:0];
-
- [_instance.avPlayerLayer setFrame:_instance.bounds];
- [_instance.layer addSublayer:_instance.avPlayerLayer];
- [_instance.avPlayer play];
-
- AVMediaSelectionGroup *audioGroup = [_instance.avAsset mediaSelectionGroupForMediaCharacteristic:AVMediaCharacteristicAudible];
-
- NSMutableArray *allAudioParams = [NSMutableArray array];
- for (id track in audioGroup.options) {
- NSString *language = [[track locale] localeIdentifier];
- NSLog(@"subtitle lang: %@", language);
-
- if ([language isEqualToString:[NSString stringWithUTF8String:p_audio_track.utf8()]]) {
- AVMutableAudioMixInputParameters *audioInputParams = [AVMutableAudioMixInputParameters audioMixInputParameters];
- [audioInputParams setVolume:p_volume atTime:kCMTimeZero];
- [audioInputParams setTrackID:[track trackID]];
- [allAudioParams addObject:audioInputParams];
-
- AVMutableAudioMix *audioMix = [AVMutableAudioMix audioMix];
- [audioMix setInputParameters:allAudioParams];
-
- [_instance.avPlayer.currentItem selectMediaOption:track inMediaSelectionGroup:audioGroup];
- [_instance.avPlayer.currentItem setAudioMix:audioMix];
-
- break;
- }
- }
-
- AVMediaSelectionGroup *subtitlesGroup = [_instance.avAsset mediaSelectionGroupForMediaCharacteristic:AVMediaCharacteristicLegible];
- NSArray *useableTracks = [AVMediaSelectionGroup mediaSelectionOptionsFromArray:subtitlesGroup.options withoutMediaCharacteristics:[NSArray arrayWithObject:AVMediaCharacteristicContainsOnlyForcedSubtitles]];
-
- for (id track in useableTracks) {
- NSString *language = [[track locale] localeIdentifier];
- NSLog(@"subtitle lang: %@", language);
-
- if ([language isEqualToString:[NSString stringWithUTF8String:p_subtitle_track.utf8()]]) {
- [_instance.avPlayer.currentItem selectMediaOption:track inMediaSelectionGroup:subtitlesGroup];
- break;
- }
- }
-
- video_playing = true;
-
- return true;
-}
-
-bool _is_video_playing() {
- if (_instance.avPlayer.error) {
- printf("Error during playback\n");
- }
- return (_instance.avPlayer.rate > 0 && !_instance.avPlayer.error);
-}
-
-void _pause_video() {
- video_current_time = _instance.avPlayer.currentTime;
- [_instance.avPlayer pause];
- video_playing = false;
-}
-
-void _focus_out_video() {
- printf("focus out pausing video\n");
- [_instance.avPlayer pause];
-};
-
-void _unpause_video() {
- [_instance.avPlayer play];
- video_playing = true;
-};
-
-void _stop_video() {
- [_instance.avPlayer pause];
- [_instance.avPlayerLayer removeFromSuperlayer];
- _instance.avPlayer = nil;
- video_playing = false;
-}
-
-CGFloat _points_to_pixels(CGFloat points) {
- float pixelPerInch;
- if (UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomPad) {
- pixelPerInch = 132;
- } else if (UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomPhone) {
- pixelPerInch = 163;
- } else {
- pixelPerInch = 160;
- }
- CGFloat pointsPerInch = 72.0;
- return (points / pointsPerInch * pixelPerInch);
-}
-
-@implementation GLView
-
-@synthesize animationInterval;
-
-static const int max_touches = 8;
-static UITouch *touches[max_touches];
-
-static void init_touches() {
- for (int i = 0; i < max_touches; i++) {
- touches[i] = NULL;
- };
-};
-
-static int get_touch_id(UITouch *p_touch) {
- int first = -1;
- for (int i = 0; i < max_touches; i++) {
- if (first == -1 && touches[i] == NULL) {
- first = i;
- continue;
- };
- if (touches[i] == p_touch)
- return i;
- };
-
- if (first != -1) {
- touches[first] = p_touch;
- return first;
- };
-
- return -1;
-};
-
-static int remove_touch(UITouch *p_touch) {
- int remaining = 0;
- for (int i = 0; i < max_touches; i++) {
- if (touches[i] == NULL)
- continue;
- if (touches[i] == p_touch)
- touches[i] = NULL;
- else
- ++remaining;
- };
- return remaining;
-};
-
-static void clear_touches() {
- for (int i = 0; i < max_touches; i++) {
- touches[i] = NULL;
- };
-};
-
-// Implement this to override the default layer class (which is [CALayer class]).
-// We do this so that our view will be backed by a layer that is capable of OpenGL ES rendering.
-+ (Class)layerClass {
- return [CAEAGLLayer class];
-}
-
-//The GL view is stored in the nib file. When it's unarchived it's sent -initWithCoder:
-- (id)initWithCoder:(NSCoder *)coder {
- active = FALSE;
- if ((self = [super initWithCoder:coder])) {
- self = [self initGLES];
- }
- return self;
-}
-
-- (id)initGLES {
- // Get our backing layer
- CAEAGLLayer *eaglLayer = (CAEAGLLayer *)self.layer;
-
- // Configure it so that it is opaque, does not retain the contents of the backbuffer when displayed, and uses RGBA8888 color.
- eaglLayer.opaque = YES;
- eaglLayer.drawableProperties = [NSDictionary
- dictionaryWithObjectsAndKeys:[NSNumber numberWithBool:FALSE],
- kEAGLDrawablePropertyRetainedBacking,
- kEAGLColorFormatRGBA8,
- kEAGLDrawablePropertyColorFormat,
- nil];
-
- // FIXME: Add Vulkan support via MoltenVK. Add fallback code back?
-
- // Create GL ES 2 context
- if (GLOBAL_GET("rendering/quality/driver/driver_name") == "GLES2") {
- context = [[EAGLContext alloc] initWithAPI:kEAGLRenderingAPIOpenGLES2];
- NSLog(@"Setting up an OpenGL ES 2.0 context.");
- if (!context) {
- NSLog(@"Failed to create OpenGL ES 2.0 context!");
- return nil;
- }
- }
-
- if (![EAGLContext setCurrentContext:context]) {
- NSLog(@"Failed to set EAGLContext!");
- return nil;
- }
- if (![self createFramebuffer]) {
- NSLog(@"Failed to create frame buffer!");
- return nil;
- }
-
- // Default the animation interval to 1/60th of a second.
- animationInterval = 1.0 / 60.0;
- return self;
-}
-
-- (id<GLViewDelegate>)delegate {
- return delegate;
-}
-
-// Update the delegate, and if it needs a -setupView: call, set our internal flag so that it will be called.
-- (void)setDelegate:(id<GLViewDelegate>)d {
- delegate = d;
- delegateSetup = ![delegate respondsToSelector:@selector(setupView:)];
-}
-
-@synthesize useCADisplayLink;
-
-// If our view is resized, we'll be asked to layout subviews.
-// This is the perfect opportunity to also update the framebuffer so that it is
-// the same size as our display area.
-
-- (void)layoutSubviews {
- [EAGLContext setCurrentContext:context];
- [self destroyFramebuffer];
- [self createFramebuffer];
- [self drawView];
-}
-
-- (BOOL)createFramebuffer {
- // Generate IDs for a framebuffer object and a color renderbuffer
- UIScreen *mainscr = [UIScreen mainScreen];
- printf("******** screen size %i, %i\n", (int)mainscr.currentMode.size.width, (int)mainscr.currentMode.size.height);
- self.contentScaleFactor = mainscr.nativeScale;
-
- glGenFramebuffersOES(1, &viewFramebuffer);
- glGenRenderbuffersOES(1, &viewRenderbuffer);
-
- glBindFramebufferOES(GL_FRAMEBUFFER_OES, viewFramebuffer);
- glBindRenderbufferOES(GL_RENDERBUFFER_OES, viewRenderbuffer);
- // This call associates the storage for the current render buffer with the EAGLDrawable (our CAEAGLLayer)
- // allowing us to draw into a buffer that will later be rendered to screen wherever the layer is (which corresponds with our view).
- [context renderbufferStorage:GL_RENDERBUFFER_OES fromDrawable:(id<EAGLDrawable>)self.layer];
- glFramebufferRenderbufferOES(GL_FRAMEBUFFER_OES, GL_COLOR_ATTACHMENT0_OES, GL_RENDERBUFFER_OES, viewRenderbuffer);
-
- glGetRenderbufferParameterivOES(GL_RENDERBUFFER_OES, GL_RENDERBUFFER_WIDTH_OES, &backingWidth);
- glGetRenderbufferParameterivOES(GL_RENDERBUFFER_OES, GL_RENDERBUFFER_HEIGHT_OES, &backingHeight);
-
- // For this sample, we also need a depth buffer, so we'll create and attach one via another renderbuffer.
- glGenRenderbuffersOES(1, &depthRenderbuffer);
- glBindRenderbufferOES(GL_RENDERBUFFER_OES, depthRenderbuffer);
- glRenderbufferStorageOES(GL_RENDERBUFFER_OES, GL_DEPTH_COMPONENT16_OES, backingWidth, backingHeight);
- glFramebufferRenderbufferOES(GL_FRAMEBUFFER_OES, GL_DEPTH_ATTACHMENT_OES, GL_RENDERBUFFER_OES, depthRenderbuffer);
-
- if (glCheckFramebufferStatusOES(GL_FRAMEBUFFER_OES) != GL_FRAMEBUFFER_COMPLETE_OES) {
- NSLog(@"failed to make complete framebuffer object %x", glCheckFramebufferStatusOES(GL_FRAMEBUFFER_OES));
- return NO;
- }
-
- if (OS::get_singleton()) {
- OS::VideoMode vm;
- vm.fullscreen = true;
- vm.width = backingWidth;
- vm.height = backingHeight;
- vm.resizable = false;
- OS::get_singleton()->set_video_mode(vm);
- OSIPhone::get_singleton()->set_base_framebuffer(viewFramebuffer);
- };
- gl_view_base_fb = viewFramebuffer;
-
- return YES;
-}
-
-// Clean up any buffers we have allocated.
-- (void)destroyFramebuffer {
- glDeleteFramebuffersOES(1, &viewFramebuffer);
- viewFramebuffer = 0;
- glDeleteRenderbuffersOES(1, &viewRenderbuffer);
- viewRenderbuffer = 0;
-
- if (depthRenderbuffer) {
- glDeleteRenderbuffersOES(1, &depthRenderbuffer);
- depthRenderbuffer = 0;
- }
-}
-
-- (void)startAnimation {
- if (active)
- return;
- active = TRUE;
- printf("start animation!\n");
- if (useCADisplayLink) {
- // Approximate frame rate
- // assumes device refreshes at 60 fps
- int frameInterval = (int)floor(animationInterval * 60.0f);
-
- displayLink = [CADisplayLink displayLinkWithTarget:self selector:@selector(drawView)];
- [displayLink setFrameInterval:frameInterval];
-
- // Setup DisplayLink in main thread
- [displayLink addToRunLoop:[NSRunLoop currentRunLoop] forMode:NSRunLoopCommonModes];
- } else {
- animationTimer = [NSTimer scheduledTimerWithTimeInterval:animationInterval target:self selector:@selector(drawView) userInfo:nil repeats:YES];
- }
-
- if (video_playing) {
- _unpause_video();
- }
-}
-
-- (void)stopAnimation {
- if (!active)
- return;
- active = FALSE;
- printf("******** stop animation!\n");
-
- if (useCADisplayLink) {
- [displayLink invalidate];
- displayLink = nil;
- } else {
- [animationTimer invalidate];
- animationTimer = nil;
- }
-
- clear_touches();
-
- if (video_playing) {
- // save position
- }
-}
-
-- (void)setAnimationInterval:(NSTimeInterval)interval {
- animationInterval = interval;
- if ((useCADisplayLink && displayLink) || (!useCADisplayLink && animationTimer)) {
- [self stopAnimation];
- [self startAnimation];
- }
-}
-
-// Updates the OpenGL view when the timer fires
-- (void)drawView {
- if (!active) {
- printf("draw view not active!\n");
- return;
- };
- if (useCADisplayLink) {
- // Pause the CADisplayLink to avoid recursion
- [displayLink setPaused:YES];
-
- // Process all input events
- while (CFRunLoopRunInMode(kCFRunLoopDefaultMode, 0.0, TRUE) == kCFRunLoopRunHandledSource)
- ;
-
- // We are good to go, resume the CADisplayLink
- [displayLink setPaused:NO];
- }
-
- // Make sure that you are drawing to the current context
- [EAGLContext setCurrentContext:context];
-
- // If our drawing delegate needs to have the view setup, then call -setupView: and flag that it won't need to be called again.
- if (!delegateSetup) {
- [delegate setupView:self];
- delegateSetup = YES;
- }
-
- glBindFramebufferOES(GL_FRAMEBUFFER_OES, viewFramebuffer);
-
- [delegate drawView:self];
-
- glBindRenderbufferOES(GL_RENDERBUFFER_OES, viewRenderbuffer);
- [context presentRenderbuffer:GL_RENDERBUFFER_OES];
-
-#ifdef DEBUG_ENABLED
- GLenum err = glGetError();
- if (err)
- NSLog(@"DrawView: %x error", err);
-#endif
-}
-
-- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {
- NSArray *tlist = [[event allTouches] allObjects];
- for (unsigned int i = 0; i < [tlist count]; i++) {
- if ([touches containsObject:[tlist objectAtIndex:i]]) {
- UITouch *touch = [tlist objectAtIndex:i];
- if (touch.phase != UITouchPhaseBegan)
- continue;
- int tid = get_touch_id(touch);
- ERR_FAIL_COND(tid == -1);
- CGPoint touchPoint = [touch locationInView:self];
- OSIPhone::get_singleton()->touch_press(tid, touchPoint.x * self.contentScaleFactor, touchPoint.y * self.contentScaleFactor, true, touch.tapCount > 1);
- };
- };
-}
-
-- (void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event {
- NSArray *tlist = [[event allTouches] allObjects];
- for (unsigned int i = 0; i < [tlist count]; i++) {
- if ([touches containsObject:[tlist objectAtIndex:i]]) {
- UITouch *touch = [tlist objectAtIndex:i];
- if (touch.phase != UITouchPhaseMoved)
- continue;
- int tid = get_touch_id(touch);
- ERR_FAIL_COND(tid == -1);
- CGPoint touchPoint = [touch locationInView:self];
- CGPoint prev_point = [touch previousLocationInView:self];
- OSIPhone::get_singleton()->touch_drag(tid, prev_point.x * self.contentScaleFactor, prev_point.y * self.contentScaleFactor, touchPoint.x * self.contentScaleFactor, touchPoint.y * self.contentScaleFactor);
- };
- };
-}
-
-- (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event {
- NSArray *tlist = [[event allTouches] allObjects];
- for (unsigned int i = 0; i < [tlist count]; i++) {
- if ([touches containsObject:[tlist objectAtIndex:i]]) {
- UITouch *touch = [tlist objectAtIndex:i];
- if (touch.phase != UITouchPhaseEnded)
- continue;
- int tid = get_touch_id(touch);
- ERR_FAIL_COND(tid == -1);
- remove_touch(touch);
- CGPoint touchPoint = [touch locationInView:self];
- OSIPhone::get_singleton()->touch_press(tid, touchPoint.x * self.contentScaleFactor, touchPoint.y * self.contentScaleFactor, false, false);
- };
- };
-}
-
-- (void)touchesCancelled:(NSSet *)touches withEvent:(UIEvent *)event {
- OSIPhone::get_singleton()->touches_cancelled();
- clear_touches();
-};
-
-- (BOOL)canBecomeFirstResponder {
- return YES;
-};
-
-- (void)open_keyboard {
- //keyboard_text = p_existing;
- [self becomeFirstResponder];
-};
-
-- (void)hide_keyboard {
- //keyboard_text = p_existing;
- [self resignFirstResponder];
-};
-
-- (void)keyboardOnScreen:(NSNotification *)notification {
- NSDictionary *info = notification.userInfo;
- NSValue *value = info[UIKeyboardFrameEndUserInfoKey];
-
- CGRect rawFrame = [value CGRectValue];
- CGRect keyboardFrame = [self convertRect:rawFrame fromView:nil];
-
- OSIPhone::get_singleton()->set_virtual_keyboard_height(_points_to_pixels(keyboardFrame.size.height));
-}
-
-- (void)keyboardHidden:(NSNotification *)notification {
- OSIPhone::get_singleton()->set_virtual_keyboard_height(0);
-}
-
-- (void)deleteBackward {
- if (keyboard_text.length())
- keyboard_text.erase(keyboard_text.length() - 1, 1);
- OSIPhone::get_singleton()->key(KEY_BACKSPACE, true);
-};
-
-- (BOOL)hasText {
- return keyboard_text.length() ? YES : NO;
-};
-
-- (void)insertText:(NSString *)p_text {
- String character;
- character.parse_utf8([p_text UTF8String]);
- keyboard_text = keyboard_text + character;
- OSIPhone::get_singleton()->key(character[0] == 10 ? KEY_ENTER : character[0], true);
- printf("inserting text with character %lc\n", (CharType)character[0]);
-};
-
-- (void)audioRouteChangeListenerCallback:(NSNotification *)notification {
- printf("*********** route changed!\n");
- NSDictionary *interuptionDict = notification.userInfo;
-
- NSInteger routeChangeReason = [[interuptionDict valueForKey:AVAudioSessionRouteChangeReasonKey] integerValue];
-
- switch (routeChangeReason) {
- case AVAudioSessionRouteChangeReasonNewDeviceAvailable: {
- NSLog(@"AVAudioSessionRouteChangeReasonNewDeviceAvailable");
- NSLog(@"Headphone/Line plugged in");
- }; break;
-
- case AVAudioSessionRouteChangeReasonOldDeviceUnavailable: {
- NSLog(@"AVAudioSessionRouteChangeReasonOldDeviceUnavailable");
- NSLog(@"Headphone/Line was pulled. Resuming video play....");
- if (_is_video_playing()) {
- dispatch_after(dispatch_time(DISPATCH_TIME_NOW, 0.5f * NSEC_PER_SEC), dispatch_get_main_queue(), ^{
- [_instance.avPlayer play]; // NOTE: change this line according your current player implementation
- NSLog(@"resumed play");
- });
- };
- }; break;
-
- case AVAudioSessionRouteChangeReasonCategoryChange: {
- // called at start - also when other audio wants to play
- NSLog(@"AVAudioSessionRouteChangeReasonCategoryChange");
- }; break;
- }
-}
-
-// When created via code however, we get initWithFrame
-- (id)initWithFrame:(CGRect)frame {
- self = [super initWithFrame:frame];
- _instance = self;
- printf("after init super %p\n", self);
- if (self != nil) {
- self = [self initGLES];
- printf("after init gles %p\n", self);
- }
- init_touches();
- self.multipleTouchEnabled = YES;
- self.autocorrectionType = UITextAutocorrectionTypeNo;
-
- printf("******** adding observer for sound routing changes\n");
- [[NSNotificationCenter defaultCenter]
- addObserver:self
- selector:@selector(audioRouteChangeListenerCallback:)
- name:AVAudioSessionRouteChangeNotification
- object:nil];
-
- printf("******** adding observer for keyboard show/hide\n");
- [[NSNotificationCenter defaultCenter]
- addObserver:self
- selector:@selector(keyboardOnScreen:)
- name:UIKeyboardDidShowNotification
- object:nil];
- [[NSNotificationCenter defaultCenter]
- addObserver:self
- selector:@selector(keyboardHidden:)
- name:UIKeyboardDidHideNotification
- object:nil];
-
- //self.autoresizesSubviews = YES;
- //[self setAutoresizingMask:UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleWidth];
-
- return self;
-}
-
-//- (BOOL)automaticallyForwardAppearanceAndRotationMethodsToChildViewControllers {
-// return YES;
-//}
-
-//- (BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)interfaceOrientation{
-// return YES;
-//}
-
-// Stop animating and release resources when they are no longer needed.
-- (void)dealloc {
- [self stopAnimation];
-
- if ([EAGLContext currentContext] == context) {
- [EAGLContext setCurrentContext:nil];
- }
-
- [context release];
- context = nil;
-
- [super dealloc];
-}
-
-- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context {
- if (object == _instance.avPlayerItem && [keyPath isEqualToString:@"status"]) {
- if (_instance.avPlayerItem.status == AVPlayerStatusFailed || _instance.avPlayer.status == AVPlayerStatusFailed) {
- _stop_video();
- video_found_error = true;
- }
-
- if (_instance.avPlayer.status == AVPlayerStatusReadyToPlay &&
- _instance.avPlayerItem.status == AVPlayerItemStatusReadyToPlay &&
- CMTIME_COMPARE_INLINE(video_current_time, ==, kCMTimeZero)) {
- //NSLog(@"time: %@", video_current_time);
-
- [_instance.avPlayer seekToTime:video_current_time];
- video_current_time = kCMTimeZero;
- }
- }
-
- if (object == _instance.avPlayer && [keyPath isEqualToString:@"rate"]) {
- NSLog(@"Player playback rate changed: %.5f", _instance.avPlayer.rate);
- if (_is_video_playing() && _instance.avPlayer.rate == 0.0 && !_instance.avPlayer.error) {
- dispatch_after(dispatch_time(DISPATCH_TIME_NOW, 0.5f * NSEC_PER_SEC), dispatch_get_main_queue(), ^{
- [_instance.avPlayer play]; // NOTE: change this line according your current player implementation
- NSLog(@"resumed play");
- });
-
- NSLog(@" . . . PAUSED (or just started)");
- }
- }
-}
-
-- (void)playerItemDidReachEnd:(NSNotification *)notification {
- _stop_video();
-}
-
-@end
diff --git a/platform/iphone/godot_iphone.cpp b/platform/iphone/godot_iphone.mm
index aa5dbd5130..090b772947 100644
--- a/platform/iphone/godot_iphone.cpp
+++ b/platform/iphone/godot_iphone.mm
@@ -1,5 +1,5 @@
/*************************************************************************/
-/* godot_iphone.cpp */
+/* godot_iphone.mm */
/*************************************************************************/
/* This file is part of: */
/* GODOT ENGINE */
@@ -38,19 +38,53 @@
static OSIPhone *os = nullptr;
-extern "C" {
-int add_path(int p_argc, char **p_args);
-int add_cmdline(int p_argc, char **p_args);
+int add_path(int, char **);
+int add_cmdline(int, char **);
+int iphone_main(int, char **, String);
+
+int add_path(int p_argc, char **p_args) {
+ NSString *str = [[[NSBundle mainBundle] infoDictionary] objectForKey:@"godot_path"];
+ if (!str) {
+ return p_argc;
+ }
+
+ p_args[p_argc++] = (char *)"--path";
+ [str retain]; // memory leak lol (maybe make it static here and delete it in ViewController destructor? @todo
+ p_args[p_argc++] = (char *)[str cStringUsingEncoding:NSUTF8StringEncoding];
+ p_args[p_argc] = NULL;
+ [str release];
+
+ return p_argc;
};
-int iphone_main(int, int, int, char **, String);
+int add_cmdline(int p_argc, char **p_args) {
+ NSArray *arr = [[[NSBundle mainBundle] infoDictionary] objectForKey:@"godot_cmdline"];
+ if (!arr) {
+ return p_argc;
+ }
+
+ for (NSUInteger i = 0; i < [arr count]; i++) {
+ NSString *str = [arr objectAtIndex:i];
+ if (!str) {
+ continue;
+ }
+ [str retain]; // @todo delete these at some point
+ p_args[p_argc++] = (char *)[str cStringUsingEncoding:NSUTF8StringEncoding];
+ [str release];
+ };
-int iphone_main(int width, int height, int argc, char **argv, String data_dir) {
+ p_args[p_argc] = NULL;
+
+ return p_argc;
+};
+
+int iphone_main(int argc, char **argv, String data_dir) {
size_t len = strlen(argv[0]);
while (len--) {
- if (argv[0][len] == '/')
+ if (argv[0][len] == '/') {
break;
+ }
}
if (len >= 0) {
@@ -65,7 +99,7 @@ int iphone_main(int width, int height, int argc, char **argv, String data_dir) {
char cwd[512];
getcwd(cwd, sizeof(cwd));
printf("cwd %s\n", cwd);
- os = new OSIPhone(width, height, data_dir);
+ os = new OSIPhone(data_dir);
// We must override main when testing is enabled
TEST_MAIN_OVERRIDE
@@ -79,10 +113,14 @@ int iphone_main(int width, int height, int argc, char **argv, String data_dir) {
argc = add_cmdline(argc, fargv);
printf("os created\n");
+
Error err = Main::setup(fargv[0], argc - 1, &fargv[1], false);
printf("setup %i\n", err);
- if (err != OK)
+ if (err != OK) {
return 255;
+ }
+
+ os->initialize_modules();
return 0;
};
diff --git a/platform/iphone/godot_view.h b/platform/iphone/godot_view.h
new file mode 100644
index 0000000000..62fa2f5a32
--- /dev/null
+++ b/platform/iphone/godot_view.h
@@ -0,0 +1,56 @@
+/*************************************************************************/
+/* godot_view.h */
+/*************************************************************************/
+/* This file is part of: */
+/* GODOT ENGINE */
+/* https://godotengine.org */
+/*************************************************************************/
+/* Copyright (c) 2007-2020 Juan Linietsky, Ariel Manzur. */
+/* Copyright (c) 2014-2020 Godot Engine contributors (cf. AUTHORS.md). */
+/* */
+/* 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 <UIKit/UIKit.h>
+
+class String;
+
+@protocol DisplayLayer;
+@protocol GodotViewRendererProtocol;
+
+@interface GodotView : UIView <UIKeyInput>
+
+@property(assign, nonatomic) id<GodotViewRendererProtocol> renderer;
+
+@property(assign, readonly, nonatomic) BOOL isActive;
+
+@property(assign, nonatomic) BOOL useCADisplayLink;
+@property(strong, readonly, nonatomic) CALayer<DisplayLayer> *renderingLayer;
+@property(assign, readonly, nonatomic) BOOL canRender;
+
+@property(assign, nonatomic) NSTimeInterval renderingInterval;
+
+- (CALayer<DisplayLayer> *)initializeRenderingForDriver:(NSString *)driverName;
+- (void)stopRendering;
+- (void)startRendering;
+
+- (BOOL)becomeFirstResponderWithString:(String)p_existing;
+
+@end
diff --git a/platform/iphone/godot_view.mm b/platform/iphone/godot_view.mm
new file mode 100644
index 0000000000..c0a31549c4
--- /dev/null
+++ b/platform/iphone/godot_view.mm
@@ -0,0 +1,499 @@
+/*************************************************************************/
+/* godot_view.mm */
+/*************************************************************************/
+/* This file is part of: */
+/* GODOT ENGINE */
+/* https://godotengine.org */
+/*************************************************************************/
+/* Copyright (c) 2007-2020 Juan Linietsky, Ariel Manzur. */
+/* Copyright (c) 2014-2020 Godot Engine contributors (cf. AUTHORS.md). */
+/* */
+/* 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 "godot_view.h"
+#include "core/os/keyboard.h"
+#include "core/ustring.h"
+#import "display_layer.h"
+#include "display_server_iphone.h"
+#import "godot_view_gesture_recognizer.h"
+#import "godot_view_renderer.h"
+
+#import <CoreMotion/CoreMotion.h>
+
+static const int max_touches = 8;
+
+@interface GodotView () {
+ UITouch *godot_touches[max_touches];
+ String keyboard_text;
+}
+
+@property(assign, nonatomic) BOOL isActive;
+
+// CADisplayLink available on 3.1+ synchronizes the animation timer & drawing with the refresh rate of the display, only supports animation intervals of 1/60 1/30 & 1/15
+@property(strong, nonatomic) CADisplayLink *displayLink;
+
+// An animation timer that, when animation is started, will periodically call -drawView at the given rate.
+// Only used if CADisplayLink is not
+@property(strong, nonatomic) NSTimer *animationTimer;
+
+@property(strong, nonatomic) CALayer<DisplayLayer> *renderingLayer;
+
+@property(strong, nonatomic) CMMotionManager *motionManager;
+
+@property(strong, nonatomic) GodotViewGestureRecognizer *delayGestureRecognizer;
+
+@end
+
+@implementation GodotView
+
+- (CALayer<DisplayLayer> *)initializeRenderingForDriver:(NSString *)driverName {
+ if (self.renderingLayer) {
+ return self.renderingLayer;
+ }
+
+ CALayer<DisplayLayer> *layer;
+
+ if ([driverName isEqualToString:@"vulkan"]) {
+ layer = [GodotMetalLayer layer];
+ } else if ([driverName isEqualToString:@"opengl_es"]) {
+ if (@available(iOS 13, *)) {
+ NSLog(@"OpenGL ES is deprecated on iOS 13");
+ }
+#if defined(TARGET_OS_SIMULATOR) && TARGET_OS_SIMULATOR
+ return nil;
+#else
+ layer = [GodotOpenGLLayer layer];
+#endif
+ } else {
+ return nil;
+ }
+
+ layer.frame = self.bounds;
+ layer.contentsScale = self.contentScaleFactor;
+
+ [self.layer addSublayer:layer];
+ self.renderingLayer = layer;
+
+ [layer initializeDisplayLayer];
+
+ return self.renderingLayer;
+}
+
+- (instancetype)initWithCoder:(NSCoder *)coder {
+ self = [super initWithCoder:coder];
+
+ if (self) {
+ [self godot_commonInit];
+ }
+
+ return self;
+}
+
+- (instancetype)initWithFrame:(CGRect)frame {
+ self = [super initWithFrame:frame];
+
+ if (self) {
+ [self godot_commonInit];
+ }
+
+ return self;
+}
+
+- (void)dealloc {
+ [self stopRendering];
+
+ self.renderer = nil;
+
+ if (self.renderingLayer) {
+ [self.renderingLayer removeFromSuperlayer];
+ self.renderingLayer = nil;
+ }
+
+ if (self.motionManager) {
+ [self.motionManager stopDeviceMotionUpdates];
+ self.motionManager = nil;
+ }
+
+ if (self.displayLink) {
+ [self.displayLink invalidate];
+ self.displayLink = nil;
+ }
+
+ if (self.animationTimer) {
+ [self.animationTimer invalidate];
+ self.animationTimer = nil;
+ }
+
+ if (self.delayGestureRecognizer) {
+ self.delayGestureRecognizer = nil;
+ }
+
+ [super dealloc];
+}
+
+- (void)godot_commonInit {
+ self.contentScaleFactor = [UIScreen mainScreen].nativeScale;
+
+ [self initTouches];
+
+ // Configure and start accelerometer
+ if (!self.motionManager) {
+ self.motionManager = [[[CMMotionManager alloc] init] autorelease];
+ if (self.motionManager.deviceMotionAvailable) {
+ self.motionManager.deviceMotionUpdateInterval = 1.0 / 70.0;
+ [self.motionManager startDeviceMotionUpdatesUsingReferenceFrame:CMAttitudeReferenceFrameXMagneticNorthZVertical];
+ } else {
+ self.motionManager = nil;
+ }
+ }
+
+ // Initialize delay gesture recognizer
+ GodotViewGestureRecognizer *gestureRecognizer = [[GodotViewGestureRecognizer alloc] init];
+ self.delayGestureRecognizer = gestureRecognizer;
+ [self addGestureRecognizer:self.delayGestureRecognizer];
+ [gestureRecognizer release];
+}
+
+- (void)stopRendering {
+ if (!self.isActive) {
+ return;
+ }
+
+ self.isActive = NO;
+
+ printf("******** stop animation!\n");
+
+ if (self.useCADisplayLink) {
+ [self.displayLink invalidate];
+ self.displayLink = nil;
+ } else {
+ [self.animationTimer invalidate];
+ self.animationTimer = nil;
+ }
+
+ [self clearTouches];
+}
+
+- (void)startRendering {
+ if (self.isActive) {
+ return;
+ }
+
+ self.isActive = YES;
+
+ printf("start animation!\n");
+
+ if (self.useCADisplayLink) {
+ self.displayLink = [CADisplayLink displayLinkWithTarget:self selector:@selector(drawView)];
+
+ // if (@available(iOS 10, *)) {
+ self.displayLink.preferredFramesPerSecond = (NSInteger)(1.0 / self.renderingInterval);
+ // } else {
+ // // Approximate frame rate
+ // // assumes device refreshes at 60 fps
+ // int frameInterval = (int)floor(self.renderingInterval * 60.0f);
+ // [self.displayLink setFrameInterval:frameInterval];
+ // }
+
+ // Setup DisplayLink in main thread
+ [self.displayLink addToRunLoop:[NSRunLoop currentRunLoop] forMode:NSRunLoopCommonModes];
+ } else {
+ self.animationTimer = [NSTimer scheduledTimerWithTimeInterval:self.renderingInterval target:self selector:@selector(drawView) userInfo:nil repeats:YES];
+ }
+}
+
+- (void)drawView {
+ if (!self.isActive) {
+ printf("draw view not active!\n");
+ return;
+ }
+
+ if (self.useCADisplayLink) {
+ // Pause the CADisplayLink to avoid recursion
+ [self.displayLink setPaused:YES];
+
+ // Process all input events
+ while (CFRunLoopRunInMode(kCFRunLoopDefaultMode, 0.0, TRUE) == kCFRunLoopRunHandledSource)
+ ;
+
+ // We are good to go, resume the CADisplayLink
+ [self.displayLink setPaused:NO];
+ }
+
+ [self.renderingLayer renderDisplayLayer];
+
+ if (!self.renderer) {
+ return;
+ }
+
+ if ([self.renderer setupView:self]) {
+ return;
+ }
+
+ [self handleMotion];
+ [self.renderer renderOnView:self];
+}
+
+- (BOOL)canRender {
+ if (self.useCADisplayLink) {
+ return self.displayLink != nil;
+ } else {
+ return self.animationTimer != nil;
+ }
+}
+
+- (void)setRenderingInterval:(NSTimeInterval)renderingInterval {
+ _renderingInterval = renderingInterval;
+
+ if (self.canRender) {
+ [self stopRendering];
+ [self startRendering];
+ }
+}
+
+- (void)layoutSubviews {
+ if (self.renderingLayer) {
+ self.renderingLayer.frame = self.bounds;
+ [self.renderingLayer layoutDisplayLayer];
+
+ if (DisplayServerIPhone::get_singleton()) {
+ DisplayServerIPhone::get_singleton()->resize_window(self.bounds.size);
+ }
+ }
+
+ [super layoutSubviews];
+}
+
+// MARK: - Input
+
+// MARK: Keyboard
+
+- (BOOL)canBecomeFirstResponder {
+ return YES;
+}
+
+- (BOOL)becomeFirstResponderWithString:(String)p_existing {
+ keyboard_text = p_existing;
+ return [self becomeFirstResponder];
+}
+
+- (BOOL)resignFirstResponder {
+ keyboard_text = String();
+ return [super resignFirstResponder];
+}
+
+- (void)deleteBackward {
+ if (keyboard_text.length()) {
+ keyboard_text.erase(keyboard_text.length() - 1, 1);
+ }
+ DisplayServerIPhone::get_singleton()->key(KEY_BACKSPACE, true);
+}
+
+- (BOOL)hasText {
+ return keyboard_text.length() > 0;
+}
+
+- (void)insertText:(NSString *)p_text {
+ String character;
+ character.parse_utf8([p_text UTF8String]);
+ keyboard_text = keyboard_text + character;
+ DisplayServerIPhone::get_singleton()->key(character[0] == 10 ? KEY_ENTER : character[0], true);
+}
+
+// MARK: Touches
+
+- (void)initTouches {
+ for (int i = 0; i < max_touches; i++) {
+ godot_touches[i] = NULL;
+ }
+}
+
+- (int)getTouchIDForTouch:(UITouch *)p_touch {
+ int first = -1;
+ for (int i = 0; i < max_touches; i++) {
+ if (first == -1 && godot_touches[i] == NULL) {
+ first = i;
+ continue;
+ }
+ if (godot_touches[i] == p_touch) {
+ return i;
+ }
+ }
+
+ if (first != -1) {
+ godot_touches[first] = p_touch;
+ return first;
+ }
+
+ return -1;
+}
+
+- (int)removeTouch:(UITouch *)p_touch {
+ int remaining = 0;
+ for (int i = 0; i < max_touches; i++) {
+ if (godot_touches[i] == NULL) {
+ continue;
+ }
+ if (godot_touches[i] == p_touch) {
+ godot_touches[i] = NULL;
+ } else {
+ ++remaining;
+ }
+ }
+ return remaining;
+}
+
+- (void)clearTouches {
+ for (int i = 0; i < max_touches; i++) {
+ godot_touches[i] = NULL;
+ }
+}
+
+- (void)touchesBegan:(NSSet *)touchesSet withEvent:(UIEvent *)event {
+ NSArray *tlist = [event.allTouches allObjects];
+ for (unsigned int i = 0; i < [tlist count]; i++) {
+ if ([touchesSet containsObject:[tlist objectAtIndex:i]]) {
+ UITouch *touch = [tlist objectAtIndex:i];
+ int tid = [self getTouchIDForTouch:touch];
+ ERR_FAIL_COND(tid == -1);
+ CGPoint touchPoint = [touch locationInView:self];
+ DisplayServerIPhone::get_singleton()->touch_press(tid, touchPoint.x * self.contentScaleFactor, touchPoint.y * self.contentScaleFactor, true, touch.tapCount > 1);
+ }
+ }
+}
+
+- (void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event {
+ NSArray *tlist = [event.allTouches allObjects];
+ for (unsigned int i = 0; i < [tlist count]; i++) {
+ if ([touches containsObject:[tlist objectAtIndex:i]]) {
+ UITouch *touch = [tlist objectAtIndex:i];
+ int tid = [self getTouchIDForTouch:touch];
+ ERR_FAIL_COND(tid == -1);
+ CGPoint touchPoint = [touch locationInView:self];
+ CGPoint prev_point = [touch previousLocationInView:self];
+ DisplayServerIPhone::get_singleton()->touch_drag(tid, prev_point.x * self.contentScaleFactor, prev_point.y * self.contentScaleFactor, touchPoint.x * self.contentScaleFactor, touchPoint.y * self.contentScaleFactor);
+ }
+ }
+}
+
+- (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event {
+ NSArray *tlist = [event.allTouches allObjects];
+ for (unsigned int i = 0; i < [tlist count]; i++) {
+ if ([touches containsObject:[tlist objectAtIndex:i]]) {
+ UITouch *touch = [tlist objectAtIndex:i];
+ int tid = [self getTouchIDForTouch:touch];
+ ERR_FAIL_COND(tid == -1);
+ [self removeTouch:touch];
+ CGPoint touchPoint = [touch locationInView:self];
+ DisplayServerIPhone::get_singleton()->touch_press(tid, touchPoint.x * self.contentScaleFactor, touchPoint.y * self.contentScaleFactor, false, false);
+ }
+ }
+}
+
+- (void)touchesCancelled:(NSSet *)touches withEvent:(UIEvent *)event {
+ NSArray *tlist = [event.allTouches allObjects];
+ for (unsigned int i = 0; i < [tlist count]; i++) {
+ if ([touches containsObject:[tlist objectAtIndex:i]]) {
+ UITouch *touch = [tlist objectAtIndex:i];
+ int tid = [self getTouchIDForTouch:touch];
+ ERR_FAIL_COND(tid == -1);
+ DisplayServerIPhone::get_singleton()->touches_cancelled(tid);
+ }
+ }
+ [self clearTouches];
+}
+
+// MARK: Motion
+
+- (void)handleMotion {
+ if (!self.motionManager) {
+ return;
+ }
+
+ // Just using polling approach for now, we can set this up so it sends
+ // data to us in intervals, might be better. See Apple reference pages
+ // for more details:
+ // https://developer.apple.com/reference/coremotion/cmmotionmanager?language=objc
+
+ // Apple splits our accelerometer date into a gravity and user movement
+ // component. We add them back together
+ CMAcceleration gravity = self.motionManager.deviceMotion.gravity;
+ CMAcceleration acceleration = self.motionManager.deviceMotion.userAcceleration;
+
+ ///@TODO We don't seem to be getting data here, is my device broken or
+ /// is this code incorrect?
+ CMMagneticField magnetic = self.motionManager.deviceMotion.magneticField.field;
+
+ ///@TODO we can access rotationRate as a CMRotationRate variable
+ ///(processed date) or CMGyroData (raw data), have to see what works
+ /// best
+ CMRotationRate rotation = self.motionManager.deviceMotion.rotationRate;
+
+ // Adjust for screen orientation.
+ // [[UIDevice currentDevice] orientation] changes even if we've fixed
+ // our orientation which is not a good thing when you're trying to get
+ // your user to move the screen in all directions and want consistent
+ // output
+
+ ///@TODO Using [[UIApplication sharedApplication] statusBarOrientation]
+ /// is a bit of a hack. Godot obviously knows the orientation so maybe
+ /// we
+ // can use that instead? (note that left and right seem swapped)
+
+ UIInterfaceOrientation interfaceOrientation = UIInterfaceOrientationUnknown;
+
+ if (@available(iOS 13, *)) {
+ interfaceOrientation = [UIApplication sharedApplication].delegate.window.windowScene.interfaceOrientation;
+#if !defined(TARGET_OS_SIMULATOR) || !TARGET_OS_SIMULATOR
+ } else {
+ interfaceOrientation = [[UIApplication sharedApplication] statusBarOrientation];
+#endif
+ }
+
+ switch (interfaceOrientation) {
+ case UIInterfaceOrientationLandscapeLeft: {
+ DisplayServerIPhone::get_singleton()->update_gravity(-gravity.y, gravity.x, gravity.z);
+ DisplayServerIPhone::get_singleton()->update_accelerometer(-(acceleration.y + gravity.y), (acceleration.x + gravity.x), acceleration.z + gravity.z);
+ DisplayServerIPhone::get_singleton()->update_magnetometer(-magnetic.y, magnetic.x, magnetic.z);
+ DisplayServerIPhone::get_singleton()->update_gyroscope(-rotation.y, rotation.x, rotation.z);
+ } break;
+ case UIInterfaceOrientationLandscapeRight: {
+ DisplayServerIPhone::get_singleton()->update_gravity(gravity.y, -gravity.x, gravity.z);
+ DisplayServerIPhone::get_singleton()->update_accelerometer((acceleration.y + gravity.y), -(acceleration.x + gravity.x), acceleration.z + gravity.z);
+ DisplayServerIPhone::get_singleton()->update_magnetometer(magnetic.y, -magnetic.x, magnetic.z);
+ DisplayServerIPhone::get_singleton()->update_gyroscope(rotation.y, -rotation.x, rotation.z);
+ } break;
+ case UIInterfaceOrientationPortraitUpsideDown: {
+ DisplayServerIPhone::get_singleton()->update_gravity(-gravity.x, gravity.y, gravity.z);
+ DisplayServerIPhone::get_singleton()->update_accelerometer(-(acceleration.x + gravity.x), (acceleration.y + gravity.y), acceleration.z + gravity.z);
+ DisplayServerIPhone::get_singleton()->update_magnetometer(-magnetic.x, magnetic.y, magnetic.z);
+ DisplayServerIPhone::get_singleton()->update_gyroscope(-rotation.x, rotation.y, rotation.z);
+ } break;
+ default: { // assume portrait
+ DisplayServerIPhone::get_singleton()->update_gravity(gravity.x, gravity.y, gravity.z);
+ DisplayServerIPhone::get_singleton()->update_accelerometer(acceleration.x + gravity.x, acceleration.y + gravity.y, acceleration.z + gravity.z);
+ DisplayServerIPhone::get_singleton()->update_magnetometer(magnetic.x, magnetic.y, magnetic.z);
+ DisplayServerIPhone::get_singleton()->update_gyroscope(rotation.x, rotation.y, rotation.z);
+ } break;
+ }
+}
+
+@end
diff --git a/platform/iphone/godot_view_gesture_recognizer.h b/platform/iphone/godot_view_gesture_recognizer.h
new file mode 100644
index 0000000000..ca3bd808d1
--- /dev/null
+++ b/platform/iphone/godot_view_gesture_recognizer.h
@@ -0,0 +1,44 @@
+/*************************************************************************/
+/* godot_view_gesture_recognizer.h */
+/*************************************************************************/
+/* This file is part of: */
+/* GODOT ENGINE */
+/* https://godotengine.org */
+/*************************************************************************/
+/* Copyright (c) 2007-2020 Juan Linietsky, Ariel Manzur. */
+/* Copyright (c) 2014-2020 Godot Engine contributors (cf. AUTHORS.md). */
+/* */
+/* 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. */
+/*************************************************************************/
+
+// GLViewGestureRecognizer allows iOS gestures to work currectly by
+// emulating UIScrollView's UIScrollViewDelayedTouchesBeganGestureRecognizer.
+// It catches all gestures incoming to UIView and delays them for 150ms
+// (the same value used by UIScrollViewDelayedTouchesBeganGestureRecognizer)
+// If touch cancelation or end message is fired it fires delayed
+// begin touch immediately as well as last touch signal
+
+#import <UIKit/UIKit.h>
+
+@interface GodotViewGestureRecognizer : UIGestureRecognizer
+
+- (instancetype)init;
+
+@end
diff --git a/platform/iphone/godot_view_gesture_recognizer.m b/platform/iphone/godot_view_gesture_recognizer.m
new file mode 100644
index 0000000000..377ccd52a5
--- /dev/null
+++ b/platform/iphone/godot_view_gesture_recognizer.m
@@ -0,0 +1,171 @@
+/*************************************************************************/
+/* godot_view_gesture_recognizer.m */
+/*************************************************************************/
+/* This file is part of: */
+/* GODOT ENGINE */
+/* https://godotengine.org */
+/*************************************************************************/
+/* Copyright (c) 2007-2020 Juan Linietsky, Ariel Manzur. */
+/* Copyright (c) 2014-2020 Godot Engine contributors (cf. AUTHORS.md). */
+/* */
+/* 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 "godot_view_gesture_recognizer.h"
+
+// Using same delay interval that is used for `UIScrollView`
+const NSTimeInterval kGLGestureDelayInterval = 0.150;
+
+// Minimum distance for touches to move to fire
+// a delay timer before scheduled time.
+// Should be the low enough to not cause issues with dragging
+// but big enough to allow click to work.
+const CGFloat kGLGestureMovementDistance = 0.5;
+
+@interface GodotViewGestureRecognizer ()
+
+// Timer used to delay begin touch message.
+// Should work as simple emulation of UIDelayedAction
+@property(strong, nonatomic) NSTimer *delayTimer;
+
+// Delayed touch parameters
+@property(strong, nonatomic) NSSet *delayedTouches;
+@property(strong, nonatomic) UIEvent *delayedEvent;
+
+@end
+
+@implementation GodotViewGestureRecognizer
+
+- (instancetype)init {
+ self = [super init];
+
+ self.cancelsTouchesInView = YES;
+ self.delaysTouchesBegan = YES;
+ self.delaysTouchesEnded = YES;
+
+ return self;
+}
+
+- (void)dealloc {
+ if (self.delayTimer) {
+ [self.delayTimer invalidate];
+ self.delayTimer = nil;
+ }
+
+ if (self.delayedTouches) {
+ self.delayedTouches = nil;
+ }
+
+ if (self.delayedEvent) {
+ self.delayedEvent = nil;
+ }
+
+ [super dealloc];
+}
+
+- (void)delayTouches:(NSSet *)touches andEvent:(UIEvent *)event {
+ [self.delayTimer fire];
+
+ self.delayedTouches = touches;
+ self.delayedEvent = event;
+
+ self.delayTimer = [NSTimer
+ scheduledTimerWithTimeInterval:kGLGestureDelayInterval
+ target:self
+ selector:@selector(fireDelayedTouches:)
+ userInfo:nil
+ repeats:NO];
+}
+
+- (void)fireDelayedTouches:(id)timer {
+ [self.delayTimer invalidate];
+ self.delayTimer = nil;
+
+ if (self.delayedTouches) {
+ [self.view touchesBegan:self.delayedTouches withEvent:self.delayedEvent];
+ }
+
+ self.delayedTouches = nil;
+ self.delayedEvent = nil;
+}
+
+- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {
+ NSSet *cleared = [self copyClearedTouches:touches phase:UITouchPhaseBegan];
+ [self delayTouches:cleared andEvent:event];
+ [cleared release];
+}
+
+- (void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event {
+ NSSet *cleared = [self copyClearedTouches:touches phase:UITouchPhaseMoved];
+
+ if (self.delayTimer) {
+ // We should check if movement was significant enough to fire an event
+ // for dragging to work correctly.
+ for (UITouch *touch in cleared) {
+ CGPoint from = [touch locationInView:self.view];
+ CGPoint to = [touch previousLocationInView:self.view];
+ CGFloat xDistance = from.x - to.x;
+ CGFloat yDistance = from.y - to.y;
+
+ CGFloat distance = sqrt(xDistance * xDistance + yDistance * yDistance);
+
+ // Early exit, since one of touches has moved enough to fire a drag event.
+ if (distance > kGLGestureMovementDistance) {
+ [self.delayTimer fire];
+ [self.view touchesMoved:cleared withEvent:event];
+ [cleared release];
+ return;
+ }
+ }
+
+ [cleared release];
+ return;
+ }
+
+ [self.view touchesMoved:cleared withEvent:event];
+ [cleared release];
+}
+
+- (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event {
+ [self.delayTimer fire];
+
+ NSSet *cleared = [self copyClearedTouches:touches phase:UITouchPhaseEnded];
+ [self.view touchesEnded:cleared withEvent:event];
+ [cleared release];
+}
+
+- (void)touchesCancelled:(NSSet *)touches withEvent:(UIEvent *)event {
+ [self.delayTimer fire];
+ [self.view touchesCancelled:touches withEvent:event];
+};
+
+- (NSSet *)copyClearedTouches:(NSSet *)touches phase:(UITouchPhase)phaseToSave {
+ NSMutableSet *cleared = [touches mutableCopy];
+
+ for (UITouch *touch in touches) {
+ if (touch.phase != phaseToSave) {
+ [cleared removeObject:touch];
+ }
+ }
+
+ return cleared;
+}
+
+@end
diff --git a/modules/mono/editor/csharp_project.h b/platform/iphone/godot_view_renderer.h
index 515b8d3d62..ea8998c808 100644
--- a/modules/mono/editor/csharp_project.h
+++ b/platform/iphone/godot_view_renderer.h
@@ -1,5 +1,5 @@
/*************************************************************************/
-/* csharp_project.h */
+/* godot_view_renderer.h */
/*************************************************************************/
/* This file is part of: */
/* GODOT ENGINE */
@@ -28,15 +28,17 @@
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
/*************************************************************************/
-#ifndef CSHARP_PROJECT_H
-#define CSHARP_PROJECT_H
+#import <UIKit/UIKit.h>
-#include "core/ustring.h"
+@protocol GodotViewRendererProtocol <NSObject>
-namespace CSharpProject {
+@property(assign, readonly, nonatomic) BOOL hasFinishedSetup;
-void add_item(const String &p_project_path, const String &p_item_type, const String &p_include);
+- (BOOL)setupView:(UIView *)view;
+- (void)renderOnView:(UIView *)view;
-} // namespace CSharpProject
+@end
-#endif // CSHARP_PROJECT_H
+@interface GodotViewRenderer : NSObject <GodotViewRendererProtocol>
+
+@end
diff --git a/platform/iphone/godot_view_renderer.mm b/platform/iphone/godot_view_renderer.mm
new file mode 100644
index 0000000000..1fc822b457
--- /dev/null
+++ b/platform/iphone/godot_view_renderer.mm
@@ -0,0 +1,146 @@
+/*************************************************************************/
+/* godot_view_renderer.mm */
+/*************************************************************************/
+/* This file is part of: */
+/* GODOT ENGINE */
+/* https://godotengine.org */
+/*************************************************************************/
+/* Copyright (c) 2007-2020 Juan Linietsky, Ariel Manzur. */
+/* Copyright (c) 2014-2020 Godot Engine contributors (cf. AUTHORS.md). */
+/* */
+/* 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 "godot_view_renderer.h"
+#include "core/os/keyboard.h"
+#include "core/project_settings.h"
+#import "display_server_iphone.h"
+#include "main/main.h"
+#include "os_iphone.h"
+#include "servers/audio_server.h"
+
+#import <AudioToolbox/AudioServices.h>
+#import <CoreMotion/CoreMotion.h>
+#import <GameController/GameController.h>
+#import <QuartzCore/QuartzCore.h>
+#import <UIKit/UIKit.h>
+
+@interface GodotViewRenderer ()
+
+@property(assign, nonatomic) BOOL hasFinishedLocaleSetup;
+@property(assign, nonatomic) BOOL hasFinishedProjectDataSetup;
+@property(assign, nonatomic) BOOL hasStartedMain;
+@property(assign, nonatomic) BOOL hasFinishedSetup;
+
+@end
+
+@implementation GodotViewRenderer
+
+- (BOOL)setupView:(UIView *)view {
+ if (self.hasFinishedSetup) {
+ return NO;
+ }
+
+ if (!self.hasFinishedLocaleSetup) {
+ [self setupLocaleAndUUID];
+ return YES;
+ }
+
+ if (!self.hasFinishedProjectDataSetup) {
+ [self setupProjectData];
+ return YES;
+ }
+
+ if (!self.hasStartedMain) {
+ self.hasStartedMain = YES;
+ OSIPhone::get_singleton()->start();
+ return YES;
+ }
+
+ self.hasFinishedSetup = YES;
+
+ return NO;
+}
+
+- (void)setupLocaleAndUUID {
+ self.hasFinishedLocaleSetup = YES;
+
+ if (!OS::get_singleton()) {
+ exit(0);
+ }
+
+ NSString *locale_code = [[NSLocale currentLocale] localeIdentifier];
+ OSIPhone::get_singleton()->set_locale(String::utf8([locale_code UTF8String]));
+
+ NSString *uuid;
+ if ([[UIDevice currentDevice] respondsToSelector:@selector(identifierForVendor)]) {
+ uuid = [UIDevice currentDevice].identifierForVendor.UUIDString;
+ } else {
+ // before iOS 6, so just generate an identifier and store it
+ uuid = [[NSUserDefaults standardUserDefaults] objectForKey:@"identiferForVendor"];
+ if (!uuid) {
+ CFUUIDRef cfuuid = CFUUIDCreate(NULL);
+ uuid = [(NSString *)CFUUIDCreateString(NULL, cfuuid) autorelease];
+ CFRelease(cfuuid);
+ [[NSUserDefaults standardUserDefaults] setObject:uuid forKey:@"identifierForVendor"];
+ }
+ }
+
+ OSIPhone::get_singleton()->set_unique_id(String::utf8([uuid UTF8String]));
+}
+
+- (void)setupProjectData {
+ self.hasFinishedProjectDataSetup = YES;
+
+ Main::setup2();
+
+ // this might be necessary before here
+ NSDictionary *dict = [[NSBundle mainBundle] infoDictionary];
+ for (NSString *key in dict) {
+ NSObject *value = [dict objectForKey:key];
+ String ukey = String::utf8([key UTF8String]);
+
+ // we need a NSObject to Variant conversor
+
+ if ([value isKindOfClass:[NSString class]]) {
+ NSString *str = (NSString *)value;
+ String uval = String::utf8([str UTF8String]);
+
+ ProjectSettings::get_singleton()->set("Info.plist/" + ukey, uval);
+
+ } else if ([value isKindOfClass:[NSNumber class]]) {
+ NSNumber *n = (NSNumber *)value;
+ double dval = [n doubleValue];
+
+ ProjectSettings::get_singleton()->set("Info.plist/" + ukey, dval);
+ };
+ // do stuff
+ }
+}
+
+- (void)renderOnView:(UIView *)view {
+ if (!OSIPhone::get_singleton()) {
+ return;
+ }
+
+ OSIPhone::get_singleton()->iterate();
+}
+
+@end
diff --git a/platform/iphone/icloud.h b/platform/iphone/icloud.h
index b11e22fec6..381edfa718 100644
--- a/platform/iphone/icloud.h
+++ b/platform/iphone/icloud.h
@@ -44,9 +44,9 @@ class ICloud : public Object {
List<Variant> pending_events;
public:
- Error remove_key(Variant p_param);
- Variant set_key_values(Variant p_param);
- Variant get_key_value(Variant p_param);
+ Error remove_key(String p_param);
+ Array set_key_values(Dictionary p_params);
+ Variant get_key_value(String p_param);
Error synchronize_key_values();
Variant get_all_key_values();
diff --git a/platform/iphone/icloud.mm b/platform/iphone/icloud.mm
index c768274b1f..d3086e6cea 100644
--- a/platform/iphone/icloud.mm
+++ b/platform/iphone/icloud.mm
@@ -48,8 +48,10 @@ ICloud *ICloud::instance = NULL;
void ICloud::_bind_methods() {
ClassDB::bind_method(D_METHOD("remove_key"), &ICloud::remove_key);
+
ClassDB::bind_method(D_METHOD("set_key_values"), &ICloud::set_key_values);
ClassDB::bind_method(D_METHOD("get_key_value"), &ICloud::get_key_value);
+
ClassDB::bind_method(D_METHOD("synchronize_key_values"), &ICloud::synchronize_key_values);
ClassDB::bind_method(D_METHOD("get_all_key_values"), &ICloud::get_all_key_values);
@@ -91,7 +93,7 @@ Variant nsobject_to_variant(NSObject *object) {
} else if ([object isKindOfClass:[NSArray class]]) {
Array result;
NSArray *array = (NSArray *)object;
- for (unsigned int i = 0; i < [array count]; ++i) {
+ for (NSUInteger i = 0; i < [array count]; ++i) {
NSObject *value = [array objectAtIndex:i];
result.push_back(nsobject_to_variant(value));
}
@@ -149,7 +151,7 @@ Variant nsobject_to_variant(NSObject *object) {
NSObject *variant_to_nsobject(Variant v) {
if (v.get_type() == Variant::STRING) {
return [[[NSString alloc] initWithUTF8String:((String)v).utf8().get_data()] autorelease];
- } else if (v.get_type() == Variant::REAL) {
+ } else if (v.get_type() == Variant::FLOAT) {
return [NSNumber numberWithDouble:(double)v];
} else if (v.get_type() == Variant::INT) {
return [NSNumber numberWithLongLong:(long)(int)v];
@@ -159,7 +161,7 @@ NSObject *variant_to_nsobject(Variant v) {
NSMutableDictionary *result = [[[NSMutableDictionary alloc] init] autorelease];
Dictionary dic = v;
Array keys = dic.keys();
- for (unsigned int i = 0; i < keys.size(); ++i) {
+ for (int i = 0; i < keys.size(); ++i) {
NSString *key = [[[NSString alloc] initWithUTF8String:((String)(keys[i])).utf8().get_data()] autorelease];
NSObject *value = variant_to_nsobject(dic[keys[i]]);
@@ -173,7 +175,7 @@ NSObject *variant_to_nsobject(Variant v) {
} else if (v.get_type() == Variant::ARRAY) {
NSMutableArray *result = [[[NSMutableArray alloc] init] autorelease];
Array arr = v;
- for (unsigned int i = 0; i < arr.size(); ++i) {
+ for (int i = 0; i < arr.size(); ++i) {
NSObject *value = variant_to_nsobject(arr[i]);
if (value == NULL) {
//trying to add something unsupported to the array. cancel the whole array
@@ -192,9 +194,8 @@ NSObject *variant_to_nsobject(Variant v) {
return NULL;
}
-Error ICloud::remove_key(Variant p_param) {
- String param = p_param;
- NSString *key = [[[NSString alloc] initWithUTF8String:param.utf8().get_data()] autorelease];
+Error ICloud::remove_key(String p_param) {
+ NSString *key = [[[NSString alloc] initWithUTF8String:p_param.utf8().get_data()] autorelease];
NSUbiquitousKeyValueStore *store = [NSUbiquitousKeyValueStore defaultStore];
@@ -207,15 +208,14 @@ Error ICloud::remove_key(Variant p_param) {
}
//return an array of the keys that could not be set
-Variant ICloud::set_key_values(Variant p_params) {
- Dictionary params = p_params;
- Array keys = params.keys();
+Array ICloud::set_key_values(Dictionary p_params) {
+ Array keys = p_params.keys();
Array error_keys;
- for (unsigned int i = 0; i < keys.size(); ++i) {
+ for (int i = 0; i < keys.size(); ++i) {
String variant_key = keys[i];
- Variant variant_value = params[variant_key];
+ Variant variant_value = p_params[variant_key];
NSString *key = [[[NSString alloc] initWithUTF8String:variant_key.utf8().get_data()] autorelease];
if (key == NULL) {
@@ -237,10 +237,8 @@ Variant ICloud::set_key_values(Variant p_params) {
return error_keys;
}
-Variant ICloud::get_key_value(Variant p_param) {
- String param = p_param;
-
- NSString *key = [[[NSString alloc] initWithUTF8String:param.utf8().get_data()] autorelease];
+Variant ICloud::get_key_value(String p_param) {
+ NSString *key = [[[NSString alloc] initWithUTF8String:p_param.utf8().get_data()] autorelease];
NSUbiquitousKeyValueStore *store = [NSUbiquitousKeyValueStore defaultStore];
if (![[store dictionaryRepresentation] objectForKey:key]) {
diff --git a/platform/iphone/in_app_store.h b/platform/iphone/in_app_store.h
index 44e65e77ed..beb58af2c7 100644
--- a/platform/iphone/in_app_store.h
+++ b/platform/iphone/in_app_store.h
@@ -44,9 +44,9 @@ class InAppStore : public Object {
List<Variant> pending_events;
public:
- Error request_product_info(Variant p_params);
+ Error request_product_info(Dictionary p_params);
Error restore_purchases();
- Error purchase(Variant p_params);
+ Error purchase(Dictionary p_params);
int get_pending_event_count();
Variant pop_pending_event();
diff --git a/platform/iphone/in_app_store.mm b/platform/iphone/in_app_store.mm
index 548dcc549d..dfec5d7634 100644
--- a/platform/iphone/in_app_store.mm
+++ b/platform/iphone/in_app_store.mm
@@ -39,8 +39,10 @@ extern "C" {
bool auto_finish_transactions = true;
NSMutableDictionary *pending_transactions = [NSMutableDictionary dictionary];
+static NSArray *latestProducts;
@interface SKProduct (LocalizedPrice)
+
@property(nonatomic, readonly) NSString *localizedPrice;
@end
@@ -82,6 +84,8 @@ void InAppStore::_bind_methods() {
- (void)productsRequest:(SKProductsRequest *)request didReceiveResponse:(SKProductsResponse *)response {
NSArray *products = response.products;
+ latestProducts = products;
+
Dictionary ret;
ret["type"] = "product_info";
ret["result"] = "ok";
@@ -126,11 +130,10 @@ void InAppStore::_bind_methods() {
@end
-Error InAppStore::request_product_info(Variant p_params) {
- Dictionary params = p_params;
- ERR_FAIL_COND_V(!params.has("product_ids"), ERR_INVALID_PARAMETER);
+Error InAppStore::request_product_info(Dictionary p_params) {
+ ERR_FAIL_COND_V(!p_params.has("product_ids"), ERR_INVALID_PARAMETER);
- PackedStringArray pids = params["product_ids"];
+ PackedStringArray pids = p_params["product_ids"];
printf("************ request product info! %i\n", pids.size());
NSMutableArray *array = [[[NSMutableArray alloc] initWithCapacity:pids.size()] autorelease];
@@ -198,11 +201,11 @@ Error InAppStore::restore_purchases() {
// which is still available in iOS 7.
// Use SKPaymentTransaction's transactionReceipt.
- receipt = transaction.transactionReceipt;
+ receipt = [NSData dataWithContentsOfURL:[[NSBundle mainBundle] appStoreReceiptURL]];
}
} else {
- receipt = transaction.transactionReceipt;
+ receipt = [NSData dataWithContentsOfURL:[[NSBundle mainBundle] appStoreReceiptURL]];
}
NSString *receipt_to_send = nil;
@@ -254,17 +257,32 @@ Error InAppStore::restore_purchases() {
@end
-Error InAppStore::purchase(Variant p_params) {
+Error InAppStore::purchase(Dictionary p_params) {
ERR_FAIL_COND_V(![SKPaymentQueue canMakePayments], ERR_UNAVAILABLE);
if (![SKPaymentQueue canMakePayments])
return ERR_UNAVAILABLE;
printf("purchasing!\n");
- Dictionary params = p_params;
- ERR_FAIL_COND_V(!params.has("product_id"), ERR_INVALID_PARAMETER);
+ ERR_FAIL_COND_V(!p_params.has("product_id"), ERR_INVALID_PARAMETER);
+
+ NSString *pid = [[[NSString alloc] initWithUTF8String:String(p_params["product_id"]).utf8().get_data()] autorelease];
+
+ SKProduct *product = nil;
+
+ if (latestProducts) {
+ for (SKProduct *storedProduct in latestProducts) {
+ if ([storedProduct.productIdentifier isEqualToString:pid]) {
+ product = storedProduct;
+ break;
+ }
+ }
+ }
+
+ if (!product) {
+ return ERR_INVALID_PARAMETER;
+ }
- NSString *pid = [[[NSString alloc] initWithUTF8String:String(params["product_id"]).utf8().get_data()] autorelease];
- SKPayment *payment = [SKPayment paymentWithProductIdentifier:pid];
+ SKPayment *payment = [SKPayment paymentWithProduct:product];
SKPaymentQueue *defq = [SKPaymentQueue defaultQueue];
[defq addPayment:payment];
printf("purchase sent!\n");
diff --git a/platform/iphone/ios.mm b/platform/iphone/ios.mm
index 5923f558a5..ad26d0ada3 100644
--- a/platform/iphone/ios.mm
+++ b/platform/iphone/ios.mm
@@ -29,17 +29,27 @@
/*************************************************************************/
#include "ios.h"
-#include <sys/sysctl.h>
-
+#import "app_delegate.h"
#import <UIKit/UIKit.h>
+#include <sys/sysctl.h>
void iOS::_bind_methods() {
ClassDB::bind_method(D_METHOD("get_rate_url", "app_id"), &iOS::get_rate_url);
};
void iOS::alert(const char *p_alert, const char *p_title) {
- UIAlertView *alert = [[[UIAlertView alloc] initWithTitle:[NSString stringWithUTF8String:p_title] message:[NSString stringWithUTF8String:p_alert] delegate:nil cancelButtonTitle:@"OK" otherButtonTitles:nil, nil] autorelease];
- [alert show];
+ NSString *title = [NSString stringWithUTF8String:p_title];
+ NSString *message = [NSString stringWithUTF8String:p_alert];
+
+ UIAlertController *alert = [UIAlertController alertControllerWithTitle:title message:message preferredStyle:UIAlertControllerStyleAlert];
+ UIAlertAction *button = [UIAlertAction actionWithTitle:@"OK"
+ style:UIAlertActionStyleCancel
+ handler:^(id){
+ }];
+
+ [alert addAction:button];
+
+ [AppDelegate.viewController presentViewController:alert animated:YES completion:nil];
}
String iOS::get_model() const {
diff --git a/platform/iphone/joypad_iphone.h b/platform/iphone/joypad_iphone.h
new file mode 100644
index 0000000000..85e26e1dc8
--- /dev/null
+++ b/platform/iphone/joypad_iphone.h
@@ -0,0 +1,50 @@
+/*************************************************************************/
+/* joypad_iphone.h */
+/*************************************************************************/
+/* This file is part of: */
+/* GODOT ENGINE */
+/* https://godotengine.org */
+/*************************************************************************/
+/* Copyright (c) 2007-2020 Juan Linietsky, Ariel Manzur. */
+/* Copyright (c) 2014-2020 Godot Engine contributors (cf. AUTHORS.md). */
+/* */
+/* 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 <GameController/GameController.h>
+
+@interface JoypadIPhoneObserver : NSObject
+
+- (void)startObserving;
+- (void)startProcessing;
+- (void)finishObserving;
+
+@end
+
+class JoypadIPhone {
+private:
+ JoypadIPhoneObserver *observer;
+
+public:
+ JoypadIPhone();
+ ~JoypadIPhone();
+
+ void start_processing();
+};
diff --git a/platform/iphone/joypad_iphone.mm b/platform/iphone/joypad_iphone.mm
new file mode 100644
index 0000000000..6088f1c25c
--- /dev/null
+++ b/platform/iphone/joypad_iphone.mm
@@ -0,0 +1,380 @@
+/*************************************************************************/
+/* joypad_iphone.mm */
+/*************************************************************************/
+/* This file is part of: */
+/* GODOT ENGINE */
+/* https://godotengine.org */
+/*************************************************************************/
+/* Copyright (c) 2007-2020 Juan Linietsky, Ariel Manzur. */
+/* Copyright (c) 2014-2020 Godot Engine contributors (cf. AUTHORS.md). */
+/* */
+/* 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 "joypad_iphone.h"
+#include "core/project_settings.h"
+#include "drivers/coreaudio/audio_driver_coreaudio.h"
+#include "main/main.h"
+
+#import "godot_view.h"
+
+#include "os_iphone.h"
+
+JoypadIPhone::JoypadIPhone() {
+ observer = [[JoypadIPhoneObserver alloc] init];
+ [observer startObserving];
+}
+
+JoypadIPhone::~JoypadIPhone() {
+ if (observer) {
+ [observer finishObserving];
+ observer = nil;
+ }
+}
+
+void JoypadIPhone::start_processing() {
+ if (observer) {
+ [observer startProcessing];
+ }
+}
+
+@interface JoypadIPhoneObserver ()
+
+@property(assign, nonatomic) BOOL isObserving;
+@property(assign, nonatomic) BOOL isProcessing;
+@property(strong, nonatomic) NSMutableDictionary *connectedJoypads;
+@property(strong, nonatomic) NSMutableArray *joypadsQueue;
+
+@end
+
+@implementation JoypadIPhoneObserver
+
+- (instancetype)init {
+ self = [super init];
+
+ if (self) {
+ [self godot_commonInit];
+ }
+
+ return self;
+}
+
+- (void)godot_commonInit {
+ self.isObserving = NO;
+ self.isProcessing = NO;
+}
+
+- (void)startProcessing {
+ self.isProcessing = YES;
+
+ for (GCController *controller in self.joypadsQueue) {
+ [self addiOSJoypad:controller];
+ }
+
+ [self.joypadsQueue removeAllObjects];
+}
+
+- (void)startObserving {
+ if (self.isObserving) {
+ return;
+ }
+
+ self.isObserving = YES;
+
+ self.connectedJoypads = [NSMutableDictionary dictionary];
+ self.joypadsQueue = [NSMutableArray array];
+
+ // get told when controllers connect, this will be called right away for
+ // already connected controllers
+ [[NSNotificationCenter defaultCenter]
+ addObserver:self
+ selector:@selector(controllerWasConnected:)
+ name:GCControllerDidConnectNotification
+ object:nil];
+
+ // get told when controllers disconnect
+ [[NSNotificationCenter defaultCenter]
+ addObserver:self
+ selector:@selector(controllerWasDisconnected:)
+ name:GCControllerDidDisconnectNotification
+ object:nil];
+}
+
+- (void)finishObserving {
+ if (self.isObserving) {
+ [[NSNotificationCenter defaultCenter] removeObserver:self];
+ }
+
+ self.isObserving = NO;
+ self.isProcessing = NO;
+
+ self.connectedJoypads = nil;
+ self.joypadsQueue = nil;
+}
+
+- (void)dealloc {
+ [self finishObserving];
+
+ [super dealloc];
+}
+
+- (int)getJoyIdForController:(GCController *)controller {
+ NSArray *keys = [self.connectedJoypads allKeysForObject:controller];
+
+ for (NSNumber *key in keys) {
+ int joy_id = [key intValue];
+ return joy_id;
+ };
+
+ return -1;
+};
+
+- (void)addiOSJoypad:(GCController *)controller {
+ // get a new id for our controller
+ int joy_id = Input::get_singleton()->get_unused_joy_id();
+
+ if (joy_id == -1) {
+ printf("Couldn't retrieve new joy id\n");
+ return;
+ }
+
+ // assign our player index
+ if (controller.playerIndex == GCControllerPlayerIndexUnset) {
+ controller.playerIndex = [self getFreePlayerIndex];
+ };
+
+ // tell Godot about our new controller
+ Input::get_singleton()->joy_connection_changed(joy_id, true, [controller.vendorName UTF8String]);
+
+ // add it to our dictionary, this will retain our controllers
+ [self.connectedJoypads setObject:controller forKey:[NSNumber numberWithInt:joy_id]];
+
+ // set our input handler
+ [self setControllerInputHandler:controller];
+}
+
+- (void)controllerWasConnected:(NSNotification *)notification {
+ // get our controller
+ GCController *controller = (GCController *)notification.object;
+
+ if (!controller) {
+ printf("Couldn't retrieve new controller\n");
+ return;
+ }
+
+ if ([[self.connectedJoypads allKeysForObject:controller] count] > 0) {
+ printf("Controller is already registered\n");
+ } else if (!self.isProcessing) {
+ [self.joypadsQueue addObject:controller];
+ } else {
+ [self addiOSJoypad:controller];
+ }
+}
+
+- (void)controllerWasDisconnected:(NSNotification *)notification {
+ // find our joystick, there should be only one in our dictionary
+ GCController *controller = (GCController *)notification.object;
+
+ if (!controller) {
+ return;
+ }
+
+ NSArray *keys = [self.connectedJoypads allKeysForObject:controller];
+ for (NSNumber *key in keys) {
+ // tell Godot this joystick is no longer there
+ int joy_id = [key intValue];
+ Input::get_singleton()->joy_connection_changed(joy_id, false, "");
+
+ // and remove it from our dictionary
+ [self.connectedJoypads removeObjectForKey:key];
+ };
+};
+
+- (GCControllerPlayerIndex)getFreePlayerIndex {
+ bool have_player_1 = false;
+ bool have_player_2 = false;
+ bool have_player_3 = false;
+ bool have_player_4 = false;
+
+ if (self.connectedJoypads == nil) {
+ NSArray *keys = [self.connectedJoypads allKeys];
+ for (NSNumber *key in keys) {
+ GCController *controller = [self.connectedJoypads objectForKey:key];
+ if (controller.playerIndex == GCControllerPlayerIndex1) {
+ have_player_1 = true;
+ } else if (controller.playerIndex == GCControllerPlayerIndex2) {
+ have_player_2 = true;
+ } else if (controller.playerIndex == GCControllerPlayerIndex3) {
+ have_player_3 = true;
+ } else if (controller.playerIndex == GCControllerPlayerIndex4) {
+ have_player_4 = true;
+ };
+ };
+ };
+
+ if (!have_player_1) {
+ return GCControllerPlayerIndex1;
+ } else if (!have_player_2) {
+ return GCControllerPlayerIndex2;
+ } else if (!have_player_3) {
+ return GCControllerPlayerIndex3;
+ } else if (!have_player_4) {
+ return GCControllerPlayerIndex4;
+ } else {
+ return GCControllerPlayerIndexUnset;
+ };
+}
+
+- (void)setControllerInputHandler:(GCController *)controller {
+ // Hook in the callback handler for the correct gamepad profile.
+ // This is a bit of a weird design choice on Apples part.
+ // You need to select the most capable gamepad profile for the
+ // gamepad attached.
+ if (controller.extendedGamepad != nil) {
+ // The extended gamepad profile has all the input you could possibly find on
+ // a gamepad but will only be active if your gamepad actually has all of
+ // these...
+ controller.extendedGamepad.valueChangedHandler = ^(
+ GCExtendedGamepad *gamepad, GCControllerElement *element) {
+ int joy_id = [self getJoyIdForController:controller];
+
+ if (element == gamepad.buttonA) {
+ Input::get_singleton()->joy_button(joy_id, JOY_BUTTON_A,
+ gamepad.buttonA.isPressed);
+ } else if (element == gamepad.buttonB) {
+ Input::get_singleton()->joy_button(joy_id, JOY_BUTTON_B,
+ gamepad.buttonB.isPressed);
+ } else if (element == gamepad.buttonX) {
+ Input::get_singleton()->joy_button(joy_id, JOY_BUTTON_X,
+ gamepad.buttonX.isPressed);
+ } else if (element == gamepad.buttonY) {
+ Input::get_singleton()->joy_button(joy_id, JOY_BUTTON_Y,
+ gamepad.buttonY.isPressed);
+ } else if (element == gamepad.leftShoulder) {
+ Input::get_singleton()->joy_button(joy_id, JOY_BUTTON_LEFT_SHOULDER,
+ gamepad.leftShoulder.isPressed);
+ } else if (element == gamepad.rightShoulder) {
+ Input::get_singleton()->joy_button(joy_id, JOY_BUTTON_RIGHT_SHOULDER,
+ gamepad.rightShoulder.isPressed);
+ } else if (element == gamepad.dpad) {
+ Input::get_singleton()->joy_button(joy_id, JOY_BUTTON_DPAD_UP,
+ gamepad.dpad.up.isPressed);
+ Input::get_singleton()->joy_button(joy_id, JOY_BUTTON_DPAD_DOWN,
+ gamepad.dpad.down.isPressed);
+ Input::get_singleton()->joy_button(joy_id, JOY_BUTTON_DPAD_LEFT,
+ gamepad.dpad.left.isPressed);
+ Input::get_singleton()->joy_button(joy_id, JOY_BUTTON_DPAD_RIGHT,
+ gamepad.dpad.right.isPressed);
+ };
+
+ Input::JoyAxis jx;
+ jx.min = -1;
+ if (element == gamepad.leftThumbstick) {
+ jx.value = gamepad.leftThumbstick.xAxis.value;
+ Input::get_singleton()->joy_axis(joy_id, JOY_AXIS_LEFT_X, jx);
+ jx.value = -gamepad.leftThumbstick.yAxis.value;
+ Input::get_singleton()->joy_axis(joy_id, JOY_AXIS_LEFT_Y, jx);
+ } else if (element == gamepad.rightThumbstick) {
+ jx.value = gamepad.rightThumbstick.xAxis.value;
+ Input::get_singleton()->joy_axis(joy_id, JOY_AXIS_RIGHT_X, jx);
+ jx.value = -gamepad.rightThumbstick.yAxis.value;
+ Input::get_singleton()->joy_axis(joy_id, JOY_AXIS_RIGHT_Y, jx);
+ } else if (element == gamepad.leftTrigger) {
+ jx.value = gamepad.leftTrigger.value;
+ Input::get_singleton()->joy_axis(joy_id, JOY_AXIS_TRIGGER_LEFT, jx);
+ } else if (element == gamepad.rightTrigger) {
+ jx.value = gamepad.rightTrigger.value;
+ Input::get_singleton()->joy_axis(joy_id, JOY_AXIS_TRIGGER_RIGHT, jx);
+ };
+ };
+ }
+ // else if (controller.gamepad != nil) {
+ // // gamepad is the standard profile with 4 buttons, shoulder buttons and a
+ // // D-pad
+ // controller.gamepad.valueChangedHandler = ^(GCGamepad *gamepad,
+ // GCControllerElement *element) {
+ // int joy_id = [self getJoyIdForController:controller];
+ //
+ // if (element == gamepad.buttonA) {
+ // Input::get_singleton()->joy_button(joy_id, JOY_BUTTON_A,
+ // gamepad.buttonA.isPressed);
+ // } else if (element == gamepad.buttonB) {
+ // Input::get_singleton()->joy_button(joy_id, JOY_BUTTON_B,
+ // gamepad.buttonB.isPressed);
+ // } else if (element == gamepad.buttonX) {
+ // Input::get_singleton()->joy_button(joy_id, JOY_BUTTON_X,
+ // gamepad.buttonX.isPressed);
+ // } else if (element == gamepad.buttonY) {
+ // Input::get_singleton()->joy_button(joy_id, JOY_BUTTON_Y,
+ // gamepad.buttonY.isPressed);
+ // } else if (element == gamepad.leftShoulder) {
+ // Input::get_singleton()->joy_button(joy_id, JOY_BUTTON_LEFT_SHOULDER,
+ // gamepad.leftShoulder.isPressed);
+ // } else if (element == gamepad.rightShoulder) {
+ // Input::get_singleton()->joy_button(joy_id, JOY_BUTTON_RIGHT_SHOULDER,
+ // gamepad.rightShoulder.isPressed);
+ // } else if (element == gamepad.dpad) {
+ // Input::get_singleton()->joy_button(joy_id, JOY_BUTTON_DPAD_UP,
+ // gamepad.dpad.up.isPressed);
+ // Input::get_singleton()->joy_button(joy_id, JOY_BUTTON_DPAD_DOWN,
+ // gamepad.dpad.down.isPressed);
+ // Input::get_singleton()->joy_button(joy_id, JOY_BUTTON_DPAD_LEFT,
+ // gamepad.dpad.left.isPressed);
+ // Input::get_singleton()->joy_button(joy_id, JOY_BUTTON_DPAD_RIGHT,
+ // gamepad.dpad.right.isPressed);
+ // };
+ // };
+ //#ifdef ADD_MICRO_GAMEPAD // disabling this for now, only available on iOS 9+,
+ // // while we are setting that as the minimum, seems our
+ // // build environment doesn't like it
+ // } else if (controller.microGamepad != nil) {
+ // // micro gamepads were added in OS 9 and feature just 2 buttons and a d-pad
+ // controller.microGamepad.valueChangedHandler =
+ // ^(GCMicroGamepad *gamepad, GCControllerElement *element) {
+ // int joy_id = [self getJoyIdForController:controller];
+ //
+ // if (element == gamepad.buttonA) {
+ // Input::get_singleton()->joy_button(joy_id, JOY_BUTTON_A,
+ // gamepad.buttonA.isPressed);
+ // } else if (element == gamepad.buttonX) {
+ // Input::get_singleton()->joy_button(joy_id, JOY_BUTTON_X,
+ // gamepad.buttonX.isPressed);
+ // } else if (element == gamepad.dpad) {
+ // Input::get_singleton()->joy_button(joy_id, JOY_BUTTON_DPAD_UP,
+ // gamepad.dpad.up.isPressed);
+ // Input::get_singleton()->joy_button(joy_id, JOY_BUTTON_DPAD_DOWN,
+ // gamepad.dpad.down.isPressed);
+ // Input::get_singleton()->joy_button(joy_id, JOY_BUTTON_DPAD_LEFT,
+ // gamepad.dpad.left.isPressed);
+ // Input::get_singleton()->joy_button(joy_id, JOY_BUTTON_DPAD_RIGHT,
+ // gamepad.dpad.right.isPressed);
+ // };
+ // };
+ //#endif
+ // };
+
+ ///@TODO need to add support for controller.motion which gives us access to
+ /// the orientation of the device (if supported)
+
+ ///@TODO need to add support for controllerPausedHandler which should be a
+ /// toggle
+};
+
+@end
diff --git a/platform/iphone/main.m b/platform/iphone/main.m
index 164db2a74b..c292f02822 100644
--- a/platform/iphone/main.m
+++ b/platform/iphone/main.m
@@ -32,20 +32,25 @@
#import <UIKit/UIKit.h>
#include <stdio.h>
+#include <vulkan/vulkan.h>
int gargc;
char **gargv;
int main(int argc, char *argv[]) {
+#if defined(VULKAN_ENABLED)
+ //MoltenVK - enable full component swizzling support
+ setenv("MVK_CONFIG_FULL_IMAGE_VIEW_SWIZZLE", "1", 1);
+#endif
+
printf("*********** main.m\n");
gargc = argc;
gargv = argv;
- NSAutoreleasePool *pool = [NSAutoreleasePool new];
- AppDelegate *app = [AppDelegate alloc];
printf("running app main\n");
- UIApplicationMain(argc, argv, nil, @"AppDelegate");
- printf("main done, pool release\n");
- [pool release];
+ @autoreleasepool {
+ UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class]));
+ }
+ printf("main done\n");
return 0;
}
diff --git a/platform/iphone/os_iphone.cpp b/platform/iphone/os_iphone.cpp
deleted file mode 100644
index 41dd623e69..0000000000
--- a/platform/iphone/os_iphone.cpp
+++ /dev/null
@@ -1,632 +0,0 @@
-/*************************************************************************/
-/* os_iphone.cpp */
-/*************************************************************************/
-/* This file is part of: */
-/* GODOT ENGINE */
-/* https://godotengine.org */
-/*************************************************************************/
-/* Copyright (c) 2007-2020 Juan Linietsky, Ariel Manzur. */
-/* Copyright (c) 2014-2020 Godot Engine contributors (cf. AUTHORS.md). */
-/* */
-/* 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. */
-/*************************************************************************/
-
-#ifdef IPHONE_ENABLED
-
-#include "os_iphone.h"
-
-#if defined(OPENGL_ENABLED)
-#include "drivers/gles2/rasterizer_gles2.h"
-#endif
-
-#if defined(VULKAN_ENABLED)
-#include "servers/rendering/rasterizer_rd/rasterizer_rd.h"
-// #import <QuartzCore/CAMetalLayer.h>
-#include <vulkan/vulkan_metal.h>
-#endif
-
-#include "servers/rendering/rendering_server_raster.h"
-#include "servers/rendering/rendering_server_wrap_mt.h"
-
-#include "main/main.h"
-
-#include "core/io/file_access_pack.h"
-#include "core/os/dir_access.h"
-#include "core/os/file_access.h"
-#include "core/project_settings.h"
-#include "drivers/unix/syslog_logger.h"
-
-#include "semaphore_iphone.h"
-
-#include <dlfcn.h>
-
-int OSIPhone::get_video_driver_count() const {
- return 2;
-};
-
-const char *OSIPhone::get_video_driver_name(int p_driver) const {
- switch (p_driver) {
- case VIDEO_DRIVER_GLES2:
- return "GLES2";
- }
- ERR_FAIL_V_MSG(nullptr, "Invalid video driver index: " + itos(p_driver) + ".");
-};
-
-OSIPhone *OSIPhone::get_singleton() {
- return (OSIPhone *)OS::get_singleton();
-};
-
-extern int gl_view_base_fb; // from gl_view.mm
-
-void OSIPhone::set_data_dir(String p_dir) {
- DirAccess *da = DirAccess::open(p_dir);
-
- data_dir = da->get_current_dir();
- printf("setting data dir to %ls from %ls\n", data_dir.c_str(), p_dir.c_str());
- memdelete(da);
-};
-
-void OSIPhone::set_unique_id(String p_id) {
- unique_id = p_id;
-};
-
-String OSIPhone::get_unique_id() const {
- return unique_id;
-};
-
-void OSIPhone::initialize_core() {
- OS_Unix::initialize_core();
-
- set_data_dir(data_dir);
-};
-
-int OSIPhone::get_current_video_driver() const {
- return video_driver_index;
-}
-
-Error OSIPhone::initialize(const VideoMode &p_desired, int p_video_driver, int p_audio_driver) {
- video_driver_index = p_video_driver;
-
-#if defined(OPENGL_ENABLED)
- bool gl_initialization_error = false;
-
- // FIXME: Add Vulkan support via MoltenVK. Add fallback code back?
-
- if (RasterizerGLES2::is_viable() == OK) {
- RasterizerGLES2::register_config();
- RasterizerGLES2::make_current();
- } else {
- gl_initialization_error = true;
- }
-
- if (gl_initialization_error) {
- OS::get_singleton()->alert("Your device does not support any of the supported OpenGL versions.",
- "Unable to initialize video driver");
- return ERR_UNAVAILABLE;
- }
-#endif
-
-#if defined(VULKAN_ENABLED)
- RasterizerRD::make_current();
-#endif
-
- rendering_server = memnew(RenderingServerRaster);
- // FIXME: Reimplement threaded rendering
- if (get_render_thread_mode() != RENDER_THREAD_UNSAFE) {
- rendering_server = memnew(RenderingServerWrapMT(rendering_server, false));
- }
- rendering_server->init();
- //rendering_server->cursor_set_visible(false, 0);
-
-#if defined(OPENGL_ENABLED)
- // reset this to what it should be, it will have been set to 0 after rendering_server->init() is called
- RasterizerStorageGLES2::system_fbo = gl_view_base_fb;
-#endif
-
- AudioDriverManager::initialize(p_audio_driver);
-
- input = memnew(InputDefault);
-
-#ifdef GAME_CENTER_ENABLED
- game_center = memnew(GameCenter);
- Engine::get_singleton()->add_singleton(Engine::Singleton("GameCenter", game_center));
- game_center->connect();
-#endif
-
-#ifdef STOREKIT_ENABLED
- store_kit = memnew(InAppStore);
- Engine::get_singleton()->add_singleton(Engine::Singleton("InAppStore", store_kit));
-#endif
-
-#ifdef ICLOUD_ENABLED
- icloud = memnew(ICloud);
- Engine::get_singleton()->add_singleton(Engine::Singleton("ICloud", icloud));
- //icloud->connect();
-#endif
- ios = memnew(iOS);
- Engine::get_singleton()->add_singleton(Engine::Singleton("iOS", ios));
-
- return OK;
-};
-
-MainLoop *OSIPhone::get_main_loop() const {
- return main_loop;
-};
-
-void OSIPhone::set_main_loop(MainLoop *p_main_loop) {
- main_loop = p_main_loop;
-
- if (main_loop) {
- input->set_main_loop(p_main_loop);
- main_loop->init();
- }
-};
-
-bool OSIPhone::iterate() {
- if (!main_loop)
- return true;
-
- if (main_loop) {
- for (int i = 0; i < event_count; i++) {
- input->parse_input_event(event_queue[i]);
- };
- };
- event_count = 0;
-
- return Main::iteration();
-};
-
-void OSIPhone::key(uint32_t p_key, bool p_pressed) {
- Ref<InputEventKey> ev;
- ev.instance();
- ev->set_echo(false);
- ev->set_pressed(p_pressed);
- ev->set_keycode(p_key);
- ev->set_physical_keycode(p_key);
- ev->set_unicode(p_key);
- queue_event(ev);
-};
-
-void OSIPhone::touch_press(int p_idx, int p_x, int p_y, bool p_pressed, bool p_doubleclick) {
- if (!GLOBAL_DEF("debug/disable_touch", false)) {
- Ref<InputEventScreenTouch> ev;
- ev.instance();
-
- ev->set_index(p_idx);
- ev->set_pressed(p_pressed);
- ev->set_position(Vector2(p_x, p_y));
- queue_event(ev);
- };
-
- touch_list.pressed[p_idx] = p_pressed;
-};
-
-void OSIPhone::touch_drag(int p_idx, int p_prev_x, int p_prev_y, int p_x, int p_y) {
- if (!GLOBAL_DEF("debug/disable_touch", false)) {
- Ref<InputEventScreenDrag> ev;
- ev.instance();
- ev->set_index(p_idx);
- ev->set_position(Vector2(p_x, p_y));
- ev->set_relative(Vector2(p_x - p_prev_x, p_y - p_prev_y));
- queue_event(ev);
- };
-};
-
-void OSIPhone::queue_event(const Ref<InputEvent> &p_event) {
- ERR_FAIL_INDEX(event_count, MAX_EVENTS);
-
- event_queue[event_count++] = p_event;
-};
-
-void OSIPhone::touches_cancelled() {
- for (int i = 0; i < MAX_MOUSE_COUNT; i++) {
- if (touch_list.pressed[i]) {
- // send a mouse_up outside the screen
- touch_press(i, -1, -1, false, false);
- };
- };
-};
-
-static const float ACCEL_RANGE = 1;
-
-void OSIPhone::update_gravity(float p_x, float p_y, float p_z) {
- input->set_gravity(Vector3(p_x, p_y, p_z));
-};
-
-void OSIPhone::update_accelerometer(float p_x, float p_y, float p_z) {
- // Found out the Z should not be negated! Pass as is!
- input->set_accelerometer(Vector3(p_x / (float)ACCEL_RANGE, p_y / (float)ACCEL_RANGE, p_z / (float)ACCEL_RANGE));
-
- /*
- if (p_x != last_accel.x) {
- //printf("updating accel x %f\n", p_x);
- InputEvent ev;
- ev.type = InputEvent::JOYPAD_MOTION;
- ev.device = 0;
- ev.joy_motion.axis = JOY_ANALOG_0;
- ev.joy_motion.axis_value = (p_x / (float)ACCEL_RANGE);
- last_accel.x = p_x;
- queue_event(ev);
- };
- if (p_y != last_accel.y) {
- //printf("updating accel y %f\n", p_y);
- InputEvent ev;
- ev.type = InputEvent::JOYPAD_MOTION;
- ev.device = 0;
- ev.joy_motion.axis = JOY_ANALOG_1;
- ev.joy_motion.axis_value = (p_y / (float)ACCEL_RANGE);
- last_accel.y = p_y;
- queue_event(ev);
- };
- if (p_z != last_accel.z) {
- //printf("updating accel z %f\n", p_z);
- InputEvent ev;
- ev.type = InputEvent::JOYPAD_MOTION;
- ev.device = 0;
- ev.joy_motion.axis = JOY_ANALOG_2;
- ev.joy_motion.axis_value = ( (1.0 - p_z) / (float)ACCEL_RANGE);
- last_accel.z = p_z;
- queue_event(ev);
- };
- */
-};
-
-void OSIPhone::update_magnetometer(float p_x, float p_y, float p_z) {
- input->set_magnetometer(Vector3(p_x, p_y, p_z));
-};
-
-void OSIPhone::update_gyroscope(float p_x, float p_y, float p_z) {
- input->set_gyroscope(Vector3(p_x, p_y, p_z));
-};
-
-int OSIPhone::get_unused_joy_id() {
- return input->get_unused_joy_id();
-};
-
-void OSIPhone::joy_connection_changed(int p_idx, bool p_connected, String p_name) {
- input->joy_connection_changed(p_idx, p_connected, p_name);
-};
-
-void OSIPhone::joy_button(int p_device, int p_button, bool p_pressed) {
- input->joy_button(p_device, p_button, p_pressed);
-};
-
-void OSIPhone::joy_axis(int p_device, int p_axis, const InputDefault::JoyAxis &p_value) {
- input->joy_axis(p_device, p_axis, p_value);
-};
-
-void OSIPhone::delete_main_loop() {
- if (main_loop) {
- main_loop->finish();
- memdelete(main_loop);
- };
-
- main_loop = nullptr;
-};
-
-void OSIPhone::finalize() {
- delete_main_loop();
-
- memdelete(input);
- memdelete(ios);
-
-#ifdef GAME_CENTER_ENABLED
- memdelete(game_center);
-#endif
-
-#ifdef STOREKIT_ENABLED
- memdelete(store_kit);
-#endif
-
-#ifdef ICLOUD_ENABLED
- memdelete(icloud);
-#endif
-
- rendering_server->finish();
- memdelete(rendering_server);
- // memdelete(rasterizer);
-
- // Free unhandled events before close
- for (int i = 0; i < MAX_EVENTS; i++) {
- event_queue[i].unref();
- };
- event_count = 0;
-};
-
-void OSIPhone::set_mouse_show(bool p_show) {}
-void OSIPhone::set_mouse_grab(bool p_grab) {}
-
-bool OSIPhone::is_mouse_grab_enabled() const {
- return true;
-};
-
-Point2 OSIPhone::get_mouse_position() const {
- return Point2();
-};
-
-int OSIPhone::get_mouse_button_state() const {
- return 0;
-};
-
-void OSIPhone::set_window_title(const String &p_title) {}
-
-void OSIPhone::alert(const String &p_alert, const String &p_title) {
- const CharString utf8_alert = p_alert.utf8();
- const CharString utf8_title = p_title.utf8();
- iOS::alert(utf8_alert.get_data(), utf8_title.get_data());
-}
-
-Error OSIPhone::open_dynamic_library(const String p_path, void *&p_library_handle, bool p_also_set_library_path) {
- if (p_path.length() == 0) {
- p_library_handle = RTLD_SELF;
- return OK;
- }
- return OS_Unix::open_dynamic_library(p_path, p_library_handle, p_also_set_library_path);
-}
-
-Error OSIPhone::close_dynamic_library(void *p_library_handle) {
- if (p_library_handle == RTLD_SELF) {
- return OK;
- }
- return OS_Unix::close_dynamic_library(p_library_handle);
-}
-
-HashMap<String, void *> OSIPhone::dynamic_symbol_lookup_table;
-void register_dynamic_symbol(char *name, void *address) {
- OSIPhone::dynamic_symbol_lookup_table[String(name)] = address;
-}
-
-Error OSIPhone::get_dynamic_library_symbol_handle(void *p_library_handle, const String p_name, void *&p_symbol_handle, bool p_optional) {
- if (p_library_handle == RTLD_SELF) {
- void **ptr = OSIPhone::dynamic_symbol_lookup_table.getptr(p_name);
- if (ptr) {
- p_symbol_handle = *ptr;
- return OK;
- }
- }
- return OS_Unix::get_dynamic_library_symbol_handle(p_library_handle, p_name, p_symbol_handle, p_optional);
-}
-
-void OSIPhone::set_video_mode(const VideoMode &p_video_mode, int p_screen) {
- video_mode = p_video_mode;
-};
-
-OS::VideoMode OSIPhone::get_video_mode(int p_screen) const {
- return video_mode;
-};
-
-void OSIPhone::get_fullscreen_mode_list(List<VideoMode> *p_list, int p_screen) const {
- p_list->push_back(video_mode);
-};
-
-bool OSIPhone::can_draw() const {
- if (native_video_is_playing())
- return false;
- return true;
-};
-
-int OSIPhone::set_base_framebuffer(int p_fb) {
-#if defined(OPENGL_ENABLED)
- // gl_view_base_fb has not been updated yet
- RasterizerStorageGLES2::system_fbo = p_fb;
-#endif
-
- return 0;
-};
-
-bool OSIPhone::has_virtual_keyboard() const {
- return true;
-};
-
-extern void _show_keyboard(String p_existing);
-extern void _hide_keyboard();
-extern Error _shell_open(String p_uri);
-extern void _set_keep_screen_on(bool p_enabled);
-extern void _vibrate();
-
-void OSIPhone::show_virtual_keyboard(const String &p_existing_text, const Rect2 &p_screen_rect, int p_max_input_length, int p_cursor_start, int p_cursor_end) {
- _show_keyboard(p_existing_text);
-};
-
-void OSIPhone::hide_virtual_keyboard() {
- _hide_keyboard();
-};
-
-void OSIPhone::set_virtual_keyboard_height(int p_height) {
- virtual_keyboard_height = p_height;
-}
-
-int OSIPhone::get_virtual_keyboard_height() const {
- return virtual_keyboard_height;
-}
-
-Error OSIPhone::shell_open(String p_uri) {
- return _shell_open(p_uri);
-};
-
-void OSIPhone::set_keep_screen_on(bool p_enabled) {
- OS::set_keep_screen_on(p_enabled);
- _set_keep_screen_on(p_enabled);
-};
-
-String OSIPhone::get_user_data_dir() const {
- return data_dir;
-};
-
-String OSIPhone::get_name() const {
- return "iOS";
-};
-
-String OSIPhone::get_model_name() const {
- String model = ios->get_model();
- if (model != "")
- return model;
-
- return OS_Unix::get_model_name();
-}
-
-Size2 OSIPhone::get_window_size() const {
- return Vector2(video_mode.width, video_mode.height);
-}
-
-extern Rect2 _get_ios_window_safe_area(float p_window_width, float p_window_height);
-
-Rect2 OSIPhone::get_window_safe_area() const {
- return _get_ios_window_safe_area(video_mode.width, video_mode.height);
-}
-
-bool OSIPhone::has_touchscreen_ui_hint() const {
- return true;
-}
-
-void OSIPhone::set_locale(String p_locale) {
- locale_code = p_locale;
-}
-
-String OSIPhone::get_locale() const {
- return locale_code;
-}
-
-extern bool _play_video(String p_path, float p_volume, String p_audio_track, String p_subtitle_track);
-extern bool _is_video_playing();
-extern void _pause_video();
-extern void _unpause_video();
-extern void _stop_video();
-extern void _focus_out_video();
-
-Error OSIPhone::native_video_play(String p_path, float p_volume, String p_audio_track, String p_subtitle_track) {
- FileAccess *f = FileAccess::open(p_path, FileAccess::READ);
- bool exists = f && f->is_open();
-
- String tempFile = get_user_data_dir();
- if (!exists)
- return FAILED;
-
- if (p_path.begins_with("res://")) {
- if (PackedData::get_singleton()->has_path(p_path)) {
- print("Unable to play %S using the native player as it resides in a .pck file\n", p_path.c_str());
- return ERR_INVALID_PARAMETER;
- } else {
- p_path = p_path.replace("res:/", ProjectSettings::get_singleton()->get_resource_path());
- }
- } else if (p_path.begins_with("user://"))
- p_path = p_path.replace("user:/", get_user_data_dir());
-
- memdelete(f);
-
- print("Playing video: %S\n", p_path.c_str());
- if (_play_video(p_path, p_volume, p_audio_track, p_subtitle_track))
- return OK;
- return FAILED;
-}
-
-bool OSIPhone::native_video_is_playing() const {
- return _is_video_playing();
-}
-
-void OSIPhone::native_video_pause() {
- if (native_video_is_playing())
- _pause_video();
-}
-
-void OSIPhone::native_video_unpause() {
- _unpause_video();
-};
-
-void OSIPhone::native_video_focus_out() {
- _focus_out_video();
-};
-
-void OSIPhone::native_video_stop() {
- if (native_video_is_playing())
- _stop_video();
-}
-
-void OSIPhone::vibrate_handheld(int p_duration_ms) {
- // iOS does not support duration for vibration
- _vibrate();
-}
-
-bool OSIPhone::_check_internal_feature_support(const String &p_feature) {
- return p_feature == "mobile";
-}
-
-// Initialization order between compilation units is not guaranteed,
-// so we use this as a hack to ensure certain code is called before
-// everything else, but after all units are initialized.
-typedef void (*init_callback)();
-static init_callback *ios_init_callbacks = nullptr;
-static int ios_init_callbacks_count = 0;
-static int ios_init_callbacks_capacity = 0;
-
-void add_ios_init_callback(init_callback cb) {
- if (ios_init_callbacks_count == ios_init_callbacks_capacity) {
- void *new_ptr = realloc(ios_init_callbacks, sizeof(cb) * 32);
- if (new_ptr) {
- ios_init_callbacks = (init_callback *)(new_ptr);
- ios_init_callbacks_capacity += 32;
- }
- }
- if (ios_init_callbacks_capacity > ios_init_callbacks_count) {
- ios_init_callbacks[ios_init_callbacks_count] = cb;
- ++ios_init_callbacks_count;
- }
-}
-
-OSIPhone::OSIPhone(int width, int height, String p_data_dir) {
- for (int i = 0; i < ios_init_callbacks_count; ++i) {
- ios_init_callbacks[i]();
- }
- free(ios_init_callbacks);
- ios_init_callbacks = nullptr;
- ios_init_callbacks_count = 0;
- ios_init_callbacks_capacity = 0;
-
- main_loop = nullptr;
- rendering_server = nullptr;
-
- VideoMode vm;
- vm.fullscreen = true;
- vm.width = width;
- vm.height = height;
- vm.resizable = false;
- set_video_mode(vm);
- event_count = 0;
- virtual_keyboard_height = 0;
-
- // can't call set_data_dir from here, since it requires DirAccess
- // which is initialized in initialize_core
- data_dir = p_data_dir;
-
- Vector<Logger *> loggers;
- loggers.push_back(memnew(SyslogLogger));
-#ifdef DEBUG_ENABLED
- // it seems iOS app's stdout/stderr is only obtainable if you launch it from Xcode
- loggers.push_back(memnew(StdLogger));
-#endif
- _set_logger(memnew(CompositeLogger(loggers)));
-
- AudioDriverManager::add_driver(&audio_driver);
-};
-
-OSIPhone::~OSIPhone() {
-}
-
-#endif
diff --git a/platform/iphone/os_iphone.h b/platform/iphone/os_iphone.h
index 955eb15d57..f3bde46717 100644
--- a/platform/iphone/os_iphone.h
+++ b/platform/iphone/os_iphone.h
@@ -33,16 +33,15 @@
#ifndef OS_IPHONE_H
#define OS_IPHONE_H
-#include "core/input/input.h"
#include "drivers/coreaudio/audio_driver_coreaudio.h"
#include "drivers/unix/os_unix.h"
#include "game_center.h"
#include "icloud.h"
#include "in_app_store.h"
#include "ios.h"
+#include "joypad_iphone.h"
#include "servers/audio_server.h"
#include "servers/rendering/rasterizer.h"
-#include "servers/rendering_server.h"
#if defined(VULKAN_ENABLED)
#include "drivers/vulkan/rendering_device_vulkan.h"
@@ -51,16 +50,9 @@
class OSIPhone : public OS_Unix {
private:
- enum {
- MAX_MOUSE_COUNT = 8,
- MAX_EVENTS = 64,
- };
-
static HashMap<String, void *> dynamic_symbol_lookup_table;
friend void register_dynamic_symbol(char *name, void *address);
- RenderingServer *rendering_server;
-
AudioDriverCoreAudio audio_driver;
#ifdef GAME_CENTER_ENABLED
@@ -74,139 +66,72 @@ private:
#endif
iOS *ios;
- MainLoop *main_loop;
-
-#if defined(VULKAN_ENABLED)
- VulkanContextIPhone *context_vulkan;
- RenderingDeviceVulkan *rendering_device_vulkan;
-#endif
- VideoMode video_mode;
-
- virtual int get_video_driver_count() const;
- virtual const char *get_video_driver_name(int p_driver) const;
+ JoypadIPhone *joypad_iphone;
- virtual int get_current_video_driver() const;
-
- virtual void initialize_core();
- virtual Error initialize(const VideoMode &p_desired, int p_video_driver, int p_audio_driver);
-
- virtual void set_main_loop(MainLoop *p_main_loop);
- virtual MainLoop *get_main_loop() const;
-
- virtual void delete_main_loop();
+ MainLoop *main_loop;
- virtual void finalize();
+ virtual void initialize_core() override;
+ virtual void initialize() override;
- struct MouseList {
- bool pressed[MAX_MOUSE_COUNT];
- MouseList() {
- for (int i = 0; i < MAX_MOUSE_COUNT; i++)
- pressed[i] = false;
- };
- };
+ virtual void initialize_joypads() override {
+ }
- MouseList touch_list;
+ virtual void set_main_loop(MainLoop *p_main_loop) override;
+ virtual MainLoop *get_main_loop() const override;
- Vector3 last_accel;
+ virtual void delete_main_loop() override;
- Ref<InputEvent> event_queue[MAX_EVENTS];
- int event_count;
- void queue_event(const Ref<InputEvent> &p_event);
+ virtual void finalize() override;
- String data_dir;
+ String user_data_dir;
String unique_id;
String locale_code;
- InputDefault *input;
+ bool is_focused = false;
- int virtual_keyboard_height;
-
- int video_driver_index;
+ void deinitialize_modules();
public:
- bool iterate();
-
- uint8_t get_orientations() const;
-
- void touch_press(int p_idx, int p_x, int p_y, bool p_pressed, bool p_doubleclick);
- void touch_drag(int p_idx, int p_prev_x, int p_prev_y, int p_x, int p_y);
- void touches_cancelled();
- void key(uint32_t p_key, bool p_pressed);
- void set_virtual_keyboard_height(int p_height);
-
- int set_base_framebuffer(int p_fb);
-
- void update_gravity(float p_x, float p_y, float p_z);
- void update_accelerometer(float p_x, float p_y, float p_z);
- void update_magnetometer(float p_x, float p_y, float p_z);
- void update_gyroscope(float p_x, float p_y, float p_z);
-
- int get_unused_joy_id();
- void joy_connection_changed(int p_idx, bool p_connected, String p_name);
- void joy_button(int p_device, int p_button, bool p_pressed);
- void joy_axis(int p_device, int p_axis, const InputDefault::JoyAxis &p_value);
-
static OSIPhone *get_singleton();
- virtual void set_mouse_show(bool p_show);
- virtual void set_mouse_grab(bool p_grab);
- virtual bool is_mouse_grab_enabled() const;
- virtual Point2 get_mouse_position() const;
- virtual int get_mouse_button_state() const;
- virtual void set_window_title(const String &p_title);
-
- virtual void alert(const String &p_alert, const String &p_title = "ALERT!");
+ OSIPhone(String p_data_dir);
+ ~OSIPhone();
- virtual Error open_dynamic_library(const String p_path, void *&p_library_handle, bool p_also_set_library_path = false);
- virtual Error close_dynamic_library(void *p_library_handle);
- virtual Error get_dynamic_library_symbol_handle(void *p_library_handle, const String p_name, void *&p_symbol_handle, bool p_optional = false);
+ void initialize_modules();
- virtual void set_video_mode(const VideoMode &p_video_mode, int p_screen = 0);
- virtual VideoMode get_video_mode(int p_screen = 0) const;
- virtual void get_fullscreen_mode_list(List<VideoMode> *p_list, int p_screen = 0) const;
+ bool iterate();
- virtual void set_keep_screen_on(bool p_enabled);
+ void start();
- virtual bool can_draw() const;
+ virtual Error open_dynamic_library(const String p_path, void *&p_library_handle, bool p_also_set_library_path = false) override;
+ virtual Error close_dynamic_library(void *p_library_handle) override;
+ virtual Error get_dynamic_library_symbol_handle(void *p_library_handle, const String p_name, void *&p_symbol_handle, bool p_optional = false) override;
- virtual bool has_virtual_keyboard() const;
- virtual void show_virtual_keyboard(const String &p_existing_text, const Rect2 &p_screen_rect = Rect2(), int p_max_input_length = -1, int p_cursor_start = -1, int p_cursor_end = -1);
- virtual void hide_virtual_keyboard();
- virtual int get_virtual_keyboard_height() const;
+ virtual void alert(const String &p_alert,
+ const String &p_title = "ALERT!") override;
- virtual Size2 get_window_size() const;
- virtual Rect2 get_window_safe_area() const;
+ virtual String get_name() const override;
+ virtual String get_model_name() const override;
- virtual bool has_touchscreen_ui_hint() const;
+ virtual Error shell_open(String p_uri) override;
- void set_data_dir(String p_dir);
+ void set_user_data_dir(String p_dir);
+ virtual String get_user_data_dir() const override;
- virtual String get_name() const;
- virtual String get_model_name() const;
+ void set_locale(String p_locale);
+ virtual String get_locale() const override;
- Error shell_open(String p_uri);
+ void set_unique_id(String p_id);
+ virtual String get_unique_id() const override;
- String get_user_data_dir() const;
+ virtual void vibrate_handheld(int p_duration_ms = 500) override;
- void set_locale(String p_locale);
- String get_locale() const;
+ virtual bool _check_internal_feature_support(const String &p_feature) override;
- void set_unique_id(String p_id);
- String get_unique_id() const;
-
- virtual Error native_video_play(String p_path, float p_volume, String p_audio_track, String p_subtitle_track);
- virtual bool native_video_is_playing() const;
- virtual void native_video_pause();
- virtual void native_video_unpause();
- virtual void native_video_focus_out();
- virtual void native_video_stop();
- virtual void vibrate_handheld(int p_duration_ms = 500);
-
- virtual bool _check_internal_feature_support(const String &p_feature);
- OSIPhone(int width, int height, String p_data_dir);
- ~OSIPhone();
+ void on_focus_out();
+ void on_focus_in();
};
#endif // OS_IPHONE_H
-#endif
+#endif // IPHONE_ENABLED
diff --git a/platform/iphone/os_iphone.mm b/platform/iphone/os_iphone.mm
new file mode 100644
index 0000000000..f0bbbd39ca
--- /dev/null
+++ b/platform/iphone/os_iphone.mm
@@ -0,0 +1,369 @@
+/*************************************************************************/
+/* os_iphone.mm */
+/*************************************************************************/
+/* This file is part of: */
+/* GODOT ENGINE */
+/* https://godotengine.org */
+/*************************************************************************/
+/* Copyright (c) 2007-2020 Juan Linietsky, Ariel Manzur. */
+/* Copyright (c) 2014-2020 Godot Engine contributors (cf. AUTHORS.md). */
+/* */
+/* 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. */
+/*************************************************************************/
+
+#ifdef IPHONE_ENABLED
+
+#include "os_iphone.h"
+#import "app_delegate.h"
+#include "core/io/file_access_pack.h"
+#include "core/os/dir_access.h"
+#include "core/os/file_access.h"
+#include "core/project_settings.h"
+#include "display_server_iphone.h"
+#include "drivers/unix/syslog_logger.h"
+#import "godot_view.h"
+#include "main/main.h"
+#import "view_controller.h"
+
+#import <AudioToolbox/AudioServices.h>
+#import <UIKit/UIKit.h>
+#import <dlfcn.h>
+
+#if defined(OPENGL_ENABLED)
+#include "drivers/gles2/rasterizer_gles2.h"
+#endif
+
+#if defined(VULKAN_ENABLED)
+#include "servers/rendering/rasterizer_rd/rasterizer_rd.h"
+#import <QuartzCore/CAMetalLayer.h>
+#include <vulkan/vulkan_metal.h>
+#endif
+
+// Initialization order between compilation units is not guaranteed,
+// so we use this as a hack to ensure certain code is called before
+// everything else, but after all units are initialized.
+typedef void (*init_callback)();
+static init_callback *ios_init_callbacks = nullptr;
+static int ios_init_callbacks_count = 0;
+static int ios_init_callbacks_capacity = 0;
+HashMap<String, void *> OSIPhone::dynamic_symbol_lookup_table;
+
+void add_ios_init_callback(init_callback cb) {
+ if (ios_init_callbacks_count == ios_init_callbacks_capacity) {
+ void *new_ptr = realloc(ios_init_callbacks, sizeof(cb) * 32);
+ if (new_ptr) {
+ ios_init_callbacks = (init_callback *)(new_ptr);
+ ios_init_callbacks_capacity += 32;
+ }
+ }
+ if (ios_init_callbacks_capacity > ios_init_callbacks_count) {
+ ios_init_callbacks[ios_init_callbacks_count] = cb;
+ ++ios_init_callbacks_count;
+ }
+}
+
+void register_dynamic_symbol(char *name, void *address) {
+ OSIPhone::dynamic_symbol_lookup_table[String(name)] = address;
+}
+
+OSIPhone *OSIPhone::get_singleton() {
+ return (OSIPhone *)OS::get_singleton();
+}
+
+OSIPhone::OSIPhone(String p_data_dir) {
+ for (int i = 0; i < ios_init_callbacks_count; ++i) {
+ ios_init_callbacks[i]();
+ }
+ free(ios_init_callbacks);
+ ios_init_callbacks = nullptr;
+ ios_init_callbacks_count = 0;
+ ios_init_callbacks_capacity = 0;
+
+ main_loop = nullptr;
+
+ // can't call set_data_dir from here, since it requires DirAccess
+ // which is initialized in initialize_core
+ user_data_dir = p_data_dir;
+
+ Vector<Logger *> loggers;
+ loggers.push_back(memnew(SyslogLogger));
+#ifdef DEBUG_ENABLED
+ // it seems iOS app's stdout/stderr is only obtainable if you launch it from
+ // Xcode
+ loggers.push_back(memnew(StdLogger));
+#endif
+ _set_logger(memnew(CompositeLogger(loggers)));
+
+ AudioDriverManager::add_driver(&audio_driver);
+
+ DisplayServerIPhone::register_iphone_driver();
+}
+
+OSIPhone::~OSIPhone() {}
+
+void OSIPhone::initialize_core() {
+ OS_Unix::initialize_core();
+
+ set_user_data_dir(user_data_dir);
+}
+
+void OSIPhone::initialize() {
+ initialize_core();
+}
+
+void OSIPhone::initialize_modules() {
+#ifdef GAME_CENTER_ENABLED
+ game_center = memnew(GameCenter);
+ Engine::get_singleton()->add_singleton(Engine::Singleton("GameCenter", game_center));
+ game_center->connect();
+#endif
+
+#ifdef STOREKIT_ENABLED
+ store_kit = memnew(InAppStore);
+ Engine::get_singleton()->add_singleton(Engine::Singleton("InAppStore", store_kit));
+#endif
+
+#ifdef ICLOUD_ENABLED
+ icloud = memnew(ICloud);
+ Engine::get_singleton()->add_singleton(Engine::Singleton("ICloud", icloud));
+#endif
+
+ ios = memnew(iOS);
+ Engine::get_singleton()->add_singleton(Engine::Singleton("iOS", ios));
+
+ joypad_iphone = memnew(JoypadIPhone);
+}
+
+void OSIPhone::deinitialize_modules() {
+ if (joypad_iphone) {
+ memdelete(joypad_iphone);
+ }
+
+ if (ios) {
+ memdelete(ios);
+ }
+
+#ifdef GAME_CENTER_ENABLED
+ if (game_center) {
+ memdelete(game_center);
+ }
+#endif
+
+#ifdef STOREKIT_ENABLED
+ if (store_kit) {
+ memdelete(store_kit);
+ }
+#endif
+
+#ifdef ICLOUD_ENABLED
+ if (icloud) {
+ memdelete(icloud);
+ }
+#endif
+}
+
+void OSIPhone::set_main_loop(MainLoop *p_main_loop) {
+ main_loop = p_main_loop;
+
+ if (main_loop) {
+ main_loop->init();
+ }
+}
+
+MainLoop *OSIPhone::get_main_loop() const {
+ return main_loop;
+}
+
+void OSIPhone::delete_main_loop() {
+ if (main_loop) {
+ main_loop->finish();
+ memdelete(main_loop);
+ };
+
+ main_loop = nullptr;
+}
+
+bool OSIPhone::iterate() {
+ if (!main_loop) {
+ return true;
+ }
+
+ if (DisplayServer::get_singleton()) {
+ DisplayServer::get_singleton()->process_events();
+ }
+
+ return Main::iteration();
+}
+
+void OSIPhone::start() {
+ Main::start();
+
+ if (joypad_iphone) {
+ joypad_iphone->start_processing();
+ }
+}
+
+void OSIPhone::finalize() {
+ deinitialize_modules();
+
+ // Already gets called
+ // delete_main_loop();
+}
+
+// MARK: Dynamic Libraries
+
+Error OSIPhone::open_dynamic_library(const String p_path, void *&p_library_handle, bool p_also_set_library_path) {
+ if (p_path.length() == 0) {
+ p_library_handle = RTLD_SELF;
+ return OK;
+ }
+ return OS_Unix::open_dynamic_library(p_path, p_library_handle, p_also_set_library_path);
+}
+
+Error OSIPhone::close_dynamic_library(void *p_library_handle) {
+ if (p_library_handle == RTLD_SELF) {
+ return OK;
+ }
+ return OS_Unix::close_dynamic_library(p_library_handle);
+}
+
+Error OSIPhone::get_dynamic_library_symbol_handle(void *p_library_handle, const String p_name, void *&p_symbol_handle, bool p_optional) {
+ if (p_library_handle == RTLD_SELF) {
+ void **ptr = OSIPhone::dynamic_symbol_lookup_table.getptr(p_name);
+ if (ptr) {
+ p_symbol_handle = *ptr;
+ return OK;
+ }
+ }
+ return OS_Unix::get_dynamic_library_symbol_handle(p_library_handle, p_name, p_symbol_handle, p_optional);
+}
+
+void OSIPhone::alert(const String &p_alert, const String &p_title) {
+ const CharString utf8_alert = p_alert.utf8();
+ const CharString utf8_title = p_title.utf8();
+ iOS::alert(utf8_alert.get_data(), utf8_title.get_data());
+}
+
+String OSIPhone::get_name() const {
+ return "iOS";
+};
+
+String OSIPhone::get_model_name() const {
+ String model = ios->get_model();
+ if (model != "")
+ return model;
+
+ return OS_Unix::get_model_name();
+}
+
+Error OSIPhone::shell_open(String p_uri) {
+ NSString *urlPath = [[NSString alloc] initWithUTF8String:p_uri.utf8().get_data()];
+ NSURL *url = [NSURL URLWithString:urlPath];
+ [urlPath release];
+
+ if (![[UIApplication sharedApplication] canOpenURL:url]) {
+ return ERR_CANT_OPEN;
+ }
+
+ printf("opening url %ls\n", p_uri.c_str());
+
+ // if (@available(iOS 10, *)) {
+ [[UIApplication sharedApplication] openURL:url options:@{} completionHandler:nil];
+ // } else {
+ // [[UIApplication sharedApplication] openURL:url];
+ // }
+
+ return OK;
+};
+
+void OSIPhone::set_user_data_dir(String p_dir) {
+ DirAccess *da = DirAccess::open(p_dir);
+
+ user_data_dir = da->get_current_dir();
+ printf("setting data dir to %ls from %ls\n", user_data_dir.c_str(), p_dir.c_str());
+ memdelete(da);
+}
+
+String OSIPhone::get_user_data_dir() const {
+ return user_data_dir;
+}
+
+void OSIPhone::set_locale(String p_locale) {
+ locale_code = p_locale;
+}
+
+String OSIPhone::get_locale() const {
+ return locale_code;
+}
+
+void OSIPhone::set_unique_id(String p_id) {
+ unique_id = p_id;
+}
+
+String OSIPhone::get_unique_id() const {
+ return unique_id;
+}
+
+void OSIPhone::vibrate_handheld(int p_duration_ms) {
+ // iOS does not support duration for vibration
+ AudioServicesPlaySystemSound(kSystemSoundID_Vibrate);
+}
+
+bool OSIPhone::_check_internal_feature_support(const String &p_feature) {
+ return p_feature == "mobile";
+}
+
+void OSIPhone::on_focus_out() {
+ if (is_focused) {
+ is_focused = false;
+
+ if (DisplayServerIPhone::get_singleton()) {
+ DisplayServerIPhone::get_singleton()->send_window_event(DisplayServer::WINDOW_EVENT_FOCUS_OUT);
+ }
+
+ [AppDelegate.viewController.godotView stopRendering];
+
+ if (DisplayServerIPhone::get_singleton() && DisplayServerIPhone::get_singleton()->native_video_is_playing()) {
+ DisplayServerIPhone::get_singleton()->native_video_pause();
+ }
+
+ audio_driver.stop();
+ }
+}
+
+void OSIPhone::on_focus_in() {
+ if (!is_focused) {
+ is_focused = true;
+
+ if (DisplayServerIPhone::get_singleton()) {
+ DisplayServerIPhone::get_singleton()->send_window_event(DisplayServer::WINDOW_EVENT_FOCUS_IN);
+ }
+
+ [AppDelegate.viewController.godotView startRendering];
+
+ if (DisplayServerIPhone::get_singleton() && DisplayServerIPhone::get_singleton()->native_video_is_playing()) {
+ DisplayServerIPhone::get_singleton()->native_video_unpause();
+ }
+
+ audio_driver.start();
+ }
+}
+
+#endif // IPHONE_ENABLED
diff --git a/platform/iphone/view_controller.h b/platform/iphone/view_controller.h
index f6bbe11d97..dffdc01d4a 100644
--- a/platform/iphone/view_controller.h
+++ b/platform/iphone/view_controller.h
@@ -31,20 +31,18 @@
#import <GameKit/GameKit.h>
#import <UIKit/UIKit.h>
-@interface ViewController : UIViewController <GKGameCenterControllerDelegate> {
-};
+@class GodotView;
-- (BOOL)shouldAutorotateToInterfaceOrientation:
- (UIInterfaceOrientation)p_orientation;
+@interface ViewController : UIViewController <GKGameCenterControllerDelegate>
-- (void)didReceiveMemoryWarning;
+- (GodotView *)godotView;
-- (void)viewDidLoad;
+// MARK: Native Video Player
-- (UIRectEdge)preferredScreenEdgesDeferringSystemGestures;
-
-- (BOOL)prefersStatusBarHidden;
-
-- (BOOL)prefersHomeIndicatorAutoHidden;
+- (BOOL)playVideoAtPath:(NSString *)filePath volume:(float)videoVolume audio:(NSString *)audioTrack subtitle:(NSString *)subtitleTrack;
+- (BOOL)isVideoPlaying;
+- (void)pauseVideo;
+- (void)unpauseVideo;
+- (void)stopVideo;
@end
diff --git a/platform/iphone/view_controller.mm b/platform/iphone/view_controller.mm
index 279bcc1226..31597f7856 100644
--- a/platform/iphone/view_controller.mm
+++ b/platform/iphone/view_controller.mm
@@ -29,96 +29,174 @@
/*************************************************************************/
#import "view_controller.h"
-
+#include "core/project_settings.h"
+#include "display_server_iphone.h"
+#import "godot_view.h"
+#import "godot_view_renderer.h"
#include "os_iphone.h"
-#include "core/project_settings.h"
+#import <GameController/GameController.h>
-extern "C" {
+@interface ViewController ()
-int add_path(int, char **);
-int add_cmdline(int, char **);
+@property(strong, nonatomic) GodotViewRenderer *renderer;
-int add_path(int p_argc, char **p_args) {
- NSString *str = [[[NSBundle mainBundle] infoDictionary] objectForKey:@"godot_path"];
- if (!str)
- return p_argc;
+// TODO: separate view to handle video
+// AVPlayer-related properties
+@property(strong, nonatomic) AVAsset *avAsset;
+@property(strong, nonatomic) AVPlayerItem *avPlayerItem;
+@property(strong, nonatomic) AVPlayer *avPlayer;
+@property(strong, nonatomic) AVPlayerLayer *avPlayerLayer;
+@property(assign, nonatomic) CMTime videoCurrentTime;
+@property(assign, nonatomic) BOOL isVideoCurrentlyPlaying;
+@property(assign, nonatomic) BOOL videoHasFoundError;
- p_args[p_argc++] = "--path";
- [str retain]; // memory leak lol (maybe make it static here and delete it in ViewController destructor? @todo
- p_args[p_argc++] = (char *)[str cString];
- p_args[p_argc] = NULL;
+@end
- return p_argc;
-};
+@implementation ViewController
-int add_cmdline(int p_argc, char **p_args) {
- NSArray *arr = [[[NSBundle mainBundle] infoDictionary] objectForKey:@"godot_cmdline"];
- if (!arr)
- return p_argc;
+- (GodotView *)godotView {
+ return (GodotView *)self.view;
+}
- for (int i = 0; i < [arr count]; i++) {
- NSString *str = [arr objectAtIndex:i];
- if (!str)
- continue;
- [str retain]; // @todo delete these at some point
- p_args[p_argc++] = (char *)[str cString];
- };
+- (void)loadView {
+ GodotView *view = [[GodotView alloc] init];
+ GodotViewRenderer *renderer = [[GodotViewRenderer alloc] init];
- p_args[p_argc] = NULL;
+ self.renderer = renderer;
+ self.view = view;
- return p_argc;
-};
-}; // extern "C"
+ view.renderer = self.renderer;
-@interface ViewController ()
+ [renderer release];
+ [view release];
+}
-@end
+- (instancetype)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil {
+ self = [super initWithNibName:nibNameOrNil bundle:nibBundleOrNil];
-@implementation ViewController
+ if (self) {
+ [self godot_commonInit];
+ }
+
+ return self;
+}
+
+- (instancetype)initWithCoder:(NSCoder *)coder {
+ self = [super initWithCoder:coder];
+
+ if (self) {
+ [self godot_commonInit];
+ }
+
+ return self;
+}
+
+- (void)godot_commonInit {
+ self.isVideoCurrentlyPlaying = NO;
+ self.videoCurrentTime = kCMTimeZero;
+ self.videoHasFoundError = false;
+}
- (void)didReceiveMemoryWarning {
+ [super didReceiveMemoryWarning];
printf("*********** did receive memory warning!\n");
-};
+}
- (void)viewDidLoad {
[super viewDidLoad];
+ [self observeKeyboard];
+ [self observeAudio];
+
if (@available(iOS 11.0, *)) {
[self setNeedsUpdateOfScreenEdgesDeferringSystemGestures];
}
}
+- (void)observeKeyboard {
+ printf("******** adding observer for keyboard show/hide\n");
+ [[NSNotificationCenter defaultCenter]
+ addObserver:self
+ selector:@selector(keyboardOnScreen:)
+ name:UIKeyboardDidShowNotification
+ object:nil];
+ [[NSNotificationCenter defaultCenter]
+ addObserver:self
+ selector:@selector(keyboardHidden:)
+ name:UIKeyboardDidHideNotification
+ object:nil];
+}
+
+- (void)observeAudio {
+ printf("******** adding observer for sound routing changes\n");
+ [[NSNotificationCenter defaultCenter]
+ addObserver:self
+ selector:@selector(audioRouteChangeListenerCallback:)
+ name:AVAudioSessionRouteChangeNotification
+ object:nil];
+}
+
+- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context {
+ if (object == self.avPlayerItem && [keyPath isEqualToString:@"status"]) {
+ [self handleVideoOrPlayerStatus];
+ }
+
+ if (object == self.avPlayer && [keyPath isEqualToString:@"rate"]) {
+ [self handleVideoPlayRate];
+ }
+}
+
+- (void)dealloc {
+ [self stopVideo];
+
+ self.renderer = nil;
+
+ [[NSNotificationCenter defaultCenter] removeObserver:self];
+
+ [super dealloc];
+}
+
+// MARK: Orientation
+
- (UIRectEdge)preferredScreenEdgesDeferringSystemGestures {
return UIRectEdgeAll;
}
- (BOOL)shouldAutorotate {
- switch (OS::get_singleton()->get_screen_orientation()) {
- case OS::SCREEN_SENSOR:
- case OS::SCREEN_SENSOR_LANDSCAPE:
- case OS::SCREEN_SENSOR_PORTRAIT:
+ if (!DisplayServerIPhone::get_singleton()) {
+ return NO;
+ }
+
+ switch (DisplayServerIPhone::get_singleton()->screen_get_orientation(DisplayServer::SCREEN_OF_MAIN_WINDOW)) {
+ case DisplayServer::SCREEN_SENSOR:
+ case DisplayServer::SCREEN_SENSOR_LANDSCAPE:
+ case DisplayServer::SCREEN_SENSOR_PORTRAIT:
return YES;
default:
return NO;
}
-};
+}
- (UIInterfaceOrientationMask)supportedInterfaceOrientations {
- switch (OS::get_singleton()->get_screen_orientation()) {
- case OS::SCREEN_PORTRAIT:
+ if (!DisplayServerIPhone::get_singleton()) {
+ return UIInterfaceOrientationMaskAll;
+ }
+
+ switch (DisplayServerIPhone::get_singleton()->screen_get_orientation(DisplayServer::SCREEN_OF_MAIN_WINDOW)) {
+ case DisplayServer::SCREEN_PORTRAIT:
return UIInterfaceOrientationMaskPortrait;
- case OS::SCREEN_REVERSE_LANDSCAPE:
+ case DisplayServer::SCREEN_REVERSE_LANDSCAPE:
return UIInterfaceOrientationMaskLandscapeRight;
- case OS::SCREEN_REVERSE_PORTRAIT:
+ case DisplayServer::SCREEN_REVERSE_PORTRAIT:
return UIInterfaceOrientationMaskPortraitUpsideDown;
- case OS::SCREEN_SENSOR_LANDSCAPE:
+ case DisplayServer::SCREEN_SENSOR_LANDSCAPE:
return UIInterfaceOrientationMaskLandscape;
- case OS::SCREEN_SENSOR_PORTRAIT:
+ case DisplayServer::SCREEN_SENSOR_PORTRAIT:
return UIInterfaceOrientationMaskPortrait | UIInterfaceOrientationMaskPortraitUpsideDown;
- case OS::SCREEN_SENSOR:
+ case DisplayServer::SCREEN_SENSOR:
return UIInterfaceOrientationMaskAll;
- case OS::SCREEN_LANDSCAPE:
+ case DisplayServer::SCREEN_LANDSCAPE:
return UIInterfaceOrientationMaskLandscapeLeft;
}
};
@@ -135,6 +213,190 @@ int add_cmdline(int p_argc, char **p_args) {
}
}
+// MARK: Keyboard
+
+- (void)keyboardOnScreen:(NSNotification *)notification {
+ NSDictionary *info = notification.userInfo;
+ NSValue *value = info[UIKeyboardFrameEndUserInfoKey];
+
+ CGRect rawFrame = [value CGRectValue];
+ CGRect keyboardFrame = [self.view convertRect:rawFrame fromView:nil];
+
+ if (DisplayServerIPhone::get_singleton()) {
+ DisplayServerIPhone::get_singleton()->virtual_keyboard_set_height(keyboardFrame.size.height);
+ }
+}
+
+- (void)keyboardHidden:(NSNotification *)notification {
+ if (DisplayServerIPhone::get_singleton()) {
+ DisplayServerIPhone::get_singleton()->virtual_keyboard_set_height(0);
+ }
+}
+
+// MARK: Audio
+
+- (void)audioRouteChangeListenerCallback:(NSNotification *)notification {
+ printf("*********** route changed!\n");
+ NSDictionary *interuptionDict = notification.userInfo;
+
+ NSInteger routeChangeReason = [[interuptionDict valueForKey:AVAudioSessionRouteChangeReasonKey] integerValue];
+
+ switch (routeChangeReason) {
+ case AVAudioSessionRouteChangeReasonNewDeviceAvailable: {
+ NSLog(@"AVAudioSessionRouteChangeReasonNewDeviceAvailable");
+ NSLog(@"Headphone/Line plugged in");
+ } break;
+ case AVAudioSessionRouteChangeReasonOldDeviceUnavailable: {
+ NSLog(@"AVAudioSessionRouteChangeReasonOldDeviceUnavailable");
+ NSLog(@"Headphone/Line was pulled. Resuming video play....");
+ if ([self isVideoPlaying]) {
+ dispatch_after(dispatch_time(DISPATCH_TIME_NOW, 0.5f * NSEC_PER_SEC), dispatch_get_main_queue(), ^{
+ [self.avPlayer play]; // NOTE: change this line according your current player implementation
+ NSLog(@"resumed play");
+ });
+ }
+ } break;
+ case AVAudioSessionRouteChangeReasonCategoryChange: {
+ // called at start - also when other audio wants to play
+ NSLog(@"AVAudioSessionRouteChangeReasonCategoryChange");
+ } break;
+ }
+}
+
+// MARK: Native Video Player
+
+- (void)handleVideoOrPlayerStatus {
+ if (self.avPlayerItem.status == AVPlayerItemStatusFailed || self.avPlayer.status == AVPlayerStatusFailed) {
+ [self stopVideo];
+ self.videoHasFoundError = true;
+ }
+
+ if (self.avPlayer.status == AVPlayerStatusReadyToPlay && self.avPlayerItem.status == AVPlayerItemStatusReadyToPlay && CMTimeCompare(self.videoCurrentTime, kCMTimeZero) == 0) {
+ // NSLog(@"time: %@", self.video_current_time);
+ [self.avPlayer seekToTime:self.videoCurrentTime];
+ self.videoCurrentTime = kCMTimeZero;
+ }
+}
+
+- (void)handleVideoPlayRate {
+ NSLog(@"Player playback rate changed: %.5f", self.avPlayer.rate);
+ if ([self isVideoPlaying] && self.avPlayer.rate == 0.0 && !self.avPlayer.error) {
+ dispatch_after(dispatch_time(DISPATCH_TIME_NOW, 0.5f * NSEC_PER_SEC), dispatch_get_main_queue(), ^{
+ [self.avPlayer play]; // NOTE: change this line according your current player implementation
+ NSLog(@"resumed play");
+ });
+
+ NSLog(@" . . . PAUSED (or just started)");
+ }
+}
+
+- (BOOL)playVideoAtPath:(NSString *)filePath volume:(float)videoVolume audio:(NSString *)audioTrack subtitle:(NSString *)subtitleTrack {
+ self.avAsset = [AVAsset assetWithURL:[NSURL fileURLWithPath:filePath]];
+
+ self.avPlayerItem = [AVPlayerItem playerItemWithAsset:self.avAsset];
+ [self.avPlayerItem addObserver:self forKeyPath:@"status" options:0 context:nil];
+
+ self.avPlayer = [AVPlayer playerWithPlayerItem:self.avPlayerItem];
+ self.avPlayerLayer = [AVPlayerLayer playerLayerWithPlayer:self.avPlayer];
+
+ [self.avPlayer addObserver:self forKeyPath:@"status" options:0 context:nil];
+ [[NSNotificationCenter defaultCenter]
+ addObserver:self
+ selector:@selector(playerItemDidReachEnd:)
+ name:AVPlayerItemDidPlayToEndTimeNotification
+ object:[self.avPlayer currentItem]];
+
+ [self.avPlayer addObserver:self forKeyPath:@"rate" options:NSKeyValueObservingOptionNew context:0];
+
+ [self.avPlayerLayer setFrame:self.view.bounds];
+ [self.view.layer addSublayer:self.avPlayerLayer];
+ [self.avPlayer play];
+
+ AVMediaSelectionGroup *audioGroup = [self.avAsset mediaSelectionGroupForMediaCharacteristic:AVMediaCharacteristicAudible];
+
+ NSMutableArray *allAudioParams = [NSMutableArray array];
+ for (id track in audioGroup.options) {
+ NSString *language = [[track locale] localeIdentifier];
+ NSLog(@"subtitle lang: %@", language);
+
+ if ([language isEqualToString:audioTrack]) {
+ AVMutableAudioMixInputParameters *audioInputParams = [AVMutableAudioMixInputParameters audioMixInputParameters];
+ [audioInputParams setVolume:videoVolume atTime:kCMTimeZero];
+ [audioInputParams setTrackID:[track trackID]];
+ [allAudioParams addObject:audioInputParams];
+
+ AVMutableAudioMix *audioMix = [AVMutableAudioMix audioMix];
+ [audioMix setInputParameters:allAudioParams];
+
+ [self.avPlayer.currentItem selectMediaOption:track inMediaSelectionGroup:audioGroup];
+ [self.avPlayer.currentItem setAudioMix:audioMix];
+
+ break;
+ }
+ }
+
+ AVMediaSelectionGroup *subtitlesGroup = [self.avAsset mediaSelectionGroupForMediaCharacteristic:AVMediaCharacteristicLegible];
+ NSArray *useableTracks = [AVMediaSelectionGroup mediaSelectionOptionsFromArray:subtitlesGroup.options withoutMediaCharacteristics:[NSArray arrayWithObject:AVMediaCharacteristicContainsOnlyForcedSubtitles]];
+
+ for (id track in useableTracks) {
+ NSString *language = [[track locale] localeIdentifier];
+ NSLog(@"subtitle lang: %@", language);
+
+ if ([language isEqualToString:subtitleTrack]) {
+ [self.avPlayer.currentItem selectMediaOption:track inMediaSelectionGroup:subtitlesGroup];
+ break;
+ }
+ }
+
+ self.isVideoCurrentlyPlaying = YES;
+
+ return true;
+}
+
+- (BOOL)isVideoPlaying {
+ if (self.avPlayer.error) {
+ printf("Error during playback\n");
+ }
+ return (self.avPlayer.rate > 0 && !self.avPlayer.error);
+}
+
+- (void)pauseVideo {
+ self.videoCurrentTime = self.avPlayer.currentTime;
+ [self.avPlayer pause];
+ self.isVideoCurrentlyPlaying = NO;
+}
+
+- (void)unpauseVideo {
+ [self.avPlayer play];
+ self.isVideoCurrentlyPlaying = YES;
+}
+
+- (void)playerItemDidReachEnd:(NSNotification *)notification {
+ [self stopVideo];
+}
+
+- (void)stopVideo {
+ [self.avPlayer pause];
+ [self.avPlayerLayer removeFromSuperlayer];
+ self.avPlayerLayer = nil;
+
+ if (self.avPlayerItem) {
+ [self.avPlayerItem removeObserver:self forKeyPath:@"status"];
+ self.avPlayerItem = nil;
+ }
+
+ if (self.avPlayer) {
+ [self.avPlayer removeObserver:self forKeyPath:@"status"];
+ self.avPlayer = nil;
+ }
+
+ self.avAsset = nil;
+
+ self.isVideoCurrentlyPlaying = NO;
+}
+
+// MARK: Delegates
+
#ifdef GAME_CENTER_ENABLED
- (void)gameCenterViewControllerDidFinish:(GKGameCenterViewController *)gameCenterViewController {
//[gameCenterViewController dismissViewControllerAnimated:YES completion:^{GameCenter::get_singleton()->game_center_closed();}];//version for signaling when overlay is completely gone
diff --git a/platform/iphone/vulkan_context_iphone.h b/platform/iphone/vulkan_context_iphone.h
index cadd701636..5c3d5fe33e 100644
--- a/platform/iphone/vulkan_context_iphone.h
+++ b/platform/iphone/vulkan_context_iphone.h
@@ -32,13 +32,14 @@
#define VULKAN_CONTEXT_IPHONE_H
#include "drivers/vulkan/vulkan_context.h"
-// #import <UIKit/UIKit.h>
+
+#import <UIKit/UIKit.h>
class VulkanContextIPhone : public VulkanContext {
virtual const char *_get_platform_surface_extension() const;
public:
- int window_create(void *p_window, int p_width, int p_height);
+ Error window_create(DisplayServer::WindowID p_window_id, CALayer *p_metal_layer, int p_width, int p_height);
VulkanContextIPhone();
~VulkanContextIPhone();
diff --git a/platform/iphone/vulkan_context_iphone.mm b/platform/iphone/vulkan_context_iphone.mm
index 44c940dc3a..cb4dbe7f85 100644
--- a/platform/iphone/vulkan_context_iphone.mm
+++ b/platform/iphone/vulkan_context_iphone.mm
@@ -35,21 +35,23 @@ const char *VulkanContextIPhone::_get_platform_surface_extension() const {
return VK_MVK_IOS_SURFACE_EXTENSION_NAME;
}
-int VulkanContextIPhone::window_create(void *p_window, int p_width, int p_height) {
+Error VulkanContextIPhone::window_create(DisplayServer::WindowID p_window_id,
+ CALayer *p_metal_layer, int p_width,
+ int p_height) {
VkIOSSurfaceCreateInfoMVK createInfo;
- createInfo.sType = VK_STRUCTURE_TYPE_MACOS_SURFACE_CREATE_INFO_MVK;
+ createInfo.sType = VK_STRUCTURE_TYPE_IOS_SURFACE_CREATE_INFO_MVK;
createInfo.pNext = NULL;
createInfo.flags = 0;
- createInfo.pView = p_window;
+ createInfo.pView = p_metal_layer;
VkSurfaceKHR surface;
- VkResult err = vkCreateIOSSurfaceMVK(_get_instance(), &createInfo, NULL, &surface);
- ERR_FAIL_COND_V(err, -1);
- return _window_create(surface, p_width, p_height);
-}
+ VkResult err =
+ vkCreateIOSSurfaceMVK(_get_instance(), &createInfo, NULL, &surface);
+ ERR_FAIL_COND_V(err, ERR_CANT_CREATE);
-VulkanContextIPhone::VulkanContextIPhone() {
+ return _window_create(p_window_id, surface, p_width, p_height);
}
-VulkanContextIPhone::~VulkanContextIPhone() {
-}
+VulkanContextIPhone::VulkanContextIPhone() {}
+
+VulkanContextIPhone::~VulkanContextIPhone() {}
diff --git a/platform/linuxbsd/display_server_x11.cpp b/platform/linuxbsd/display_server_x11.cpp
index 874a3a6392..4aec6d256c 100644
--- a/platform/linuxbsd/display_server_x11.cpp
+++ b/platform/linuxbsd/display_server_x11.cpp
@@ -685,6 +685,14 @@ DisplayServer::WindowID DisplayServerX11::create_sub_window(WindowMode p_mode, u
return id;
}
+void DisplayServerX11::show_window(WindowID p_id) {
+ _THREAD_SAFE_METHOD_
+
+ WindowData &wd = windows[p_id];
+
+ XMapWindow(x11_display, wd.x11_window);
+}
+
void DisplayServerX11::delete_sub_window(WindowID p_id) {
_THREAD_SAFE_METHOD_
@@ -3218,8 +3226,6 @@ DisplayServerX11::WindowID DisplayServerX11::_create_window(WindowMode p_mode, u
WindowData wd;
wd.x11_window = XCreateWindow(x11_display, RootWindow(x11_display, visualInfo->screen), p_rect.position.x, p_rect.position.y, p_rect.size.width > 0 ? p_rect.size.width : 1, p_rect.size.height > 0 ? p_rect.size.height : 1, 0, visualInfo->depth, InputOutput, visualInfo->visual, valuemask, &windowAttributes);
- XMapWindow(x11_display, wd.x11_window);
-
//associate PID
// make PID known to X11
{
@@ -3414,6 +3420,7 @@ DisplayServerX11::WindowID DisplayServerX11::_create_window(WindowMode p_mode, u
if (cursors[current_cursor] != None) {
XDefineCursor(x11_display, wd.x11_window, cursors[current_cursor]);
}
+
return id;
}
@@ -3653,6 +3660,7 @@ DisplayServerX11::DisplayServerX11(const String &p_rendering_driver, WindowMode
window_set_flag(WindowFlags(i), true, main_window);
}
}
+ show_window(main_window);
//create RenderingDevice if used
#if defined(VULKAN_ENABLED)
diff --git a/platform/linuxbsd/display_server_x11.h b/platform/linuxbsd/display_server_x11.h
index b5d2ea1c63..0ba1359145 100644
--- a/platform/linuxbsd/display_server_x11.h
+++ b/platform/linuxbsd/display_server_x11.h
@@ -276,6 +276,7 @@ public:
virtual Vector<DisplayServer::WindowID> get_window_list() const;
virtual WindowID create_sub_window(WindowMode p_mode, uint32_t p_flags, const Rect2i &p_rect = Rect2i());
+ virtual void show_window(WindowID p_id);
virtual void delete_sub_window(WindowID p_id);
virtual WindowID get_window_at_screen_position(const Point2i &p_position) const;
diff --git a/platform/osx/detect.py b/platform/osx/detect.py
index 25d230fc89..272ae1b620 100644
--- a/platform/osx/detect.py
+++ b/platform/osx/detect.py
@@ -28,7 +28,8 @@ def get_opts():
("MACOS_SDK_PATH", "Path to the macOS SDK", ""),
BoolVariable(
"use_static_mvk",
- "Link MoltenVK statically as Level-0 driver (better portability) or use Vulkan ICD loader (enables validation layers)",
+ "Link MoltenVK statically as Level-0 driver (better portability) or use Vulkan ICD loader (enables"
+ " validation layers)",
False,
),
EnumVariable("debug_symbols", "Add debugging symbols to release builds", "yes", ("yes", "no", "full")),
@@ -50,9 +51,11 @@ def configure(env):
if env["target"] == "release":
if env["optimize"] == "speed": # optimize for speed (default)
- env.Prepend(CCFLAGS=["-O3", "-fomit-frame-pointer", "-ftree-vectorize", "-msse2"])
+ env.Prepend(CCFLAGS=["-O3", "-fomit-frame-pointer", "-ftree-vectorize"])
else: # optimize for size
- env.Prepend(CCFLAGS=["-Os", "-ftree-vectorize", "-msse2"])
+ env.Prepend(CCFLAGS=["-Os", "-ftree-vectorize"])
+ if env["arch"] != "arm64":
+ env.Prepend(CCFLAGS=["-msse2"])
if env["debug_symbols"] == "yes":
env.Prepend(CCFLAGS=["-g1"])
diff --git a/platform/osx/display_server_osx.h b/platform/osx/display_server_osx.h
index 68e8454fd0..d8f3f81ff6 100644
--- a/platform/osx/display_server_osx.h
+++ b/platform/osx/display_server_osx.h
@@ -230,6 +230,7 @@ public:
virtual Vector<int> get_window_list() const override;
virtual WindowID create_sub_window(WindowMode p_mode, uint32_t p_flags, const Rect2i &p_rect = Rect2i()) override;
+ virtual void show_window(WindowID p_id) override;
virtual void delete_sub_window(WindowID p_id) override;
virtual void window_set_rect_changed_callback(const Callable &p_callable, WindowID p_window = MAIN_WINDOW_ID) override;
diff --git a/platform/osx/display_server_osx.mm b/platform/osx/display_server_osx.mm
index 5e209a6e6b..4c04151791 100644
--- a/platform/osx/display_server_osx.mm
+++ b/platform/osx/display_server_osx.mm
@@ -1944,8 +1944,12 @@ void DisplayServerOSX::alert(const String &p_alert, const String &p_title) {
[window setInformativeText:ns_alert];
[window setAlertStyle:NSAlertStyleWarning];
+ id key_window = [[NSApplication sharedApplication] keyWindow];
[window runModal];
[window release];
+ if (key_window) {
+ [key_window makeKeyAndOrderFront:nil];
+ }
}
Error DisplayServerOSX::dialog_show(String p_title, String p_description, Vector<String> p_buttons, const Callable &p_callback) {
@@ -2311,18 +2315,23 @@ DisplayServer::WindowID DisplayServerOSX::create_sub_window(WindowMode p_mode, u
_THREAD_SAFE_METHOD_
WindowID id = _create_window(p_mode, p_rect);
- WindowData &wd = windows[id];
for (int i = 0; i < WINDOW_FLAG_MAX; i++) {
if (p_flags & (1 << i)) {
window_set_flag(WindowFlags(i), true, id);
}
}
+
+ return id;
+}
+
+void DisplayServerOSX::show_window(WindowID p_id) {
+ WindowData &wd = windows[p_id];
+
if (wd.no_focus) {
[wd.window_object orderFront:nil];
} else {
[wd.window_object makeKeyAndOrderFront:nil];
}
- return id;
}
void DisplayServerOSX::_send_window_event(const WindowData &wd, WindowEvent p_event) {
@@ -2793,7 +2802,9 @@ void DisplayServerOSX::window_set_flag(WindowFlags p_flag, bool p_enabled, Windo
} break;
case WINDOW_FLAG_BORDERLESS: {
// OrderOut prevents a lose focus bug with the window
- [wd.window_object orderOut:nil];
+ if ([wd.window_object isVisible]) {
+ [wd.window_object orderOut:nil];
+ }
wd.borderless = p_enabled;
if (p_enabled) {
[wd.window_object setStyleMask:NSWindowStyleMaskBorderless];
@@ -2807,7 +2818,13 @@ void DisplayServerOSX::window_set_flag(WindowFlags p_flag, bool p_enabled, Windo
[wd.window_object setFrame:frameRect display:NO];
}
_update_window(wd);
- [wd.window_object makeKeyAndOrderFront:nil];
+ if ([wd.window_object isVisible]) {
+ if (wd.no_focus) {
+ [wd.window_object orderFront:nil];
+ } else {
+ [wd.window_object makeKeyAndOrderFront:nil];
+ }
+ }
} break;
case WINDOW_FLAG_ALWAYS_ON_TOP: {
wd.on_top = p_enabled;
@@ -2875,7 +2892,11 @@ void DisplayServerOSX::window_move_to_foreground(WindowID p_window) {
const WindowData &wd = windows[p_window];
[[NSApplication sharedApplication] activateIgnoringOtherApps:YES];
- [wd.window_object makeKeyAndOrderFront:nil];
+ if (wd.no_focus) {
+ [wd.window_object orderFront:nil];
+ } else {
+ [wd.window_object makeKeyAndOrderFront:nil];
+ }
}
bool DisplayServerOSX::window_can_draw(WindowID p_window) const {
@@ -3755,7 +3776,7 @@ DisplayServerOSX::DisplayServerOSX(const String &p_rendering_driver, WindowMode
window_set_flag(WindowFlags(i), true, main_window);
}
}
- [windows[main_window].window_object makeKeyAndOrderFront:nil];
+ show_window(MAIN_WINDOW_ID);
#if defined(OPENGL_ENABLED)
if (rendering_driver == "opengl_es") {
diff --git a/platform/uwp/detect.py b/platform/uwp/detect.py
index c23a65ef75..04b743f2c8 100644
--- a/platform/uwp/detect.py
+++ b/platform/uwp/detect.py
@@ -120,7 +120,9 @@ def configure(env):
print("Compiled program architecture will be a x86 executable. (forcing bits=32).")
else:
print(
- "Failed to detect MSVC compiler architecture version... Defaulting to 32-bit executable settings (forcing bits=32). Compilation attempt will continue, but SCons can not detect for what architecture this build is compiled for. You should check your settings/compilation setup."
+ "Failed to detect MSVC compiler architecture version... Defaulting to 32-bit executable settings"
+ " (forcing bits=32). Compilation attempt will continue, but SCons can not detect for what architecture"
+ " this build is compiled for. You should check your settings/compilation setup."
)
env["bits"] = "32"
@@ -160,7 +162,10 @@ def configure(env):
env.Append(CPPFLAGS=["/AI", vc_base_path + "lib/x86/store/references"])
env.Append(
- CCFLAGS='/FS /MP /GS /wd"4453" /wd"28204" /wd"4291" /Zc:wchar_t /Gm- /fp:precise /errorReport:prompt /WX- /Zc:forScope /Gd /EHsc /nologo'.split()
+ CCFLAGS=(
+ '/FS /MP /GS /wd"4453" /wd"28204" /wd"4291" /Zc:wchar_t /Gm- /fp:precise /errorReport:prompt /WX-'
+ " /Zc:forScope /Gd /EHsc /nologo".split()
+ )
)
env.Append(CPPDEFINES=["_UNICODE", "UNICODE", ("WINAPI_FAMILY", "WINAPI_FAMILY_APP")])
env.Append(CXXFLAGS=["/ZW"])
diff --git a/platform/uwp/os_uwp.cpp b/platform/uwp/os_uwp.cpp
index ee25754704..1dddb07990 100644
--- a/platform/uwp/os_uwp.cpp
+++ b/platform/uwp/os_uwp.cpp
@@ -715,7 +715,7 @@ bool OS_UWP::has_virtual_keyboard() const {
return UIViewSettings::GetForCurrentView()->UserInteractionMode == UserInteractionMode::Touch;
}
-void OS_UWP::show_virtual_keyboard(const String &p_existing_text, const Rect2 &p_screen_rect, int p_max_input_length, int p_cursor_start, int p_cursor_end) {
+void OS_UWP::show_virtual_keyboard(const String &p_existing_text, const Rect2 &p_screen_rect, bool p_multiline, int p_max_input_length, int p_cursor_start, int p_cursor_end) {
InputPane ^ pane = InputPane::GetForCurrentView();
pane->TryShow();
}
diff --git a/platform/uwp/os_uwp.h b/platform/uwp/os_uwp.h
index c35b634353..892327bac5 100644
--- a/platform/uwp/os_uwp.h
+++ b/platform/uwp/os_uwp.h
@@ -234,7 +234,7 @@ public:
virtual bool has_touchscreen_ui_hint() const;
virtual bool has_virtual_keyboard() const;
- virtual void show_virtual_keyboard(const String &p_existing_text, const Rect2 &p_screen_rect = Rect2(), int p_max_input_length = -1, int p_cursor_start = -1, int p_cursor_end = -1);
+ virtual void show_virtual_keyboard(const String &p_existing_text, const Rect2 &p_screen_rect = Rect2(), bool p_multiline = false, int p_max_input_length = -1, int p_cursor_start = -1, int p_cursor_end = -1);
virtual void hide_virtual_keyboard();
virtual Error open_dynamic_library(const String p_path, void *&p_library_handle, bool p_also_set_library_path = false);
diff --git a/platform/windows/detect.py b/platform/windows/detect.py
index 0ab0e1ed3c..271ffc8871 100644
--- a/platform/windows/detect.py
+++ b/platform/windows/detect.py
@@ -128,7 +128,9 @@ def setup_msvc_manual(env):
print("Compiled program architecture will be a 32 bit executable. (forcing bits=32).")
else:
print(
- "Failed to manually detect MSVC compiler architecture version... Defaulting to 32bit executable settings (forcing bits=32). Compilation attempt will continue, but SCons can not detect for what architecture this build is compiled for. You should check your settings/compilation setup, or avoid setting VCINSTALLDIR."
+ "Failed to manually detect MSVC compiler architecture version... Defaulting to 32bit executable settings"
+ " (forcing bits=32). Compilation attempt will continue, but SCons can not detect for what architecture this"
+ " build is compiled for. You should check your settings/compilation setup, or avoid setting VCINSTALLDIR."
)
diff --git a/platform/windows/display_server_windows.cpp b/platform/windows/display_server_windows.cpp
index 6ee9b6d698..da2fc1c2c1 100644
--- a/platform/windows/display_server_windows.cpp
+++ b/platform/windows/display_server_windows.cpp
@@ -495,13 +495,17 @@ DisplayServer::WindowID DisplayServerWindows::create_sub_window(WindowMode p_mod
_update_window_style(window_id);
- ShowWindow(wd.hWnd, (p_flags & WINDOW_FLAG_NO_FOCUS_BIT) ? SW_SHOWNOACTIVATE : SW_SHOW); // Show The Window
- if (!(p_flags & WINDOW_FLAG_NO_FOCUS_BIT)) {
+ return window_id;
+}
+
+void DisplayServerWindows::show_window(WindowID p_id) {
+ WindowData &wd = windows[p_id];
+
+ ShowWindow(wd.hWnd, wd.no_focus ? SW_SHOWNOACTIVATE : SW_SHOW); // Show The Window
+ if (!wd.no_focus) {
SetForegroundWindow(wd.hWnd); // Slightly Higher Priority
SetFocus(wd.hWnd); // Sets Keyboard Focus To
}
-
- return window_id;
}
void DisplayServerWindows::delete_sub_window(WindowID p_window) {
@@ -3121,9 +3125,7 @@ DisplayServerWindows::DisplayServerWindows(const String &p_rendering_driver, Win
}
}
- ShowWindow(windows[MAIN_WINDOW_ID].hWnd, SW_SHOW); // Show The Window
- SetForegroundWindow(windows[MAIN_WINDOW_ID].hWnd); // Slightly Higher Priority
- SetFocus(windows[MAIN_WINDOW_ID].hWnd); // Sets Keyboard Focus To
+ show_window(MAIN_WINDOW_ID);
#if defined(VULKAN_ENABLED)
diff --git a/platform/windows/display_server_windows.h b/platform/windows/display_server_windows.h
index 725f9697c5..7bd93a7086 100644
--- a/platform/windows/display_server_windows.h
+++ b/platform/windows/display_server_windows.h
@@ -460,6 +460,7 @@ public:
virtual Vector<DisplayServer::WindowID> get_window_list() const;
virtual WindowID create_sub_window(WindowMode p_mode, uint32_t p_flags, const Rect2i &p_rect = Rect2i());
+ virtual void show_window(WindowID p_window);
virtual void delete_sub_window(WindowID p_window);
virtual WindowID get_window_at_screen_position(const Point2i &p_position) const;
diff --git a/scene/3d/node_3d.cpp b/scene/3d/node_3d.cpp
index 73f17060df..bd9e4f5bde 100644
--- a/scene/3d/node_3d.cpp
+++ b/scene/3d/node_3d.cpp
@@ -174,7 +174,7 @@ void Node3D::_notification(int p_what) {
ERR_FAIL_COND(!data.viewport);
if (get_script_instance()) {
- get_script_instance()->call_multilevel(SceneStringNames::get_singleton()->_enter_world, nullptr, 0);
+ get_script_instance()->call(SceneStringNames::get_singleton()->_enter_world);
}
#ifdef TOOLS_ENABLED
if (Engine::get_singleton()->is_editor_hint() && get_tree()->is_node_being_edited(this)) {
@@ -202,7 +202,7 @@ void Node3D::_notification(int p_what) {
#endif
if (get_script_instance()) {
- get_script_instance()->call_multilevel(SceneStringNames::get_singleton()->_exit_world, nullptr, 0);
+ get_script_instance()->call(SceneStringNames::get_singleton()->_exit_world);
}
data.viewport = nullptr;
diff --git a/scene/gui/line_edit.cpp b/scene/gui/line_edit.cpp
index 646f9f6095..5afc1f438e 100644
--- a/scene/gui/line_edit.cpp
+++ b/scene/gui/line_edit.cpp
@@ -120,9 +120,9 @@ void LineEdit::_gui_input(Ref<InputEvent> p_event) {
if (DisplayServer::get_singleton()->has_feature(DisplayServer::FEATURE_VIRTUAL_KEYBOARD) && virtual_keyboard_enabled) {
if (selection.enabled) {
- DisplayServer::get_singleton()->virtual_keyboard_show(text, get_global_rect(), max_length, selection.begin, selection.end);
+ DisplayServer::get_singleton()->virtual_keyboard_show(text, get_global_rect(), false, max_length, selection.begin, selection.end);
} else {
- DisplayServer::get_singleton()->virtual_keyboard_show(text, get_global_rect(), max_length, cursor_pos);
+ DisplayServer::get_singleton()->virtual_keyboard_show(text, get_global_rect(), false, max_length, cursor_pos);
}
}
}
@@ -313,6 +313,7 @@ void LineEdit::_gui_input(Ref<InputEvent> p_event) {
DisplayServer::get_singleton()->virtual_keyboard_hide();
}
+ return;
} break;
case KEY_BACKSPACE: {
@@ -943,9 +944,9 @@ void LineEdit::_notification(int p_what) {
if (DisplayServer::get_singleton()->has_feature(DisplayServer::FEATURE_VIRTUAL_KEYBOARD) && virtual_keyboard_enabled) {
if (selection.enabled) {
- DisplayServer::get_singleton()->virtual_keyboard_show(text, get_global_rect(), max_length, selection.begin, selection.end);
+ DisplayServer::get_singleton()->virtual_keyboard_show(text, get_global_rect(), false, max_length, selection.begin, selection.end);
} else {
- DisplayServer::get_singleton()->virtual_keyboard_show(text, get_global_rect(), max_length, cursor_pos);
+ DisplayServer::get_singleton()->virtual_keyboard_show(text, get_global_rect(), false, max_length, cursor_pos);
}
}
diff --git a/scene/gui/scroll_bar.cpp b/scene/gui/scroll_bar.cpp
index 4db6ca2949..0e9ef71892 100644
--- a/scene/gui/scroll_bar.cpp
+++ b/scene/gui/scroll_bar.cpp
@@ -522,7 +522,7 @@ void ScrollBar::_drag_node_input(const Ref<InputEvent> &p_input) {
drag_node_accum = Vector2();
last_drag_node_accum = Vector2();
drag_node_from = Vector2(orientation == HORIZONTAL ? get_value() : 0, orientation == VERTICAL ? get_value() : 0);
- drag_node_touching = !DisplayServer::get_singleton()->screen_is_touchscreen(DisplayServer::get_singleton()->window_get_current_screen(get_viewport()->get_window_id()));
+ drag_node_touching = DisplayServer::get_singleton()->screen_is_touchscreen(DisplayServer::get_singleton()->window_get_current_screen(get_viewport()->get_window_id()));
drag_node_touching_deaccel = false;
time_since_motion = 0;
diff --git a/scene/gui/text_edit.cpp b/scene/gui/text_edit.cpp
index 07ebdb6523..39ac10a46e 100644
--- a/scene/gui/text_edit.cpp
+++ b/scene/gui/text_edit.cpp
@@ -1632,7 +1632,7 @@ void TextEdit::_notification(int p_what) {
}
if (DisplayServer::get_singleton()->has_feature(DisplayServer::FEATURE_VIRTUAL_KEYBOARD)) {
- DisplayServer::get_singleton()->virtual_keyboard_show(get_text(), get_global_rect());
+ DisplayServer::get_singleton()->virtual_keyboard_show(get_text(), get_global_rect(), true);
}
} break;
case NOTIFICATION_FOCUS_EXIT: {
diff --git a/scene/main/canvas_item.cpp b/scene/main/canvas_item.cpp
index d1bf038b8d..d6d1134cc9 100644
--- a/scene/main/canvas_item.cpp
+++ b/scene/main/canvas_item.cpp
@@ -434,7 +434,7 @@ void CanvasItem::_update_callback() {
notification(NOTIFICATION_DRAW);
emit_signal(SceneStringNames::get_singleton()->draw);
if (get_script_instance()) {
- get_script_instance()->call_multilevel_reversed(SceneStringNames::get_singleton()->_draw, nullptr, 0);
+ get_script_instance()->call(SceneStringNames::get_singleton()->_draw);
}
current_item_drawn = nullptr;
drawing = false;
diff --git a/scene/main/node.cpp b/scene/main/node.cpp
index 88f9730f78..4dcfcd9d96 100644
--- a/scene/main/node.cpp
+++ b/scene/main/node.cpp
@@ -55,15 +55,13 @@ void Node::_notification(int p_notification) {
case NOTIFICATION_PROCESS: {
if (get_script_instance()) {
Variant time = get_process_delta_time();
- const Variant *ptr[1] = { &time };
- get_script_instance()->call_multilevel(SceneStringNames::get_singleton()->_process, ptr, 1);
+ get_script_instance()->call(SceneStringNames::get_singleton()->_process, time);
}
} break;
case NOTIFICATION_PHYSICS_PROCESS: {
if (get_script_instance()) {
Variant time = get_physics_process_delta_time();
- const Variant *ptr[1] = { &time };
- get_script_instance()->call_multilevel(SceneStringNames::get_singleton()->_physics_process, ptr, 1);
+ get_script_instance()->call(SceneStringNames::get_singleton()->_physics_process, time);
}
} break;
@@ -146,7 +144,7 @@ void Node::_notification(int p_notification) {
set_physics_process(true);
}
- get_script_instance()->call_multilevel_reversed(SceneStringNames::get_singleton()->_ready, nullptr, 0);
+ get_script_instance()->call(SceneStringNames::get_singleton()->_ready);
}
} break;
@@ -216,7 +214,7 @@ void Node::_propagate_enter_tree() {
notification(NOTIFICATION_ENTER_TREE);
if (get_script_instance()) {
- get_script_instance()->call_multilevel_reversed(SceneStringNames::get_singleton()->_enter_tree, nullptr, 0);
+ get_script_instance()->call(SceneStringNames::get_singleton()->_enter_tree);
}
emit_signal(SceneStringNames::get_singleton()->tree_entered);
@@ -264,7 +262,7 @@ void Node::_propagate_exit_tree() {
data.blocked--;
if (get_script_instance()) {
- get_script_instance()->call_multilevel(SceneStringNames::get_singleton()->_exit_tree, nullptr, 0);
+ get_script_instance()->call(SceneStringNames::get_singleton()->_exit_tree);
}
emit_signal(SceneStringNames::get_singleton()->tree_exiting);
@@ -1060,7 +1058,7 @@ void Node::_validate_child_name(Node *p_child, bool p_force_human_readable) {
bool unique = true;
- if (p_child->data.name == StringName() || p_child->data.name.operator String()[0] == '@') {
+ if (p_child->data.name == StringName()) {
//new unique name must be assigned
unique = false;
} else {
diff --git a/scene/main/scene_tree.cpp b/scene/main/scene_tree.cpp
index d6159e089b..75b3d7a73d 100644
--- a/scene/main/scene_tree.cpp
+++ b/scene/main/scene_tree.cpp
@@ -250,11 +250,7 @@ void SceneTree::call_group_flags(uint32_t p_call_flags, const StringName &p_grou
}
if (p_call_flags & GROUP_CALL_REALTIME) {
- if (p_call_flags & GROUP_CALL_MULTILEVEL) {
- nodes[i]->call_multilevel(p_function, VARIANT_ARG_PASS);
- } else {
- nodes[i]->call(p_function, VARIANT_ARG_PASS);
- }
+ nodes[i]->call(p_function, VARIANT_ARG_PASS);
} else {
MessageQueue::get_singleton()->push_call(nodes[i], p_function, VARIANT_ARG_PASS);
}
@@ -267,11 +263,7 @@ void SceneTree::call_group_flags(uint32_t p_call_flags, const StringName &p_grou
}
if (p_call_flags & GROUP_CALL_REALTIME) {
- if (p_call_flags & GROUP_CALL_MULTILEVEL) {
- nodes[i]->call_multilevel(p_function, VARIANT_ARG_PASS);
- } else {
- nodes[i]->call(p_function, VARIANT_ARG_PASS);
- }
+ nodes[i]->call(p_function, VARIANT_ARG_PASS);
} else {
MessageQueue::get_singleton()->push_call(nodes[i], p_function, VARIANT_ARG_PASS);
}
@@ -883,7 +875,7 @@ void SceneTree::_call_input_pause(const StringName &p_group, const StringName &p
continue;
}
- n->call_multilevel(p_method, (const Variant **)v, 1);
+ n->call(p_method, (const Variant **)v, 1);
//ERR_FAIL_COND(node_count != g.nodes.size());
}
diff --git a/scene/main/scene_tree.h b/scene/main/scene_tree.h
index 41dc49bc64..0f74f2e973 100644
--- a/scene/main/scene_tree.h
+++ b/scene/main/scene_tree.h
@@ -223,7 +223,6 @@ public:
GROUP_CALL_REVERSE = 1,
GROUP_CALL_REALTIME = 2,
GROUP_CALL_UNIQUE = 4,
- GROUP_CALL_MULTILEVEL = 8,
};
_FORCE_INLINE_ Window *get_root() const { return root; }
diff --git a/scene/main/viewport.cpp b/scene/main/viewport.cpp
index 1c259b7d32..16d0325881 100644
--- a/scene/main/viewport.cpp
+++ b/scene/main/viewport.cpp
@@ -1607,7 +1607,7 @@ void Viewport::_gui_call_input(Control *p_control, const Ref<InputEvent> &p_inpu
}
if (control->data.mouse_filter != Control::MOUSE_FILTER_IGNORE) {
- control->call_multilevel(SceneStringNames::get_singleton()->_gui_input, ev);
+ control->call(SceneStringNames::get_singleton()->_gui_input, ev);
}
if (!control->is_inside_tree() || control->is_set_as_toplevel()) {
@@ -2306,7 +2306,7 @@ void Viewport::_gui_input_event(Ref<InputEvent> p_event) {
if (gui.key_focus) {
gui.key_event_accepted = false;
if (gui.key_focus->can_process()) {
- gui.key_focus->call_multilevel(SceneStringNames::get_singleton()->_gui_input, p_event);
+ gui.key_focus->call(SceneStringNames::get_singleton()->_gui_input, p_event);
if (gui.key_focus) { //maybe lost it
gui.key_focus->emit_signal(SceneStringNames::get_singleton()->gui_input, p_event);
}
@@ -2516,7 +2516,7 @@ void Viewport::_drop_mouse_focus() {
mb->set_global_position(c->get_local_mouse_position());
mb->set_button_index(i + 1);
mb->set_pressed(false);
- c->call_multilevel(SceneStringNames::get_singleton()->_gui_input, mb);
+ c->call(SceneStringNames::get_singleton()->_gui_input, mb);
}
}
}
@@ -2581,7 +2581,7 @@ void Viewport::_post_gui_grab_click_focus() {
mb->set_position(click);
mb->set_button_index(i + 1);
mb->set_pressed(false);
- gui.mouse_focus->call_multilevel(SceneStringNames::get_singleton()->_gui_input, mb);
+ gui.mouse_focus->call(SceneStringNames::get_singleton()->_gui_input, mb);
}
}
diff --git a/scene/main/window.cpp b/scene/main/window.cpp
index 81f33d74fe..8c985242f1 100644
--- a/scene/main/window.cpp
+++ b/scene/main/window.cpp
@@ -247,6 +247,7 @@ void Window::_make_window() {
}
RS::get_singleton()->viewport_set_update_mode(get_viewport_rid(), RS::VIEWPORT_UPDATE_WHEN_VISIBLE);
+ DisplayServer::get_singleton()->show_window(window_id);
}
void Window::_update_from_window() {
diff --git a/scene/register_scene_types.cpp b/scene/register_scene_types.cpp
index 3cbc64c075..db80dbe814 100644
--- a/scene/register_scene_types.cpp
+++ b/scene/register_scene_types.cpp
@@ -372,7 +372,7 @@ void register_scene_types() {
OS::get_singleton()->yield(); //may take time to init
- AcceptDialog::set_swap_cancel_ok(GLOBAL_DEF("gui/common/swap_cancel_ok", bool(DisplayServer::get_singleton()->get_swap_cancel_ok())));
+ AcceptDialog::set_swap_cancel_ok(GLOBAL_DEF_NOVAL("gui/common/swap_cancel_ok", bool(DisplayServer::get_singleton()->get_swap_cancel_ok())));
#endif
/* REGISTER 3D */
diff --git a/scene/resources/visual_shader.cpp b/scene/resources/visual_shader.cpp
index 8236f9a9e3..792e1ac2d7 100644
--- a/scene/resources/visual_shader.cpp
+++ b/scene/resources/visual_shader.cpp
@@ -63,6 +63,21 @@ bool VisualShaderNode::is_port_separator(int p_index) const {
return false;
}
+bool VisualShaderNode::is_output_port_connected(int p_port) const {
+ if (connected_output_ports.has(p_port)) {
+ return connected_output_ports[p_port];
+ }
+ return false;
+}
+
+void VisualShaderNode::set_output_port_connected(int p_port, bool p_connected) {
+ connected_output_ports[p_port] = p_connected;
+}
+
+bool VisualShaderNode::is_code_generated() const {
+ return true;
+}
+
Vector<VisualShader::DefaultTextureParam> VisualShaderNode::get_default_texture_parameters(VisualShader::Type p_type, int p_id) const {
return Vector<VisualShader::DefaultTextureParam>();
}
@@ -526,6 +541,7 @@ void VisualShader::connect_nodes_forced(Type p_type, int p_from_node, int p_from
c.to_port = p_to_port;
g->connections.push_back(c);
g->nodes[p_to_node].prev_connected_nodes.push_back(p_from_node);
+ g->nodes[p_from_node].node->set_output_port_connected(p_from_port, true);
_queue_update();
}
@@ -557,6 +573,7 @@ Error VisualShader::connect_nodes(Type p_type, int p_from_node, int p_from_port,
c.to_port = p_to_port;
g->connections.push_back(c);
g->nodes[p_to_node].prev_connected_nodes.push_back(p_from_node);
+ g->nodes[p_from_node].node->set_output_port_connected(p_from_port, true);
_queue_update();
return OK;
@@ -570,6 +587,7 @@ void VisualShader::disconnect_nodes(Type p_type, int p_from_node, int p_from_por
if (E->get().from_node == p_from_node && E->get().from_port == p_from_port && E->get().to_node == p_to_node && E->get().to_port == p_to_port) {
g->connections.erase(E);
g->nodes[p_to_node].prev_connected_nodes.erase(p_from_node);
+ g->nodes[p_from_node].node->set_output_port_connected(p_from_port, false);
_queue_update();
return;
}
@@ -1105,6 +1123,35 @@ Error VisualShader::_write_node(Type type, StringBuilder &global_code, StringBui
// then this node
+ Vector<VisualShader::DefaultTextureParam> params = vsnode->get_default_texture_parameters(type, node);
+ for (int i = 0; i < params.size(); i++) {
+ def_tex_params.push_back(params[i]);
+ }
+
+ Ref<VisualShaderNodeInput> input = vsnode;
+ bool skip_global = input.is_valid() && for_preview;
+
+ if (!skip_global) {
+ global_code += vsnode->generate_global(get_mode(), type, node);
+
+ String class_name = vsnode->get_class_name();
+ if (class_name == "VisualShaderNodeCustom") {
+ class_name = vsnode->get_script_instance()->get_script()->get_path();
+ }
+ if (!r_classes.has(class_name)) {
+ global_code_per_node += vsnode->generate_global_per_node(get_mode(), type, node);
+ for (int i = 0; i < TYPE_MAX; i++) {
+ global_code_per_func[Type(i)] += vsnode->generate_global_per_func(get_mode(), Type(i), node);
+ }
+ r_classes.insert(class_name);
+ }
+ }
+
+ if (!vsnode->is_code_generated()) { // just generate globals and ignore locals
+ processed.insert(node);
+ return OK;
+ }
+
code += "// " + vsnode->get_caption() + ":" + itos(node) + "\n";
Vector<String> input_vars;
@@ -1255,30 +1302,6 @@ Error VisualShader::_write_node(Type type, StringBuilder &global_code, StringBui
}
}
- Vector<VisualShader::DefaultTextureParam> params = vsnode->get_default_texture_parameters(type, node);
- for (int i = 0; i < params.size(); i++) {
- def_tex_params.push_back(params[i]);
- }
-
- Ref<VisualShaderNodeInput> input = vsnode;
- bool skip_global = input.is_valid() && for_preview;
-
- if (!skip_global) {
- global_code += vsnode->generate_global(get_mode(), type, node);
-
- String class_name = vsnode->get_class_name();
- if (class_name == "VisualShaderNodeCustom") {
- class_name = vsnode->get_script_instance()->get_script()->get_path();
- }
- if (!r_classes.has(class_name)) {
- global_code_per_node += vsnode->generate_global_per_node(get_mode(), type, node);
- for (int i = 0; i < TYPE_MAX; i++) {
- global_code_per_func[Type(i)] += vsnode->generate_global_per_func(get_mode(), Type(i), node);
- }
- r_classes.insert(class_name);
- }
- }
-
code += vsnode->generate_code(get_mode(), type, node, inputs, outputs, for_preview);
code += "\n"; //
diff --git a/scene/resources/visual_shader.h b/scene/resources/visual_shader.h
index dbb8d1d28c..cad567d32f 100644
--- a/scene/resources/visual_shader.h
+++ b/scene/resources/visual_shader.h
@@ -182,6 +182,7 @@ class VisualShaderNode : public Resource {
int port_preview;
Map<int, Variant> default_input_values;
+ Map<int, bool> connected_output_ports;
protected:
bool simple_decl;
@@ -222,6 +223,11 @@ public:
virtual bool is_port_separator(int p_index) const;
+ bool is_output_port_connected(int p_port) const;
+ void set_output_port_connected(int p_port, bool p_connected);
+
+ virtual bool is_code_generated() const;
+
virtual Vector<StringName> get_editable_properties() const;
virtual Vector<VisualShader::DefaultTextureParam> get_default_texture_parameters(VisualShader::Type p_type, int p_id) const;
diff --git a/scene/resources/visual_shader_nodes.cpp b/scene/resources/visual_shader_nodes.cpp
index 88f5287831..4cf382a933 100644
--- a/scene/resources/visual_shader_nodes.cpp
+++ b/scene/resources/visual_shader_nodes.cpp
@@ -3915,6 +3915,10 @@ String VisualShaderNodeTextureUniform::generate_global(Shader::Mode p_mode, Visu
return code;
}
+bool VisualShaderNodeTextureUniform::is_code_generated() const {
+ return is_output_port_connected(0) || is_output_port_connected(1); // rgb or alpha
+}
+
String VisualShaderNodeTextureUniform::generate_code(Shader::Mode p_mode, VisualShader::Type p_type, int p_id, const String *p_input_vars, const String *p_output_vars, bool p_for_preview) const {
String id = get_uniform_name();
String code = "\t{\n";
diff --git a/scene/resources/visual_shader_nodes.h b/scene/resources/visual_shader_nodes.h
index 13a132c60e..b82d5c65c9 100644
--- a/scene/resources/visual_shader_nodes.h
+++ b/scene/resources/visual_shader_nodes.h
@@ -1715,6 +1715,8 @@ public:
virtual String generate_global(Shader::Mode p_mode, VisualShader::Type p_type, int p_id) const override;
virtual String generate_code(Shader::Mode p_mode, VisualShader::Type p_type, int p_id, const String *p_input_vars, const String *p_output_vars, bool p_for_preview = false) const override; //if no output is connected, the output var passed will be empty. if no input is connected and input is NIL, the input var passed will be empty
+ virtual bool is_code_generated() const override;
+
Vector<StringName> get_editable_properties() const override;
void set_texture_type(TextureType p_type);
diff --git a/servers/display_server.cpp b/servers/display_server.cpp
index f46e56cd5a..8f6d6d3b99 100644
--- a/servers/display_server.cpp
+++ b/servers/display_server.cpp
@@ -31,6 +31,7 @@
#include "display_server.h"
#include "core/input/input.h"
+#include "core/method_bind_ext.gen.inc"
#include "scene/resources/texture.h"
DisplayServer *DisplayServer::singleton = nullptr;
@@ -185,6 +186,10 @@ DisplayServer::WindowID DisplayServer::create_sub_window(WindowMode p_mode, uint
ERR_FAIL_V_MSG(INVALID_WINDOW_ID, "Sub-windows not supported by this display server.");
}
+void DisplayServer::show_window(WindowID p_id) {
+ ERR_FAIL_MSG("Sub-windows not supported by this display server.");
+}
+
void DisplayServer::delete_sub_window(WindowID p_id) {
ERR_FAIL_MSG("Sub-windows not supported by this display server.");
}
@@ -213,7 +218,7 @@ bool DisplayServer::is_console_visible() const {
return false;
}
-void DisplayServer::virtual_keyboard_show(const String &p_existing_text, const Rect2 &p_screen_rect, int p_max_length, int p_cursor_start, int p_cursor_end) {
+void DisplayServer::virtual_keyboard_show(const String &p_existing_text, const Rect2 &p_screen_rect, bool p_multiline, int p_max_length, int p_cursor_start, int p_cursor_end) {
WARN_PRINT("Virtual keyboard not supported by this display server.");
}
@@ -455,7 +460,7 @@ void DisplayServer::_bind_methods() {
ClassDB::bind_method(D_METHOD("console_set_visible", "console_visible"), &DisplayServer::console_set_visible);
ClassDB::bind_method(D_METHOD("is_console_visible"), &DisplayServer::is_console_visible);
- ClassDB::bind_method(D_METHOD("virtual_keyboard_show", "existing_text", "position", "max_length", "cursor_start", "cursor_end"), &DisplayServer::virtual_keyboard_show, DEFVAL(Rect2i()), DEFVAL(-1), DEFVAL(-1), DEFVAL(-1));
+ ClassDB::bind_method(D_METHOD("virtual_keyboard_show", "existing_text", "position", "multiline", "max_length", "cursor_start", "cursor_end"), &DisplayServer::virtual_keyboard_show, DEFVAL(Rect2i()), DEFVAL(false), DEFVAL(-1), DEFVAL(-1), DEFVAL(-1));
ClassDB::bind_method(D_METHOD("virtual_keyboard_hide"), &DisplayServer::virtual_keyboard_hide);
ClassDB::bind_method(D_METHOD("virtual_keyboard_get_height"), &DisplayServer::virtual_keyboard_get_height);
diff --git a/servers/display_server.h b/servers/display_server.h
index 2cf0a83dbd..b652418244 100644
--- a/servers/display_server.h
+++ b/servers/display_server.h
@@ -220,6 +220,7 @@ public:
};
virtual WindowID create_sub_window(WindowMode p_mode, uint32_t p_flags, const Rect2i &p_rect = Rect2i());
+ virtual void show_window(WindowID p_id);
virtual void delete_sub_window(WindowID p_id);
virtual WindowID get_window_at_screen_position(const Point2i &p_position) const = 0;
@@ -288,7 +289,7 @@ public:
virtual void console_set_visible(bool p_enabled);
virtual bool is_console_visible() const;
- virtual void virtual_keyboard_show(const String &p_existing_text, const Rect2 &p_screen_rect = Rect2(), int p_max_length = -1, int p_cursor_start = -1, int p_cursor_end = -1);
+ virtual void virtual_keyboard_show(const String &p_existing_text, const Rect2 &p_screen_rect = Rect2(), bool p_multiline = false, int p_max_length = -1, int p_cursor_start = -1, int p_cursor_end = -1);
virtual void virtual_keyboard_hide();
// returns height of the currently shown virtual keyboard (0 if keyboard is hidden)
diff --git a/tests/SCsub b/tests/SCsub
new file mode 100644
index 0000000000..84c9fc1ffe
--- /dev/null
+++ b/tests/SCsub
@@ -0,0 +1,22 @@
+#!/usr/bin/python
+
+Import("env")
+
+env.tests_sources = []
+
+env_tests = env.Clone()
+
+# Enable test framework and inform it of configuration method.
+env_tests.Append(CPPDEFINES=["DOCTEST_CONFIG_IMPLEMENT"])
+
+# We must disable the THREAD_LOCAL entirely in doctest to prevent crashes on debugging
+# Since we link with /MT thread_local is always expired when the header is used
+# So the debugger crashes the engine and it causes weird errors
+# Explained in https://github.com/onqtam/doctest/issues/401
+if env_tests["platform"] == "windows":
+ env_tests.Append(CPPDEFINES=[("DOCTEST_THREAD_LOCAL", "")])
+
+env_tests.add_source_files(env.tests_sources, "*.cpp")
+
+lib = env_tests.add_library("tests", env.tests_sources)
+env.Prepend(LIBS=[lib])
diff --git a/main/tests/test_astar.cpp b/tests/test_astar.cpp
index cb5fcfe37b..cb5fcfe37b 100644
--- a/main/tests/test_astar.cpp
+++ b/tests/test_astar.cpp
diff --git a/main/tests/test_astar.h b/tests/test_astar.h
index 0992812c18..0992812c18 100644
--- a/main/tests/test_astar.h
+++ b/tests/test_astar.h
diff --git a/main/tests/test_basis.cpp b/tests/test_basis.cpp
index 5904fc386a..5904fc386a 100644
--- a/main/tests/test_basis.cpp
+++ b/tests/test_basis.cpp
diff --git a/main/tests/test_basis.h b/tests/test_basis.h
index 63297bd3b8..63297bd3b8 100644
--- a/main/tests/test_basis.h
+++ b/tests/test_basis.h
diff --git a/main/tests/test_class_db.cpp b/tests/test_class_db.cpp
index 3171091402..3171091402 100644
--- a/main/tests/test_class_db.cpp
+++ b/tests/test_class_db.cpp
diff --git a/main/tests/test_class_db.h b/tests/test_class_db.h
index 1a31cfb01b..1a31cfb01b 100644
--- a/main/tests/test_class_db.h
+++ b/tests/test_class_db.h
diff --git a/main/tests/test_gdscript.cpp b/tests/test_gdscript.cpp
index a50311972f..a50311972f 100644
--- a/main/tests/test_gdscript.cpp
+++ b/tests/test_gdscript.cpp
diff --git a/main/tests/test_gdscript.h b/tests/test_gdscript.h
index 6595da1430..6595da1430 100644
--- a/main/tests/test_gdscript.h
+++ b/tests/test_gdscript.h
diff --git a/main/tests/test_gui.cpp b/tests/test_gui.cpp
index d46a13d2c0..d46a13d2c0 100644
--- a/main/tests/test_gui.cpp
+++ b/tests/test_gui.cpp
diff --git a/main/tests/test_gui.h b/tests/test_gui.h
index 5a23179eee..5a23179eee 100644
--- a/main/tests/test_gui.h
+++ b/tests/test_gui.h
diff --git a/main/tests/test_main.cpp b/tests/test_main.cpp
index 91eff28f86..0fb9f2fcda 100644
--- a/main/tests/test_main.cpp
+++ b/tests/test_main.cpp
@@ -49,6 +49,8 @@
#include "test_string.h"
#include "test_validate_testing.h"
+#include "modules/modules_tests.gen.h"
+
#include "thirdparty/doctest/doctest.h"
const char **tests_get_names() {
diff --git a/main/tests/test_main.h b/tests/test_main.h
index 8273b74eac..8273b74eac 100644
--- a/main/tests/test_main.h
+++ b/tests/test_main.h
diff --git a/main/tests/test_math.cpp b/tests/test_math.cpp
index 5f84bad4e9..5f84bad4e9 100644
--- a/main/tests/test_math.cpp
+++ b/tests/test_math.cpp
diff --git a/main/tests/test_math.h b/tests/test_math.h
index 77bce8dd66..77bce8dd66 100644
--- a/main/tests/test_math.h
+++ b/tests/test_math.h
diff --git a/main/tests/test_oa_hash_map.cpp b/tests/test_oa_hash_map.cpp
index 9182f66b61..9182f66b61 100644
--- a/main/tests/test_oa_hash_map.cpp
+++ b/tests/test_oa_hash_map.cpp
diff --git a/main/tests/test_oa_hash_map.h b/tests/test_oa_hash_map.h
index eb2b3d1e99..eb2b3d1e99 100644
--- a/main/tests/test_oa_hash_map.h
+++ b/tests/test_oa_hash_map.h
diff --git a/main/tests/test_ordered_hash_map.cpp b/tests/test_ordered_hash_map.cpp
index d18a3784be..d18a3784be 100644
--- a/main/tests/test_ordered_hash_map.cpp
+++ b/tests/test_ordered_hash_map.cpp
diff --git a/main/tests/test_ordered_hash_map.h b/tests/test_ordered_hash_map.h
index f251da0ba2..f251da0ba2 100644
--- a/main/tests/test_ordered_hash_map.h
+++ b/tests/test_ordered_hash_map.h
diff --git a/main/tests/test_physics_2d.cpp b/tests/test_physics_2d.cpp
index c82ae920bc..c82ae920bc 100644
--- a/main/tests/test_physics_2d.cpp
+++ b/tests/test_physics_2d.cpp
diff --git a/main/tests/test_physics_2d.h b/tests/test_physics_2d.h
index 517d324f3b..517d324f3b 100644
--- a/main/tests/test_physics_2d.h
+++ b/tests/test_physics_2d.h
diff --git a/main/tests/test_physics_3d.cpp b/tests/test_physics_3d.cpp
index 72de2041e4..72de2041e4 100644
--- a/main/tests/test_physics_3d.cpp
+++ b/tests/test_physics_3d.cpp
diff --git a/main/tests/test_physics_3d.h b/tests/test_physics_3d.h
index d03f2c6573..d03f2c6573 100644
--- a/main/tests/test_physics_3d.h
+++ b/tests/test_physics_3d.h
diff --git a/main/tests/test_render.cpp b/tests/test_render.cpp
index d936dd72e7..d936dd72e7 100644
--- a/main/tests/test_render.cpp
+++ b/tests/test_render.cpp
diff --git a/main/tests/test_render.h b/tests/test_render.h
index 4a6340c443..4a6340c443 100644
--- a/main/tests/test_render.h
+++ b/tests/test_render.h
diff --git a/main/tests/test_shader_lang.cpp b/tests/test_shader_lang.cpp
index 34ee3e3210..34ee3e3210 100644
--- a/main/tests/test_shader_lang.cpp
+++ b/tests/test_shader_lang.cpp
diff --git a/main/tests/test_shader_lang.h b/tests/test_shader_lang.h
index 2811c5f46e..2811c5f46e 100644
--- a/main/tests/test_shader_lang.h
+++ b/tests/test_shader_lang.h
diff --git a/main/tests/test_string.h b/tests/test_string.h
index 25fd513a1a..25fd513a1a 100644
--- a/main/tests/test_string.h
+++ b/tests/test_string.h
diff --git a/main/tests/test_validate_testing.h b/tests/test_validate_testing.h
index 5be7d45185..5be7d45185 100644
--- a/main/tests/test_validate_testing.h
+++ b/tests/test_validate_testing.h
diff --git a/thirdparty/README.md b/thirdparty/README.md
index f5b44d7a39..c1b230cfb7 100644
--- a/thirdparty/README.md
+++ b/thirdparty/README.md
@@ -81,6 +81,9 @@ Files extracted from upstream source:
Extracted from .zip provided. Extracted license and header only.
+Important: Some files have Godot-made changes.
+They are marked with `// -- GODOT start --` and `// -- GODOT end --`
+comments.
## enet
diff --git a/thirdparty/doctest/doctest.h b/thirdparty/doctest/doctest.h
index 9444698286..e4fed12767 100644
--- a/thirdparty/doctest/doctest.h
+++ b/thirdparty/doctest/doctest.h
@@ -356,7 +356,13 @@ DOCTEST_MSVC_SUPPRESS_WARNING(26812) // Prefer 'enum class' over 'enum'
#ifndef DOCTEST_BREAK_INTO_DEBUGGER
// should probably take a look at https://github.com/scottt/debugbreak
#ifdef DOCTEST_PLATFORM_MAC
+// -- GODOT start --
+#if defined(__x86_64) || defined(__x86_64__) || defined(__amd64__)
#define DOCTEST_BREAK_INTO_DEBUGGER() __asm__("int $3\n" : :)
+#else
+#define DOCTEST_BREAK_INTO_DEBUGGER() __asm__("brk #0");
+#endif
+// -- GODOT end --
#elif DOCTEST_MSVC
#define DOCTEST_BREAK_INTO_DEBUGGER() __debugbreak()
#elif defined(__MINGW32__)
diff --git a/thirdparty/doctest/patches/fix-arm64-mac.patch b/thirdparty/doctest/patches/fix-arm64-mac.patch
new file mode 100644
index 0000000000..f78014534f
--- /dev/null
+++ b/thirdparty/doctest/patches/fix-arm64-mac.patch
@@ -0,0 +1,18 @@
+diff --git a/thirdparty/doctest/doctest.h b/thirdparty/doctest/doctest.h
+index 9444698286..e4fed12767 100644
+--- a/thirdparty/doctest/doctest.h
++++ b/thirdparty/doctest/doctest.h
+@@ -356,7 +356,13 @@ DOCTEST_MSVC_SUPPRESS_WARNING(26812) // Prefer 'enum class' over 'enum'
+ #ifndef DOCTEST_BREAK_INTO_DEBUGGER
+ // should probably take a look at https://github.com/scottt/debugbreak
+ #ifdef DOCTEST_PLATFORM_MAC
++// -- GODOT start --
++#if defined(__x86_64) || defined(__x86_64__) || defined(__amd64__)
+ #define DOCTEST_BREAK_INTO_DEBUGGER() __asm__("int $3\n" : :)
++#else
++#define DOCTEST_BREAK_INTO_DEBUGGER() __asm__("brk #0");
++#endif
++// -- GODOT end --
+ #elif DOCTEST_MSVC
+ #define DOCTEST_BREAK_INTO_DEBUGGER() __debugbreak()
+ #elif defined(__MINGW32__)
diff --git a/thirdparty/vulkan/patches/VMA-assert-remove.patch b/thirdparty/vulkan/patches/VMA-assert-remove.patch
new file mode 100644
index 0000000000..3d57ab7d42
--- /dev/null
+++ b/thirdparty/vulkan/patches/VMA-assert-remove.patch
@@ -0,0 +1,29 @@
+diff --git a/thirdparty/vulkan/vk_mem_alloc.h b/thirdparty/vulkan/vk_mem_alloc.h
+index 0dfb66efc6..8a42699e7f 100644
+--- a/thirdparty/vulkan/vk_mem_alloc.h
++++ b/thirdparty/vulkan/vk_mem_alloc.h
+@@ -17508,24 +17508,6 @@ VMA_CALL_PRE VkResult VMA_CALL_POST vmaCreateBuffer(
+ allocator->GetBufferMemoryRequirements(*pBuffer, vkMemReq,
+ requiresDedicatedAllocation, prefersDedicatedAllocation);
+
+- // Make sure alignment requirements for specific buffer usages reported
+- // in Physical Device Properties are included in alignment reported by memory requirements.
+- if((pBufferCreateInfo->usage & VK_BUFFER_USAGE_UNIFORM_TEXEL_BUFFER_BIT) != 0)
+- {
+- VMA_ASSERT(vkMemReq.alignment %
+- allocator->m_PhysicalDeviceProperties.limits.minTexelBufferOffsetAlignment == 0);
+- }
+- if((pBufferCreateInfo->usage & VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT) != 0)
+- {
+- VMA_ASSERT(vkMemReq.alignment %
+- allocator->m_PhysicalDeviceProperties.limits.minUniformBufferOffsetAlignment == 0);
+- }
+- if((pBufferCreateInfo->usage & VK_BUFFER_USAGE_STORAGE_BUFFER_BIT) != 0)
+- {
+- VMA_ASSERT(vkMemReq.alignment %
+- allocator->m_PhysicalDeviceProperties.limits.minStorageBufferOffsetAlignment == 0);
+- }
+-
+ // 3. Allocate memory using allocator.
+ res = allocator->AllocateMemory(
+ vkMemReq,
diff --git a/thirdparty/vulkan/vk_mem_alloc.h b/thirdparty/vulkan/vk_mem_alloc.h
index 0dfb66efc6..8a42699e7f 100644
--- a/thirdparty/vulkan/vk_mem_alloc.h
+++ b/thirdparty/vulkan/vk_mem_alloc.h
@@ -17508,24 +17508,6 @@ VMA_CALL_PRE VkResult VMA_CALL_POST vmaCreateBuffer(
allocator->GetBufferMemoryRequirements(*pBuffer, vkMemReq,
requiresDedicatedAllocation, prefersDedicatedAllocation);
- // Make sure alignment requirements for specific buffer usages reported
- // in Physical Device Properties are included in alignment reported by memory requirements.
- if((pBufferCreateInfo->usage & VK_BUFFER_USAGE_UNIFORM_TEXEL_BUFFER_BIT) != 0)
- {
- VMA_ASSERT(vkMemReq.alignment %
- allocator->m_PhysicalDeviceProperties.limits.minTexelBufferOffsetAlignment == 0);
- }
- if((pBufferCreateInfo->usage & VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT) != 0)
- {
- VMA_ASSERT(vkMemReq.alignment %
- allocator->m_PhysicalDeviceProperties.limits.minUniformBufferOffsetAlignment == 0);
- }
- if((pBufferCreateInfo->usage & VK_BUFFER_USAGE_STORAGE_BUFFER_BIT) != 0)
- {
- VMA_ASSERT(vkMemReq.alignment %
- allocator->m_PhysicalDeviceProperties.limits.minStorageBufferOffsetAlignment == 0);
- }
-
// 3. Allocate memory using allocator.
res = allocator->AllocateMemory(
vkMemReq,