summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--core/extension/gdextension_interface.cpp4
-rw-r--r--core/extension/gdextension_interface.h2
-rw-r--r--core/string/ustring.cpp8
-rw-r--r--drivers/alsa/audio_driver_alsa.cpp52
-rw-r--r--drivers/alsa/audio_driver_alsa.h36
-rw-r--r--drivers/coreaudio/audio_driver_coreaudio.cpp30
-rw-r--r--drivers/coreaudio/audio_driver_coreaudio.h44
-rw-r--r--drivers/pulseaudio/audio_driver_pulseaudio.cpp16
-rw-r--r--drivers/pulseaudio/audio_driver_pulseaudio.h35
-rw-r--r--drivers/wasapi/audio_driver_wasapi.cpp16
-rw-r--r--drivers/wasapi/audio_driver_wasapi.h39
-rw-r--r--drivers/xaudio2/audio_driver_xaudio2.cpp4
-rw-r--r--drivers/xaudio2/audio_driver_xaudio2.h23
-rw-r--r--editor/editor_locale_dialog.cpp6
-rw-r--r--editor/import/dynamic_font_import_settings.cpp6
-rw-r--r--editor/plugins/debugger_editor_plugin.cpp12
-rw-r--r--editor/plugins/font_config_plugin.cpp3
-rw-r--r--editor/plugins/script_editor_plugin.cpp10
-rw-r--r--modules/gdscript/gdscript_parser.cpp82
-rw-r--r--modules/gdscript/gdscript_tokenizer.cpp17
-rw-r--r--modules/gdscript/language_server/gdscript_text_document.cpp1
-rw-r--r--modules/gdscript/tests/scripts/parser/features/allow_id_similar_to_keyword_in_ascii.gd3
-rw-r--r--modules/gdscript/tests/scripts/parser/features/allow_id_similar_to_keyword_in_ascii.out2
-rw-r--r--modules/gdscript/tests/scripts/parser/features/allow_strings_as_comments.gd21
-rw-r--r--modules/gdscript/tests/scripts/parser/features/allow_strings_as_comments.out2
-rw-r--r--modules/gdscript/tests/scripts/parser/features/allowed_keywords_as_identifiers.gd16
-rw-r--r--modules/gdscript/tests/scripts/parser/features/allowed_keywords_as_identifiers.out6
-rw-r--r--modules/gdscript/tests/scripts/parser/warnings/confusable_identifier.gd7
-rw-r--r--modules/gdscript/tests/scripts/parser/warnings/confusable_identifier.out6
-rw-r--r--modules/gdscript/tests/scripts/parser/warnings/standalone_expression.gd1
-rw-r--r--modules/gdscript/tests/scripts/parser/warnings/standalone_expression.out6
-rw-r--r--modules/multiplayer/editor/editor_network_profiler.cpp6
-rw-r--r--platform/android/audio_driver_opensl.cpp12
-rw-r--r--platform/android/audio_driver_opensl.h27
-rw-r--r--platform/android/java_godot_lib_jni.cpp2
-rw-r--r--platform/web/audio_driver_web.cpp8
-rw-r--r--platform/web/audio_driver_web.h14
-rw-r--r--platform/web/godot_audio.h4
-rw-r--r--platform/web/js/libs/library_godot_audio.js12
-rw-r--r--scene/3d/collision_shape_3d.cpp2
-rw-r--r--scene/gui/view_panner.cpp2
-rw-r--r--scene/resources/text_file.cpp5
-rw-r--r--servers/audio/audio_driver_dummy.cpp20
-rw-r--r--servers/audio/audio_driver_dummy.h17
-rw-r--r--servers/audio/audio_stream.cpp4
-rw-r--r--servers/audio_server.cpp7
-rw-r--r--servers/audio_server.h24
-rw-r--r--servers/rendering/shader_language.cpp4
-rw-r--r--servers/rendering/shader_preprocessor.h2
-rw-r--r--tests/core/string/test_string.h8
-rw-r--r--tests/display_server_mock.h20
-rw-r--r--tests/scene/test_text_edit.h3
-rw-r--r--tests/scene/test_viewport.h718
-rw-r--r--tests/test_macros.h1
-rw-r--r--tests/test_main.cpp4
55 files changed, 1162 insertions, 280 deletions
diff --git a/core/extension/gdextension_interface.cpp b/core/extension/gdextension_interface.cpp
index 8f606f7948..2bedb623e4 100644
--- a/core/extension/gdextension_interface.cpp
+++ b/core/extension/gdextension_interface.cpp
@@ -871,11 +871,11 @@ void gdextension_array_ref(GDExtensionTypePtr p_self, GDExtensionConstTypePtr p_
self->_ref(*from);
}
-void gdextension_array_set_typed(GDExtensionTypePtr p_self, uint32_t p_type, GDExtensionConstStringNamePtr p_class_name, GDExtensionConstVariantPtr p_script) {
+void gdextension_array_set_typed(GDExtensionTypePtr p_self, GDExtensionVariantType p_type, GDExtensionConstStringNamePtr p_class_name, GDExtensionConstVariantPtr p_script) {
Array *self = reinterpret_cast<Array *>(p_self);
const StringName *class_name = reinterpret_cast<const StringName *>(p_class_name);
const Variant *script = reinterpret_cast<const Variant *>(p_script);
- self->set_typed(p_type, *class_name, *script);
+ self->set_typed((uint32_t)p_type, *class_name, *script);
}
/* Dictionary functions */
diff --git a/core/extension/gdextension_interface.h b/core/extension/gdextension_interface.h
index 3865b152bd..f323b2aa53 100644
--- a/core/extension/gdextension_interface.h
+++ b/core/extension/gdextension_interface.h
@@ -554,7 +554,7 @@ typedef struct {
GDExtensionVariantPtr (*array_operator_index)(GDExtensionTypePtr p_self, GDExtensionInt p_index); // p_self should be an Array ptr
GDExtensionVariantPtr (*array_operator_index_const)(GDExtensionConstTypePtr p_self, GDExtensionInt p_index); // p_self should be an Array ptr
void (*array_ref)(GDExtensionTypePtr p_self, GDExtensionConstTypePtr p_from); // p_self should be an Array ptr
- void (*array_set_typed)(GDExtensionTypePtr p_self, uint32_t p_type, GDExtensionConstStringNamePtr p_class_name, GDExtensionConstVariantPtr p_script); // p_self should be an Array ptr
+ void (*array_set_typed)(GDExtensionTypePtr p_self, GDExtensionVariantType p_type, GDExtensionConstStringNamePtr p_class_name, GDExtensionConstVariantPtr p_script); // p_self should be an Array ptr
/* Dictionary functions */
diff --git a/core/string/ustring.cpp b/core/string/ustring.cpp
index b34d9f3271..1b3b070592 100644
--- a/core/string/ustring.cpp
+++ b/core/string/ustring.cpp
@@ -1157,6 +1157,14 @@ Vector<String> String::split_spaces() const {
Vector<String> String::split(const String &p_splitter, bool p_allow_empty, int p_maxsplit) const {
Vector<String> ret;
+
+ if (is_empty()) {
+ if (p_allow_empty) {
+ ret.push_back("");
+ }
+ return ret;
+ }
+
int from = 0;
int len = length();
diff --git a/drivers/alsa/audio_driver_alsa.cpp b/drivers/alsa/audio_driver_alsa.cpp
index 09e4f48204..20cd8dd26c 100644
--- a/drivers/alsa/audio_driver_alsa.cpp
+++ b/drivers/alsa/audio_driver_alsa.cpp
@@ -43,17 +43,17 @@ extern int initialize_pulse(int verbose);
}
#endif
-Error AudioDriverALSA::init_device() {
+Error AudioDriverALSA::init_output_device() {
mix_rate = GLOBAL_GET("audio/driver/mix_rate");
speaker_mode = SPEAKER_MODE_STEREO;
channels = 2;
- // If there is a specified device check that it is really present
- if (device_name != "Default") {
- PackedStringArray list = get_device_list();
- if (list.find(device_name) == -1) {
- device_name = "Default";
- new_device = "Default";
+ // If there is a specified output device check that it is really present
+ if (output_device_name != "Default") {
+ PackedStringArray list = get_output_device_list();
+ if (list.find(output_device_name) == -1) {
+ output_device_name = "Default";
+ new_output_device = "Default";
}
}
@@ -75,10 +75,10 @@ Error AudioDriverALSA::init_device() {
//6 chans - "plug:surround51"
//4 chans - "plug:surround40";
- if (device_name == "Default") {
+ if (output_device_name == "Default") {
status = snd_pcm_open(&pcm_handle, "default", SND_PCM_STREAM_PLAYBACK, SND_PCM_NONBLOCK);
} else {
- String device = device_name;
+ String device = output_device_name;
int pos = device.find(";");
if (pos != -1) {
device = device.substr(0, pos);
@@ -171,7 +171,7 @@ Error AudioDriverALSA::init() {
active.clear();
exit_thread.clear();
- Error err = init_device();
+ Error err = init_output_device();
if (err == OK) {
thread.start(AudioDriverALSA::thread_func, this);
}
@@ -227,18 +227,18 @@ void AudioDriverALSA::thread_func(void *p_udata) {
}
}
- // User selected a new device, finish the current one so we'll init the new device
- if (ad->device_name != ad->new_device) {
- ad->device_name = ad->new_device;
- ad->finish_device();
+ // User selected a new output device, finish the current one so we'll init the new device.
+ if (ad->output_device_name != ad->new_output_device) {
+ ad->output_device_name = ad->new_output_device;
+ ad->finish_output_device();
- Error err = ad->init_device();
+ Error err = ad->init_output_device();
if (err != OK) {
- ERR_PRINT("ALSA: init_device error");
- ad->device_name = "Default";
- ad->new_device = "Default";
+ ERR_PRINT("ALSA: init_output_device error");
+ ad->output_device_name = "Default";
+ ad->new_output_device = "Default";
- err = ad->init_device();
+ err = ad->init_output_device();
if (err != OK) {
ad->active.clear();
ad->exit_thread.set();
@@ -263,7 +263,7 @@ AudioDriver::SpeakerMode AudioDriverALSA::get_speaker_mode() const {
return speaker_mode;
}
-PackedStringArray AudioDriverALSA::get_device_list() {
+PackedStringArray AudioDriverALSA::get_output_device_list() {
PackedStringArray list;
list.push_back("Default");
@@ -298,13 +298,13 @@ PackedStringArray AudioDriverALSA::get_device_list() {
return list;
}
-String AudioDriverALSA::get_device() {
- return device_name;
+String AudioDriverALSA::get_output_device() {
+ return output_device_name;
}
-void AudioDriverALSA::set_device(String device) {
+void AudioDriverALSA::set_output_device(const String &p_name) {
lock();
- new_device = device;
+ new_output_device = p_name;
unlock();
}
@@ -316,7 +316,7 @@ void AudioDriverALSA::unlock() {
mutex.unlock();
}
-void AudioDriverALSA::finish_device() {
+void AudioDriverALSA::finish_output_device() {
if (pcm_handle) {
snd_pcm_close(pcm_handle);
pcm_handle = nullptr;
@@ -327,7 +327,7 @@ void AudioDriverALSA::finish() {
exit_thread.set();
thread.wait_to_finish();
- finish_device();
+ finish_output_device();
}
#endif // ALSA_ENABLED
diff --git a/drivers/alsa/audio_driver_alsa.h b/drivers/alsa/audio_driver_alsa.h
index 6e9cf4dba9..821ba1d145 100644
--- a/drivers/alsa/audio_driver_alsa.h
+++ b/drivers/alsa/audio_driver_alsa.h
@@ -46,14 +46,14 @@ class AudioDriverALSA : public AudioDriver {
snd_pcm_t *pcm_handle = nullptr;
- String device_name = "Default";
- String new_device = "Default";
+ String output_device_name = "Default";
+ String new_output_device = "Default";
Vector<int32_t> samples_in;
Vector<int16_t> samples_out;
- Error init_device();
- void finish_device();
+ Error init_output_device();
+ void finish_output_device();
static void thread_func(void *p_udata);
@@ -69,20 +69,22 @@ class AudioDriverALSA : public AudioDriver {
SafeFlag exit_thread;
public:
- const char *get_name() const {
+ virtual const char *get_name() const override {
return "ALSA";
- };
-
- virtual Error init();
- virtual void start();
- virtual int get_mix_rate() const;
- virtual SpeakerMode get_speaker_mode() const;
- virtual PackedStringArray get_device_list();
- virtual String get_device();
- virtual void set_device(String device);
- virtual void lock();
- virtual void unlock();
- virtual void finish();
+ }
+
+ virtual Error init() override;
+ virtual void start() override;
+ virtual int get_mix_rate() const override;
+ virtual SpeakerMode get_speaker_mode() const override;
+
+ virtual void lock() override;
+ virtual void unlock() override;
+ virtual void finish() override;
+
+ virtual PackedStringArray get_output_device_list() override;
+ virtual String get_output_device() override;
+ virtual void set_output_device(const String &p_name) override;
AudioDriverALSA() {}
~AudioDriverALSA() {}
diff --git a/drivers/coreaudio/audio_driver_coreaudio.cpp b/drivers/coreaudio/audio_driver_coreaudio.cpp
index c454da8e23..2c959bb07b 100644
--- a/drivers/coreaudio/audio_driver_coreaudio.cpp
+++ b/drivers/coreaudio/audio_driver_coreaudio.cpp
@@ -158,7 +158,7 @@ Error AudioDriverCoreAudio::init() {
ERR_FAIL_COND_V(result != noErr, FAILED);
if (GLOBAL_GET("audio/driver/enable_input")) {
- return capture_init();
+ return init_input_device();
}
return OK;
}
@@ -287,7 +287,7 @@ bool AudioDriverCoreAudio::try_lock() {
}
void AudioDriverCoreAudio::finish() {
- capture_finish();
+ finish_input_device();
if (audio_unit) {
OSStatus result;
@@ -337,7 +337,7 @@ void AudioDriverCoreAudio::finish() {
}
}
-Error AudioDriverCoreAudio::capture_init() {
+Error AudioDriverCoreAudio::init_input_device() {
AudioComponentDescription desc;
memset(&desc, 0, sizeof(desc));
desc.componentType = kAudioUnitType_Output;
@@ -433,7 +433,7 @@ Error AudioDriverCoreAudio::capture_init() {
return OK;
}
-void AudioDriverCoreAudio::capture_finish() {
+void AudioDriverCoreAudio::finish_input_device() {
if (input_unit) {
lock();
@@ -471,7 +471,7 @@ void AudioDriverCoreAudio::capture_finish() {
}
}
-Error AudioDriverCoreAudio::capture_start() {
+Error AudioDriverCoreAudio::input_start() {
input_buffer_init(buffer_frames);
OSStatus result = AudioOutputUnitStart(input_unit);
@@ -482,7 +482,7 @@ Error AudioDriverCoreAudio::capture_start() {
return OK;
}
-Error AudioDriverCoreAudio::capture_stop() {
+Error AudioDriverCoreAudio::input_stop() {
if (input_unit) {
OSStatus result = AudioOutputUnitStop(input_unit);
if (result != noErr) {
@@ -647,20 +647,13 @@ String AudioDriverCoreAudio::get_output_device() {
return output_device_name;
}
-void AudioDriverCoreAudio::set_output_device(String output_device) {
- output_device_name = output_device;
+void AudioDriverCoreAudio::set_output_device(const String &p_name) {
+ output_device_name = p_name;
if (active) {
_set_device(output_device_name);
}
}
-void AudioDriverCoreAudio::set_input_device(const String &p_name) {
- input_device_name = p_name;
- if (active) {
- _set_device(input_device_name, true);
- }
-}
-
PackedStringArray AudioDriverCoreAudio::get_input_device_list() {
return _get_device_list(true);
}
@@ -669,6 +662,13 @@ String AudioDriverCoreAudio::get_input_device() {
return input_device_name;
}
+void AudioDriverCoreAudio::set_input_device(const String &p_name) {
+ input_device_name = p_name;
+ if (active) {
+ _set_device(input_device_name, true);
+ }
+}
+
#endif
AudioDriverCoreAudio::AudioDriverCoreAudio() {
diff --git a/drivers/coreaudio/audio_driver_coreaudio.h b/drivers/coreaudio/audio_driver_coreaudio.h
index 2b192e630e..67ff3f3efc 100644
--- a/drivers/coreaudio/audio_driver_coreaudio.h
+++ b/drivers/coreaudio/audio_driver_coreaudio.h
@@ -83,39 +83,39 @@ class AudioDriverCoreAudio : public AudioDriver {
UInt32 inBusNumber, UInt32 inNumberFrames,
AudioBufferList *ioData);
- Error capture_init();
- void capture_finish();
+ Error init_input_device();
+ void finish_input_device();
public:
- const char *get_name() const {
+ virtual const char *get_name() const override {
return "CoreAudio";
};
- virtual Error init();
- virtual void start();
- virtual int get_mix_rate() const;
- virtual SpeakerMode get_speaker_mode() const;
+ virtual Error init() override;
+ virtual void start() override;
+ virtual int get_mix_rate() const override;
+ virtual SpeakerMode get_speaker_mode() const override;
- virtual void lock();
- virtual void unlock();
- virtual void finish();
-
- virtual Error capture_start();
- virtual Error capture_stop();
-
- bool try_lock();
- void stop();
+ virtual void lock() override;
+ virtual void unlock() override;
+ virtual void finish() override;
#ifdef MACOS_ENABLED
- virtual PackedStringArray get_output_device_list();
- virtual String get_output_device();
- virtual void set_output_device(String output_device);
+ virtual PackedStringArray get_output_device_list() override;
+ virtual String get_output_device() override;
+ virtual void set_output_device(const String &p_name) override;
- virtual PackedStringArray get_input_device_list();
- virtual void set_input_device(const String &p_name);
- virtual String get_input_device();
+ virtual PackedStringArray get_input_device_list() override;
+ virtual String get_input_device() override;
+ virtual void set_input_device(const String &p_name) override;
#endif
+ virtual Error input_start() override;
+ virtual Error input_stop() override;
+
+ bool try_lock();
+ void stop();
+
AudioDriverCoreAudio();
~AudioDriverCoreAudio() {}
};
diff --git a/drivers/pulseaudio/audio_driver_pulseaudio.cpp b/drivers/pulseaudio/audio_driver_pulseaudio.cpp
index f96247a473..0246af4fea 100644
--- a/drivers/pulseaudio/audio_driver_pulseaudio.cpp
+++ b/drivers/pulseaudio/audio_driver_pulseaudio.cpp
@@ -631,9 +631,9 @@ String AudioDriverPulseAudio::get_output_device() {
return output_device_name;
}
-void AudioDriverPulseAudio::set_output_device(String output_device) {
+void AudioDriverPulseAudio::set_output_device(const String &p_name) {
lock();
- new_output_device = output_device;
+ new_output_device = p_name;
unlock();
}
@@ -761,12 +761,6 @@ Error AudioDriverPulseAudio::input_stop() {
return OK;
}
-void AudioDriverPulseAudio::set_input_device(const String &p_name) {
- lock();
- new_input_device = p_name;
- unlock();
-}
-
void AudioDriverPulseAudio::pa_sourcelist_cb(pa_context *c, const pa_source_info *l, int eol, void *userdata) {
AudioDriverPulseAudio *ad = static_cast<AudioDriverPulseAudio *>(userdata);
@@ -821,6 +815,12 @@ String AudioDriverPulseAudio::get_input_device() {
return name;
}
+void AudioDriverPulseAudio::set_input_device(const String &p_name) {
+ lock();
+ new_input_device = p_name;
+ unlock();
+}
+
AudioDriverPulseAudio::AudioDriverPulseAudio() {
samples_in.clear();
samples_out.clear();
diff --git a/drivers/pulseaudio/audio_driver_pulseaudio.h b/drivers/pulseaudio/audio_driver_pulseaudio.h
index 68df03cb60..f4ff44d361 100644
--- a/drivers/pulseaudio/audio_driver_pulseaudio.h
+++ b/drivers/pulseaudio/audio_driver_pulseaudio.h
@@ -94,31 +94,30 @@ class AudioDriverPulseAudio : public AudioDriver {
static void thread_func(void *p_udata);
public:
- const char *get_name() const {
+ virtual const char *get_name() const override {
return "PulseAudio";
};
- virtual Error init();
- virtual void start();
- virtual int get_mix_rate() const;
- virtual SpeakerMode get_speaker_mode() const;
+ virtual Error init() override;
+ virtual void start() override;
+ virtual int get_mix_rate() const override;
+ virtual SpeakerMode get_speaker_mode() const override;
+ virtual float get_latency() override;
- virtual PackedStringArray get_output_device_list();
- virtual String get_output_device();
- virtual void set_output_device(String output_device);
+ virtual void lock() override;
+ virtual void unlock() override;
+ virtual void finish() override;
- virtual PackedStringArray get_input_device_list();
- virtual void set_input_device(const String &p_name);
- virtual String get_input_device();
+ virtual PackedStringArray get_output_device_list() override;
+ virtual String get_output_device() override;
+ virtual void set_output_device(const String &p_name) override;
- virtual void lock();
- virtual void unlock();
- virtual void finish();
+ virtual Error input_start() override;
+ virtual Error input_stop() override;
- virtual float get_latency();
-
- virtual Error input_start();
- virtual Error input_stop();
+ virtual PackedStringArray get_input_device_list() override;
+ virtual String get_input_device() override;
+ virtual void set_input_device(const String &p_name) override;
AudioDriverPulseAudio();
~AudioDriverPulseAudio() {}
diff --git a/drivers/wasapi/audio_driver_wasapi.cpp b/drivers/wasapi/audio_driver_wasapi.cpp
index 42a2b85200..72ec0c19ab 100644
--- a/drivers/wasapi/audio_driver_wasapi.cpp
+++ b/drivers/wasapi/audio_driver_wasapi.cpp
@@ -634,9 +634,9 @@ String AudioDriverWASAPI::get_output_device() {
return name;
}
-void AudioDriverWASAPI::set_output_device(String output_device) {
+void AudioDriverWASAPI::set_output_device(const String &p_name) {
lock();
- audio_output.new_device = output_device;
+ audio_output.new_device = p_name;
unlock();
}
@@ -964,12 +964,6 @@ Error AudioDriverWASAPI::input_stop() {
return FAILED;
}
-void AudioDriverWASAPI::set_input_device(const String &p_name) {
- lock();
- audio_input.new_device = p_name;
- unlock();
-}
-
PackedStringArray AudioDriverWASAPI::get_input_device_list() {
return audio_device_get_list(true);
}
@@ -982,6 +976,12 @@ String AudioDriverWASAPI::get_input_device() {
return name;
}
+void AudioDriverWASAPI::set_input_device(const String &p_name) {
+ lock();
+ audio_input.new_device = p_name;
+ unlock();
+}
+
AudioDriverWASAPI::AudioDriverWASAPI() {
samples_in.clear();
}
diff --git a/drivers/wasapi/audio_driver_wasapi.h b/drivers/wasapi/audio_driver_wasapi.h
index bf18ba8c99..367c30607a 100644
--- a/drivers/wasapi/audio_driver_wasapi.h
+++ b/drivers/wasapi/audio_driver_wasapi.h
@@ -94,27 +94,30 @@ class AudioDriverWASAPI : public AudioDriver {
PackedStringArray audio_device_get_list(bool p_input);
public:
- virtual const char *get_name() const {
+ virtual const char *get_name() const override {
return "WASAPI";
}
- virtual Error init();
- virtual void start();
- virtual int get_mix_rate() const;
- virtual float get_latency();
- virtual SpeakerMode get_speaker_mode() const;
- virtual PackedStringArray get_output_device_list();
- virtual String get_output_device();
- virtual void set_output_device(String output_device);
- virtual void lock();
- virtual void unlock();
- virtual void finish();
-
- virtual Error input_start();
- virtual Error input_stop();
- virtual PackedStringArray get_input_device_list();
- virtual void set_input_device(const String &p_name);
- virtual String get_input_device();
+ virtual Error init() override;
+ virtual void start() override;
+ virtual int get_mix_rate() const override;
+ virtual SpeakerMode get_speaker_mode() const override;
+ virtual float get_latency() override;
+
+ virtual void lock() override;
+ virtual void unlock() override;
+ virtual void finish() override;
+
+ virtual PackedStringArray get_output_device_list() override;
+ virtual String get_output_device() override;
+ virtual void set_output_device(const String &p_name) override;
+
+ virtual Error input_start() override;
+ virtual Error input_stop() override;
+
+ virtual PackedStringArray get_input_device_list() override;
+ virtual String get_input_device() override;
+ virtual void set_input_device(const String &p_name) override;
AudioDriverWASAPI();
};
diff --git a/drivers/xaudio2/audio_driver_xaudio2.cpp b/drivers/xaudio2/audio_driver_xaudio2.cpp
index cd1b29e17d..44ce01d4d7 100644
--- a/drivers/xaudio2/audio_driver_xaudio2.cpp
+++ b/drivers/xaudio2/audio_driver_xaudio2.cpp
@@ -33,10 +33,6 @@
#include "core/config/project_settings.h"
#include "core/os/os.h"
-const char *AudioDriverXAudio2::get_name() const {
- return "XAudio2";
-}
-
Error AudioDriverXAudio2::init() {
active.clear();
exit_thread.clear();
diff --git a/drivers/xaudio2/audio_driver_xaudio2.h b/drivers/xaudio2/audio_driver_xaudio2.h
index df5ef52e88..c659b6ffdc 100644
--- a/drivers/xaudio2/audio_driver_xaudio2.h
+++ b/drivers/xaudio2/audio_driver_xaudio2.h
@@ -91,16 +91,19 @@ class AudioDriverXAudio2 : public AudioDriver {
XAudio2DriverVoiceCallback voice_callback;
public:
- const char *get_name() const;
-
- virtual Error init();
- virtual void start();
- virtual int get_mix_rate() const;
- virtual SpeakerMode get_speaker_mode() const;
- virtual float get_latency();
- virtual void lock();
- virtual void unlock();
- virtual void finish();
+ virtual const char *get_name() const override {
+ return "XAudio2";
+ }
+
+ virtual Error init() override;
+ virtual void start() override;
+ virtual int get_mix_rate() const override;
+ virtual SpeakerMode get_speaker_mode() const override;
+ virtual float get_latency() override;
+
+ virtual void lock() override;
+ virtual void unlock() override;
+ virtual void finish() override;
AudioDriverXAudio2();
~AudioDriverXAudio2() {}
diff --git a/editor/editor_locale_dialog.cpp b/editor/editor_locale_dialog.cpp
index 5a372412fa..fbf3c99690 100644
--- a/editor/editor_locale_dialog.cpp
+++ b/editor/editor_locale_dialog.cpp
@@ -446,7 +446,8 @@ EditorLocaleDialog::EditorLocaleDialog() {
vb_script_list->set_h_size_flags(Control::SIZE_EXPAND_FILL);
{
Label *script_lbl = memnew(Label);
- script_lbl->set_text(TTR("Script:"));
+ // TRANSLATORS: This is the label for a list of writing systems.
+ script_lbl->set_text(TTR("Script:", "Locale"));
vb_script_list->add_child(script_lbl);
}
{
@@ -504,7 +505,8 @@ EditorLocaleDialog::EditorLocaleDialog() {
vb_script->set_h_size_flags(Control::SIZE_EXPAND_FILL);
{
Label *script_lbl = memnew(Label);
- script_lbl->set_text(TTR("Script"));
+ // TRANSLATORS: This refers to a writing system.
+ script_lbl->set_text(TTR("Script", "Locale"));
vb_script->add_child(script_lbl);
}
{
diff --git a/editor/import/dynamic_font_import_settings.cpp b/editor/import/dynamic_font_import_settings.cpp
index cfbe34b868..6e74c42b2c 100644
--- a/editor/import/dynamic_font_import_settings.cpp
+++ b/editor/import/dynamic_font_import_settings.cpp
@@ -528,7 +528,7 @@ void DynamicFontImportSettings::_variation_selected() {
inspector_vars->edit(import_variation_data.ptr());
import_variation_data->notify_property_list_changed();
- label_glyphs->set_text(TTR("Preloaded glyphs: ") + itos(import_variation_data->selected_glyphs.size()));
+ label_glyphs->set_text(vformat(TTR("Preloaded glyphs: %d"), import_variation_data->selected_glyphs.size()));
_range_selected();
_change_text_opts();
@@ -659,7 +659,7 @@ void DynamicFontImportSettings::_glyph_update_lbl() {
}
}
int unlinked_glyphs = import_variation_data->selected_glyphs.size() - linked_glyphs;
- label_glyphs->set_text(TTR("Preloaded glyphs:") + " " + itos(unlinked_glyphs + import_variation_data->selected_chars.size()));
+ label_glyphs->set_text(vformat(TTR("Preloaded glyphs: %d"), unlinked_glyphs + import_variation_data->selected_chars.size()));
}
void DynamicFontImportSettings::_glyph_clear() {
@@ -1403,7 +1403,7 @@ DynamicFontImportSettings::DynamicFontImportSettings() {
label_glyphs = memnew(Label);
gl_hb->add_child(label_glyphs);
- label_glyphs->set_text(TTR("Preloaded glyphs:") + " " + itos(0));
+ label_glyphs->set_text(vformat(TTR("Preloaded glyphs: %d"), 0));
label_glyphs->set_custom_minimum_size(Size2(50 * EDSCALE, 0));
Button *btn_clear = memnew(Button);
diff --git a/editor/plugins/debugger_editor_plugin.cpp b/editor/plugins/debugger_editor_plugin.cpp
index 28151800b6..224d221d9a 100644
--- a/editor/plugins/debugger_editor_plugin.cpp
+++ b/editor/plugins/debugger_editor_plugin.cpp
@@ -97,14 +97,10 @@ DebuggerEditorPlugin::DebuggerEditorPlugin(PopupMenu *p_debug_menu) {
debug_menu->add_separator();
debug_menu->add_submenu_item(TTR("Run Multiple Instances"), "run_instances");
- instances_menu->add_radio_check_item(TTR("Run 1 Instance"));
- instances_menu->set_item_metadata(0, 1);
- instances_menu->add_radio_check_item(TTR("Run 2 Instances"));
- instances_menu->set_item_metadata(1, 2);
- instances_menu->add_radio_check_item(TTR("Run 3 Instances"));
- instances_menu->set_item_metadata(2, 3);
- instances_menu->add_radio_check_item(TTR("Run 4 Instances"));
- instances_menu->set_item_metadata(3, 4);
+ for (int i = 1; i <= 4; i++) {
+ instances_menu->add_radio_check_item(vformat(TTRN("Run %d Instance", "Run %d Instances", i), i));
+ instances_menu->set_item_metadata(i - 1, i);
+ }
instances_menu->set_item_checked(0, true);
instances_menu->connect("index_pressed", callable_mp(this, &DebuggerEditorPlugin::_select_run_count));
debug_menu->connect("id_pressed", callable_mp(this, &DebuggerEditorPlugin::_menu_option));
diff --git a/editor/plugins/font_config_plugin.cpp b/editor/plugins/font_config_plugin.cpp
index d5f3b897c9..7618ec3903 100644
--- a/editor/plugins/font_config_plugin.cpp
+++ b/editor/plugins/font_config_plugin.cpp
@@ -310,7 +310,8 @@ void EditorPropertyFontMetaOverride::update_property() {
}
if (script_editor) {
- button_add = EditorInspector::create_inspector_action_button(TTR("Add Script"));
+ // TRANSLATORS: Script refers to a writing system.
+ button_add = EditorInspector::create_inspector_action_button(TTR("Add Script", "Locale"));
} else {
button_add = EditorInspector::create_inspector_action_button(TTR("Add Locale"));
}
diff --git a/editor/plugins/script_editor_plugin.cpp b/editor/plugins/script_editor_plugin.cpp
index e515b46b1e..74d82aa4a2 100644
--- a/editor/plugins/script_editor_plugin.cpp
+++ b/editor/plugins/script_editor_plugin.cpp
@@ -2528,7 +2528,7 @@ void ScriptEditor::reload_scripts(bool p_refresh_only) {
}
Ref<Script> scr = edited_res;
- if (scr != nullptr) {
+ if (scr.is_valid()) {
Ref<Script> rel_scr = ResourceLoader::load(scr->get_path(), scr->get_class(), ResourceFormatLoader::CACHE_MODE_IGNORE);
ERR_CONTINUE(!rel_scr.is_valid());
scr->set_source_code(rel_scr->get_source_code());
@@ -2537,12 +2537,8 @@ void ScriptEditor::reload_scripts(bool p_refresh_only) {
}
Ref<TextFile> text_file = edited_res;
- if (text_file != nullptr) {
- Error err;
- Ref<TextFile> rel_text_file = _load_text_file(text_file->get_path(), &err);
- ERR_CONTINUE(!rel_text_file.is_valid());
- text_file->set_text(rel_text_file->get_text());
- text_file->set_last_modified_time(rel_text_file->get_last_modified_time());
+ if (text_file.is_valid()) {
+ text_file->reload_from_file();
}
}
diff --git a/modules/gdscript/gdscript_parser.cpp b/modules/gdscript/gdscript_parser.cpp
index c6e4222213..42e1f27603 100644
--- a/modules/gdscript/gdscript_parser.cpp
+++ b/modules/gdscript/gdscript_parser.cpp
@@ -483,24 +483,34 @@ void GDScriptParser::parse_program() {
current_class = head;
bool can_have_class_or_extends = true;
- while (match(GDScriptTokenizer::Token::ANNOTATION)) {
- AnnotationNode *annotation = parse_annotation(AnnotationInfo::SCRIPT | AnnotationInfo::STANDALONE | AnnotationInfo::CLASS_LEVEL);
- if (annotation != nullptr) {
- if (annotation->applies_to(AnnotationInfo::SCRIPT)) {
- // `@icon` needs to be applied in the parser. See GH-72444.
- if (annotation->name == SNAME("@icon")) {
- annotation->apply(this, head);
+ while (!check(GDScriptTokenizer::Token::TK_EOF)) {
+ if (match(GDScriptTokenizer::Token::ANNOTATION)) {
+ AnnotationNode *annotation = parse_annotation(AnnotationInfo::SCRIPT | AnnotationInfo::STANDALONE | AnnotationInfo::CLASS_LEVEL);
+ if (annotation != nullptr) {
+ if (annotation->applies_to(AnnotationInfo::SCRIPT)) {
+ // `@icon` needs to be applied in the parser. See GH-72444.
+ if (annotation->name == SNAME("@icon")) {
+ annotation->apply(this, head);
+ } else {
+ head->annotations.push_back(annotation);
+ }
} else {
- head->annotations.push_back(annotation);
+ annotation_stack.push_back(annotation);
+ // This annotation must appear after script-level annotations
+ // and class_name/extends (ex: could be @onready or @export),
+ // so we stop looking for script-level stuff.
+ can_have_class_or_extends = false;
+ break;
}
- } else {
- annotation_stack.push_back(annotation);
- // This annotation must appear after script-level annotations
- // and class_name/extends (ex: could be @onready or @export),
- // so we stop looking for script-level stuff.
- can_have_class_or_extends = false;
- break;
}
+ } else if (check(GDScriptTokenizer::Token::LITERAL) && current.literal.get_type() == Variant::STRING) {
+ // Allow strings in class body as multiline comments.
+ advance();
+ if (!match(GDScriptTokenizer::Token::NEWLINE)) {
+ push_error("Expected newline after comment string.");
+ }
+ } else {
+ break;
}
}
@@ -524,6 +534,16 @@ void GDScriptParser::parse_program() {
end_statement("superclass");
}
break;
+ case GDScriptTokenizer::Token::LITERAL:
+ if (current.literal.get_type() == Variant::STRING) {
+ // Allow strings in class body as multiline comments.
+ advance();
+ if (!match(GDScriptTokenizer::Token::NEWLINE)) {
+ push_error("Expected newline after comment string.");
+ }
+ break;
+ }
+ [[fallthrough]];
default:
// No tokens are allowed between script annotations and class/extends.
can_have_class_or_extends = false;
@@ -829,6 +849,16 @@ void GDScriptParser::parse_class_body(bool p_is_multiline) {
case GDScriptTokenizer::Token::DEDENT:
class_end = true;
break;
+ case GDScriptTokenizer::Token::LITERAL:
+ if (current.literal.get_type() == Variant::STRING) {
+ // Allow strings in class body as multiline comments.
+ advance();
+ if (!match(GDScriptTokenizer::Token::NEWLINE)) {
+ push_error("Expected newline after comment string.");
+ }
+ break;
+ }
+ [[fallthrough]];
default:
// Display a completion with identifiers.
make_completion_context(COMPLETION_IDENTIFIER, nullptr);
@@ -1675,6 +1705,12 @@ GDScriptParser::Node *GDScriptParser::parse_statement() {
// Standalone lambdas can't be used, so make this an error.
push_error("Standalone lambdas cannot be accessed. Consider assigning it to a variable.", expression);
break;
+ case Node::LITERAL:
+ if (static_cast<GDScriptParser::LiteralNode *>(expression)->value.get_type() == Variant::STRING) {
+ // Allow strings as multiline comments.
+ break;
+ }
+ [[fallthrough]];
default:
push_warning(expression, GDScriptWarning::STANDALONE_EXPRESSION);
}
@@ -2145,7 +2181,12 @@ GDScriptParser::ExpressionNode *GDScriptParser::parse_precedence(Precedence p_pr
make_completion_context(COMPLETION_IDENTIFIER, nullptr);
GDScriptTokenizer::Token token = current;
- ParseFunction prefix_rule = get_rule(token.type)->prefix;
+ GDScriptTokenizer::Token::Type token_type = token.type;
+ if (token.is_identifier()) {
+ // Allow keywords that can be treated as identifiers.
+ token_type = GDScriptTokenizer::Token::IDENTIFIER;
+ }
+ ParseFunction prefix_rule = get_rule(token_type)->prefix;
if (prefix_rule == nullptr) {
// Expected expression. Let the caller give the proper error message.
@@ -3010,7 +3051,14 @@ GDScriptParser::ExpressionNode *GDScriptParser::parse_get_node(ExpressionNode *p
path_state = PATH_STATE_NODE_NAME;
} else if (current.is_node_name()) {
advance();
- get_node->full_path += previous.get_identifier();
+ String identifier = previous.get_identifier();
+#ifdef DEBUG_ENABLED
+ // Check spoofing.
+ if (TS->has_feature(TextServer::FEATURE_UNICODE_SECURITY) && TS->spoof_check(identifier)) {
+ push_warning(get_node, GDScriptWarning::CONFUSABLE_IDENTIFIER, identifier);
+ }
+#endif
+ get_node->full_path += identifier;
path_state = PATH_STATE_NODE_NAME;
} else if (!check(GDScriptTokenizer::Token::SLASH) && !check(GDScriptTokenizer::Token::PERCENT)) {
diff --git a/modules/gdscript/gdscript_tokenizer.cpp b/modules/gdscript/gdscript_tokenizer.cpp
index d7f1114fd3..d586380c41 100644
--- a/modules/gdscript/gdscript_tokenizer.cpp
+++ b/modules/gdscript/gdscript_tokenizer.cpp
@@ -168,7 +168,11 @@ bool GDScriptTokenizer::Token::is_identifier() const {
switch (type) {
case IDENTIFIER:
case MATCH: // Used in String.match().
- case CONST_INF: // Used in Vector{2,3,4}.INF
+ // Allow constants to be treated as regular identifiers.
+ case CONST_PI:
+ case CONST_INF:
+ case CONST_NAN:
+ case CONST_TAU:
return true;
default:
return false;
@@ -188,6 +192,10 @@ bool GDScriptTokenizer::Token::is_node_name() const {
case CLASS_NAME:
case CLASS:
case CONST:
+ case CONST_PI:
+ case CONST_INF:
+ case CONST_NAN:
+ case CONST_TAU:
case CONTINUE:
case ELIF:
case ELSE:
@@ -530,9 +538,12 @@ void GDScriptTokenizer::make_keyword_list() {
#endif // DEBUG_ENABLED
GDScriptTokenizer::Token GDScriptTokenizer::potential_identifier() {
+ bool only_ascii = _peek(-1) < 128;
+
// Consume all identifier characters.
while (is_unicode_identifier_continue(_peek())) {
- _advance();
+ char32_t c = _advance();
+ only_ascii = only_ascii && c < 128;
}
int len = _current - _start;
@@ -587,7 +598,7 @@ GDScriptTokenizer::Token GDScriptTokenizer::potential_identifier() {
#ifdef DEBUG_ENABLED
// Additional checks for identifiers but only in debug and if it's available in TextServer.
- if (TS->has_feature(TextServer::FEATURE_UNICODE_SECURITY)) {
+ if (!only_ascii && TS->has_feature(TextServer::FEATURE_UNICODE_SECURITY)) {
int64_t confusable = TS->is_confusable(name, keyword_list);
if (confusable >= 0) {
push_error(vformat(R"(Identifier "%s" is visually similar to the GDScript keyword "%s" and thus not allowed.)", name, keyword_list[confusable]));
diff --git a/modules/gdscript/language_server/gdscript_text_document.cpp b/modules/gdscript/language_server/gdscript_text_document.cpp
index b9e6921034..35fbdca949 100644
--- a/modules/gdscript/language_server/gdscript_text_document.cpp
+++ b/modules/gdscript/language_server/gdscript_text_document.cpp
@@ -108,6 +108,7 @@ void GDScriptTextDocument::didSave(const Variant &p_param) {
scr->reload(true);
}
scr->update_exports();
+ ScriptEditor::get_singleton()->reload_scripts(true);
ScriptEditor::get_singleton()->update_docs_from_script(scr);
}
}
diff --git a/modules/gdscript/tests/scripts/parser/features/allow_id_similar_to_keyword_in_ascii.gd b/modules/gdscript/tests/scripts/parser/features/allow_id_similar_to_keyword_in_ascii.gd
new file mode 100644
index 0000000000..390d314b94
--- /dev/null
+++ b/modules/gdscript/tests/scripts/parser/features/allow_id_similar_to_keyword_in_ascii.gd
@@ -0,0 +1,3 @@
+func test():
+ var P1 = "ok" # Technically it is visually similar to keyword "PI" but allowed since it's in ASCII range.
+ print(P1)
diff --git a/modules/gdscript/tests/scripts/parser/features/allow_id_similar_to_keyword_in_ascii.out b/modules/gdscript/tests/scripts/parser/features/allow_id_similar_to_keyword_in_ascii.out
new file mode 100644
index 0000000000..1b47ed10dc
--- /dev/null
+++ b/modules/gdscript/tests/scripts/parser/features/allow_id_similar_to_keyword_in_ascii.out
@@ -0,0 +1,2 @@
+GDTEST_OK
+ok
diff --git a/modules/gdscript/tests/scripts/parser/features/allow_strings_as_comments.gd b/modules/gdscript/tests/scripts/parser/features/allow_strings_as_comments.gd
new file mode 100644
index 0000000000..3ecd65ad9c
--- /dev/null
+++ b/modules/gdscript/tests/scripts/parser/features/allow_strings_as_comments.gd
@@ -0,0 +1,21 @@
+"""
+This is a comment.
+"""
+
+@tool
+
+"""
+This is also a comment.
+"""
+
+extends RefCounted
+
+'''
+This is a comment too.
+'''
+
+func test():
+ """
+ This too is a comment.
+ """
+ print("ok")
diff --git a/modules/gdscript/tests/scripts/parser/features/allow_strings_as_comments.out b/modules/gdscript/tests/scripts/parser/features/allow_strings_as_comments.out
new file mode 100644
index 0000000000..1b47ed10dc
--- /dev/null
+++ b/modules/gdscript/tests/scripts/parser/features/allow_strings_as_comments.out
@@ -0,0 +1,2 @@
+GDTEST_OK
+ok
diff --git a/modules/gdscript/tests/scripts/parser/features/allowed_keywords_as_identifiers.gd b/modules/gdscript/tests/scripts/parser/features/allowed_keywords_as_identifiers.gd
new file mode 100644
index 0000000000..7e1982597c
--- /dev/null
+++ b/modules/gdscript/tests/scripts/parser/features/allowed_keywords_as_identifiers.gd
@@ -0,0 +1,16 @@
+func test():
+ # The following keywords are allowed as identifiers:
+ var match = "match"
+ print(match)
+
+ var PI = "PI"
+ print(PI)
+
+ var INF = "INF"
+ print(INF)
+
+ var NAN = "NAN"
+ print(NAN)
+
+ var TAU = "TAU"
+ print(TAU)
diff --git a/modules/gdscript/tests/scripts/parser/features/allowed_keywords_as_identifiers.out b/modules/gdscript/tests/scripts/parser/features/allowed_keywords_as_identifiers.out
new file mode 100644
index 0000000000..aae2ae13d5
--- /dev/null
+++ b/modules/gdscript/tests/scripts/parser/features/allowed_keywords_as_identifiers.out
@@ -0,0 +1,6 @@
+GDTEST_OK
+match
+PI
+INF
+NAN
+TAU
diff --git a/modules/gdscript/tests/scripts/parser/warnings/confusable_identifier.gd b/modules/gdscript/tests/scripts/parser/warnings/confusable_identifier.gd
index e2caac8ffd..41b38c4bba 100644
--- a/modules/gdscript/tests/scripts/parser/warnings/confusable_identifier.gd
+++ b/modules/gdscript/tests/scripts/parser/warnings/confusable_identifier.gd
@@ -1,5 +1,12 @@
+extends Node
+
func test():
var port = 0 # Only latin characters.
var pοrt = 1 # The "ο" is Greek omicron.
prints(port, pοrt)
+
+# Do not call this since nodes aren't in the tree. It is just a parser check.
+func nodes():
+ var _node1 = $port # Only latin characters.
+ var _node2 = $pοrt # The "ο" is Greek omicron.
diff --git a/modules/gdscript/tests/scripts/parser/warnings/confusable_identifier.out b/modules/gdscript/tests/scripts/parser/warnings/confusable_identifier.out
index c483396443..c189204285 100644
--- a/modules/gdscript/tests/scripts/parser/warnings/confusable_identifier.out
+++ b/modules/gdscript/tests/scripts/parser/warnings/confusable_identifier.out
@@ -1,6 +1,10 @@
GDTEST_OK
>> WARNING
->> Line: 3
+>> Line: 5
+>> CONFUSABLE_IDENTIFIER
+>> The identifier "pοrt" has misleading characters and might be confused with something else.
+>> WARNING
+>> Line: 12
>> CONFUSABLE_IDENTIFIER
>> The identifier "pοrt" has misleading characters and might be confused with something else.
0 1
diff --git a/modules/gdscript/tests/scripts/parser/warnings/standalone_expression.gd b/modules/gdscript/tests/scripts/parser/warnings/standalone_expression.gd
index 18ea260fa2..dc4223ec2d 100644
--- a/modules/gdscript/tests/scripts/parser/warnings/standalone_expression.gd
+++ b/modules/gdscript/tests/scripts/parser/warnings/standalone_expression.gd
@@ -1,6 +1,5 @@
func test():
# The following statements should all be reported as standalone expressions:
- "This is a standalone expression"
1234
0.0 + 0.0
Color(1, 1, 1)
diff --git a/modules/gdscript/tests/scripts/parser/warnings/standalone_expression.out b/modules/gdscript/tests/scripts/parser/warnings/standalone_expression.out
index 99ec87438e..a2c67a6e51 100644
--- a/modules/gdscript/tests/scripts/parser/warnings/standalone_expression.out
+++ b/modules/gdscript/tests/scripts/parser/warnings/standalone_expression.out
@@ -8,14 +8,10 @@ GDTEST_OK
>> STANDALONE_EXPRESSION
>> Standalone expression (the line has no effect).
>> WARNING
->> Line: 5
+>> Line: 6
>> STANDALONE_EXPRESSION
>> Standalone expression (the line has no effect).
>> WARNING
>> Line: 7
>> STANDALONE_EXPRESSION
>> Standalone expression (the line has no effect).
->> WARNING
->> Line: 8
->> STANDALONE_EXPRESSION
->> Standalone expression (the line has no effect).
diff --git a/modules/multiplayer/editor/editor_network_profiler.cpp b/modules/multiplayer/editor/editor_network_profiler.cpp
index e320657ab5..f8e75d5ef5 100644
--- a/modules/multiplayer/editor/editor_network_profiler.cpp
+++ b/modules/multiplayer/editor/editor_network_profiler.cpp
@@ -253,7 +253,8 @@ EditorNetworkProfiler::EditorNetworkProfiler() {
hb->add_spacer();
Label *lb = memnew(Label);
- lb->set_text(TTR("Down"));
+ // TRANSLATORS: This is the label for the network profiler's incoming bandwidth.
+ lb->set_text(TTR("Down", "Network"));
hb->add_child(lb);
incoming_bandwidth_text = memnew(LineEdit);
@@ -267,7 +268,8 @@ EditorNetworkProfiler::EditorNetworkProfiler() {
hb->add_child(down_up_spacer);
lb = memnew(Label);
- lb->set_text(TTR("Up"));
+ // TRANSLATORS: This is the label for the network profiler's outgoing bandwidth.
+ lb->set_text(TTR("Up", "Network"));
hb->add_child(lb);
outgoing_bandwidth_text = memnew(LineEdit);
diff --git a/platform/android/audio_driver_opensl.cpp b/platform/android/audio_driver_opensl.cpp
index 9dad0c9357..5fc32132e3 100644
--- a/platform/android/audio_driver_opensl.cpp
+++ b/platform/android/audio_driver_opensl.cpp
@@ -80,10 +80,6 @@ void AudioDriverOpenSL::_buffer_callbacks(
ad->_buffer_callback(queueItf);
}
-const char *AudioDriverOpenSL::get_name() const {
- return "Android";
-}
-
Error AudioDriverOpenSL::init() {
SLresult res;
SLEngineOption EngineOption[] = {
@@ -204,7 +200,7 @@ void AudioDriverOpenSL::_record_buffer_callbacks(SLAndroidSimpleBufferQueueItf q
ad->_record_buffer_callback(queueItf);
}
-Error AudioDriverOpenSL::capture_init_device() {
+Error AudioDriverOpenSL::init_input_device() {
SLDataLocator_IODevice loc_dev = {
SL_DATALOCATOR_IODEVICE,
SL_IODEVICE_AUDIOINPUT,
@@ -271,15 +267,15 @@ Error AudioDriverOpenSL::capture_init_device() {
return OK;
}
-Error AudioDriverOpenSL::capture_start() {
+Error AudioDriverOpenSL::input_start() {
if (OS::get_singleton()->request_permission("RECORD_AUDIO")) {
- return capture_init_device();
+ return init_input_device();
}
return OK;
}
-Error AudioDriverOpenSL::capture_stop() {
+Error AudioDriverOpenSL::input_stop() {
SLuint32 state;
SLresult res = (*recordItf)->GetRecordState(recordItf, &state);
ERR_FAIL_COND_V(res != SL_RESULT_SUCCESS, ERR_CANT_OPEN);
diff --git a/platform/android/audio_driver_opensl.h b/platform/android/audio_driver_opensl.h
index ae8c33fec0..6ea0f77def 100644
--- a/platform/android/audio_driver_opensl.h
+++ b/platform/android/audio_driver_opensl.h
@@ -84,23 +84,26 @@ class AudioDriverOpenSL : public AudioDriver {
SLAndroidSimpleBufferQueueItf queueItf,
void *pContext);
- virtual Error capture_init_device();
+ Error init_input_device();
public:
- virtual const char *get_name() const;
+ virtual const char *get_name() const override {
+ return "Android";
+ }
- virtual Error init();
- virtual void start();
- virtual int get_mix_rate() const;
- virtual SpeakerMode get_speaker_mode() const;
- virtual void lock();
- virtual void unlock();
- virtual void finish();
+ virtual Error init() override;
+ virtual void start() override;
+ virtual int get_mix_rate() const override;
+ virtual SpeakerMode get_speaker_mode() const override;
- virtual void set_pause(bool p_pause);
+ virtual void lock() override;
+ virtual void unlock() override;
+ virtual void finish() override;
- virtual Error capture_start();
- virtual Error capture_stop();
+ virtual Error input_start() override;
+ virtual Error input_stop() override;
+
+ void set_pause(bool p_pause);
AudioDriverOpenSL();
};
diff --git a/platform/android/java_godot_lib_jni.cpp b/platform/android/java_godot_lib_jni.cpp
index 1ee1cccb82..e7abe580f1 100644
--- a/platform/android/java_godot_lib_jni.cpp
+++ b/platform/android/java_godot_lib_jni.cpp
@@ -488,7 +488,7 @@ JNIEXPORT void JNICALL Java_org_godotengine_godot_GodotLib_calldeferred(JNIEnv *
JNIEXPORT void JNICALL Java_org_godotengine_godot_GodotLib_requestPermissionResult(JNIEnv *env, jclass clazz, jstring p_permission, jboolean p_result) {
String permission = jstring_to_string(p_permission, env);
if (permission == "android.permission.RECORD_AUDIO" && p_result) {
- AudioDriver::get_singleton()->capture_start();
+ AudioDriver::get_singleton()->input_start();
}
if (os_android->get_main_loop()) {
diff --git a/platform/web/audio_driver_web.cpp b/platform/web/audio_driver_web.cpp
index a5234627d1..1d7b96d707 100644
--- a/platform/web/audio_driver_web.cpp
+++ b/platform/web/audio_driver_web.cpp
@@ -166,18 +166,18 @@ void AudioDriverWeb::finish() {
}
}
-Error AudioDriverWeb::capture_start() {
+Error AudioDriverWeb::input_start() {
lock();
input_buffer_init(buffer_length);
unlock();
- if (godot_audio_capture_start()) {
+ if (godot_audio_input_start()) {
return FAILED;
}
return OK;
}
-Error AudioDriverWeb::capture_stop() {
- godot_audio_capture_stop();
+Error AudioDriverWeb::input_stop() {
+ godot_audio_input_stop();
lock();
input_buffer.clear();
unlock();
diff --git a/platform/web/audio_driver_web.h b/platform/web/audio_driver_web.h
index f3afbdbb92..be13935bd9 100644
--- a/platform/web/audio_driver_web.h
+++ b/platform/web/audio_driver_web.h
@@ -77,12 +77,12 @@ public:
virtual void start() final;
virtual void finish() final;
- virtual float get_latency() override;
virtual int get_mix_rate() const override;
virtual SpeakerMode get_speaker_mode() const override;
+ virtual float get_latency() override;
- virtual Error capture_start() override;
- virtual Error capture_stop() override;
+ virtual Error input_start() override;
+ virtual Error input_stop() override;
static void resume();
@@ -111,10 +111,12 @@ protected:
virtual void finish_driver() override;
public:
- virtual const char *get_name() const override { return "AudioWorklet"; }
+ virtual const char *get_name() const override {
+ return "AudioWorklet";
+ }
- void lock() override;
- void unlock() override;
+ virtual void lock() override;
+ virtual void unlock() override;
};
#endif // AUDIO_DRIVER_WEB_H
diff --git a/platform/web/godot_audio.h b/platform/web/godot_audio.h
index d7bff078f8..c6f92161fa 100644
--- a/platform/web/godot_audio.h
+++ b/platform/web/godot_audio.h
@@ -43,8 +43,8 @@ extern int godot_audio_has_script_processor();
extern int godot_audio_init(int *p_mix_rate, int p_latency, void (*_state_cb)(int), void (*_latency_cb)(float));
extern void godot_audio_resume();
-extern int godot_audio_capture_start();
-extern void godot_audio_capture_stop();
+extern int godot_audio_input_start();
+extern void godot_audio_input_stop();
// Worklet
typedef int32_t GodotAudioState[4];
diff --git a/platform/web/js/libs/library_godot_audio.js b/platform/web/js/libs/library_godot_audio.js
index 68348a3962..1993d66310 100644
--- a/platform/web/js/libs/library_godot_audio.js
+++ b/platform/web/js/libs/library_godot_audio.js
@@ -186,17 +186,17 @@ const GodotAudio = {
}
},
- godot_audio_capture_start__proxy: 'sync',
- godot_audio_capture_start__sig: 'i',
- godot_audio_capture_start: function () {
+ godot_audio_input_start__proxy: 'sync',
+ godot_audio_input_start__sig: 'i',
+ godot_audio_input_start: function () {
return GodotAudio.create_input(function (input) {
input.connect(GodotAudio.driver.get_node());
});
},
- godot_audio_capture_stop__proxy: 'sync',
- godot_audio_capture_stop__sig: 'v',
- godot_audio_capture_stop: function () {
+ godot_audio_input_stop__proxy: 'sync',
+ godot_audio_input_stop__sig: 'v',
+ godot_audio_input_stop: function () {
if (GodotAudio.input) {
const tracks = GodotAudio.input['mediaStream']['getTracks']();
for (let i = 0; i < tracks.length; i++) {
diff --git a/scene/3d/collision_shape_3d.cpp b/scene/3d/collision_shape_3d.cpp
index dbd50cfd19..f1d918ad9b 100644
--- a/scene/3d/collision_shape_3d.cpp
+++ b/scene/3d/collision_shape_3d.cpp
@@ -124,7 +124,7 @@ PackedStringArray CollisionShape3D::get_configuration_warnings() const {
PackedStringArray warnings = Node::get_configuration_warnings();
if (!Object::cast_to<CollisionObject3D>(get_parent())) {
- warnings.push_back(RTR("CollisionShape3D only serves to provide a collision shape to a CollisionObject3D derived node. Please only use it as a child of Area3D, StaticBody3D, RigidBody3D, CharacterBody3D, etc. to give them a shape."));
+ warnings.push_back(RTR("CollisionShape3D only serves to provide a collision shape to a CollisionObject3D derived node.\nPlease only use it as a child of Area3D, StaticBody3D, RigidBody3D, CharacterBody3D, etc. to give them a shape."));
}
if (!shape.is_valid()) {
diff --git a/scene/gui/view_panner.cpp b/scene/gui/view_panner.cpp
index 145497fa61..51af886709 100644
--- a/scene/gui/view_panner.cpp
+++ b/scene/gui/view_panner.cpp
@@ -125,7 +125,7 @@ bool ViewPanner::gui_input(const Ref<InputEvent> &p_event, Rect2 p_canvas_rect)
Ref<InputEventPanGesture> pan_gesture = p_event;
if (pan_gesture.is_valid()) {
- callback_helper(pan_callback, varray(-pan_gesture->get_delta(), p_event));
+ callback_helper(pan_callback, varray(-pan_gesture->get_delta() * scroll_speed, p_event));
}
Ref<InputEventScreenDrag> screen_drag = p_event;
diff --git a/scene/resources/text_file.cpp b/scene/resources/text_file.cpp
index 9b61a95edb..77ff0f55b1 100644
--- a/scene/resources/text_file.cpp
+++ b/scene/resources/text_file.cpp
@@ -67,5 +67,10 @@ Error TextFile::load_text(const String &p_path) {
ERR_FAIL_COND_V_MSG(s.parse_utf8((const char *)w) != OK, ERR_INVALID_DATA, "Script '" + p_path + "' contains invalid unicode (UTF-8), so it was not loaded. Please ensure that scripts are saved in valid UTF-8 unicode.");
text = s;
path = p_path;
+#ifdef TOOLS_ENABLED
+ if (ResourceLoader::get_timestamp_on_load()) {
+ set_last_modified_time(FileAccess::get_modified_time(path));
+ }
+#endif // TOOLS_ENABLED
return OK;
}
diff --git a/servers/audio/audio_driver_dummy.cpp b/servers/audio/audio_driver_dummy.cpp
index ed140c2244..e6257d9260 100644
--- a/servers/audio/audio_driver_dummy.cpp
+++ b/servers/audio/audio_driver_dummy.cpp
@@ -52,7 +52,7 @@ Error AudioDriverDummy::init() {
}
return OK;
-};
+}
void AudioDriverDummy::thread_func(void *p_udata) {
AudioDriverDummy *ad = static_cast<AudioDriverDummy *>(p_udata);
@@ -68,31 +68,31 @@ void AudioDriverDummy::thread_func(void *p_udata) {
ad->stop_counting_ticks();
ad->unlock();
- };
+ }
OS::get_singleton()->delay_usec(usdelay);
- };
-};
+ }
+}
void AudioDriverDummy::start() {
active.set();
-};
+}
int AudioDriverDummy::get_mix_rate() const {
return mix_rate;
-};
+}
AudioDriver::SpeakerMode AudioDriverDummy::get_speaker_mode() const {
return speaker_mode;
-};
+}
void AudioDriverDummy::lock() {
mutex.lock();
-};
+}
void AudioDriverDummy::unlock() {
mutex.unlock();
-};
+}
void AudioDriverDummy::set_use_threads(bool p_use_threads) {
use_threads = p_use_threads;
@@ -141,7 +141,7 @@ void AudioDriverDummy::finish() {
if (samples_in) {
memdelete_arr(samples_in);
- };
+ }
}
AudioDriverDummy::AudioDriverDummy() {
diff --git a/servers/audio/audio_driver_dummy.h b/servers/audio/audio_driver_dummy.h
index 8c858a2483..823bad1d2e 100644
--- a/servers/audio/audio_driver_dummy.h
+++ b/servers/audio/audio_driver_dummy.h
@@ -59,17 +59,18 @@ class AudioDriverDummy : public AudioDriver {
static AudioDriverDummy *singleton;
public:
- const char *get_name() const {
+ virtual const char *get_name() const override {
return "Dummy";
};
- virtual Error init();
- virtual void start();
- virtual int get_mix_rate() const;
- virtual SpeakerMode get_speaker_mode() const;
- virtual void lock();
- virtual void unlock();
- virtual void finish();
+ virtual Error init() override;
+ virtual void start() override;
+ virtual int get_mix_rate() const override;
+ virtual SpeakerMode get_speaker_mode() const override;
+
+ virtual void lock() override;
+ virtual void unlock() override;
+ virtual void finish() override;
void set_use_threads(bool p_use_threads);
void set_speaker_mode(SpeakerMode p_mode);
diff --git a/servers/audio/audio_stream.cpp b/servers/audio/audio_stream.cpp
index 90997ac16e..32ee650f8d 100644
--- a/servers/audio/audio_stream.cpp
+++ b/servers/audio/audio_stream.cpp
@@ -367,7 +367,7 @@ void AudioStreamPlaybackMicrophone::start(double p_from_pos) {
input_ofs = 0;
- if (AudioDriver::get_singleton()->capture_start() == OK) {
+ if (AudioDriver::get_singleton()->input_start() == OK) {
active = true;
begin_resample();
}
@@ -375,7 +375,7 @@ void AudioStreamPlaybackMicrophone::start(double p_from_pos) {
void AudioStreamPlaybackMicrophone::stop() {
if (active) {
- AudioDriver::get_singleton()->capture_stop();
+ AudioDriver::get_singleton()->input_stop();
active = false;
}
}
diff --git a/servers/audio_server.cpp b/servers/audio_server.cpp
index 8c877e4eed..0344bf322d 100644
--- a/servers/audio_server.cpp
+++ b/servers/audio_server.cpp
@@ -1640,8 +1640,8 @@ String AudioServer::get_output_device() {
return AudioDriver::get_singleton()->get_output_device();
}
-void AudioServer::set_output_device(String output_device) {
- AudioDriver::get_singleton()->set_output_device(output_device);
+void AudioServer::set_output_device(const String &p_name) {
+ AudioDriver::get_singleton()->set_output_device(p_name);
}
PackedStringArray AudioServer::get_input_device_list() {
@@ -1711,9 +1711,10 @@ void AudioServer::_bind_methods() {
ClassDB::bind_method(D_METHOD("get_speaker_mode"), &AudioServer::get_speaker_mode);
ClassDB::bind_method(D_METHOD("get_mix_rate"), &AudioServer::get_mix_rate);
+
ClassDB::bind_method(D_METHOD("get_output_device_list"), &AudioServer::get_output_device_list);
ClassDB::bind_method(D_METHOD("get_output_device"), &AudioServer::get_output_device);
- ClassDB::bind_method(D_METHOD("set_output_device", "output_device"), &AudioServer::set_output_device);
+ ClassDB::bind_method(D_METHOD("set_output_device", "name"), &AudioServer::set_output_device);
ClassDB::bind_method(D_METHOD("get_time_to_next_mix"), &AudioServer::get_time_to_next_mix);
ClassDB::bind_method(D_METHOD("get_time_since_last_mix"), &AudioServer::get_time_since_last_mix);
diff --git a/servers/audio_server.h b/servers/audio_server.h
index d3d87a8400..155beb2000 100644
--- a/servers/audio_server.h
+++ b/servers/audio_server.h
@@ -88,26 +88,32 @@ public:
static AudioDriver *get_singleton();
void set_singleton();
+ // Virtual API to implement.
+
virtual const char *get_name() const = 0;
virtual Error init() = 0;
virtual void start() = 0;
virtual int get_mix_rate() const = 0;
virtual SpeakerMode get_speaker_mode() const = 0;
- virtual PackedStringArray get_output_device_list();
- virtual String get_output_device();
- virtual void set_output_device(String output_device) {}
+ virtual float get_latency() { return 0; }
+
virtual void lock() = 0;
virtual void unlock() = 0;
virtual void finish() = 0;
- virtual Error capture_start() { return FAILED; }
- virtual Error capture_stop() { return FAILED; }
- virtual void set_input_device(const String &p_name) {}
- virtual String get_input_device() { return "Default"; }
+ virtual PackedStringArray get_output_device_list();
+ virtual String get_output_device();
+ virtual void set_output_device(const String &p_name) {}
+
+ virtual Error input_start() { return FAILED; }
+ virtual Error input_stop() { return FAILED; }
+
virtual PackedStringArray get_input_device_list();
+ virtual String get_input_device() { return "Default"; }
+ virtual void set_input_device(const String &p_name) {}
- virtual float get_latency() { return 0; }
+ //
SpeakerMode get_speaker_mode_by_total_channels(int p_channels) const;
int get_total_channels_by_speaker_mode(SpeakerMode) const;
@@ -421,7 +427,7 @@ public:
PackedStringArray get_output_device_list();
String get_output_device();
- void set_output_device(String output_device);
+ void set_output_device(const String &p_name);
PackedStringArray get_input_device_list();
String get_input_device();
diff --git a/servers/rendering/shader_language.cpp b/servers/rendering/shader_language.cpp
index e9c13f88ca..a727e83513 100644
--- a/servers/rendering/shader_language.cpp
+++ b/servers/rendering/shader_language.cpp
@@ -6553,7 +6553,7 @@ ShaderLanguage::Node *ShaderLanguage::_parse_expression(BlockNode *p_block, cons
OperatorNode *op = alloc_node<OperatorNode>();
op->op = expression[i].op;
if ((op->op == OP_INCREMENT || op->op == OP_DECREMENT) && !_validate_assign(expression[i + 1].node, p_function_info)) {
- _set_error(RTR("Can't use increment/decrement operator in a constant expression."));
+ _set_error(RTR("Invalid use of increment/decrement operator in a constant expression."));
return nullptr;
}
op->arguments.push_back(expression[i + 1].node);
@@ -8421,7 +8421,7 @@ Error ShaderLanguage::_parse_shader(const HashMap<StringName, FunctionInfo> &p_f
_set_error(vformat(RTR("The '%s' data type is not supported for uniforms."), "struct"));
return ERR_PARSE_ERROR;
} else {
- _set_error(vformat(RTR("The '%s' data type not allowed here."), "struct"));
+ _set_error(vformat(RTR("The '%s' data type is not allowed here."), "struct"));
return ERR_PARSE_ERROR;
}
}
diff --git a/servers/rendering/shader_preprocessor.h b/servers/rendering/shader_preprocessor.h
index 2b7d2c274e..f198af66f0 100644
--- a/servers/rendering/shader_preprocessor.h
+++ b/servers/rendering/shader_preprocessor.h
@@ -182,7 +182,7 @@ private:
}
void _set_unexpected_token_error(const String &p_what, int p_line) {
- set_error(vformat(RTR("Unexpected token '%s'."), p_what), p_line);
+ set_error(vformat(RTR("Unexpected token: '%s'."), p_what), p_line);
}
void process_directive(Tokenizer *p_tokenizer);
diff --git a/tests/core/string/test_string.h b/tests/core/string/test_string.h
index 0cf3448a48..5d19b5a164 100644
--- a/tests/core/string/test_string.h
+++ b/tests/core/string/test_string.h
@@ -512,6 +512,14 @@ TEST_CASE("[String] Splitting") {
CHECK(l[i] == slices_3[i]);
}
+ s = "";
+ l = s.split();
+ CHECK(l.size() == 1);
+ CHECK(l[0] == "");
+
+ l = s.split("", false);
+ CHECK(l.size() == 0);
+
s = "Mars Jupiter Saturn Uranus";
const char *slices_s[4] = { "Mars", "Jupiter", "Saturn", "Uranus" };
l = s.split_spaces();
diff --git a/tests/display_server_mock.h b/tests/display_server_mock.h
index 1736f2c452..fe36fa0b69 100644
--- a/tests/display_server_mock.h
+++ b/tests/display_server_mock.h
@@ -42,6 +42,7 @@ private:
friend class DisplayServer;
Point2i mouse_position = Point2i(-1, -1); // Outside of Window.
+ CursorShape cursor_shape = CursorShape::CURSOR_ARROW;
bool window_over = false;
Callable event_callback;
Callable input_event_callback;
@@ -103,6 +104,7 @@ public:
bool has_feature(Feature p_feature) const override {
switch (p_feature) {
case FEATURE_MOUSE:
+ case FEATURE_CURSOR_SHAPE:
return true;
default: {
}
@@ -115,12 +117,24 @@ public:
// You can simulate DisplayServer-events by calling this function.
// The events will be deliverd to Godot's Input-system.
// Mouse-events (Button & Motion) will additionally update the DisplayServer's mouse position.
+ // For Mouse motion events, the `relative`-property is set based on the distance to the previous mouse position.
void simulate_event(Ref<InputEvent> p_event) {
+ Ref<InputEvent> event = p_event;
Ref<InputEventMouse> me = p_event;
if (me.is_valid()) {
+ Ref<InputEventMouseMotion> mm = p_event;
+ if (mm.is_valid()) {
+ mm->set_relative(mm->get_position() - mouse_position);
+ event = mm;
+ }
_set_mouse_position(me->get_position());
}
- Input::get_singleton()->parse_input_event(p_event);
+ Input::get_singleton()->parse_input_event(event);
+ }
+
+ // Returns the current cursor shape.
+ CursorShape get_cursor_shape() {
+ return cursor_shape;
}
virtual Point2i mouse_get_position() const override { return mouse_position; }
@@ -129,6 +143,10 @@ public:
return Size2i(1920, 1080);
}
+ virtual void cursor_set_shape(CursorShape p_shape) override {
+ cursor_shape = p_shape;
+ }
+
virtual void window_set_window_event_callback(const Callable &p_callable, WindowID p_window = MAIN_WINDOW_ID) override {
event_callback = p_callable;
}
diff --git a/tests/scene/test_text_edit.h b/tests/scene/test_text_edit.h
index d42ef8859a..64ad3bd5b0 100644
--- a/tests/scene/test_text_edit.h
+++ b/tests/scene/test_text_edit.h
@@ -1134,7 +1134,6 @@ TEST_CASE("[SceneTree][TextEdit] text entry") {
SUBCASE("[TextEdit] text drag") {
TextEdit *target_text_edit = memnew(TextEdit);
SceneTree::get_singleton()->get_root()->add_child(target_text_edit);
- text_edit->get_viewport()->set_embedding_subwindows(true); // Bypass display server for drop handling.
target_text_edit->set_size(Size2(200, 200));
target_text_edit->set_position(Point2(400, 0));
@@ -3083,8 +3082,6 @@ TEST_CASE("[SceneTree][TextEdit] context menu") {
TextEdit *text_edit = memnew(TextEdit);
SceneTree::get_singleton()->get_root()->add_child(text_edit);
- text_edit->get_viewport()->set_embedding_subwindows(true); // Bypass display server for drop handling.
-
text_edit->set_size(Size2(800, 200));
text_edit->set_line(0, "Lorem ipsum dolor sit amet, consectetur adipiscing elit. Donec vasius mattis leo, sed porta ex lacinia bibendum. Nunc bibendum pellentesque.");
MessageQueue::get_singleton()->flush();
diff --git a/tests/scene/test_viewport.h b/tests/scene/test_viewport.h
new file mode 100644
index 0000000000..62f4635927
--- /dev/null
+++ b/tests/scene/test_viewport.h
@@ -0,0 +1,718 @@
+/**************************************************************************/
+/* test_viewport.h */
+/**************************************************************************/
+/* This file is part of: */
+/* GODOT ENGINE */
+/* https://godotengine.org */
+/**************************************************************************/
+/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
+/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
+/* */
+/* Permission is hereby granted, free of charge, to any person obtaining */
+/* a copy of this software and associated documentation files (the */
+/* "Software"), to deal in the Software without restriction, including */
+/* without limitation the rights to use, copy, modify, merge, publish, */
+/* distribute, sublicense, and/or sell copies of the Software, and to */
+/* permit persons to whom the Software is furnished to do so, subject to */
+/* the following conditions: */
+/* */
+/* The above copyright notice and this permission notice shall be */
+/* included in all copies or substantial portions of the Software. */
+/* */
+/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
+/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
+/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */
+/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
+/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
+/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
+/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
+/**************************************************************************/
+
+#ifndef TEST_VIEWPORT_H
+#define TEST_VIEWPORT_H
+
+#include "scene/2d/node_2d.h"
+#include "scene/gui/control.h"
+#include "scene/main/window.h"
+
+#include "tests/test_macros.h"
+
+namespace TestViewport {
+
+class NotificationControl : public Control {
+ GDCLASS(NotificationControl, Control);
+
+protected:
+ void _notification(int p_what) {
+ switch (p_what) {
+ case NOTIFICATION_MOUSE_ENTER: {
+ mouse_over = true;
+ } break;
+
+ case NOTIFICATION_MOUSE_EXIT: {
+ mouse_over = false;
+ } break;
+ }
+ }
+
+public:
+ bool mouse_over = false;
+};
+
+// `NotificationControl`-derived class that additionally
+// - allows start Dragging
+// - stores mouse information of last event
+class DragStart : public NotificationControl {
+ GDCLASS(DragStart, NotificationControl);
+
+public:
+ MouseButton last_mouse_button;
+ Point2i last_mouse_move_position;
+ StringName drag_data_name = SNAME("Drag Data");
+
+ virtual Variant get_drag_data(const Point2 &p_point) override {
+ return drag_data_name;
+ }
+
+ virtual void gui_input(const Ref<InputEvent> &p_event) override {
+ Ref<InputEventMouseButton> mb = p_event;
+ if (mb.is_valid()) {
+ last_mouse_button = mb->get_button_index();
+ return;
+ }
+
+ Ref<InputEventMouseMotion> mm = p_event;
+ if (mm.is_valid()) {
+ last_mouse_move_position = mm->get_position();
+ return;
+ }
+ }
+};
+
+// `NotificationControl`-derived class that acts as a Drag and Drop target.
+class DragTarget : public NotificationControl {
+ GDCLASS(DragTarget, NotificationControl);
+
+public:
+ Variant drag_data;
+ virtual bool can_drop_data(const Point2 &p_point, const Variant &p_data) const override {
+ StringName string_data = p_data;
+ // Verify drag data is compatible.
+ if (string_data != SNAME("Drag Data")) {
+ return false;
+ }
+ // Only the left half is droppable area.
+ if (p_point.x * 2 > get_size().x) {
+ return false;
+ }
+ return true;
+ }
+
+ virtual void drop_data(const Point2 &p_point, const Variant &p_data) override {
+ drag_data = p_data;
+ }
+};
+
+TEST_CASE("[SceneTree][Viewport] Controls and InputEvent handling") {
+ DragStart *node_a = memnew(DragStart);
+ Control *node_b = memnew(Control);
+ Node2D *node_c = memnew(Node2D);
+ DragTarget *node_d = memnew(DragTarget);
+ Control *node_e = memnew(Control);
+ Node *node_f = memnew(Node);
+ Control *node_g = memnew(Control);
+
+ node_a->set_name(SNAME("NodeA"));
+ node_b->set_name(SNAME("NodeB"));
+ node_c->set_name(SNAME("NodeC"));
+ node_d->set_name(SNAME("NodeD"));
+ node_e->set_name(SNAME("NodeE"));
+ node_f->set_name(SNAME("NodeF"));
+ node_g->set_name(SNAME("NodeG"));
+
+ node_a->set_position(Point2i(0, 0));
+ node_b->set_position(Point2i(10, 10));
+ node_c->set_position(Point2i(0, 0));
+ node_d->set_position(Point2i(10, 10));
+ node_e->set_position(Point2i(10, 100));
+ node_g->set_position(Point2i(10, 100));
+ node_a->set_size(Point2i(30, 30));
+ node_b->set_size(Point2i(30, 30));
+ node_d->set_size(Point2i(30, 30));
+ node_e->set_size(Point2i(10, 10));
+ node_g->set_size(Point2i(10, 10));
+ node_a->set_focus_mode(Control::FOCUS_CLICK);
+ node_b->set_focus_mode(Control::FOCUS_CLICK);
+ node_d->set_focus_mode(Control::FOCUS_CLICK);
+ node_e->set_focus_mode(Control::FOCUS_CLICK);
+ node_g->set_focus_mode(Control::FOCUS_CLICK);
+ Window *root = SceneTree::get_singleton()->get_root();
+ DisplayServerMock *DS = (DisplayServerMock *)(DisplayServer::get_singleton());
+
+ // Scene tree:
+ // - root
+ // - a (Control)
+ // - b (Control)
+ // - c (Node2D)
+ // - d (Control)
+ // - e (Control)
+ // - f (Node)
+ // - g (Control)
+ root->add_child(node_a);
+ root->add_child(node_b);
+ node_b->add_child(node_c);
+ node_c->add_child(node_d);
+ root->add_child(node_e);
+ node_e->add_child(node_f);
+ node_f->add_child(node_g);
+
+ Point2i on_a = Point2i(5, 5);
+ Point2i on_b = Point2i(15, 15);
+ Point2i on_d = Point2i(25, 25);
+ Point2i on_e = Point2i(15, 105);
+ Point2i on_g = Point2i(15, 105);
+ Point2i on_background = Point2i(500, 500);
+ Point2i on_outside = Point2i(-1, -1);
+
+ // Unit tests for Viewport::gui_find_control and Viewport::_gui_find_control_at_pos
+ SUBCASE("[VIEWPORT][GuiFindControl] Finding Controls at a Viewport-position") {
+ // FIXME: It is extremely difficult to create a situation where the Control has a zero determinant.
+ // Leaving that if-branch untested.
+
+ SUBCASE("[VIEWPORT][GuiFindControl] Basic position tests") {
+ CHECK(root->gui_find_control(on_a) == node_a);
+ CHECK(root->gui_find_control(on_b) == node_b);
+ CHECK(root->gui_find_control(on_d) == node_d);
+ CHECK(root->gui_find_control(on_e) == node_g); // Node F makes G a Root Control at the same position as E
+ CHECK(root->gui_find_control(on_g) == node_g);
+ CHECK_FALSE(root->gui_find_control(on_background));
+ }
+
+ SUBCASE("[VIEWPORT][GuiFindControl] Invisible nodes are not considered as results.") {
+ // Non-Root Control
+ node_d->hide();
+ CHECK(root->gui_find_control(on_d) == node_b);
+ // Root Control
+ node_b->hide();
+ CHECK(root->gui_find_control(on_b) == node_a);
+ }
+
+ SUBCASE("[VIEWPORT][GuiFindControl] Root Control with CanvasItem as parent is affected by parent's transform.") {
+ node_b->remove_child(node_c);
+ node_c->set_position(Point2i(50, 50));
+ root->add_child(node_c);
+ CHECK(root->gui_find_control(Point2i(65, 65)) == node_d);
+ }
+
+ SUBCASE("[VIEWPORT][GuiFindControl] Control Contents Clipping clips accessible position of children.") {
+ CHECK_FALSE(node_b->is_clipping_contents());
+ CHECK(root->gui_find_control(on_d + Point2i(20, 20)) == node_d);
+ node_b->set_clip_contents(true);
+ CHECK(root->gui_find_control(on_d) == node_d);
+ CHECK_FALSE(root->gui_find_control(on_d + Point2i(20, 20)));
+ }
+
+ SUBCASE("[VIEWPORT][GuiFindControl] Top Level Control as descendant of CanvasItem isn't affected by parent's transform.") {
+ CHECK(root->gui_find_control(on_d + Point2i(20, 20)) == node_d);
+ node_d->set_as_top_level(true);
+ CHECK_FALSE(root->gui_find_control(on_d + Point2i(20, 20)));
+ CHECK(root->gui_find_control(on_b) == node_d);
+ }
+ }
+
+ SUBCASE("[Viewport][GuiInputEvent] nullptr as argument doesn't lead to a crash.") {
+ CHECK_NOTHROW(root->push_input(nullptr));
+ }
+
+ // Unit tests for Viewport::_gui_input_event (Mouse Buttons)
+ SUBCASE("[Viewport][GuiInputEvent] Mouse Button Down/Up.") {
+ SUBCASE("[Viewport][GuiInputEvent] Mouse Button Control Focus Change.") {
+ SUBCASE("[Viewport][GuiInputEvent] Grab Focus while no Control has focus.") {
+ CHECK_FALSE(root->gui_get_focus_owner());
+
+ // Click on A
+ SEND_GUI_MOUSE_BUTTON_EVENT(on_a, MouseButton::LEFT, MouseButtonMask::LEFT, Key::NONE);
+ CHECK(node_a->has_focus());
+ SEND_GUI_MOUSE_BUTTON_RELEASED_EVENT(on_a, MouseButton::LEFT, MouseButtonMask::NONE, Key::NONE);
+ }
+
+ SUBCASE("[Viewport][GuiInputEvent] Grab Focus from other Control.") {
+ node_a->grab_focus();
+ CHECK(node_a->has_focus());
+
+ // Click on D
+ SEND_GUI_MOUSE_BUTTON_EVENT(on_d, MouseButton::LEFT, MouseButtonMask::LEFT, Key::NONE);
+ CHECK(node_d->has_focus());
+ SEND_GUI_MOUSE_BUTTON_RELEASED_EVENT(on_d, MouseButton::LEFT, MouseButtonMask::NONE, Key::NONE);
+ }
+
+ SUBCASE("[Viewport][GuiInputEvent] Non-CanvasItem breaks Transform hierarchy.") {
+ CHECK_FALSE(root->gui_get_focus_owner());
+
+ // Click on G absolute coordinates
+ SEND_GUI_MOUSE_BUTTON_EVENT(Point2i(15, 105), MouseButton::LEFT, MouseButtonMask::LEFT, Key::NONE);
+ CHECK(node_g->has_focus());
+ SEND_GUI_MOUSE_BUTTON_RELEASED_EVENT(Point2i(15, 105), MouseButton::LEFT, MouseButtonMask::NONE, Key::NONE);
+ }
+
+ SUBCASE("[Viewport][GuiInputEvent] No Focus change when clicking in background.") {
+ CHECK_FALSE(root->gui_get_focus_owner());
+
+ SEND_GUI_MOUSE_BUTTON_EVENT(on_background, MouseButton::LEFT, MouseButtonMask::LEFT, Key::NONE);
+ CHECK_FALSE(root->gui_get_focus_owner());
+ SEND_GUI_MOUSE_BUTTON_RELEASED_EVENT(on_background, MouseButton::LEFT, MouseButtonMask::NONE, Key::NONE);
+
+ node_a->grab_focus();
+ CHECK(node_a->has_focus());
+
+ SEND_GUI_MOUSE_BUTTON_EVENT(on_background, MouseButton::LEFT, MouseButtonMask::LEFT, Key::NONE);
+ SEND_GUI_MOUSE_BUTTON_RELEASED_EVENT(on_background, MouseButton::LEFT, MouseButtonMask::NONE, Key::NONE);
+ CHECK(node_a->has_focus());
+ }
+
+ SUBCASE("[Viewport][GuiInputEvent] Mouse Button No Focus Steal while other Mouse Button is pressed.") {
+ CHECK_FALSE(root->gui_get_focus_owner());
+
+ SEND_GUI_MOUSE_BUTTON_EVENT(on_a, MouseButton::LEFT, MouseButtonMask::LEFT, Key::NONE);
+ CHECK(node_a->has_focus());
+
+ SEND_GUI_MOUSE_BUTTON_EVENT(on_b, MouseButton::RIGHT, (int)MouseButtonMask::LEFT | (int)MouseButtonMask::RIGHT, Key::NONE);
+ CHECK(node_a->has_focus());
+
+ SEND_GUI_MOUSE_BUTTON_RELEASED_EVENT(on_b, MouseButton::RIGHT, MouseButtonMask::LEFT, Key::NONE);
+ SEND_GUI_MOUSE_BUTTON_RELEASED_EVENT(on_b, MouseButton::LEFT, MouseButtonMask::NONE, Key::NONE);
+ CHECK(node_a->has_focus());
+ }
+
+ SUBCASE("[Viewport][GuiInputEvent] Allow Focus Steal with LMB while other Mouse Button is held down and was initially pressed without being over a Control.") {
+ // TODO: Not sure, if this is intended behavior, but this is an edge case.
+ CHECK_FALSE(root->gui_get_focus_owner());
+
+ SEND_GUI_MOUSE_BUTTON_EVENT(on_background, MouseButton::RIGHT, MouseButtonMask::RIGHT, Key::NONE);
+ CHECK_FALSE(root->gui_get_focus_owner());
+
+ SEND_GUI_MOUSE_BUTTON_EVENT(on_a, MouseButton::LEFT, (int)MouseButtonMask::LEFT | (int)MouseButtonMask::RIGHT, Key::NONE);
+ CHECK(node_a->has_focus());
+ SEND_GUI_MOUSE_BUTTON_RELEASED_EVENT(on_a, MouseButton::LEFT, MouseButtonMask::RIGHT, Key::NONE);
+ CHECK(node_a->has_focus());
+
+ SEND_GUI_MOUSE_BUTTON_EVENT(on_b, MouseButton::LEFT, (int)MouseButtonMask::LEFT | (int)MouseButtonMask::RIGHT, Key::NONE);
+ CHECK(node_b->has_focus());
+
+ SEND_GUI_MOUSE_BUTTON_RELEASED_EVENT(on_d, MouseButton::LEFT, MouseButtonMask::RIGHT, Key::NONE);
+ SEND_GUI_MOUSE_BUTTON_RELEASED_EVENT(on_d, MouseButton::RIGHT, MouseButtonMask::NONE, Key::NONE);
+ CHECK(node_b->has_focus());
+ }
+
+ SUBCASE("[Viewport][GuiInputEvent] Ignore Focus from Mouse Buttons when mouse-filter is set to ignore.") {
+ node_d->grab_focus();
+ node_d->set_mouse_filter(Control::MOUSE_FILTER_IGNORE);
+ CHECK(node_d->has_focus());
+
+ // Click on overlapping area B&D.
+ SEND_GUI_MOUSE_BUTTON_EVENT(on_d, MouseButton::LEFT, MouseButtonMask::LEFT, Key::NONE);
+ CHECK(node_b->has_focus());
+ SEND_GUI_MOUSE_BUTTON_RELEASED_EVENT(on_d, MouseButton::LEFT, MouseButtonMask::NONE, Key::NONE);
+ }
+
+ SUBCASE("[Viewport][GuiInputEvent] RMB doesn't grab focus.") {
+ node_a->grab_focus();
+ CHECK(node_a->has_focus());
+
+ SEND_GUI_MOUSE_BUTTON_EVENT(on_d, MouseButton::RIGHT, MouseButtonMask::RIGHT, Key::NONE);
+ SEND_GUI_MOUSE_BUTTON_RELEASED_EVENT(on_d, MouseButton::RIGHT, MouseButtonMask::NONE, Key::NONE);
+ CHECK(node_a->has_focus());
+ }
+
+ SUBCASE("[Viewport][GuiInputEvent] LMB on unfocusable Control doesn't grab focus.") {
+ CHECK_FALSE(node_g->has_focus());
+ node_g->set_focus_mode(Control::FOCUS_NONE);
+
+ SEND_GUI_MOUSE_BUTTON_EVENT(on_g, MouseButton::LEFT, MouseButtonMask::LEFT, Key::NONE);
+ SEND_GUI_MOUSE_BUTTON_RELEASED_EVENT(on_g, MouseButton::LEFT, MouseButtonMask::NONE, Key::NONE);
+ CHECK_FALSE(node_g->has_focus());
+
+ // Now verify the opposite with FOCUS_CLICK
+ node_g->set_focus_mode(Control::FOCUS_CLICK);
+ SEND_GUI_MOUSE_BUTTON_EVENT(on_g, MouseButton::LEFT, MouseButtonMask::LEFT, Key::NONE);
+ SEND_GUI_MOUSE_BUTTON_RELEASED_EVENT(on_g, MouseButton::LEFT, MouseButtonMask::NONE, Key::NONE);
+ CHECK(node_g->has_focus());
+ node_g->set_focus_mode(Control::FOCUS_CLICK);
+ }
+
+ SUBCASE("[Viewport][GuiInputEvent] Signal 'gui_focus_changed' is only emitted if a previously unfocused Control grabs focus.") {
+ SIGNAL_WATCH(root, SNAME("gui_focus_changed"));
+ Array node_array;
+ node_array.push_back(node_a);
+ Array signal_args;
+ signal_args.push_back(node_array);
+
+ SEND_GUI_MOUSE_BUTTON_EVENT(on_a, MouseButton::LEFT, MouseButtonMask::LEFT, Key::NONE);
+ SEND_GUI_MOUSE_BUTTON_RELEASED_EVENT(on_a, MouseButton::LEFT, MouseButtonMask::NONE, Key::NONE);
+ SIGNAL_CHECK(SNAME("gui_focus_changed"), signal_args);
+
+ SEND_GUI_MOUSE_BUTTON_EVENT(on_a, MouseButton::LEFT, MouseButtonMask::LEFT, Key::NONE);
+ SEND_GUI_MOUSE_BUTTON_RELEASED_EVENT(on_a, MouseButton::LEFT, MouseButtonMask::NONE, Key::NONE);
+ CHECK(node_a->has_focus());
+ SIGNAL_CHECK_FALSE(SNAME("gui_focus_changed"));
+
+ SIGNAL_UNWATCH(root, SNAME("gui_focus_changed"));
+ }
+
+ SUBCASE("[Viewport][GuiInputEvent] Focus Propagation to parent items.") {
+ SUBCASE("[Viewport][GuiInputEvent] Unfocusable Control with MOUSE_FILTER_PASS propagates focus to parent CanvasItem.") {
+ node_d->set_focus_mode(Control::FOCUS_NONE);
+ node_d->set_mouse_filter(Control::MOUSE_FILTER_PASS);
+
+ SEND_GUI_MOUSE_BUTTON_EVENT(on_d + Point2i(20, 20), MouseButton::LEFT, MouseButtonMask::LEFT, Key::NONE);
+ CHECK(node_b->has_focus());
+ SEND_GUI_MOUSE_BUTTON_RELEASED_EVENT(on_d + Point2i(20, 20), MouseButton::LEFT, MouseButtonMask::NONE, Key::NONE);
+
+ // Verify break condition for Root Control.
+ node_a->set_focus_mode(Control::FOCUS_NONE);
+ node_a->set_mouse_filter(Control::MOUSE_FILTER_PASS);
+
+ SEND_GUI_MOUSE_BUTTON_EVENT(on_a, MouseButton::LEFT, MouseButtonMask::LEFT, Key::NONE);
+ SEND_GUI_MOUSE_BUTTON_RELEASED_EVENT(on_a, MouseButton::LEFT, MouseButtonMask::NONE, Key::NONE);
+ CHECK(node_b->has_focus());
+ }
+
+ SUBCASE("[Viewport][GuiInputEvent] Top Level CanvasItem stops focus propagation.") {
+ node_d->set_focus_mode(Control::FOCUS_NONE);
+ node_d->set_mouse_filter(Control::MOUSE_FILTER_PASS);
+ node_c->set_as_top_level(true);
+
+ SEND_GUI_MOUSE_BUTTON_EVENT(on_b, MouseButton::LEFT, MouseButtonMask::LEFT, Key::NONE);
+ SEND_GUI_MOUSE_BUTTON_RELEASED_EVENT(on_b, MouseButton::LEFT, MouseButtonMask::NONE, Key::NONE);
+ CHECK_FALSE(root->gui_get_focus_owner());
+
+ node_d->set_focus_mode(Control::FOCUS_CLICK);
+ SEND_GUI_MOUSE_BUTTON_EVENT(on_b, MouseButton::LEFT, MouseButtonMask::LEFT, Key::NONE);
+ SEND_GUI_MOUSE_BUTTON_RELEASED_EVENT(on_b, MouseButton::LEFT, MouseButtonMask::NONE, Key::NONE);
+ CHECK(node_d->has_focus());
+ }
+ }
+ }
+
+ SUBCASE("[Viewport][GuiInputEvent] Process-Mode affects, if GUI Mouse Button Events are processed.") {
+ node_a->last_mouse_button = MouseButton::NONE;
+ node_a->set_process_mode(Node::PROCESS_MODE_DISABLED);
+ SEND_GUI_MOUSE_BUTTON_EVENT(on_a, MouseButton::LEFT, MouseButtonMask::LEFT, Key::NONE);
+ SEND_GUI_MOUSE_BUTTON_RELEASED_EVENT(on_a, MouseButton::LEFT, MouseButtonMask::NONE, Key::NONE);
+ CHECK(node_a->last_mouse_button == MouseButton::NONE);
+
+ // Now verify that with allowed processing the event is processed.
+ node_a->set_process_mode(Node::PROCESS_MODE_ALWAYS);
+ SEND_GUI_MOUSE_BUTTON_EVENT(on_a, MouseButton::LEFT, MouseButtonMask::LEFT, Key::NONE);
+ SEND_GUI_MOUSE_BUTTON_RELEASED_EVENT(on_a, MouseButton::LEFT, MouseButtonMask::NONE, Key::NONE);
+ CHECK(node_a->last_mouse_button == MouseButton::LEFT);
+ }
+ }
+
+ // Unit tests for Viewport::_gui_input_event (Mouse Motion)
+ SUBCASE("[Viewport][GuiInputEvent] Mouse Motion") {
+ // FIXME: Tooltips are not yet tested. They likely require an internal clock.
+
+ SUBCASE("[Viewport][GuiInputEvent] Mouse Motion changes the Control, that it is over.") {
+ SEND_GUI_MOUSE_MOTION_EVENT(on_background, MouseButtonMask::NONE, Key::NONE);
+ CHECK_FALSE(node_a->mouse_over);
+
+ // Move over Control.
+ SEND_GUI_MOUSE_MOTION_EVENT(on_a, MouseButtonMask::NONE, Key::NONE);
+ CHECK(node_a->mouse_over);
+
+ // No change.
+ SEND_GUI_MOUSE_MOTION_EVENT(on_a + Point2i(1, 1), MouseButtonMask::NONE, Key::NONE);
+ CHECK(node_a->mouse_over);
+
+ // Move over other Control.
+ SEND_GUI_MOUSE_MOTION_EVENT(on_d, MouseButtonMask::NONE, Key::NONE);
+ CHECK_FALSE(node_a->mouse_over);
+ CHECK(node_d->mouse_over);
+
+ // Move to background
+ SEND_GUI_MOUSE_MOTION_EVENT(on_background, MouseButtonMask::NONE, Key::NONE);
+ CHECK_FALSE(node_d->mouse_over);
+ }
+
+ SUBCASE("[Viewport][GuiInputEvent] Window Mouse Enter/Exit signals.") {
+ SIGNAL_WATCH(root, SNAME("mouse_entered"));
+ SIGNAL_WATCH(root, SNAME("mouse_exited"));
+ Array signal_args;
+ signal_args.push_back(Array());
+
+ SEND_GUI_MOUSE_MOTION_EVENT(on_outside, MouseButtonMask::NONE, Key::NONE);
+ SIGNAL_CHECK_FALSE(SNAME("mouse_entered"));
+ SIGNAL_CHECK(SNAME("mouse_exited"), signal_args);
+
+ SEND_GUI_MOUSE_MOTION_EVENT(on_a, MouseButtonMask::NONE, Key::NONE);
+ SIGNAL_CHECK(SNAME("mouse_entered"), signal_args);
+ SIGNAL_CHECK_FALSE(SNAME("mouse_exited"));
+
+ SIGNAL_UNWATCH(root, SNAME("mouse_entered"));
+ SIGNAL_UNWATCH(root, SNAME("mouse_exited"));
+ }
+
+ SUBCASE("[Viewport][GuiInputEvent] Process-Mode affects, if GUI Mouse Motion Events are processed.") {
+ node_a->last_mouse_move_position = on_outside;
+ node_a->set_process_mode(Node::PROCESS_MODE_DISABLED);
+ SEND_GUI_MOUSE_MOTION_EVENT(on_a, MouseButtonMask::NONE, Key::NONE);
+ CHECK(node_a->last_mouse_move_position == on_outside);
+
+ // Now verify that with allowed processing the event is processed.
+ node_a->set_process_mode(Node::PROCESS_MODE_ALWAYS);
+ SEND_GUI_MOUSE_MOTION_EVENT(on_a, MouseButtonMask::NONE, Key::NONE);
+ CHECK(node_a->last_mouse_move_position == on_a);
+ }
+ }
+
+ // Unit tests for Viewport::_gui_input_event (Drag and Drop)
+ SUBCASE("[Viewport][GuiInputEvent] Drag and Drop") {
+ // FIXME: Drag-Preview will likely change. Tests for this part would have to be rewritten anyway.
+ // See https://github.com/godotengine/godot/pull/67531#issuecomment-1385353430 for details.
+ // FIXME: Testing Drag and Drop with non-embedded windows would require DisplayServerMock additions
+ // FIXME: Drag and Drop currently doesn't work with embedded Windows and SubViewports - not testing.
+ // See https://github.com/godotengine/godot/issues/28522 for example.
+ int min_grab_movement = 11;
+ SUBCASE("[Viewport][GuiInputEvent] Drag from one Control to another in the same viewport.") {
+ SUBCASE("[Viewport][GuiInputEvent] Perform successful Drag and Drop on a different Control.") {
+ SEND_GUI_MOUSE_BUTTON_EVENT(on_a, MouseButton::LEFT, MouseButtonMask::LEFT, Key::NONE);
+ CHECK_FALSE(root->gui_is_dragging());
+
+ SEND_GUI_MOUSE_MOTION_EVENT(on_a + Point2i(min_grab_movement, 0), MouseButtonMask::LEFT, Key::NONE);
+ CHECK(root->gui_is_dragging());
+
+ // Move above a Control, that is a Drop target and allows dropping at this point.
+ SEND_GUI_MOUSE_MOTION_EVENT(on_d, MouseButtonMask::LEFT, Key::NONE);
+ CHECK(DS->get_cursor_shape() == DisplayServer::CURSOR_CAN_DROP);
+
+ CHECK(root->gui_is_dragging());
+ CHECK_FALSE(root->gui_is_drag_successful());
+ SEND_GUI_MOUSE_BUTTON_RELEASED_EVENT(on_d, MouseButton::LEFT, MouseButtonMask::NONE, Key::NONE);
+ CHECK_FALSE(root->gui_is_dragging());
+ CHECK(root->gui_is_drag_successful());
+ CHECK((StringName)node_d->drag_data == SNAME("Drag Data"));
+ }
+
+ SUBCASE("[Viewport][GuiInputEvent] Perform unsuccessful drop on Control.") {
+ SEND_GUI_MOUSE_BUTTON_EVENT(on_a, MouseButton::LEFT, MouseButtonMask::LEFT, Key::NONE);
+ CHECK_FALSE(root->gui_is_dragging());
+
+ // Move, but don't trigger DnD yet.
+ SEND_GUI_MOUSE_MOTION_EVENT(on_a + Point2i(0, min_grab_movement - 1), MouseButtonMask::LEFT, Key::NONE);
+ CHECK_FALSE(root->gui_is_dragging());
+
+ // Move and trigger DnD.
+ SEND_GUI_MOUSE_MOTION_EVENT(on_a + Point2i(0, min_grab_movement), MouseButtonMask::LEFT, Key::NONE);
+ CHECK(root->gui_is_dragging());
+
+ // Move above a Control, that is not a Drop target.
+ SEND_GUI_MOUSE_MOTION_EVENT(on_a, MouseButtonMask::LEFT, Key::NONE);
+ CHECK(DS->get_cursor_shape() == DisplayServer::CURSOR_FORBIDDEN);
+
+ // Move above a Control, that is a Drop target, but has disallowed this point.
+ SEND_GUI_MOUSE_MOTION_EVENT(on_d + Point2i(20, 0), MouseButtonMask::LEFT, Key::NONE);
+ CHECK(DS->get_cursor_shape() == DisplayServer::CURSOR_FORBIDDEN);
+ CHECK(root->gui_is_dragging());
+
+ SEND_GUI_MOUSE_BUTTON_RELEASED_EVENT(on_d + Point2i(20, 0), MouseButton::LEFT, MouseButtonMask::NONE, Key::NONE);
+ CHECK_FALSE(root->gui_is_dragging());
+ CHECK_FALSE(root->gui_is_drag_successful());
+ }
+
+ SUBCASE("[Viewport][GuiInputEvent] Perform unsuccessful drop on No-Control.") {
+ SEND_GUI_MOUSE_BUTTON_EVENT(on_a, MouseButton::LEFT, MouseButtonMask::LEFT, Key::NONE);
+ CHECK_FALSE(root->gui_is_dragging());
+
+ // Move, but don't trigger DnD yet.
+ SEND_GUI_MOUSE_MOTION_EVENT(on_a + Point2i(min_grab_movement - 1, 0), MouseButtonMask::LEFT, Key::NONE);
+ CHECK_FALSE(root->gui_is_dragging());
+
+ // Move and trigger DnD.
+ SEND_GUI_MOUSE_MOTION_EVENT(on_a + Point2i(min_grab_movement, 0), MouseButtonMask::LEFT, Key::NONE);
+ CHECK(root->gui_is_dragging());
+
+ // Move away from Controls.
+ SEND_GUI_MOUSE_MOTION_EVENT(on_background, MouseButtonMask::LEFT, Key::NONE);
+ CHECK(DS->get_cursor_shape() == DisplayServer::CURSOR_ARROW); // This could also be CURSOR_FORBIDDEN.
+
+ CHECK(root->gui_is_dragging());
+ SEND_GUI_MOUSE_BUTTON_RELEASED_EVENT(on_background, MouseButton::LEFT, MouseButtonMask::NONE, Key::NONE);
+ CHECK_FALSE(root->gui_is_dragging());
+ CHECK_FALSE(root->gui_is_drag_successful());
+ }
+
+ SUBCASE("[Viewport][GuiInputEvent] Perform unsuccessful drop outside of window.") {
+ SEND_GUI_MOUSE_BUTTON_EVENT(on_a, MouseButton::LEFT, MouseButtonMask::LEFT, Key::NONE);
+ CHECK_FALSE(root->gui_is_dragging());
+
+ // Move and trigger DnD.
+ SEND_GUI_MOUSE_MOTION_EVENT(on_a + Point2i(min_grab_movement, 0), MouseButtonMask::LEFT, Key::NONE);
+ CHECK(root->gui_is_dragging());
+
+ SEND_GUI_MOUSE_MOTION_EVENT(on_d, MouseButtonMask::LEFT, Key::NONE);
+ CHECK(DS->get_cursor_shape() == DisplayServer::CURSOR_CAN_DROP);
+
+ // Move outside of window.
+ SEND_GUI_MOUSE_MOTION_EVENT(on_outside, MouseButtonMask::LEFT, Key::NONE);
+ CHECK(DS->get_cursor_shape() == DisplayServer::CURSOR_ARROW);
+ CHECK(root->gui_is_dragging());
+
+ SEND_GUI_MOUSE_BUTTON_RELEASED_EVENT(on_outside, MouseButton::LEFT, MouseButtonMask::NONE, Key::NONE);
+ CHECK_FALSE(root->gui_is_dragging());
+ CHECK_FALSE(root->gui_is_drag_successful());
+ }
+
+ SUBCASE("[Viewport][GuiInputEvent] Drag and Drop doesn't work with other Mouse Buttons than LMB.") {
+ SEND_GUI_MOUSE_BUTTON_EVENT(on_a, MouseButton::MIDDLE, MouseButtonMask::MIDDLE, Key::NONE);
+ CHECK_FALSE(root->gui_is_dragging());
+
+ SEND_GUI_MOUSE_MOTION_EVENT(on_a + Point2i(min_grab_movement, 0), MouseButtonMask::MIDDLE, Key::NONE);
+ CHECK_FALSE(root->gui_is_dragging());
+ SEND_GUI_MOUSE_BUTTON_RELEASED_EVENT(on_a, MouseButton::MIDDLE, MouseButtonMask::NONE, Key::NONE);
+ }
+
+ SUBCASE("[Viewport][GuiInputEvent] Drag and Drop parent propagation.") {
+ Node2D *node_aa = memnew(Node2D);
+ Control *node_aaa = memnew(Control);
+ Node2D *node_dd = memnew(Node2D);
+ Control *node_ddd = memnew(Control);
+ node_aaa->set_size(Size2i(10, 10));
+ node_aaa->set_position(Point2i(0, 5));
+ node_ddd->set_size(Size2i(10, 10));
+ node_ddd->set_position(Point2i(0, 5));
+ node_a->add_child(node_aa);
+ node_aa->add_child(node_aaa);
+ node_d->add_child(node_dd);
+ node_dd->add_child(node_ddd);
+ Point2i on_aaa = on_a + Point2i(-2, 2);
+ Point2i on_ddd = on_d + Point2i(-2, 2);
+
+ SUBCASE("[Viewport][GuiInputEvent] Drag and Drop propagation to parent Controls.") {
+ node_aaa->set_mouse_filter(Control::MOUSE_FILTER_PASS);
+ node_ddd->set_mouse_filter(Control::MOUSE_FILTER_PASS);
+
+ SEND_GUI_MOUSE_BUTTON_EVENT(on_aaa, MouseButton::LEFT, MouseButtonMask::LEFT, Key::NONE);
+ CHECK_FALSE(root->gui_is_dragging());
+
+ SEND_GUI_MOUSE_MOTION_EVENT(on_aaa + Point2i(0, min_grab_movement), MouseButtonMask::LEFT, Key::NONE);
+ CHECK(root->gui_is_dragging());
+
+ SEND_GUI_MOUSE_MOTION_EVENT(on_ddd, MouseButtonMask::LEFT, Key::NONE);
+
+ CHECK(root->gui_is_dragging());
+ CHECK_FALSE(root->gui_is_drag_successful());
+ SEND_GUI_MOUSE_BUTTON_RELEASED_EVENT(on_ddd, MouseButton::LEFT, MouseButtonMask::NONE, Key::NONE);
+ CHECK_FALSE(root->gui_is_dragging());
+ CHECK(root->gui_is_drag_successful());
+
+ node_aaa->set_mouse_filter(Control::MOUSE_FILTER_STOP);
+ node_ddd->set_mouse_filter(Control::MOUSE_FILTER_STOP);
+ }
+
+ SUBCASE("[Viewport][GuiInputEvent] Drag and Drop grab-propagation stopped by Top Level.") {
+ node_aaa->set_mouse_filter(Control::MOUSE_FILTER_PASS);
+ node_aaa->set_as_top_level(true);
+
+ SEND_GUI_MOUSE_BUTTON_EVENT(on_aaa, MouseButton::LEFT, MouseButtonMask::LEFT, Key::NONE);
+ CHECK_FALSE(root->gui_is_dragging());
+
+ SEND_GUI_MOUSE_MOTION_EVENT(on_aaa + Point2i(0, min_grab_movement), MouseButtonMask::LEFT, Key::NONE);
+ CHECK_FALSE(root->gui_is_dragging());
+
+ SEND_GUI_MOUSE_BUTTON_RELEASED_EVENT(on_background, MouseButton::LEFT, MouseButtonMask::NONE, Key::NONE);
+ node_aaa->set_as_top_level(false);
+ node_aaa->set_mouse_filter(Control::MOUSE_FILTER_STOP);
+ }
+
+ SUBCASE("[Viewport][GuiInputEvent] Drag and Drop target-propagation stopped by Top Level.") {
+ node_aaa->set_mouse_filter(Control::MOUSE_FILTER_PASS);
+ node_ddd->set_mouse_filter(Control::MOUSE_FILTER_PASS);
+ node_ddd->set_as_top_level(true);
+ node_ddd->set_position(Point2i(30, 100));
+
+ SEND_GUI_MOUSE_BUTTON_EVENT(on_aaa, MouseButton::LEFT, MouseButtonMask::LEFT, Key::NONE);
+ CHECK_FALSE(root->gui_is_dragging());
+
+ SEND_GUI_MOUSE_MOTION_EVENT(on_aaa + Point2i(0, min_grab_movement), MouseButtonMask::LEFT, Key::NONE);
+ CHECK(root->gui_is_dragging());
+
+ SEND_GUI_MOUSE_MOTION_EVENT(Point2i(35, 105), MouseButtonMask::LEFT, Key::NONE);
+
+ CHECK(root->gui_is_dragging());
+ SEND_GUI_MOUSE_BUTTON_RELEASED_EVENT(Point2i(35, 105), MouseButton::LEFT, MouseButtonMask::NONE, Key::NONE);
+ CHECK_FALSE(root->gui_is_dragging());
+ CHECK_FALSE(root->gui_is_drag_successful());
+
+ node_ddd->set_position(Point2i(0, 5));
+ node_ddd->set_as_top_level(false);
+ node_aaa->set_mouse_filter(Control::MOUSE_FILTER_STOP);
+ node_ddd->set_mouse_filter(Control::MOUSE_FILTER_STOP);
+ }
+
+ SUBCASE("[Viewport][GuiInputEvent] Drag and Drop grab-propagation stopped by non-CanvasItem.") {
+ node_g->set_mouse_filter(Control::MOUSE_FILTER_PASS);
+
+ SEND_GUI_MOUSE_BUTTON_EVENT(on_g, MouseButton::LEFT, MouseButtonMask::LEFT, Key::NONE);
+ SEND_GUI_MOUSE_MOTION_EVENT(on_g + Point2i(0, min_grab_movement), MouseButtonMask::LEFT, Key::NONE);
+ CHECK_FALSE(root->gui_is_dragging());
+
+ SEND_GUI_MOUSE_BUTTON_RELEASED_EVENT(on_background, MouseButton::LEFT, MouseButtonMask::NONE, Key::NONE);
+ node_g->set_mouse_filter(Control::MOUSE_FILTER_STOP);
+ }
+
+ SUBCASE("[Viewport][GuiInputEvent] Drag and Drop target-propagation stopped by non-CanvasItem.") {
+ node_g->set_mouse_filter(Control::MOUSE_FILTER_PASS);
+
+ SEND_GUI_MOUSE_BUTTON_EVENT(on_a - Point2i(1, 1), MouseButton::LEFT, MouseButtonMask::LEFT, Key::NONE); // Offset for node_aaa.
+ SEND_GUI_MOUSE_MOTION_EVENT(on_a + Point2i(0, min_grab_movement), MouseButtonMask::LEFT, Key::NONE);
+ CHECK(root->gui_is_dragging());
+
+ SEND_GUI_MOUSE_MOTION_EVENT(on_g, MouseButtonMask::LEFT, Key::NONE);
+ SEND_GUI_MOUSE_BUTTON_RELEASED_EVENT(on_g, MouseButton::LEFT, MouseButtonMask::NONE, Key::NONE);
+ CHECK_FALSE(root->gui_is_dragging());
+
+ node_g->set_mouse_filter(Control::MOUSE_FILTER_STOP);
+ }
+
+ memdelete(node_ddd);
+ memdelete(node_dd);
+ memdelete(node_aaa);
+ memdelete(node_aa);
+ }
+
+ SUBCASE("[Viewport][GuiInputEvent] Force Drag and Drop.") {
+ SEND_GUI_MOUSE_MOTION_EVENT(on_background, MouseButtonMask::NONE, Key::NONE);
+ CHECK_FALSE(root->gui_is_dragging());
+ node_a->force_drag(SNAME("Drag Data"), nullptr);
+ CHECK(root->gui_is_dragging());
+
+ SEND_GUI_MOUSE_MOTION_EVENT(on_d, MouseButtonMask::NONE, Key::NONE);
+
+ // Force Drop doesn't get triggered by mouse Buttons other than LMB.
+ SEND_GUI_MOUSE_BUTTON_EVENT(on_d, MouseButton::RIGHT, MouseButtonMask::RIGHT, Key::NONE);
+ SEND_GUI_MOUSE_BUTTON_RELEASED_EVENT(on_a, MouseButton::RIGHT, MouseButtonMask::NONE, Key::NONE);
+ CHECK(root->gui_is_dragging());
+
+ // Force Drop with LMB-Down.
+ SEND_GUI_MOUSE_BUTTON_EVENT(on_d, MouseButton::LEFT, MouseButtonMask::LEFT, Key::NONE);
+ CHECK_FALSE(root->gui_is_dragging());
+ CHECK(root->gui_is_drag_successful());
+
+ SEND_GUI_MOUSE_BUTTON_RELEASED_EVENT(on_d, MouseButton::LEFT, MouseButtonMask::NONE, Key::NONE);
+ }
+ }
+ }
+
+ memdelete(node_g);
+ memdelete(node_f);
+ memdelete(node_e);
+ memdelete(node_d);
+ memdelete(node_c);
+ memdelete(node_b);
+ memdelete(node_a);
+}
+
+} // namespace TestViewport
+
+#endif // TEST_VIEWPORT_H
diff --git a/tests/test_macros.h b/tests/test_macros.h
index 9fd95465f6..5d1bcdecf4 100644
--- a/tests/test_macros.h
+++ b/tests/test_macros.h
@@ -209,7 +209,6 @@ int register_test_command(String p_command, TestFunc p_function);
event.instantiate(); \
event->set_position(m_screen_pos); \
event->set_button_mask(m_mask); \
- event->set_relative(Vector2(10, 10)); \
_UPDATE_EVENT_MODIFERS(event, m_modifers); \
_SEND_DISPLAYSERVER_EVENT(event); \
MessageQueue::get_singleton()->flush(); \
diff --git a/tests/test_main.cpp b/tests/test_main.cpp
index ea6058f707..e029ea7190 100644
--- a/tests/test_main.cpp
+++ b/tests/test_main.cpp
@@ -101,6 +101,7 @@
#include "tests/scene/test_sprite_frames.h"
#include "tests/scene/test_text_edit.h"
#include "tests/scene/test_theme.h"
+#include "tests/scene/test_viewport.h"
#include "tests/scene/test_visual_shader.h"
#include "tests/servers/test_text_server.h"
#include "tests/test_validate_testing.h"
@@ -233,6 +234,9 @@ struct GodotTestCaseListener : public doctest::IReporter {
memnew(SceneTree);
SceneTree::get_singleton()->initialize();
+ if (!DisplayServer::get_singleton()->has_feature(DisplayServer::Feature::FEATURE_SUBWINDOWS)) {
+ SceneTree::get_singleton()->get_root()->set_embedding_subwindows(true);
+ }
return;
}