summaryrefslogtreecommitdiff
path: root/platform
diff options
context:
space:
mode:
Diffstat (limited to 'platform')
-rw-r--r--platform/android/dir_access_jandroid.cpp14
-rw-r--r--platform/android/dir_access_jandroid.h1
-rw-r--r--platform/android/display_server_android.cpp4
-rw-r--r--platform/android/display_server_android.h4
-rw-r--r--platform/android/export/export_plugin.cpp27
-rw-r--r--platform/android/export/export_plugin.h5
-rw-r--r--platform/android/file_access_filesystem_jandroid.cpp13
-rw-r--r--platform/android/file_access_filesystem_jandroid.h2
-rw-r--r--platform/android/java/lib/src/org/godotengine/godot/io/file/DataAccess.kt8
-rw-r--r--platform/android/java/lib/src/org/godotengine/godot/io/file/FileAccessHandler.kt5
-rw-r--r--platform/android/java_godot_io_wrapper.cpp4
-rw-r--r--platform/android/java_godot_io_wrapper.h4
-rw-r--r--platform/ios/display_server_ios.h2
-rw-r--r--platform/ios/display_server_ios.mm4
-rw-r--r--platform/ios/export/export_plugin.cpp19
-rw-r--r--platform/ios/export/export_plugin.h7
-rw-r--r--platform/javascript/display_server_javascript.cpp2
-rw-r--r--platform/javascript/display_server_javascript.h2
-rw-r--r--platform/javascript/export/export_plugin.cpp24
-rw-r--r--platform/javascript/export/export_plugin.h3
-rw-r--r--platform/javascript/js/libs/library_godot_display.js28
-rw-r--r--platform/javascript/os_javascript.h1
-rw-r--r--platform/linuxbsd/display_server_x11.cpp4
-rw-r--r--platform/linuxbsd/display_server_x11.h2
-rw-r--r--platform/macos/display_server_macos.h25
-rw-r--r--platform/macos/display_server_macos.mm412
-rw-r--r--platform/macos/export/export_plugin.cpp13
-rw-r--r--platform/macos/export/export_plugin.h3
-rw-r--r--platform/macos/godot_menu_item.h1
-rw-r--r--platform/uwp/export/export_plugin.cpp23
-rw-r--r--platform/uwp/export/export_plugin.h3
-rw-r--r--platform/windows/display_server_windows.cpp4
-rw-r--r--platform/windows/display_server_windows.h2
-rw-r--r--platform/windows/export/export_plugin.cpp15
-rw-r--r--platform/windows/export/export_plugin.h3
35 files changed, 503 insertions, 190 deletions
diff --git a/platform/android/dir_access_jandroid.cpp b/platform/android/dir_access_jandroid.cpp
index eb344d3b43..428135de56 100644
--- a/platform/android/dir_access_jandroid.cpp
+++ b/platform/android/dir_access_jandroid.cpp
@@ -135,6 +135,20 @@ String DirAccessJAndroid::get_drive(int p_drive) {
}
}
+String DirAccessJAndroid::get_current_dir(bool p_include_drive) const {
+ String base = _get_root_path();
+ String bd = current_dir;
+ if (!base.is_empty()) {
+ bd = current_dir.replace_first(base, "");
+ }
+
+ if (bd.begins_with("/")) {
+ return _get_root_string() + bd.substr(1, bd.length());
+ } else {
+ return _get_root_string() + bd;
+ }
+}
+
Error DirAccessJAndroid::change_dir(String p_dir) {
String new_dir = get_absolute_path(p_dir);
if (new_dir == current_dir) {
diff --git a/platform/android/dir_access_jandroid.h b/platform/android/dir_access_jandroid.h
index d469c9d317..5b7b4a9c4d 100644
--- a/platform/android/dir_access_jandroid.h
+++ b/platform/android/dir_access_jandroid.h
@@ -67,6 +67,7 @@ public:
virtual int get_drive_count() override;
virtual String get_drive(int p_drive) override;
+ virtual String get_current_dir(bool p_include_drive = true) const override; ///< return current dir location
virtual Error change_dir(String p_dir) override; ///< can be relative or absolute, return false on success
diff --git a/platform/android/display_server_android.cpp b/platform/android/display_server_android.cpp
index b51dd18af6..d3bce12de1 100644
--- a/platform/android/display_server_android.cpp
+++ b/platform/android/display_server_android.cpp
@@ -83,7 +83,7 @@ bool DisplayServerAndroid::tts_is_paused() const {
return TTS_Android::is_paused();
}
-Array DisplayServerAndroid::tts_get_voices() const {
+TypedArray<Dictionary> DisplayServerAndroid::tts_get_voices() const {
return TTS_Android::get_voices();
}
@@ -136,7 +136,7 @@ bool DisplayServerAndroid::clipboard_has() const {
}
}
-Array DisplayServerAndroid::get_display_cutouts() const {
+TypedArray<Rect2> DisplayServerAndroid::get_display_cutouts() const {
GodotIOJavaWrapper *godot_io_java = OS_Android::get_singleton()->get_godot_io_java();
ERR_FAIL_NULL_V(godot_io_java, Array());
return godot_io_java->get_display_cutouts();
diff --git a/platform/android/display_server_android.h b/platform/android/display_server_android.h
index 2f30642319..6e14ba3e23 100644
--- a/platform/android/display_server_android.h
+++ b/platform/android/display_server_android.h
@@ -93,7 +93,7 @@ public:
virtual bool tts_is_speaking() const override;
virtual bool tts_is_paused() const override;
- virtual Array tts_get_voices() const override;
+ virtual TypedArray<Dictionary> tts_get_voices() const override;
virtual void tts_speak(const String &p_text, const String &p_voice, int p_volume = 50, float p_pitch = 1.f, float p_rate = 1.f, int p_utterance_id = 0, bool p_interrupt = false) override;
virtual void tts_pause() override;
@@ -104,7 +104,7 @@ public:
virtual String clipboard_get() const override;
virtual bool clipboard_has() const override;
- virtual Array get_display_cutouts() const override;
+ virtual TypedArray<Rect2> get_display_cutouts() const override;
virtual Rect2i get_display_safe_area() const override;
virtual void screen_set_keep_on(bool p_enable) override;
diff --git a/platform/android/export/export_plugin.cpp b/platform/android/export/export_plugin.cpp
index 6f1b4bde40..685b1f01af 100644
--- a/platform/android/export/export_plugin.cpp
+++ b/platform/android/export/export_plugin.cpp
@@ -248,6 +248,7 @@ static const char *AAB_ASSETS_DIRECTORY = "res://android/build/assetPacks/instal
static const int DEFAULT_MIN_SDK_VERSION = 19; // Should match the value in 'platform/android/java/app/config.gradle#minSdk'
static const int DEFAULT_TARGET_SDK_VERSION = 32; // Should match the value in 'platform/android/java/app/config.gradle#targetSdk'
+#ifndef ANDROID_ENABLED
void EditorExportPlatformAndroid::_check_for_changes_poll_thread(void *ud) {
EditorExportPlatformAndroid *ea = static_cast<EditorExportPlatformAndroid *>(ud);
@@ -277,7 +278,6 @@ void EditorExportPlatformAndroid::_check_for_changes_poll_thread(void *ud) {
}
}
-#ifndef ANDROID_ENABLED
// Check for devices updates
String adb = get_adb_path();
if (FileAccess::exists(adb)) {
@@ -389,7 +389,6 @@ void EditorExportPlatformAndroid::_check_for_changes_poll_thread(void *ud) {
ea->devices_changed.set();
}
}
-#endif
uint64_t sleep = 200;
uint64_t wait = 3000000;
@@ -402,7 +401,6 @@ void EditorExportPlatformAndroid::_check_for_changes_poll_thread(void *ud) {
}
}
-#ifndef ANDROID_ENABLED
if (EditorSettings::get_singleton()->get("export/android/shutdown_adb_on_exit")) {
String adb = get_adb_path();
if (!FileAccess::exists(adb)) {
@@ -413,8 +411,8 @@ void EditorExportPlatformAndroid::_check_for_changes_poll_thread(void *ud) {
args.push_back("kill-server");
OS::get_singleton()->execute(adb, args);
}
-#endif
}
+#endif
String EditorExportPlatformAndroid::get_project_name(const String &p_name) const {
String aname;
@@ -2049,7 +2047,7 @@ String EditorExportPlatformAndroid::get_apksigner_path() {
return apksigner_path;
}
-bool EditorExportPlatformAndroid::can_export(const Ref<EditorExportPreset> &p_preset, String &r_error, bool &r_missing_templates) const {
+bool EditorExportPlatformAndroid::has_valid_export_configuration(const Ref<EditorExportPreset> &p_preset, String &r_error, bool &r_missing_templates) const {
String err;
bool valid = false;
const bool custom_build_enabled = p_preset->get("custom_build/use_custom_build");
@@ -2097,7 +2095,7 @@ bool EditorExportPlatformAndroid::can_export(const Ref<EditorExportPreset> &p_pr
valid = installed_android_build_template && !r_missing_templates;
}
- // Validate the rest of the configuration.
+ // Validate the rest of the export configuration.
String dk = p_preset->get("keystore/debug");
String dk_user = p_preset->get("keystore/debug_user");
@@ -2173,6 +2171,19 @@ bool EditorExportPlatformAndroid::can_export(const Ref<EditorExportPreset> &p_pr
}
}
+ if (!err.is_empty()) {
+ r_error = err;
+ }
+
+ return valid;
+}
+
+bool EditorExportPlatformAndroid::has_valid_project_configuration(const Ref<EditorExportPreset> &p_preset, String &r_error) const {
+ String err;
+ bool valid = true;
+ const bool custom_build_enabled = p_preset->get("custom_build/use_custom_build");
+
+ // Validate the project configuration.
bool apk_expansion = p_preset->get("apk_expansion/enable");
if (apk_expansion) {
@@ -3125,10 +3136,14 @@ EditorExportPlatformAndroid::EditorExportPlatformAndroid() {
devices_changed.set();
plugins_changed.set();
+#ifndef ANDROID_ENABLED
check_for_changes_thread.start(_check_for_changes_poll_thread, this);
+#endif
}
EditorExportPlatformAndroid::~EditorExportPlatformAndroid() {
+#ifndef ANDROID_ENABLED
quit_request.set();
check_for_changes_thread.wait_to_finish();
+#endif
}
diff --git a/platform/android/export/export_plugin.h b/platform/android/export/export_plugin.h
index 1da3f68f9a..46012bd46c 100644
--- a/platform/android/export/export_plugin.h
+++ b/platform/android/export/export_plugin.h
@@ -80,10 +80,12 @@ class EditorExportPlatformAndroid : public EditorExportPlatform {
Vector<Device> devices;
SafeFlag devices_changed;
Mutex device_lock;
+#ifndef ANDROID_ENABLED
Thread check_for_changes_thread;
SafeFlag quit_request;
static void _check_for_changes_poll_thread(void *ud);
+#endif
String get_project_name(const String &p_name) const;
@@ -186,7 +188,8 @@ public:
static String get_apksigner_path();
- virtual bool can_export(const Ref<EditorExportPreset> &p_preset, String &r_error, bool &r_missing_templates) const override;
+ virtual bool has_valid_export_configuration(const Ref<EditorExportPreset> &p_preset, String &r_error, bool &r_missing_templates) const override;
+ virtual bool has_valid_project_configuration(const Ref<EditorExportPreset> &p_preset, String &r_error) const override;
virtual List<String> get_binary_extensions(const Ref<EditorExportPreset> &p_preset) const override;
diff --git a/platform/android/file_access_filesystem_jandroid.cpp b/platform/android/file_access_filesystem_jandroid.cpp
index 6b21c18d59..56561cb616 100644
--- a/platform/android/file_access_filesystem_jandroid.cpp
+++ b/platform/android/file_access_filesystem_jandroid.cpp
@@ -46,6 +46,7 @@ jmethodID FileAccessFilesystemJAndroid::_file_seek_end = nullptr;
jmethodID FileAccessFilesystemJAndroid::_file_read = nullptr;
jmethodID FileAccessFilesystemJAndroid::_file_tell = nullptr;
jmethodID FileAccessFilesystemJAndroid::_file_eof = nullptr;
+jmethodID FileAccessFilesystemJAndroid::_file_set_eof = nullptr;
jmethodID FileAccessFilesystemJAndroid::_file_close = nullptr;
jmethodID FileAccessFilesystemJAndroid::_file_write = nullptr;
jmethodID FileAccessFilesystemJAndroid::_file_flush = nullptr;
@@ -162,6 +163,16 @@ bool FileAccessFilesystemJAndroid::eof_reached() const {
}
}
+void FileAccessFilesystemJAndroid::_set_eof(bool eof) {
+ if (_file_set_eof) {
+ ERR_FAIL_COND_MSG(!is_open(), "File must be opened before use.");
+
+ JNIEnv *env = get_jni_env();
+ ERR_FAIL_COND(env == nullptr);
+ env->CallVoidMethod(file_access_handler, _file_set_eof, id, eof);
+ }
+}
+
uint8_t FileAccessFilesystemJAndroid::get_8() const {
ERR_FAIL_COND_V_MSG(!is_open(), 0, "File must be opened before use.");
uint8_t byte;
@@ -184,6 +195,7 @@ String FileAccessFilesystemJAndroid::get_line() const {
while (true) {
size_t line_buffer_size = MIN(buffer_size_limit, file_size - get_position());
if (line_buffer_size <= 0) {
+ const_cast<FileAccessFilesystemJAndroid *>(this)->_set_eof(true);
break;
}
@@ -310,6 +322,7 @@ void FileAccessFilesystemJAndroid::setup(jobject p_file_access_handler) {
_file_get_size = env->GetMethodID(cls, "fileGetSize", "(I)J");
_file_tell = env->GetMethodID(cls, "fileGetPosition", "(I)J");
_file_eof = env->GetMethodID(cls, "isFileEof", "(I)Z");
+ _file_set_eof = env->GetMethodID(cls, "setFileEof", "(IZ)V");
_file_seek = env->GetMethodID(cls, "fileSeek", "(IJ)V");
_file_seek_end = env->GetMethodID(cls, "fileSeekFromEnd", "(IJ)V");
_file_read = env->GetMethodID(cls, "fileRead", "(ILjava/nio/ByteBuffer;)I");
diff --git a/platform/android/file_access_filesystem_jandroid.h b/platform/android/file_access_filesystem_jandroid.h
index 7deb8de37b..76d7db6e3a 100644
--- a/platform/android/file_access_filesystem_jandroid.h
+++ b/platform/android/file_access_filesystem_jandroid.h
@@ -44,6 +44,7 @@ class FileAccessFilesystemJAndroid : public FileAccess {
static jmethodID _file_seek_end;
static jmethodID _file_tell;
static jmethodID _file_eof;
+ static jmethodID _file_set_eof;
static jmethodID _file_read;
static jmethodID _file_write;
static jmethodID _file_flush;
@@ -56,6 +57,7 @@ class FileAccessFilesystemJAndroid : public FileAccess {
String path_src;
void _close(); ///< close a file
+ void _set_eof(bool eof);
public:
virtual Error _open(const String &p_path, int p_mode_flags) override; ///< open a file
diff --git a/platform/android/java/lib/src/org/godotengine/godot/io/file/DataAccess.kt b/platform/android/java/lib/src/org/godotengine/godot/io/file/DataAccess.kt
index 463dabfb23..f23537a29e 100644
--- a/platform/android/java/lib/src/org/godotengine/godot/io/file/DataAccess.kt
+++ b/platform/android/java/lib/src/org/godotengine/godot/io/file/DataAccess.kt
@@ -104,7 +104,6 @@ internal abstract class DataAccess(private val filePath: String) {
protected abstract val fileChannel: FileChannel
internal var endOfFile = false
- private set
fun close() {
try {
@@ -125,9 +124,7 @@ internal abstract class DataAccess(private val filePath: String) {
fun seek(position: Long) {
try {
fileChannel.position(position)
- if (position <= size()) {
- endOfFile = false
- }
+ endOfFile = position >= fileChannel.size()
} catch (e: Exception) {
Log.w(TAG, "Exception when seeking file $filePath.", e)
}
@@ -161,8 +158,7 @@ internal abstract class DataAccess(private val filePath: String) {
fun read(buffer: ByteBuffer): Int {
return try {
val readBytes = fileChannel.read(buffer)
- endOfFile = readBytes == -1
- || (fileChannel.position() >= fileChannel.size() && fileChannel.size() > 0)
+ endOfFile = readBytes == -1 || (fileChannel.position() >= fileChannel.size())
if (readBytes == -1) {
0
} else {
diff --git a/platform/android/java/lib/src/org/godotengine/godot/io/file/FileAccessHandler.kt b/platform/android/java/lib/src/org/godotengine/godot/io/file/FileAccessHandler.kt
index 04b6772c45..83da3a24b3 100644
--- a/platform/android/java/lib/src/org/godotengine/godot/io/file/FileAccessHandler.kt
+++ b/platform/android/java/lib/src/org/godotengine/godot/io/file/FileAccessHandler.kt
@@ -194,6 +194,11 @@ class FileAccessHandler(val context: Context) {
return files[fileId].endOfFile
}
+ fun setFileEof(fileId: Int, eof: Boolean) {
+ val file = files[fileId] ?: return
+ file.endOfFile = eof
+ }
+
fun fileClose(fileId: Int) {
if (hasFileId(fileId)) {
files[fileId].close()
diff --git a/platform/android/java_godot_io_wrapper.cpp b/platform/android/java_godot_io_wrapper.cpp
index 5877c15114..cea64a7f22 100644
--- a/platform/android/java_godot_io_wrapper.cpp
+++ b/platform/android/java_godot_io_wrapper.cpp
@@ -165,8 +165,8 @@ float GodotIOJavaWrapper::get_screen_refresh_rate(float fallback) {
return fallback;
}
-Array GodotIOJavaWrapper::get_display_cutouts() {
- Array result;
+TypedArray<Rect2> GodotIOJavaWrapper::get_display_cutouts() {
+ TypedArray<Rect2> result;
ERR_FAIL_NULL_V(_get_display_cutouts, result);
JNIEnv *env = get_jni_env();
ERR_FAIL_NULL_V(env, result);
diff --git a/platform/android/java_godot_io_wrapper.h b/platform/android/java_godot_io_wrapper.h
index dc68f4d90d..9a1a877b6f 100644
--- a/platform/android/java_godot_io_wrapper.h
+++ b/platform/android/java_godot_io_wrapper.h
@@ -38,7 +38,7 @@
#include <jni.h>
#include "core/math/rect2i.h"
-#include "core/variant/array.h"
+#include "core/variant/typed_array.h"
#include "string_android.h"
// Class that makes functions in java/src/org/godotengine/godot/GodotIO.java callable from C++
@@ -78,7 +78,7 @@ public:
int get_screen_dpi();
float get_scaled_density();
float get_screen_refresh_rate(float fallback);
- Array get_display_cutouts();
+ TypedArray<Rect2> get_display_cutouts();
Rect2i get_display_safe_area();
String get_unique_id();
bool has_vk();
diff --git a/platform/ios/display_server_ios.h b/platform/ios/display_server_ios.h
index bbb2dd3ab3..f3624f24ab 100644
--- a/platform/ios/display_server_ios.h
+++ b/platform/ios/display_server_ios.h
@@ -127,7 +127,7 @@ public:
virtual bool tts_is_speaking() const override;
virtual bool tts_is_paused() const override;
- virtual Array tts_get_voices() const override;
+ virtual TypedArray<Dictionary> tts_get_voices() const override;
virtual void tts_speak(const String &p_text, const String &p_voice, int p_volume = 50, float p_pitch = 1.f, float p_rate = 1.f, int p_utterance_id = 0, bool p_interrupt = false) override;
virtual void tts_pause() override;
diff --git a/platform/ios/display_server_ios.mm b/platform/ios/display_server_ios.mm
index 6ce7e676a2..74d6bc2e97 100644
--- a/platform/ios/display_server_ios.mm
+++ b/platform/ios/display_server_ios.mm
@@ -336,8 +336,8 @@ bool DisplayServerIOS::tts_is_paused() const {
return [tts isPaused];
}
-Array DisplayServerIOS::tts_get_voices() const {
- ERR_FAIL_COND_V(!tts, Array());
+TypedArray<Dictionary> DisplayServerIOS::tts_get_voices() const {
+ ERR_FAIL_COND_V(!tts, TypedArray<Dictionary>());
return [tts getVoices];
}
diff --git a/platform/ios/export/export_plugin.cpp b/platform/ios/export/export_plugin.cpp
index 001c9b803d..425a977569 100644
--- a/platform/ios/export/export_plugin.cpp
+++ b/platform/ios/export/export_plugin.cpp
@@ -1817,7 +1817,7 @@ Error EditorExportPlatformIOS::export_project(const Ref<EditorExportPreset> &p_p
return OK;
}
-bool EditorExportPlatformIOS::can_export(const Ref<EditorExportPreset> &p_preset, String &r_error, bool &r_missing_templates) const {
+bool EditorExportPlatformIOS::has_valid_export_configuration(const Ref<EditorExportPreset> &p_preset, String &r_error, bool &r_missing_templates) const {
String err;
bool valid = false;
@@ -1842,7 +1842,18 @@ bool EditorExportPlatformIOS::can_export(const Ref<EditorExportPreset> &p_preset
valid = dvalid || rvalid;
r_missing_templates = !valid;
- // Validate the rest of the configuration.
+ if (!err.is_empty()) {
+ r_error = err;
+ }
+
+ return valid;
+}
+
+bool EditorExportPlatformIOS::has_valid_project_configuration(const Ref<EditorExportPreset> &p_preset, String &r_error) const {
+ String err;
+ bool valid = true;
+
+ // Validate the project configuration.
String team_id = p_preset->get("application/app_store_team_id");
if (team_id.length() == 0) {
@@ -1873,10 +1884,14 @@ bool EditorExportPlatformIOS::can_export(const Ref<EditorExportPreset> &p_preset
EditorExportPlatformIOS::EditorExportPlatformIOS() {
logo = ImageTexture::create_from_image(memnew(Image(_ios_logo)));
plugins_changed.set();
+#ifndef ANDROID_ENABLED
check_for_changes_thread.start(_check_for_changes_poll_thread, this);
+#endif
}
EditorExportPlatformIOS::~EditorExportPlatformIOS() {
+#ifndef ANDROID_ENABLED
quit_request.set();
check_for_changes_thread.wait_to_finish();
+#endif
}
diff --git a/platform/ios/export/export_plugin.h b/platform/ios/export/export_plugin.h
index e32aef82dd..abda8e218a 100644
--- a/platform/ios/export/export_plugin.h
+++ b/platform/ios/export/export_plugin.h
@@ -57,8 +57,10 @@ class EditorExportPlatformIOS : public EditorExportPlatform {
// Plugins
SafeFlag plugins_changed;
+#ifndef ANDROID_ENABLED
Thread check_for_changes_thread;
SafeFlag quit_request;
+#endif
Mutex plugins_lock;
Vector<PluginConfigIOS> plugins;
@@ -139,6 +141,7 @@ class EditorExportPlatformIOS : public EditorExportPlatform {
return true;
}
+#ifndef ANDROID_ENABLED
static void _check_for_changes_poll_thread(void *ud) {
EditorExportPlatformIOS *ea = static_cast<EditorExportPlatformIOS *>(ud);
@@ -172,6 +175,7 @@ class EditorExportPlatformIOS : public EditorExportPlatform {
}
}
}
+#endif
protected:
virtual void get_preset_features(const Ref<EditorExportPreset> &p_preset, List<String> *r_features) const override;
@@ -198,7 +202,8 @@ public:
}
virtual Error export_project(const Ref<EditorExportPreset> &p_preset, bool p_debug, const String &p_path, int p_flags = 0) override;
- virtual bool can_export(const Ref<EditorExportPreset> &p_preset, String &r_error, bool &r_missing_templates) const override;
+ virtual bool has_valid_export_configuration(const Ref<EditorExportPreset> &p_preset, String &r_error, bool &r_missing_templates) const override;
+ virtual bool has_valid_project_configuration(const Ref<EditorExportPreset> &p_preset, String &r_error) const override;
virtual void get_platform_features(List<String> *r_features) const override {
r_features->push_back("mobile");
diff --git a/platform/javascript/display_server_javascript.cpp b/platform/javascript/display_server_javascript.cpp
index 48f637fcfe..30240ad2db 100644
--- a/platform/javascript/display_server_javascript.cpp
+++ b/platform/javascript/display_server_javascript.cpp
@@ -296,7 +296,7 @@ void DisplayServerJavaScript::update_voices_callback(int p_size, const char **p_
}
}
-Array DisplayServerJavaScript::tts_get_voices() const {
+TypedArray<Dictionary> DisplayServerJavaScript::tts_get_voices() const {
godot_js_tts_get_voices(update_voices_callback);
return voices;
}
diff --git a/platform/javascript/display_server_javascript.h b/platform/javascript/display_server_javascript.h
index fb7f5d02a8..cbb91477b7 100644
--- a/platform/javascript/display_server_javascript.h
+++ b/platform/javascript/display_server_javascript.h
@@ -124,7 +124,7 @@ public:
// tts
virtual bool tts_is_speaking() const override;
virtual bool tts_is_paused() const override;
- virtual Array tts_get_voices() const override;
+ virtual TypedArray<Dictionary> tts_get_voices() const override;
virtual void tts_speak(const String &p_text, const String &p_voice, int p_volume = 50, float p_pitch = 1.f, float p_rate = 1.f, int p_utterance_id = 0, bool p_interrupt = false) override;
virtual void tts_pause() override;
diff --git a/platform/javascript/export/export_plugin.cpp b/platform/javascript/export/export_plugin.cpp
index b99f88d067..0bdee11018 100644
--- a/platform/javascript/export/export_plugin.cpp
+++ b/platform/javascript/export/export_plugin.cpp
@@ -362,7 +362,7 @@ Ref<Texture2D> EditorExportPlatformJavaScript::get_logo() const {
return logo;
}
-bool EditorExportPlatformJavaScript::can_export(const Ref<EditorExportPreset> &p_preset, String &r_error, bool &r_missing_templates) const {
+bool EditorExportPlatformJavaScript::has_valid_export_configuration(const Ref<EditorExportPreset> &p_preset, String &r_error, bool &r_missing_templates) const {
#ifndef DEV_ENABLED
// We don't provide export templates for the HTML5 platform currently as there
// is no suitable renderer to use with them. So we forbid exporting and tell
@@ -396,7 +396,27 @@ bool EditorExportPlatformJavaScript::can_export(const Ref<EditorExportPreset> &p
valid = dvalid || rvalid;
r_missing_templates = !valid;
- // Validate the rest of the configuration.
+ if (!err.is_empty()) {
+ r_error = err;
+ }
+
+ return valid;
+}
+
+bool EditorExportPlatformJavaScript::has_valid_project_configuration(const Ref<EditorExportPreset> &p_preset, String &r_error) const {
+#ifndef DEV_ENABLED
+ // We don't provide export templates for the HTML5 platform currently as there
+ // is no suitable renderer to use with them. So we forbid exporting and tell
+ // users why. This is skipped in DEV_ENABLED so that contributors can still test
+ // the pipeline once we start having WebGL or WebGPU support.
+ r_error = "The HTML5 platform is currently not supported in Godot 4.0, as there is no suitable renderer for it.\n";
+ return false;
+#endif
+
+ String err;
+ bool valid = true;
+
+ // Validate the project configuration.
if (p_preset->get("vram_texture_compression/for_mobile")) {
String etc_error = test_etc2();
diff --git a/platform/javascript/export/export_plugin.h b/platform/javascript/export/export_plugin.h
index fbaa3615cb..16bab02d54 100644
--- a/platform/javascript/export/export_plugin.h
+++ b/platform/javascript/export/export_plugin.h
@@ -118,7 +118,8 @@ public:
virtual String get_os_name() const override;
virtual Ref<Texture2D> get_logo() const override;
- virtual bool can_export(const Ref<EditorExportPreset> &p_preset, String &r_error, bool &r_missing_templates) const override;
+ virtual bool has_valid_export_configuration(const Ref<EditorExportPreset> &p_preset, String &r_error, bool &r_missing_templates) const override;
+ virtual bool has_valid_project_configuration(const Ref<EditorExportPreset> &p_preset, String &r_error) const override;
virtual List<String> get_binary_extensions(const Ref<EditorExportPreset> &p_preset) const override;
virtual Error export_project(const Ref<EditorExportPreset> &p_preset, bool p_debug, const String &p_path, int p_flags = 0) override;
diff --git a/platform/javascript/js/libs/library_godot_display.js b/platform/javascript/js/libs/library_godot_display.js
index c7729a8c5b..768eaf9e1d 100644
--- a/platform/javascript/js/libs/library_godot_display.js
+++ b/platform/javascript/js/libs/library_godot_display.js
@@ -336,26 +336,12 @@ const GodotDisplay = {
$GodotDisplay__deps: ['$GodotConfig', '$GodotRuntime', '$GodotDisplayCursor', '$GodotEventListeners', '$GodotDisplayScreen', '$GodotDisplayVK'],
$GodotDisplay: {
window_icon: '',
- findDPI: function () {
- function testDPI(dpi) {
- return window.matchMedia(`(max-resolution: ${dpi}dpi)`).matches;
- }
- function bisect(low, high, func) {
- const mid = parseInt(((high - low) / 2) + low, 10);
- if (high - low <= 1) {
- return func(high) ? high : low;
- }
- if (func(mid)) {
- return bisect(low, mid, func);
- }
- return bisect(mid, high, func);
- }
- try {
- const dpi = bisect(0, 800, testDPI);
- return dpi >= 96 ? dpi : 96;
- } catch (e) {
- return 96;
- }
+ getDPI: function () {
+ // devicePixelRatio is given in dppx
+ // https://drafts.csswg.org/css-values/#resolution
+ // > due to the 1:96 fixed ratio of CSS *in* to CSS *px*, 1dppx is equivalent to 96dpi.
+ const dpi = Math.round(window.devicePixelRatio * 96);
+ return dpi >= 96 ? dpi : 96;
},
},
@@ -461,7 +447,7 @@ const GodotDisplay = {
godot_js_display_screen_dpi_get__sig: 'i',
godot_js_display_screen_dpi_get: function () {
- return GodotDisplay.findDPI();
+ return GodotDisplay.getDPI();
},
godot_js_display_pixel_ratio_get__sig: 'f',
diff --git a/platform/javascript/os_javascript.h b/platform/javascript/os_javascript.h
index 35e13c94fc..d932745177 100644
--- a/platform/javascript/os_javascript.h
+++ b/platform/javascript/os_javascript.h
@@ -98,7 +98,6 @@ public:
String get_user_data_dir() const override;
bool is_userfs_persistent() const override;
- bool is_single_window() const override { return true; }
void alert(const String &p_alert, const String &p_title = "ALERT!") override;
diff --git a/platform/linuxbsd/display_server_x11.cpp b/platform/linuxbsd/display_server_x11.cpp
index 8efbd2e3c5..63998e2fde 100644
--- a/platform/linuxbsd/display_server_x11.cpp
+++ b/platform/linuxbsd/display_server_x11.cpp
@@ -322,8 +322,8 @@ bool DisplayServerX11::tts_is_paused() const {
return tts->is_paused();
}
-Array DisplayServerX11::tts_get_voices() const {
- ERR_FAIL_COND_V(!tts, Array());
+TypedArray<Dictionary> DisplayServerX11::tts_get_voices() const {
+ ERR_FAIL_COND_V(!tts, TypedArray<Dictionary>());
return tts->get_voices();
}
diff --git a/platform/linuxbsd/display_server_x11.h b/platform/linuxbsd/display_server_x11.h
index 9ce6a557db..0174cfb881 100644
--- a/platform/linuxbsd/display_server_x11.h
+++ b/platform/linuxbsd/display_server_x11.h
@@ -308,7 +308,7 @@ public:
#ifdef SPEECHD_ENABLED
virtual bool tts_is_speaking() const override;
virtual bool tts_is_paused() const override;
- virtual Array tts_get_voices() const override;
+ virtual TypedArray<Dictionary> tts_get_voices() const override;
virtual void tts_speak(const String &p_text, const String &p_voice, int p_volume = 50, float p_pitch = 1.f, float p_rate = 1.f, int p_utterance_id = 0, bool p_interrupt = false) override;
virtual void tts_pause() override;
diff --git a/platform/macos/display_server_macos.h b/platform/macos/display_server_macos.h
index 54c479fc81..e305ff3593 100644
--- a/platform/macos/display_server_macos.h
+++ b/platform/macos/display_server_macos.h
@@ -196,6 +196,9 @@ private:
static NSCursor *_cursor_from_selector(SEL p_selector, SEL p_fallback = nil);
+ bool _has_help_menu() const;
+ NSMenuItem *_menu_add_item(const String &p_menu_root, const String &p_label, Key p_accel, int p_index, int *r_out);
+
public:
NSMenu *get_dock_menu() const;
void menu_callback(id p_sender);
@@ -224,15 +227,15 @@ public:
virtual bool has_feature(Feature p_feature) const override;
virtual String get_name() const override;
- virtual void global_menu_add_item(const String &p_menu_root, const String &p_label, const Callable &p_callback = Callable(), const Variant &p_tag = Variant(), Key p_accel = Key::NONE, int p_index = -1) override;
- virtual void global_menu_add_check_item(const String &p_menu_root, const String &p_label, const Callable &p_callback = Callable(), const Variant &p_tag = Variant(), Key p_accel = Key::NONE, int p_index = -1) override;
- virtual void global_menu_add_icon_item(const String &p_menu_root, const Ref<Texture2D> &p_icon, const String &p_label, const Callable &p_callback = Callable(), const Variant &p_tag = Variant(), Key p_accel = Key::NONE, int p_index = -1) override;
- virtual void global_menu_add_icon_check_item(const String &p_menu_root, const Ref<Texture2D> &p_icon, const String &p_label, const Callable &p_callback = Callable(), const Variant &p_tag = Variant(), Key p_accel = Key::NONE, int p_index = -1) override;
- virtual void global_menu_add_radio_check_item(const String &p_menu_root, const String &p_label, const Callable &p_callback = Callable(), const Variant &p_tag = Variant(), Key p_accel = Key::NONE, int p_index = -1) override;
- virtual void global_menu_add_icon_radio_check_item(const String &p_menu_root, const Ref<Texture2D> &p_icon, const String &p_label, const Callable &p_callback = Callable(), const Variant &p_tag = Variant(), Key p_accel = Key::NONE, int p_index = -1) override;
- virtual void global_menu_add_multistate_item(const String &p_menu_root, const String &p_label, int p_max_states, int p_default_state, const Callable &p_callback = Callable(), const Variant &p_tag = Variant(), Key p_accel = Key::NONE, int p_index = -1) override;
- virtual void global_menu_add_submenu_item(const String &p_menu_root, const String &p_label, const String &p_submenu, int p_index = -1) override;
- virtual void global_menu_add_separator(const String &p_menu_root, int p_index = -1) override;
+ virtual int global_menu_add_item(const String &p_menu_root, const String &p_label, const Callable &p_callback = Callable(), const Variant &p_tag = Variant(), Key p_accel = Key::NONE, int p_index = -1) override;
+ virtual int global_menu_add_check_item(const String &p_menu_root, const String &p_label, const Callable &p_callback = Callable(), const Variant &p_tag = Variant(), Key p_accel = Key::NONE, int p_index = -1) override;
+ virtual int global_menu_add_icon_item(const String &p_menu_root, const Ref<Texture2D> &p_icon, const String &p_label, const Callable &p_callback = Callable(), const Variant &p_tag = Variant(), Key p_accel = Key::NONE, int p_index = -1) override;
+ virtual int global_menu_add_icon_check_item(const String &p_menu_root, const Ref<Texture2D> &p_icon, const String &p_label, const Callable &p_callback = Callable(), const Variant &p_tag = Variant(), Key p_accel = Key::NONE, int p_index = -1) override;
+ virtual int global_menu_add_radio_check_item(const String &p_menu_root, const String &p_label, const Callable &p_callback = Callable(), const Variant &p_tag = Variant(), Key p_accel = Key::NONE, int p_index = -1) override;
+ virtual int global_menu_add_icon_radio_check_item(const String &p_menu_root, const Ref<Texture2D> &p_icon, const String &p_label, const Callable &p_callback = Callable(), const Variant &p_tag = Variant(), Key p_accel = Key::NONE, int p_index = -1) override;
+ virtual int global_menu_add_multistate_item(const String &p_menu_root, const String &p_label, int p_max_states, int p_default_state, const Callable &p_callback = Callable(), const Variant &p_tag = Variant(), Key p_accel = Key::NONE, int p_index = -1) override;
+ virtual int global_menu_add_submenu_item(const String &p_menu_root, const String &p_label, const String &p_submenu, int p_index = -1) override;
+ virtual int global_menu_add_separator(const String &p_menu_root, int p_index = -1) override;
virtual int global_menu_get_item_index_from_text(const String &p_menu_root, const String &p_text) const override;
virtual int global_menu_get_item_index_from_tag(const String &p_menu_root, const Variant &p_tag) const override;
@@ -250,6 +253,7 @@ public:
virtual int global_menu_get_item_state(const String &p_menu_root, int p_idx) const override;
virtual int global_menu_get_item_max_states(const String &p_menu_root, int p_idx) const override;
virtual Ref<Texture2D> global_menu_get_item_icon(const String &p_menu_root, int p_idx) const override;
+ virtual int global_menu_get_item_indentation_level(const String &p_menu_root, int p_idx) const override;
virtual void global_menu_set_item_checked(const String &p_menu_root, int p_idx, bool p_checked) override;
virtual void global_menu_set_item_checkable(const String &p_menu_root, int p_idx, bool p_checkable) override;
@@ -264,6 +268,7 @@ public:
virtual void global_menu_set_item_state(const String &p_menu_root, int p_idx, int p_state) override;
virtual void global_menu_set_item_max_states(const String &p_menu_root, int p_idx, int p_max_states) override;
virtual void global_menu_set_item_icon(const String &p_menu_root, int p_idx, const Ref<Texture2D> &p_icon) override;
+ virtual void global_menu_set_item_indentation_level(const String &p_menu_root, int p_idx, int p_level) override;
virtual int global_menu_get_item_count(const String &p_menu_root) const override;
@@ -272,7 +277,7 @@ public:
virtual bool tts_is_speaking() const override;
virtual bool tts_is_paused() const override;
- virtual Array tts_get_voices() const override;
+ virtual TypedArray<Dictionary> tts_get_voices() const override;
virtual void tts_speak(const String &p_text, const String &p_voice, int p_volume = 50, float p_pitch = 1.f, float p_rate = 1.f, int p_utterance_id = 0, bool p_interrupt = false) override;
virtual void tts_pause() override;
diff --git a/platform/macos/display_server_macos.mm b/platform/macos/display_server_macos.mm
index a49485154b..fa6bcda902 100644
--- a/platform/macos/display_server_macos.mm
+++ b/platform/macos/display_server_macos.mm
@@ -63,7 +63,7 @@
const NSMenu *DisplayServerMacOS::_get_menu_root(const String &p_menu_root) const {
const NSMenu *menu = nullptr;
- if (p_menu_root == "") {
+ if (p_menu_root == "" || p_menu_root.to_lower() == "_main") {
// Main menu.
menu = [NSApp mainMenu];
} else if (p_menu_root.to_lower() == "_dock") {
@@ -84,7 +84,7 @@ const NSMenu *DisplayServerMacOS::_get_menu_root(const String &p_menu_root) cons
NSMenu *DisplayServerMacOS::_get_menu_root(const String &p_menu_root) {
NSMenu *menu = nullptr;
- if (p_menu_root == "") {
+ if (p_menu_root == "" || p_menu_root.to_lower() == "_main") {
// Main menu.
menu = [NSApp mainMenu];
} else if (p_menu_root.to_lower() == "_dock") {
@@ -714,18 +714,51 @@ String DisplayServerMacOS::get_name() const {
return "macOS";
}
-void DisplayServerMacOS::global_menu_add_item(const String &p_menu_root, const String &p_label, const Callable &p_callback, const Variant &p_tag, Key p_accel, int p_index) {
- _THREAD_SAFE_METHOD_
+bool DisplayServerMacOS::_has_help_menu() const {
+ if ([NSApp helpMenu]) {
+ return true;
+ } else {
+ NSMenu *menu = [NSApp mainMenu];
+ const NSMenuItem *menu_item = [menu itemAtIndex:[menu numberOfItems] - 1];
+ if (menu_item) {
+ String menu_name = String::utf8([[menu_item title] UTF8String]);
+ if (menu_name == "Help" || menu_name == RTR("Help")) {
+ return true;
+ }
+ }
+ return false;
+ }
+}
+NSMenuItem *DisplayServerMacOS::_menu_add_item(const String &p_menu_root, const String &p_label, Key p_accel, int p_index, int *r_out) {
NSMenu *menu = _get_menu_root(p_menu_root);
if (menu) {
String keycode = KeyMappingMacOS::keycode_get_native_string(p_accel & KeyModifierMask::CODE_MASK);
NSMenuItem *menu_item;
- if (p_index != -1) {
- menu_item = [menu insertItemWithTitle:[NSString stringWithUTF8String:p_label.utf8().get_data()] action:@selector(globalMenuCallback:) keyEquivalent:[NSString stringWithUTF8String:keycode.utf8().get_data()] atIndex:p_index];
+ int item_count = ((menu == [NSApp mainMenu]) && _has_help_menu()) ? [menu numberOfItems] - 1 : [menu numberOfItems];
+ if ((menu == [NSApp mainMenu]) && (p_label == "Help" || p_label == RTR("Help"))) {
+ p_index = [menu numberOfItems];
+ } else if (p_index < 0) {
+ p_index = item_count;
} else {
- menu_item = [menu addItemWithTitle:[NSString stringWithUTF8String:p_label.utf8().get_data()] action:@selector(globalMenuCallback:) keyEquivalent:[NSString stringWithUTF8String:keycode.utf8().get_data()]];
+ if (menu == [NSApp mainMenu]) { // Skip Apple menu.
+ p_index++;
+ }
+ p_index = CLAMP(p_index, 0, item_count);
}
+ menu_item = [menu insertItemWithTitle:[NSString stringWithUTF8String:p_label.utf8().get_data()] action:@selector(globalMenuCallback:) keyEquivalent:[NSString stringWithUTF8String:keycode.utf8().get_data()] atIndex:p_index];
+ *r_out = (menu == [NSApp mainMenu]) ? p_index - 1 : p_index;
+ return menu_item;
+ }
+ return nullptr;
+}
+
+int DisplayServerMacOS::global_menu_add_item(const String &p_menu_root, const String &p_label, const Callable &p_callback, const Variant &p_tag, Key p_accel, int p_index) {
+ _THREAD_SAFE_METHOD_
+
+ int out = -1;
+ NSMenuItem *menu_item = _menu_add_item(p_menu_root, p_label, p_accel, p_index, &out);
+ if (menu_item) {
GodotMenuItem *obj = [[GodotMenuItem alloc] init];
obj->callback = p_callback;
obj->meta = p_tag;
@@ -735,20 +768,15 @@ void DisplayServerMacOS::global_menu_add_item(const String &p_menu_root, const S
[menu_item setKeyEquivalentModifierMask:KeyMappingMacOS::keycode_get_native_mask(p_accel)];
[menu_item setRepresentedObject:obj];
}
+ return out;
}
-void DisplayServerMacOS::global_menu_add_check_item(const String &p_menu_root, const String &p_label, const Callable &p_callback, const Variant &p_tag, Key p_accel, int p_index) {
+int DisplayServerMacOS::global_menu_add_check_item(const String &p_menu_root, const String &p_label, const Callable &p_callback, const Variant &p_tag, Key p_accel, int p_index) {
_THREAD_SAFE_METHOD_
- NSMenu *menu = _get_menu_root(p_menu_root);
- if (menu) {
- String keycode = KeyMappingMacOS::keycode_get_native_string(p_accel & KeyModifierMask::CODE_MASK);
- NSMenuItem *menu_item;
- if (p_index != -1) {
- menu_item = [menu insertItemWithTitle:[NSString stringWithUTF8String:p_label.utf8().get_data()] action:@selector(globalMenuCallback:) keyEquivalent:[NSString stringWithUTF8String:keycode.utf8().get_data()] atIndex:p_index];
- } else {
- menu_item = [menu addItemWithTitle:[NSString stringWithUTF8String:p_label.utf8().get_data()] action:@selector(globalMenuCallback:) keyEquivalent:[NSString stringWithUTF8String:keycode.utf8().get_data()]];
- }
+ int out = -1;
+ NSMenuItem *menu_item = _menu_add_item(p_menu_root, p_label, p_accel, p_index, &out);
+ if (menu_item) {
GodotMenuItem *obj = [[GodotMenuItem alloc] init];
obj->callback = p_callback;
obj->meta = p_tag;
@@ -758,20 +786,15 @@ void DisplayServerMacOS::global_menu_add_check_item(const String &p_menu_root, c
[menu_item setKeyEquivalentModifierMask:KeyMappingMacOS::keycode_get_native_mask(p_accel)];
[menu_item setRepresentedObject:obj];
}
+ return out;
}
-void DisplayServerMacOS::global_menu_add_icon_item(const String &p_menu_root, const Ref<Texture2D> &p_icon, const String &p_label, const Callable &p_callback, const Variant &p_tag, Key p_accel, int p_index) {
+int DisplayServerMacOS::global_menu_add_icon_item(const String &p_menu_root, const Ref<Texture2D> &p_icon, const String &p_label, const Callable &p_callback, const Variant &p_tag, Key p_accel, int p_index) {
_THREAD_SAFE_METHOD_
- NSMenu *menu = _get_menu_root(p_menu_root);
- if (menu) {
- String keycode = KeyMappingMacOS::keycode_get_native_string(p_accel & KeyModifierMask::CODE_MASK);
- NSMenuItem *menu_item;
- if (p_index != -1) {
- menu_item = [menu insertItemWithTitle:[NSString stringWithUTF8String:p_label.utf8().get_data()] action:@selector(globalMenuCallback:) keyEquivalent:[NSString stringWithUTF8String:keycode.utf8().get_data()] atIndex:p_index];
- } else {
- menu_item = [menu addItemWithTitle:[NSString stringWithUTF8String:p_label.utf8().get_data()] action:@selector(globalMenuCallback:) keyEquivalent:[NSString stringWithUTF8String:keycode.utf8().get_data()]];
- }
+ int out = -1;
+ NSMenuItem *menu_item = _menu_add_item(p_menu_root, p_label, p_accel, p_index, &out);
+ if (menu_item) {
GodotMenuItem *obj = [[GodotMenuItem alloc] init];
obj->callback = p_callback;
obj->meta = p_tag;
@@ -790,20 +813,15 @@ void DisplayServerMacOS::global_menu_add_icon_item(const String &p_menu_root, co
[menu_item setKeyEquivalentModifierMask:KeyMappingMacOS::keycode_get_native_mask(p_accel)];
[menu_item setRepresentedObject:obj];
}
+ return out;
}
-void DisplayServerMacOS::global_menu_add_icon_check_item(const String &p_menu_root, const Ref<Texture2D> &p_icon, const String &p_label, const Callable &p_callback, const Variant &p_tag, Key p_accel, int p_index) {
+int DisplayServerMacOS::global_menu_add_icon_check_item(const String &p_menu_root, const Ref<Texture2D> &p_icon, const String &p_label, const Callable &p_callback, const Variant &p_tag, Key p_accel, int p_index) {
_THREAD_SAFE_METHOD_
- NSMenu *menu = _get_menu_root(p_menu_root);
- if (menu) {
- String keycode = KeyMappingMacOS::keycode_get_native_string(p_accel & KeyModifierMask::CODE_MASK);
- NSMenuItem *menu_item;
- if (p_index != -1) {
- menu_item = [menu insertItemWithTitle:[NSString stringWithUTF8String:p_label.utf8().get_data()] action:@selector(globalMenuCallback:) keyEquivalent:[NSString stringWithUTF8String:keycode.utf8().get_data()] atIndex:p_index];
- } else {
- menu_item = [menu addItemWithTitle:[NSString stringWithUTF8String:p_label.utf8().get_data()] action:@selector(globalMenuCallback:) keyEquivalent:[NSString stringWithUTF8String:keycode.utf8().get_data()]];
- }
+ int out = -1;
+ NSMenuItem *menu_item = _menu_add_item(p_menu_root, p_label, p_accel, p_index, &out);
+ if (menu_item) {
GodotMenuItem *obj = [[GodotMenuItem alloc] init];
obj->callback = p_callback;
obj->meta = p_tag;
@@ -822,20 +840,15 @@ void DisplayServerMacOS::global_menu_add_icon_check_item(const String &p_menu_ro
[menu_item setKeyEquivalentModifierMask:KeyMappingMacOS::keycode_get_native_mask(p_accel)];
[menu_item setRepresentedObject:obj];
}
+ return out;
}
-void DisplayServerMacOS::global_menu_add_radio_check_item(const String &p_menu_root, const String &p_label, const Callable &p_callback, const Variant &p_tag, Key p_accel, int p_index) {
+int DisplayServerMacOS::global_menu_add_radio_check_item(const String &p_menu_root, const String &p_label, const Callable &p_callback, const Variant &p_tag, Key p_accel, int p_index) {
_THREAD_SAFE_METHOD_
- NSMenu *menu = _get_menu_root(p_menu_root);
- if (menu) {
- String keycode = KeyMappingMacOS::keycode_get_native_string(p_accel & KeyModifierMask::CODE_MASK);
- NSMenuItem *menu_item;
- if (p_index != -1) {
- menu_item = [menu insertItemWithTitle:[NSString stringWithUTF8String:p_label.utf8().get_data()] action:@selector(globalMenuCallback:) keyEquivalent:[NSString stringWithUTF8String:keycode.utf8().get_data()] atIndex:p_index];
- } else {
- menu_item = [menu addItemWithTitle:[NSString stringWithUTF8String:p_label.utf8().get_data()] action:@selector(globalMenuCallback:) keyEquivalent:[NSString stringWithUTF8String:keycode.utf8().get_data()]];
- }
+ int out = -1;
+ NSMenuItem *menu_item = _menu_add_item(p_menu_root, p_label, p_accel, p_index, &out);
+ if (menu_item) {
GodotMenuItem *obj = [[GodotMenuItem alloc] init];
obj->callback = p_callback;
obj->meta = p_tag;
@@ -845,20 +858,15 @@ void DisplayServerMacOS::global_menu_add_radio_check_item(const String &p_menu_r
[menu_item setKeyEquivalentModifierMask:KeyMappingMacOS::keycode_get_native_mask(p_accel)];
[menu_item setRepresentedObject:obj];
}
+ return out;
}
-void DisplayServerMacOS::global_menu_add_icon_radio_check_item(const String &p_menu_root, const Ref<Texture2D> &p_icon, const String &p_label, const Callable &p_callback, const Variant &p_tag, Key p_accel, int p_index) {
+int DisplayServerMacOS::global_menu_add_icon_radio_check_item(const String &p_menu_root, const Ref<Texture2D> &p_icon, const String &p_label, const Callable &p_callback, const Variant &p_tag, Key p_accel, int p_index) {
_THREAD_SAFE_METHOD_
- NSMenu *menu = _get_menu_root(p_menu_root);
- if (menu) {
- String keycode = KeyMappingMacOS::keycode_get_native_string(p_accel & KeyModifierMask::CODE_MASK);
- NSMenuItem *menu_item;
- if (p_index != -1) {
- menu_item = [menu insertItemWithTitle:[NSString stringWithUTF8String:p_label.utf8().get_data()] action:@selector(globalMenuCallback:) keyEquivalent:[NSString stringWithUTF8String:keycode.utf8().get_data()] atIndex:p_index];
- } else {
- menu_item = [menu addItemWithTitle:[NSString stringWithUTF8String:p_label.utf8().get_data()] action:@selector(globalMenuCallback:) keyEquivalent:[NSString stringWithUTF8String:keycode.utf8().get_data()]];
- }
+ int out = -1;
+ NSMenuItem *menu_item = _menu_add_item(p_menu_root, p_label, p_accel, p_index, &out);
+ if (menu_item) {
GodotMenuItem *obj = [[GodotMenuItem alloc] init];
obj->callback = p_callback;
obj->meta = p_tag;
@@ -877,20 +885,31 @@ void DisplayServerMacOS::global_menu_add_icon_radio_check_item(const String &p_m
[menu_item setKeyEquivalentModifierMask:KeyMappingMacOS::keycode_get_native_mask(p_accel)];
[menu_item setRepresentedObject:obj];
}
+ return out;
}
-void DisplayServerMacOS::global_menu_add_multistate_item(const String &p_menu_root, const String &p_label, int p_max_states, int p_default_state, const Callable &p_callback, const Variant &p_tag, Key p_accel, int p_index) {
+int DisplayServerMacOS::global_menu_add_multistate_item(const String &p_menu_root, const String &p_label, int p_max_states, int p_default_state, const Callable &p_callback, const Variant &p_tag, Key p_accel, int p_index) {
_THREAD_SAFE_METHOD_
NSMenu *menu = _get_menu_root(p_menu_root);
+ int out = -1;
if (menu) {
String keycode = KeyMappingMacOS::keycode_get_native_string(p_accel & KeyModifierMask::CODE_MASK);
NSMenuItem *menu_item;
- if (p_index != -1) {
- menu_item = [menu insertItemWithTitle:[NSString stringWithUTF8String:p_label.utf8().get_data()] action:@selector(globalMenuCallback:) keyEquivalent:[NSString stringWithUTF8String:keycode.utf8().get_data()] atIndex:p_index];
+ int item_count = ((menu == [NSApp mainMenu]) && _has_help_menu()) ? [menu numberOfItems] - 1 : [menu numberOfItems];
+ if ((menu == [NSApp mainMenu]) && (p_label == "Help" || p_label == RTR("Help"))) {
+ p_index = [menu numberOfItems];
+ } else if (p_index < 0) {
+ p_index = item_count;
} else {
- menu_item = [menu addItemWithTitle:[NSString stringWithUTF8String:p_label.utf8().get_data()] action:@selector(globalMenuCallback:) keyEquivalent:[NSString stringWithUTF8String:keycode.utf8().get_data()]];
+ if (menu == [NSApp mainMenu]) { // Skip Apple menu.
+ p_index++;
+ }
+ p_index = CLAMP(p_index, 0, item_count);
}
+ menu_item = [menu insertItemWithTitle:[NSString stringWithUTF8String:p_label.utf8().get_data()] action:@selector(globalMenuCallback:) keyEquivalent:[NSString stringWithUTF8String:keycode.utf8().get_data()] atIndex:p_index];
+ out = (menu == [NSApp mainMenu]) ? p_index - 1 : p_index;
+
GodotMenuItem *obj = [[GodotMenuItem alloc] init];
obj->callback = p_callback;
obj->meta = p_tag;
@@ -900,44 +919,69 @@ void DisplayServerMacOS::global_menu_add_multistate_item(const String &p_menu_ro
[menu_item setKeyEquivalentModifierMask:KeyMappingMacOS::keycode_get_native_mask(p_accel)];
[menu_item setRepresentedObject:obj];
}
+ return out;
}
-void DisplayServerMacOS::global_menu_add_submenu_item(const String &p_menu_root, const String &p_label, const String &p_submenu, int p_index) {
+int DisplayServerMacOS::global_menu_add_submenu_item(const String &p_menu_root, const String &p_label, const String &p_submenu, int p_index) {
_THREAD_SAFE_METHOD_
NSMenu *menu = _get_menu_root(p_menu_root);
NSMenu *sub_menu = _get_menu_root(p_submenu);
+ int out = -1;
if (menu && sub_menu) {
if (sub_menu == menu) {
ERR_PRINT("Can't set submenu to self!");
- return;
+ return -1;
}
if ([sub_menu supermenu]) {
ERR_PRINT("Can't set submenu to menu that is already a submenu of some other menu!");
- return;
+ return -1;
}
NSMenuItem *menu_item;
- if (p_index != -1) {
- menu_item = [menu insertItemWithTitle:[NSString stringWithUTF8String:p_label.utf8().get_data()] action:nil keyEquivalent:@"" atIndex:p_index];
+ int item_count = ((menu == [NSApp mainMenu]) && _has_help_menu()) ? [menu numberOfItems] - 1 : [menu numberOfItems];
+ if ((menu == [NSApp mainMenu]) && (p_label == "Help" || p_label == RTR("Help"))) {
+ p_index = [menu numberOfItems];
+ } else if (p_index < 0) {
+ p_index = item_count;
} else {
- menu_item = [menu addItemWithTitle:[NSString stringWithUTF8String:p_label.utf8().get_data()] action:nil keyEquivalent:@""];
+ if (menu == [NSApp mainMenu]) { // Skip Apple menu.
+ p_index++;
+ }
+ p_index = CLAMP(p_index, 0, item_count);
}
+ menu_item = [menu insertItemWithTitle:[NSString stringWithUTF8String:p_label.utf8().get_data()] action:nil keyEquivalent:@"" atIndex:p_index];
+ out = (menu == [NSApp mainMenu]) ? p_index - 1 : p_index;
+
+ GodotMenuItem *obj = [[GodotMenuItem alloc] init];
+ obj->callback = Callable();
+ obj->checkable_type = CHECKABLE_TYPE_NONE;
+ obj->max_states = 0;
+ obj->state = 0;
+ [menu_item setRepresentedObject:obj];
+
[sub_menu setTitle:[NSString stringWithUTF8String:p_label.utf8().get_data()]];
[menu setSubmenu:sub_menu forItem:menu_item];
}
+ return out;
}
-void DisplayServerMacOS::global_menu_add_separator(const String &p_menu_root, int p_index) {
+int DisplayServerMacOS::global_menu_add_separator(const String &p_menu_root, int p_index) {
_THREAD_SAFE_METHOD_
NSMenu *menu = _get_menu_root(p_menu_root);
if (menu) {
- if (p_index != -1) {
- [menu insertItem:[NSMenuItem separatorItem] atIndex:p_index];
+ if (menu == [NSApp mainMenu]) { // Do not add separators into main menu.
+ return -1;
+ }
+ if (p_index < 0) {
+ p_index = [menu numberOfItems];
} else {
- [menu addItem:[NSMenuItem separatorItem]];
+ p_index = CLAMP(p_index, 0, [menu numberOfItems]);
}
+ [menu insertItem:[NSMenuItem separatorItem] atIndex:p_index];
+ return p_index;
}
+ return -1;
}
int DisplayServerMacOS::global_menu_get_item_index_from_text(const String &p_menu_root, const String &p_text) const {
@@ -945,7 +989,11 @@ int DisplayServerMacOS::global_menu_get_item_index_from_text(const String &p_men
const NSMenu *menu = _get_menu_root(p_menu_root);
if (menu) {
- return [menu indexOfItemWithTitle:[NSString stringWithUTF8String:p_text.utf8().get_data()]];
+ if (menu == [NSApp mainMenu]) { // Skip Apple menu.
+ return [menu indexOfItemWithTitle:[NSString stringWithUTF8String:p_text.utf8().get_data()]] - 1;
+ } else {
+ return [menu indexOfItemWithTitle:[NSString stringWithUTF8String:p_text.utf8().get_data()]];
+ }
}
return -1;
@@ -956,12 +1004,16 @@ int DisplayServerMacOS::global_menu_get_item_index_from_tag(const String &p_menu
const NSMenu *menu = _get_menu_root(p_menu_root);
if (menu) {
- for (NSInteger i = 0; i < [menu numberOfItems]; i++) {
+ for (NSInteger i = (menu == [NSApp mainMenu]) ? 1 : 0; i < [menu numberOfItems]; i++) {
const NSMenuItem *menu_item = [menu itemAtIndex:i];
if (menu_item) {
const GodotMenuItem *obj = [menu_item representedObject];
if (obj && obj->meta == p_tag) {
- return i;
+ if (menu == [NSApp mainMenu]) { // Skip Apple menu.
+ return i - 1;
+ } else {
+ return i;
+ }
}
}
}
@@ -975,6 +1027,11 @@ bool DisplayServerMacOS::global_menu_is_item_checked(const String &p_menu_root,
const NSMenu *menu = _get_menu_root(p_menu_root);
if (menu) {
+ ERR_FAIL_COND_V(p_idx < 0, false);
+ if (menu == [NSApp mainMenu]) { // Skip Apple menu.
+ p_idx++;
+ }
+ ERR_FAIL_COND_V(p_idx >= [menu numberOfItems], false);
const NSMenuItem *menu_item = [menu itemAtIndex:p_idx];
if (menu_item) {
return ([menu_item state] == NSControlStateValueOn);
@@ -988,6 +1045,11 @@ bool DisplayServerMacOS::global_menu_is_item_checkable(const String &p_menu_root
const NSMenu *menu = _get_menu_root(p_menu_root);
if (menu) {
+ ERR_FAIL_COND_V(p_idx < 0, false);
+ if (menu == [NSApp mainMenu]) { // Skip Apple menu.
+ p_idx++;
+ }
+ ERR_FAIL_COND_V(p_idx >= [menu numberOfItems], false);
const NSMenuItem *menu_item = [menu itemAtIndex:p_idx];
if (menu_item) {
GodotMenuItem *obj = [menu_item representedObject];
@@ -1004,6 +1066,11 @@ bool DisplayServerMacOS::global_menu_is_item_radio_checkable(const String &p_men
const NSMenu *menu = _get_menu_root(p_menu_root);
if (menu) {
+ ERR_FAIL_COND_V(p_idx < 0, false);
+ if (menu == [NSApp mainMenu]) { // Skip Apple menu.
+ p_idx++;
+ }
+ ERR_FAIL_COND_V(p_idx >= [menu numberOfItems], false);
const NSMenuItem *menu_item = [menu itemAtIndex:p_idx];
if (menu_item) {
GodotMenuItem *obj = [menu_item representedObject];
@@ -1020,6 +1087,11 @@ Callable DisplayServerMacOS::global_menu_get_item_callback(const String &p_menu_
const NSMenu *menu = _get_menu_root(p_menu_root);
if (menu) {
+ ERR_FAIL_COND_V(p_idx < 0, Callable());
+ if (menu == [NSApp mainMenu]) { // Skip Apple menu.
+ p_idx++;
+ }
+ ERR_FAIL_COND_V(p_idx >= [menu numberOfItems], Callable());
const NSMenuItem *menu_item = [menu itemAtIndex:p_idx];
if (menu_item) {
GodotMenuItem *obj = [menu_item representedObject];
@@ -1036,6 +1108,11 @@ Variant DisplayServerMacOS::global_menu_get_item_tag(const String &p_menu_root,
const NSMenu *menu = _get_menu_root(p_menu_root);
if (menu) {
+ ERR_FAIL_COND_V(p_idx < 0, Variant());
+ if (menu == [NSApp mainMenu]) { // Skip Apple menu.
+ p_idx++;
+ }
+ ERR_FAIL_COND_V(p_idx >= [menu numberOfItems], Variant());
const NSMenuItem *menu_item = [menu itemAtIndex:p_idx];
if (menu_item) {
GodotMenuItem *obj = [menu_item representedObject];
@@ -1052,6 +1129,11 @@ String DisplayServerMacOS::global_menu_get_item_text(const String &p_menu_root,
const NSMenu *menu = _get_menu_root(p_menu_root);
if (menu) {
+ ERR_FAIL_COND_V(p_idx < 0, String());
+ if (menu == [NSApp mainMenu]) { // Skip Apple menu.
+ p_idx++;
+ }
+ ERR_FAIL_COND_V(p_idx >= [menu numberOfItems], String());
const NSMenuItem *menu_item = [menu itemAtIndex:p_idx];
if (menu_item) {
return String::utf8([[menu_item title] UTF8String]);
@@ -1065,6 +1147,11 @@ String DisplayServerMacOS::global_menu_get_item_submenu(const String &p_menu_roo
const NSMenu *menu = _get_menu_root(p_menu_root);
if (menu) {
+ ERR_FAIL_COND_V(p_idx < 0, String());
+ if (menu == [NSApp mainMenu]) { // Skip Apple menu.
+ p_idx++;
+ }
+ ERR_FAIL_COND_V(p_idx >= [menu numberOfItems], String());
const NSMenuItem *menu_item = [menu itemAtIndex:p_idx];
if (menu_item) {
const NSMenu *sub_menu = [menu_item submenu];
@@ -1085,6 +1172,11 @@ Key DisplayServerMacOS::global_menu_get_item_accelerator(const String &p_menu_ro
const NSMenu *menu = _get_menu_root(p_menu_root);
if (menu) {
+ ERR_FAIL_COND_V(p_idx < 0, Key::NONE);
+ if (menu == [NSApp mainMenu]) { // Skip Apple menu.
+ p_idx++;
+ }
+ ERR_FAIL_COND_V(p_idx >= [menu numberOfItems], Key::NONE);
const NSMenuItem *menu_item = [menu itemAtIndex:p_idx];
if (menu_item) {
String ret = String::utf8([[menu_item keyEquivalent] UTF8String]);
@@ -1116,6 +1208,11 @@ bool DisplayServerMacOS::global_menu_is_item_disabled(const String &p_menu_root,
const NSMenu *menu = _get_menu_root(p_menu_root);
if (menu) {
+ ERR_FAIL_COND_V(p_idx < 0, false);
+ if (menu == [NSApp mainMenu]) { // Skip Apple menu.
+ p_idx++;
+ }
+ ERR_FAIL_COND_V(p_idx >= [menu numberOfItems], false);
const NSMenuItem *menu_item = [menu itemAtIndex:p_idx];
if (menu_item) {
return ![menu_item isEnabled];
@@ -1129,6 +1226,11 @@ String DisplayServerMacOS::global_menu_get_item_tooltip(const String &p_menu_roo
const NSMenu *menu = _get_menu_root(p_menu_root);
if (menu) {
+ ERR_FAIL_COND_V(p_idx < 0, String());
+ if (menu == [NSApp mainMenu]) { // Skip Apple menu.
+ p_idx++;
+ }
+ ERR_FAIL_COND_V(p_idx >= [menu numberOfItems], String());
const NSMenuItem *menu_item = [menu itemAtIndex:p_idx];
if (menu_item) {
return String::utf8([[menu_item toolTip] UTF8String]);
@@ -1142,6 +1244,11 @@ int DisplayServerMacOS::global_menu_get_item_state(const String &p_menu_root, in
const NSMenu *menu = _get_menu_root(p_menu_root);
if (menu) {
+ ERR_FAIL_COND_V(p_idx < 0, 0);
+ if (menu == [NSApp mainMenu]) { // Skip Apple menu.
+ p_idx++;
+ }
+ ERR_FAIL_COND_V(p_idx >= [menu numberOfItems], 0);
const NSMenuItem *menu_item = [menu itemAtIndex:p_idx];
if (menu_item) {
GodotMenuItem *obj = [menu_item representedObject];
@@ -1158,6 +1265,11 @@ int DisplayServerMacOS::global_menu_get_item_max_states(const String &p_menu_roo
const NSMenu *menu = _get_menu_root(p_menu_root);
if (menu) {
+ ERR_FAIL_COND_V(p_idx < 0, 0);
+ if (menu == [NSApp mainMenu]) { // Skip Apple menu.
+ p_idx++;
+ }
+ ERR_FAIL_COND_V(p_idx >= [menu numberOfItems], 0);
const NSMenuItem *menu_item = [menu itemAtIndex:p_idx];
if (menu_item) {
GodotMenuItem *obj = [menu_item representedObject];
@@ -1174,6 +1286,11 @@ Ref<Texture2D> DisplayServerMacOS::global_menu_get_item_icon(const String &p_men
const NSMenu *menu = _get_menu_root(p_menu_root);
if (menu) {
+ ERR_FAIL_COND_V(p_idx < 0, Ref<Texture2D>());
+ if (menu == [NSApp mainMenu]) { // Skip Apple menu.
+ p_idx++;
+ }
+ ERR_FAIL_COND_V(p_idx >= [menu numberOfItems], Ref<Texture2D>());
const NSMenuItem *menu_item = [menu itemAtIndex:p_idx];
if (menu_item) {
GodotMenuItem *obj = [menu_item representedObject];
@@ -1187,14 +1304,34 @@ Ref<Texture2D> DisplayServerMacOS::global_menu_get_item_icon(const String &p_men
return Ref<Texture2D>();
}
+int DisplayServerMacOS::global_menu_get_item_indentation_level(const String &p_menu_root, int p_idx) const {
+ _THREAD_SAFE_METHOD_
+
+ const NSMenu *menu = _get_menu_root(p_menu_root);
+ if (menu) {
+ ERR_FAIL_COND_V(p_idx < 0, 0);
+ if (menu == [NSApp mainMenu]) { // Skip Apple menu.
+ p_idx++;
+ }
+ ERR_FAIL_COND_V(p_idx >= [menu numberOfItems], 0);
+ const NSMenuItem *menu_item = [menu itemAtIndex:p_idx];
+ if (menu_item) {
+ return [menu_item indentationLevel];
+ }
+ }
+ return 0;
+}
+
void DisplayServerMacOS::global_menu_set_item_checked(const String &p_menu_root, int p_idx, bool p_checked) {
_THREAD_SAFE_METHOD_
NSMenu *menu = _get_menu_root(p_menu_root);
if (menu) {
- if ((menu == [NSApp mainMenu]) && (p_idx == 0)) { // Do not edit Apple menu.
- return;
+ ERR_FAIL_COND(p_idx < 0);
+ if (menu == [NSApp mainMenu]) { // Skip Apple menu.
+ p_idx++;
}
+ ERR_FAIL_COND(p_idx >= [menu numberOfItems]);
NSMenuItem *menu_item = [menu itemAtIndex:p_idx];
if (menu_item) {
if (p_checked) {
@@ -1211,12 +1348,15 @@ void DisplayServerMacOS::global_menu_set_item_checkable(const String &p_menu_roo
NSMenu *menu = _get_menu_root(p_menu_root);
if (menu) {
- if ((menu == [NSApp mainMenu]) && (p_idx == 0)) { // Do not edit Apple menu.
- return;
+ ERR_FAIL_COND(p_idx < 0);
+ if (menu == [NSApp mainMenu]) { // Skip Apple menu.
+ p_idx++;
}
+ ERR_FAIL_COND(p_idx >= [menu numberOfItems]);
NSMenuItem *menu_item = [menu itemAtIndex:p_idx];
if (menu_item) {
GodotMenuItem *obj = [menu_item representedObject];
+ ERR_FAIL_COND(!obj);
obj->checkable_type = (p_checkable) ? CHECKABLE_TYPE_CHECK_BOX : CHECKABLE_TYPE_NONE;
}
}
@@ -1227,12 +1367,15 @@ void DisplayServerMacOS::global_menu_set_item_radio_checkable(const String &p_me
NSMenu *menu = _get_menu_root(p_menu_root);
if (menu) {
- if ((menu == [NSApp mainMenu]) && (p_idx == 0)) { // Do not edit Apple menu.
- return;
+ ERR_FAIL_COND(p_idx < 0);
+ if (menu == [NSApp mainMenu]) { // Skip Apple menu.
+ p_idx++;
}
+ ERR_FAIL_COND(p_idx >= [menu numberOfItems]);
NSMenuItem *menu_item = [menu itemAtIndex:p_idx];
if (menu_item) {
GodotMenuItem *obj = [menu_item representedObject];
+ ERR_FAIL_COND(!obj);
obj->checkable_type = (p_checkable) ? CHECKABLE_TYPE_RADIO_BUTTON : CHECKABLE_TYPE_NONE;
}
}
@@ -1243,12 +1386,15 @@ void DisplayServerMacOS::global_menu_set_item_callback(const String &p_menu_root
NSMenu *menu = _get_menu_root(p_menu_root);
if (menu) {
- if ((menu == [NSApp mainMenu]) && (p_idx == 0)) { // Do not edit Apple menu.
- return;
+ ERR_FAIL_COND(p_idx < 0);
+ if (menu == [NSApp mainMenu]) { // Skip Apple menu.
+ p_idx++;
}
+ ERR_FAIL_COND(p_idx >= [menu numberOfItems]);
NSMenuItem *menu_item = [menu itemAtIndex:p_idx];
if (menu_item) {
GodotMenuItem *obj = [menu_item representedObject];
+ ERR_FAIL_COND(!obj);
obj->callback = p_callback;
}
}
@@ -1259,12 +1405,15 @@ void DisplayServerMacOS::global_menu_set_item_tag(const String &p_menu_root, int
NSMenu *menu = _get_menu_root(p_menu_root);
if (menu) {
- if ((menu == [NSApp mainMenu]) && (p_idx == 0)) { // Do not edit Apple menu.
- return;
+ ERR_FAIL_COND(p_idx < 0);
+ if (menu == [NSApp mainMenu]) { // Skip Apple menu.
+ p_idx++;
}
+ ERR_FAIL_COND(p_idx >= [menu numberOfItems]);
NSMenuItem *menu_item = [menu itemAtIndex:p_idx];
if (menu_item) {
GodotMenuItem *obj = [menu_item representedObject];
+ ERR_FAIL_COND(!obj);
obj->meta = p_tag;
}
}
@@ -1275,9 +1424,11 @@ void DisplayServerMacOS::global_menu_set_item_text(const String &p_menu_root, in
NSMenu *menu = _get_menu_root(p_menu_root);
if (menu) {
- if ((menu == [NSApp mainMenu]) && (p_idx == 0)) { // Do not edit Apple menu.
- return;
+ ERR_FAIL_COND(p_idx < 0);
+ if (menu == [NSApp mainMenu]) { // Skip Apple menu.
+ p_idx++;
}
+ ERR_FAIL_COND(p_idx >= [menu numberOfItems]);
NSMenuItem *menu_item = [menu itemAtIndex:p_idx];
if (menu_item) {
[menu_item setTitle:[NSString stringWithUTF8String:p_text.utf8().get_data()]];
@@ -1299,9 +1450,11 @@ void DisplayServerMacOS::global_menu_set_item_submenu(const String &p_menu_root,
ERR_PRINT("Can't set submenu to menu that is already a submenu of some other menu!");
return;
}
- if ((menu == [NSApp mainMenu]) && (p_idx == 0)) { // Do not edit Apple menu.
- return;
+ ERR_FAIL_COND(p_idx < 0);
+ if (menu == [NSApp mainMenu]) { // Skip Apple menu.
+ p_idx++;
}
+ ERR_FAIL_COND(p_idx >= [menu numberOfItems]);
NSMenuItem *menu_item = [menu itemAtIndex:p_idx];
if (menu_item) {
[menu setSubmenu:sub_menu forItem:menu_item];
@@ -1314,9 +1467,11 @@ void DisplayServerMacOS::global_menu_set_item_accelerator(const String &p_menu_r
NSMenu *menu = _get_menu_root(p_menu_root);
if (menu) {
- if ((menu == [NSApp mainMenu]) && (p_idx == 0)) { // Do not edit Apple menu.
- return;
+ ERR_FAIL_COND(p_idx < 0);
+ if (menu == [NSApp mainMenu]) { // Skip Apple menu.
+ p_idx++;
}
+ ERR_FAIL_COND(p_idx >= [menu numberOfItems]);
NSMenuItem *menu_item = [menu itemAtIndex:p_idx];
if (menu_item) {
[menu_item setKeyEquivalentModifierMask:KeyMappingMacOS::keycode_get_native_mask(p_keycode)];
@@ -1331,9 +1486,11 @@ void DisplayServerMacOS::global_menu_set_item_disabled(const String &p_menu_root
NSMenu *menu = _get_menu_root(p_menu_root);
if (menu) {
- if ((menu == [NSApp mainMenu]) && (p_idx == 0)) { // Do not edit Apple menu.
- return;
+ ERR_FAIL_COND(p_idx < 0);
+ if (menu == [NSApp mainMenu]) { // Skip Apple menu.
+ p_idx++;
}
+ ERR_FAIL_COND(p_idx >= [menu numberOfItems]);
NSMenuItem *menu_item = [menu itemAtIndex:p_idx];
if (menu_item) {
[menu_item setEnabled:(!p_disabled)];
@@ -1346,9 +1503,11 @@ void DisplayServerMacOS::global_menu_set_item_tooltip(const String &p_menu_root,
NSMenu *menu = _get_menu_root(p_menu_root);
if (menu) {
- if ((menu == [NSApp mainMenu]) && (p_idx == 0)) { // Do not edit Apple menu.
- return;
+ ERR_FAIL_COND(p_idx < 0);
+ if (menu == [NSApp mainMenu]) { // Skip Apple menu.
+ p_idx++;
}
+ ERR_FAIL_COND(p_idx >= [menu numberOfItems]);
NSMenuItem *menu_item = [menu itemAtIndex:p_idx];
if (menu_item) {
[menu_item setToolTip:[NSString stringWithUTF8String:p_tooltip.utf8().get_data()]];
@@ -1361,15 +1520,16 @@ void DisplayServerMacOS::global_menu_set_item_state(const String &p_menu_root, i
NSMenu *menu = _get_menu_root(p_menu_root);
if (menu) {
- if ((menu == [NSApp mainMenu]) && (p_idx == 0)) { // Do not edit Apple menu.
- return;
+ ERR_FAIL_COND(p_idx < 0);
+ if (menu == [NSApp mainMenu]) { // Skip Apple menu.
+ p_idx++;
}
+ ERR_FAIL_COND(p_idx >= [menu numberOfItems]);
NSMenuItem *menu_item = [menu itemAtIndex:p_idx];
if (menu_item) {
GodotMenuItem *obj = [menu_item representedObject];
- if (obj) {
- obj->state = p_state;
- }
+ ERR_FAIL_COND(!obj);
+ obj->state = p_state;
}
}
}
@@ -1379,15 +1539,16 @@ void DisplayServerMacOS::global_menu_set_item_max_states(const String &p_menu_ro
NSMenu *menu = _get_menu_root(p_menu_root);
if (menu) {
- if ((menu == [NSApp mainMenu]) && (p_idx == 0)) { // Do not edit Apple menu.
- return;
+ ERR_FAIL_COND(p_idx < 0);
+ if (menu == [NSApp mainMenu]) { // Skip Apple menu.
+ p_idx++;
}
+ ERR_FAIL_COND(p_idx >= [menu numberOfItems]);
NSMenuItem *menu_item = [menu itemAtIndex:p_idx];
if (menu_item) {
GodotMenuItem *obj = [menu_item representedObject];
- if (obj) {
- obj->max_states = p_max_states;
- }
+ ERR_FAIL_COND(!obj);
+ obj->max_states = p_max_states;
}
}
}
@@ -1397,12 +1558,15 @@ void DisplayServerMacOS::global_menu_set_item_icon(const String &p_menu_root, in
NSMenu *menu = _get_menu_root(p_menu_root);
if (menu) {
- if ((menu == [NSApp mainMenu]) && (p_idx == 0)) { // Do not edit Apple menu.
- return;
+ ERR_FAIL_COND(p_idx < 0);
+ if (menu == [NSApp mainMenu]) { // Skip Apple menu.
+ p_idx++;
}
+ ERR_FAIL_COND(p_idx >= [menu numberOfItems]);
NSMenuItem *menu_item = [menu itemAtIndex:p_idx];
if (menu_item) {
GodotMenuItem *obj = [menu_item representedObject];
+ ERR_FAIL_COND(!obj);
if (p_icon.is_valid()) {
obj->img = p_icon->get_image();
obj->img = obj->img->duplicate();
@@ -1419,12 +1583,33 @@ void DisplayServerMacOS::global_menu_set_item_icon(const String &p_menu_root, in
}
}
+void DisplayServerMacOS::global_menu_set_item_indentation_level(const String &p_menu_root, int p_idx, int p_level) {
+ _THREAD_SAFE_METHOD_
+
+ NSMenu *menu = _get_menu_root(p_menu_root);
+ if (menu) {
+ ERR_FAIL_COND(p_idx < 0);
+ if (menu == [NSApp mainMenu]) { // Skip Apple menu.
+ p_idx++;
+ }
+ ERR_FAIL_COND(p_idx >= [menu numberOfItems]);
+ NSMenuItem *menu_item = [menu itemAtIndex:p_idx];
+ if (menu_item) {
+ [menu_item setIndentationLevel:p_level];
+ }
+ }
+}
+
int DisplayServerMacOS::global_menu_get_item_count(const String &p_menu_root) const {
_THREAD_SAFE_METHOD_
const NSMenu *menu = _get_menu_root(p_menu_root);
if (menu) {
- return [menu numberOfItems];
+ if (menu == [NSApp mainMenu]) { // Skip Apple menu.
+ return [menu numberOfItems] - 1;
+ } else {
+ return [menu numberOfItems];
+ }
} else {
return 0;
}
@@ -1435,9 +1620,11 @@ void DisplayServerMacOS::global_menu_remove_item(const String &p_menu_root, int
NSMenu *menu = _get_menu_root(p_menu_root);
if (menu) {
- if ((menu == [NSApp mainMenu]) && (p_idx == 0)) { // Do not delete Apple menu.
- return;
+ ERR_FAIL_COND(p_idx < 0);
+ if (menu == [NSApp mainMenu]) { // Skip Apple menu.
+ p_idx++;
}
+ ERR_FAIL_COND(p_idx >= [menu numberOfItems]);
[menu removeItemAtIndex:p_idx];
}
}
@@ -1453,6 +1640,9 @@ void DisplayServerMacOS::global_menu_clear(const String &p_menu_root) {
NSMenuItem *menu_item = [menu addItemWithTitle:@"" action:nil keyEquivalent:@""];
[menu setSubmenu:apple_menu forItem:menu_item];
}
+ if (submenu.has(p_menu_root)) {
+ submenu.erase(p_menu_root);
+ }
}
}
@@ -1466,7 +1656,7 @@ bool DisplayServerMacOS::tts_is_paused() const {
return [tts isPaused];
}
-Array DisplayServerMacOS::tts_get_voices() const {
+TypedArray<Dictionary> DisplayServerMacOS::tts_get_voices() const {
ERR_FAIL_COND_V(!tts, Array());
return [tts getVoices];
}
@@ -3222,7 +3412,7 @@ DisplayServerMacOS::DisplayServerMacOS(const String &p_rendering_driver, WindowM
[apple_menu addItem:[NSMenuItem separatorItem]];
- title = [NSString stringWithFormat:NSLocalizedString(@"Quit %@", nil), nsappname];
+ title = [NSString stringWithFormat:NSLocalizedString(@"\t\tQuit %@", nil), nsappname];
[apple_menu addItemWithTitle:title action:@selector(terminate:) keyEquivalent:@"q"];
// Add items to the menu bar.
diff --git a/platform/macos/export/export_plugin.cpp b/platform/macos/export/export_plugin.cpp
index bcc2636c07..edce9c0380 100644
--- a/platform/macos/export/export_plugin.cpp
+++ b/platform/macos/export/export_plugin.cpp
@@ -1550,7 +1550,7 @@ void EditorExportPlatformMacOS::_zip_folder_recursive(zipFile &p_zip, const Stri
da->list_dir_end();
}
-bool EditorExportPlatformMacOS::can_export(const Ref<EditorExportPreset> &p_preset, String &r_error, bool &r_missing_templates) const {
+bool EditorExportPlatformMacOS::has_valid_export_configuration(const Ref<EditorExportPreset> &p_preset, String &r_error, bool &r_missing_templates) const {
String err;
bool valid = false;
@@ -1580,6 +1580,17 @@ bool EditorExportPlatformMacOS::can_export(const Ref<EditorExportPreset> &p_pres
valid = dvalid || rvalid;
r_missing_templates = !valid;
+ if (!err.is_empty()) {
+ r_error = err;
+ }
+
+ return valid;
+}
+
+bool EditorExportPlatformMacOS::has_valid_project_configuration(const Ref<EditorExportPreset> &p_preset, String &r_error) const {
+ String err;
+ bool valid = true;
+
String identifier = p_preset->get("application/bundle_identifier");
String pn_err;
if (!is_package_name_valid(identifier, &pn_err)) {
diff --git a/platform/macos/export/export_plugin.h b/platform/macos/export/export_plugin.h
index 21bc380d55..4603c61a28 100644
--- a/platform/macos/export/export_plugin.h
+++ b/platform/macos/export/export_plugin.h
@@ -119,7 +119,8 @@ public:
}
virtual Error export_project(const Ref<EditorExportPreset> &p_preset, bool p_debug, const String &p_path, int p_flags = 0) override;
- virtual bool can_export(const Ref<EditorExportPreset> &p_preset, String &r_error, bool &r_missing_templates) const override;
+ virtual bool has_valid_export_configuration(const Ref<EditorExportPreset> &p_preset, String &r_error, bool &r_missing_templates) const override;
+ virtual bool has_valid_project_configuration(const Ref<EditorExportPreset> &p_preset, String &r_error) const override;
virtual void get_platform_features(List<String> *r_features) const override {
r_features->push_back("pc");
diff --git a/platform/macos/godot_menu_item.h b/platform/macos/godot_menu_item.h
index 2c12897f10..e0b9f41632 100644
--- a/platform/macos/godot_menu_item.h
+++ b/platform/macos/godot_menu_item.h
@@ -46,7 +46,6 @@ enum GlobalMenuCheckType {
@public
Callable callback;
Variant meta;
- int id;
GlobalMenuCheckType checkable_type;
int max_states;
int state;
diff --git a/platform/uwp/export/export_plugin.cpp b/platform/uwp/export/export_plugin.cpp
index 070c46242f..4e4afb9704 100644
--- a/platform/uwp/export/export_plugin.cpp
+++ b/platform/uwp/export/export_plugin.cpp
@@ -121,7 +121,7 @@ void EditorExportPlatformUWP::get_export_options(List<ExportOption> *r_options)
}
}
-bool EditorExportPlatformUWP::can_export(const Ref<EditorExportPreset> &p_preset, String &r_error, bool &r_missing_templates) const {
+bool EditorExportPlatformUWP::has_valid_export_configuration(const Ref<EditorExportPreset> &p_preset, String &r_error, bool &r_missing_templates) const {
#ifndef DEV_ENABLED
// We don't provide export templates for the UWP platform currently as it
// has not been ported for Godot 4.0. This is skipped in DEV_ENABLED so that
@@ -163,7 +163,26 @@ bool EditorExportPlatformUWP::can_export(const Ref<EditorExportPreset> &p_preset
valid = dvalid || rvalid;
r_missing_templates = !valid;
- // Validate the rest of the configuration.
+ if (!err.is_empty()) {
+ r_error = err;
+ }
+
+ return valid;
+}
+
+bool EditorExportPlatformUWP::has_valid_project_configuration(const Ref<EditorExportPreset> &p_preset, String &r_error) const {
+#ifndef DEV_ENABLED
+ // We don't provide export templates for the UWP platform currently as it
+ // has not been ported for Godot 4.0. This is skipped in DEV_ENABLED so that
+ // contributors can still test the pipeline if/when we can build it again.
+ r_error = "The UWP platform is currently not supported in Godot 4.0.\n";
+ return false;
+#endif
+
+ String err;
+ bool valid = true;
+
+ // Validate the project configuration.
if (!_valid_resource_name(p_preset->get("package/short_name"))) {
valid = false;
diff --git a/platform/uwp/export/export_plugin.h b/platform/uwp/export/export_plugin.h
index 4a3c5db377..71d0479543 100644
--- a/platform/uwp/export/export_plugin.h
+++ b/platform/uwp/export/export_plugin.h
@@ -429,7 +429,8 @@ public:
virtual void get_export_options(List<ExportOption> *r_options) override;
- virtual bool can_export(const Ref<EditorExportPreset> &p_preset, String &r_error, bool &r_missing_templates) const override;
+ virtual bool has_valid_export_configuration(const Ref<EditorExportPreset> &p_preset, String &r_error, bool &r_missing_templates) const override;
+ virtual bool has_valid_project_configuration(const Ref<EditorExportPreset> &p_preset, String &r_error) const override;
virtual Error export_project(const Ref<EditorExportPreset> &p_preset, bool p_debug, const String &p_path, int p_flags = 0) override;
diff --git a/platform/windows/display_server_windows.cpp b/platform/windows/display_server_windows.cpp
index 8c8dbef8a4..7eb61b3038 100644
--- a/platform/windows/display_server_windows.cpp
+++ b/platform/windows/display_server_windows.cpp
@@ -144,8 +144,8 @@ bool DisplayServerWindows::tts_is_paused() const {
return tts->is_paused();
}
-Array DisplayServerWindows::tts_get_voices() const {
- ERR_FAIL_COND_V(!tts, Array());
+TypedArray<Dictionary> DisplayServerWindows::tts_get_voices() const {
+ ERR_FAIL_COND_V(!tts, TypedArray<Dictionary>());
return tts->get_voices();
}
diff --git a/platform/windows/display_server_windows.h b/platform/windows/display_server_windows.h
index db9b589304..556ce9ff5d 100644
--- a/platform/windows/display_server_windows.h
+++ b/platform/windows/display_server_windows.h
@@ -478,7 +478,7 @@ public:
virtual bool tts_is_speaking() const override;
virtual bool tts_is_paused() const override;
- virtual Array tts_get_voices() const override;
+ virtual TypedArray<Dictionary> tts_get_voices() const override;
virtual void tts_speak(const String &p_text, const String &p_voice, int p_volume = 50, float p_pitch = 1.f, float p_rate = 1.f, int p_utterance_id = 0, bool p_interrupt = false) override;
virtual void tts_pause() override;
diff --git a/platform/windows/export/export_plugin.cpp b/platform/windows/export/export_plugin.cpp
index febef5ad12..9d3ec31f73 100644
--- a/platform/windows/export/export_plugin.cpp
+++ b/platform/windows/export/export_plugin.cpp
@@ -412,15 +412,26 @@ Error EditorExportPlatformWindows::_code_sign(const Ref<EditorExportPreset> &p_p
return OK;
}
-bool EditorExportPlatformWindows::can_export(const Ref<EditorExportPreset> &p_preset, String &r_error, bool &r_missing_templates) const {
+bool EditorExportPlatformWindows::has_valid_export_configuration(const Ref<EditorExportPreset> &p_preset, String &r_error, bool &r_missing_templates) const {
String err = "";
- bool valid = EditorExportPlatformPC::can_export(p_preset, err, r_missing_templates);
+ bool valid = EditorExportPlatformPC::has_valid_export_configuration(p_preset, err, r_missing_templates);
String rcedit_path = EditorSettings::get_singleton()->get("export/windows/rcedit");
if (p_preset->get("application/modify_resources") && rcedit_path.is_empty()) {
err += TTR("The rcedit tool must be configured in the Editor Settings (Export > Windows > Rcedit) to change the icon or app information data.") + "\n";
}
+ if (!err.is_empty()) {
+ r_error = err;
+ }
+
+ return valid;
+}
+
+bool EditorExportPlatformWindows::has_valid_project_configuration(const Ref<EditorExportPreset> &p_preset, String &r_error) const {
+ String err = "";
+ bool valid = true;
+
String icon_path = ProjectSettings::get_singleton()->globalize_path(p_preset->get("application/icon"));
if (!icon_path.is_empty() && !FileAccess::exists(icon_path)) {
err += TTR("Invalid icon path:") + " " + icon_path + "\n";
diff --git a/platform/windows/export/export_plugin.h b/platform/windows/export/export_plugin.h
index b9e59829a0..3ea8ff3dc9 100644
--- a/platform/windows/export/export_plugin.h
+++ b/platform/windows/export/export_plugin.h
@@ -49,7 +49,8 @@ public:
virtual List<String> get_binary_extensions(const Ref<EditorExportPreset> &p_preset) const override;
virtual void get_export_options(List<ExportOption> *r_options) override;
virtual bool get_export_option_visibility(const String &p_option, const HashMap<StringName, Variant> &p_options) const override;
- virtual bool can_export(const Ref<EditorExportPreset> &p_preset, String &r_error, bool &r_missing_templates) const override;
+ virtual bool has_valid_export_configuration(const Ref<EditorExportPreset> &p_preset, String &r_error, bool &r_missing_templates) const override;
+ virtual bool has_valid_project_configuration(const Ref<EditorExportPreset> &p_preset, String &r_error) const override;
virtual String get_template_file_name(const String &p_target, const String &p_arch) const override;
virtual Error fixup_embedded_pck(const String &p_path, int64_t p_embedded_start, int64_t p_embedded_size) override;
};