summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--core/math/aabb.h2
-rw-r--r--core/math/basis.h2
-rw-r--r--core/math/color.h2
-rw-r--r--core/math/face3.h2
-rw-r--r--core/math/plane.h2
-rw-r--r--core/math/projection.h2
-rw-r--r--core/math/quaternion.h2
-rw-r--r--core/math/rect2.h2
-rw-r--r--core/math/rect2i.h2
-rw-r--r--core/math/transform_2d.h2
-rw-r--r--core/math/transform_3d.h2
-rw-r--r--core/math/vector2.h2
-rw-r--r--core/math/vector2i.h2
-rw-r--r--core/math/vector3.h2
-rw-r--r--core/math/vector3i.h2
-rw-r--r--core/math/vector4.h2
-rw-r--r--core/math/vector4i.h2
-rw-r--r--core/os/midi_driver.cpp199
-rw-r--r--core/os/midi_driver.h68
-rw-r--r--core/os/os.cpp4
-rw-r--r--core/typedefs.h7
-rw-r--r--doc/classes/AudioStreamWAV.xml4
-rw-r--r--doc/classes/EditorSettings.xml9
-rw-r--r--doc/classes/EditorUndoRedoManager.xml7
-rw-r--r--doc/classes/InputEvent.xml5
-rw-r--r--doc/classes/InputEventKey.xml5
-rw-r--r--doc/classes/PhysicalBone3D.xml1
-rw-r--r--doc/classes/Skeleton3D.xml2
-rw-r--r--doc/classes/TextEdit.xml9
-rw-r--r--drivers/alsamidi/midi_driver_alsamidi.cpp177
-rw-r--r--drivers/alsamidi/midi_driver_alsamidi.h41
-rw-r--r--drivers/coremidi/midi_driver_coremidi.cpp79
-rw-r--r--drivers/coremidi/midi_driver_coremidi.h21
-rw-r--r--drivers/gles3/rasterizer_gles3.cpp2
-rw-r--r--drivers/gles3/rasterizer_scene_gles3.cpp3
-rw-r--r--drivers/gles3/storage/config.cpp22
-rw-r--r--drivers/gles3/storage/render_scene_buffers_gles3.cpp6
-rw-r--r--drivers/gles3/storage/render_scene_buffers_gles3.h4
-rw-r--r--drivers/winmidi/midi_driver_winmidi.cpp55
-rw-r--r--drivers/winmidi/midi_driver_winmidi.h8
-rw-r--r--editor/code_editor.cpp3
-rw-r--r--editor/create_dialog.cpp9
-rw-r--r--editor/editor_audio_buses.cpp10
-rw-r--r--editor/editor_inspector.cpp4
-rw-r--r--editor/editor_node.cpp23
-rw-r--r--editor/editor_node.h2
-rw-r--r--editor/editor_properties_array_dict.cpp4
-rw-r--r--editor/editor_settings.cpp3
-rw-r--r--editor/editor_undo_redo_manager.cpp16
-rw-r--r--editor/editor_undo_redo_manager.h2
-rw-r--r--editor/plugins/cast_2d_editor_plugin.cpp1
-rw-r--r--editor/plugins/editor_plugin.cpp4
-rw-r--r--editor/plugins/packed_scene_translation_parser_plugin.cpp1
-rw-r--r--editor/plugins/script_editor_plugin.cpp9
-rw-r--r--editor/plugins/sprite_frames_editor_plugin.cpp4
-rw-r--r--editor/plugins/visual_shader_editor_plugin.cpp6
-rw-r--r--[-rwxr-xr-x]misc/utility/godot_gdb_pretty_print.py (renamed from misc/scripts/godot_gdb_pretty_print.py)48
-rw-r--r--modules/gdscript/gdscript_analyzer.cpp3
-rw-r--r--modules/gridmap/editor/grid_map_editor_plugin.cpp21
-rw-r--r--modules/webxr/native/library_godot_webxr.js3
-rw-r--r--modules/webxr/native/webxr.externs.js5
-rw-r--r--modules/webxr/webxr_interface_js.cpp20
-rw-r--r--platform/web/detect.py7
-rw-r--r--platform/web/export/export_plugin.cpp122
-rw-r--r--platform/web/export/export_plugin.h10
-rw-r--r--platform/web/platform_gl.h4
-rw-r--r--scene/2d/animated_sprite_2d.cpp5
-rw-r--r--scene/3d/physics/physical_bone_3d.cpp12
-rw-r--r--scene/3d/skeleton_3d.cpp31
-rw-r--r--scene/3d/skeleton_3d.h11
-rw-r--r--scene/3d/sprite_3d.cpp5
-rw-r--r--scene/animation/animation_mixer.cpp1
-rw-r--r--scene/gui/text_edit.cpp105
-rw-r--r--scene/gui/text_edit.h28
-rw-r--r--scene/main/viewport.cpp11
-rw-r--r--scene/main/viewport.h1
-rw-r--r--servers/rendering/renderer_rd/forward_clustered/render_forward_clustered.cpp9
-rw-r--r--servers/rendering/renderer_rd/shaders/particles.glsl18
-rw-r--r--servers/rendering/shader_language.cpp118
-rw-r--r--servers/rendering/shader_language.h20
80 files changed, 1004 insertions, 487 deletions
diff --git a/core/math/aabb.h b/core/math/aabb.h
index 9a74266ff7..cb358ca7ef 100644
--- a/core/math/aabb.h
+++ b/core/math/aabb.h
@@ -41,7 +41,7 @@
class Variant;
-struct _NO_DISCARD_ AABB {
+struct [[nodiscard]] AABB {
Vector3 position;
Vector3 size;
diff --git a/core/math/basis.h b/core/math/basis.h
index 918cbc18d4..5c1a5fbdda 100644
--- a/core/math/basis.h
+++ b/core/math/basis.h
@@ -34,7 +34,7 @@
#include "core/math/quaternion.h"
#include "core/math/vector3.h"
-struct _NO_DISCARD_ Basis {
+struct [[nodiscard]] Basis {
Vector3 rows[3] = {
Vector3(1, 0, 0),
Vector3(0, 1, 0),
diff --git a/core/math/color.h b/core/math/color.h
index 65d7377c1c..e17b8c9fd7 100644
--- a/core/math/color.h
+++ b/core/math/color.h
@@ -35,7 +35,7 @@
class String;
-struct _NO_DISCARD_ Color {
+struct [[nodiscard]] Color {
union {
struct {
float r;
diff --git a/core/math/face3.h b/core/math/face3.h
index 3dd47d0226..519dcb6414 100644
--- a/core/math/face3.h
+++ b/core/math/face3.h
@@ -36,7 +36,7 @@
#include "core/math/transform_3d.h"
#include "core/math/vector3.h"
-struct _NO_DISCARD_ Face3 {
+struct [[nodiscard]] Face3 {
enum Side {
SIDE_OVER,
SIDE_UNDER,
diff --git a/core/math/plane.h b/core/math/plane.h
index 8159f25342..6529fea60a 100644
--- a/core/math/plane.h
+++ b/core/math/plane.h
@@ -35,7 +35,7 @@
class Variant;
-struct _NO_DISCARD_ Plane {
+struct [[nodiscard]] Plane {
Vector3 normal;
real_t d = 0;
diff --git a/core/math/projection.h b/core/math/projection.h
index f3ed9d7b1c..5af43561c0 100644
--- a/core/math/projection.h
+++ b/core/math/projection.h
@@ -43,7 +43,7 @@ struct Rect2;
struct Transform3D;
struct Vector2;
-struct _NO_DISCARD_ Projection {
+struct [[nodiscard]] Projection {
enum Planes {
PLANE_NEAR,
PLANE_FAR,
diff --git a/core/math/quaternion.h b/core/math/quaternion.h
index 868a2916f5..655e55e0a2 100644
--- a/core/math/quaternion.h
+++ b/core/math/quaternion.h
@@ -35,7 +35,7 @@
#include "core/math/vector3.h"
#include "core/string/ustring.h"
-struct _NO_DISCARD_ Quaternion {
+struct [[nodiscard]] Quaternion {
union {
struct {
real_t x;
diff --git a/core/math/rect2.h b/core/math/rect2.h
index b4069ae86a..9cb341b689 100644
--- a/core/math/rect2.h
+++ b/core/math/rect2.h
@@ -38,7 +38,7 @@ class String;
struct Rect2i;
struct Transform2D;
-struct _NO_DISCARD_ Rect2 {
+struct [[nodiscard]] Rect2 {
Point2 position;
Size2 size;
diff --git a/core/math/rect2i.h b/core/math/rect2i.h
index a1338da0bb..5f3a3d54f5 100644
--- a/core/math/rect2i.h
+++ b/core/math/rect2i.h
@@ -37,7 +37,7 @@
class String;
struct Rect2;
-struct _NO_DISCARD_ Rect2i {
+struct [[nodiscard]] Rect2i {
Point2i position;
Size2i size;
diff --git a/core/math/transform_2d.h b/core/math/transform_2d.h
index 4ec2dc119c..476577508f 100644
--- a/core/math/transform_2d.h
+++ b/core/math/transform_2d.h
@@ -38,7 +38,7 @@
class String;
-struct _NO_DISCARD_ Transform2D {
+struct [[nodiscard]] Transform2D {
// Warning #1: basis of Transform2D is stored differently from Basis. In terms of columns array, the basis matrix looks like "on paper":
// M = (columns[0][0] columns[1][0])
// (columns[0][1] columns[1][1])
diff --git a/core/math/transform_3d.h b/core/math/transform_3d.h
index 7d89b86c75..b1de233445 100644
--- a/core/math/transform_3d.h
+++ b/core/math/transform_3d.h
@@ -36,7 +36,7 @@
#include "core/math/plane.h"
#include "core/templates/vector.h"
-struct _NO_DISCARD_ Transform3D {
+struct [[nodiscard]] Transform3D {
Basis basis;
Vector3 origin;
diff --git a/core/math/vector2.h b/core/math/vector2.h
index 8851942cdd..edb47db6fd 100644
--- a/core/math/vector2.h
+++ b/core/math/vector2.h
@@ -37,7 +37,7 @@
class String;
struct Vector2i;
-struct _NO_DISCARD_ Vector2 {
+struct [[nodiscard]] Vector2 {
static const int AXIS_COUNT = 2;
enum Axis {
diff --git a/core/math/vector2i.h b/core/math/vector2i.h
index aca9ae8272..fff9b0a658 100644
--- a/core/math/vector2i.h
+++ b/core/math/vector2i.h
@@ -37,7 +37,7 @@
class String;
struct Vector2;
-struct _NO_DISCARD_ Vector2i {
+struct [[nodiscard]] Vector2i {
static const int AXIS_COUNT = 2;
enum Axis {
diff --git a/core/math/vector3.h b/core/math/vector3.h
index 2313eb557a..14bc44c4e7 100644
--- a/core/math/vector3.h
+++ b/core/math/vector3.h
@@ -39,7 +39,7 @@ struct Basis;
struct Vector2;
struct Vector3i;
-struct _NO_DISCARD_ Vector3 {
+struct [[nodiscard]] Vector3 {
static const int AXIS_COUNT = 3;
enum Axis {
diff --git a/core/math/vector3i.h b/core/math/vector3i.h
index 035cfcf9e2..40d0700bf7 100644
--- a/core/math/vector3i.h
+++ b/core/math/vector3i.h
@@ -37,7 +37,7 @@
class String;
struct Vector3;
-struct _NO_DISCARD_ Vector3i {
+struct [[nodiscard]] Vector3i {
static const int AXIS_COUNT = 3;
enum Axis {
diff --git a/core/math/vector4.h b/core/math/vector4.h
index f69b4752bb..8632f69f57 100644
--- a/core/math/vector4.h
+++ b/core/math/vector4.h
@@ -38,7 +38,7 @@
class String;
struct Vector4i;
-struct _NO_DISCARD_ Vector4 {
+struct [[nodiscard]] Vector4 {
static const int AXIS_COUNT = 4;
enum Axis {
diff --git a/core/math/vector4i.h b/core/math/vector4i.h
index 8a9c580bc1..a9036d684a 100644
--- a/core/math/vector4i.h
+++ b/core/math/vector4i.h
@@ -37,7 +37,7 @@
class String;
struct Vector4;
-struct _NO_DISCARD_ Vector4i {
+struct [[nodiscard]] Vector4i {
static const int AXIS_COUNT = 4;
enum Axis {
diff --git a/core/os/midi_driver.cpp b/core/os/midi_driver.cpp
index 6870c84b49..6c748b1498 100644
--- a/core/os/midi_driver.cpp
+++ b/core/os/midi_driver.cpp
@@ -38,88 +38,167 @@ MIDIDriver *MIDIDriver::get_singleton() {
return singleton;
}
-void MIDIDriver::set_singleton() {
+MIDIDriver::MIDIDriver() {
singleton = this;
}
-void MIDIDriver::receive_input_packet(int device_index, uint64_t timestamp, uint8_t *data, uint32_t length) {
- Ref<InputEventMIDI> event;
- event.instantiate();
- event->set_device(device_index);
- uint32_t param_position = 1;
-
- if (length >= 1) {
- if (data[0] >= 0xF0) {
- // channel does not apply to system common messages
- event->set_channel(0);
- event->set_message(MIDIMessage(data[0]));
- last_received_message = data[0];
- } else if ((data[0] & 0x80) == 0x00) {
- // running status
- event->set_channel(last_received_message & 0xF);
- event->set_message(MIDIMessage(last_received_message >> 4));
- param_position = 0;
+MIDIDriver::MessageCategory MIDIDriver::Parser::category(uint8_t p_midi_fragment) {
+ if (p_midi_fragment >= 0xf8) {
+ return MessageCategory::RealTime;
+ } else if (p_midi_fragment >= 0xf0) {
+ // System Exclusive begin/end are specified as System Common Category
+ // messages, but we separate them here and give them their own categories
+ // as their behavior is significantly different.
+ if (p_midi_fragment == 0xf0) {
+ return MessageCategory::SysExBegin;
+ } else if (p_midi_fragment == 0xf7) {
+ return MessageCategory::SysExEnd;
+ }
+ return MessageCategory::SystemCommon;
+ } else if (p_midi_fragment >= 0x80) {
+ return MessageCategory::Voice;
+ }
+ return MessageCategory::Data;
+}
+
+MIDIMessage MIDIDriver::Parser::status_to_msg_enum(uint8_t p_status_byte) {
+ if (p_status_byte & 0x80) {
+ if (p_status_byte < 0xf0) {
+ return MIDIMessage(p_status_byte >> 4);
} else {
- event->set_channel(data[0] & 0xF);
- event->set_message(MIDIMessage(data[0] >> 4));
- param_position = 1;
- last_received_message = data[0];
+ return MIDIMessage(p_status_byte);
}
}
+ return MIDIMessage::NONE;
+}
- switch (event->get_message()) {
- case MIDIMessage::AFTERTOUCH:
- if (length >= 2 + param_position) {
- event->set_pitch(data[param_position]);
- event->set_pressure(data[param_position + 1]);
- }
- break;
+size_t MIDIDriver::Parser::expected_data(uint8_t p_status_byte) {
+ return expected_data(status_to_msg_enum(p_status_byte));
+}
+size_t MIDIDriver::Parser::expected_data(MIDIMessage p_msg_type) {
+ switch (p_msg_type) {
+ case MIDIMessage::NOTE_OFF:
+ case MIDIMessage::NOTE_ON:
+ case MIDIMessage::AFTERTOUCH:
case MIDIMessage::CONTROL_CHANGE:
- if (length >= 2 + param_position) {
- event->set_controller_number(data[param_position]);
- event->set_controller_value(data[param_position + 1]);
- }
- break;
+ case MIDIMessage::PITCH_BEND:
+ case MIDIMessage::SONG_POSITION_POINTER:
+ return 2;
+ case MIDIMessage::PROGRAM_CHANGE:
+ case MIDIMessage::CHANNEL_PRESSURE:
+ case MIDIMessage::QUARTER_FRAME:
+ case MIDIMessage::SONG_SELECT:
+ return 1;
+ default:
+ return 0;
+ }
+}
- case MIDIMessage::NOTE_ON:
+uint8_t MIDIDriver::Parser::channel(uint8_t p_status_byte) {
+ if (category(p_status_byte) == MessageCategory::Voice) {
+ return p_status_byte & 0x0f;
+ }
+ return 0;
+}
+
+void MIDIDriver::send_event(int p_device_index, uint8_t p_status,
+ const uint8_t *p_data, size_t p_data_len) {
+ const MIDIMessage msg = Parser::status_to_msg_enum(p_status);
+ ERR_FAIL_COND(p_data_len < Parser::expected_data(msg));
+
+ Ref<InputEventMIDI> event;
+ event.instantiate();
+ event->set_device(p_device_index);
+ event->set_channel(Parser::channel(p_status));
+ event->set_message(msg);
+ switch (msg) {
case MIDIMessage::NOTE_OFF:
- if (length >= 2 + param_position) {
- event->set_pitch(data[param_position]);
- event->set_velocity(data[param_position + 1]);
- }
+ case MIDIMessage::NOTE_ON:
+ event->set_pitch(p_data[0]);
+ event->set_velocity(p_data[1]);
break;
-
- case MIDIMessage::PITCH_BEND:
- if (length >= 2 + param_position) {
- event->set_pitch((data[param_position + 1] << 7) | data[param_position]);
- }
+ case MIDIMessage::AFTERTOUCH:
+ event->set_pitch(p_data[0]);
+ event->set_pressure(p_data[1]);
+ break;
+ case MIDIMessage::CONTROL_CHANGE:
+ event->set_controller_number(p_data[0]);
+ event->set_controller_value(p_data[1]);
break;
-
case MIDIMessage::PROGRAM_CHANGE:
- if (length >= 1 + param_position) {
- event->set_instrument(data[param_position]);
- }
+ event->set_instrument(p_data[0]);
break;
-
case MIDIMessage::CHANNEL_PRESSURE:
- if (length >= 1 + param_position) {
- event->set_pressure(data[param_position]);
- }
+ event->set_pressure(p_data[0]);
+ break;
+ case MIDIMessage::PITCH_BEND:
+ event->set_pitch((p_data[1] << 7) | p_data[0]);
break;
+ // QUARTER_FRAME, SONG_POSITION_POINTER, and SONG_SELECT not yet implemented.
default:
break;
}
-
- Input *id = Input::get_singleton();
- id->parse_input_event(event);
+ Input::get_singleton()->parse_input_event(event);
}
-PackedStringArray MIDIDriver::get_connected_inputs() {
- PackedStringArray list;
- return list;
+void MIDIDriver::Parser::parse_fragment(uint8_t p_fragment) {
+ switch (category(p_fragment)) {
+ case MessageCategory::RealTime:
+ // Real-Time messages are single byte messages that can
+ // occur at any point and do not interrupt other messages.
+ // We pass them straight through.
+ MIDIDriver::send_event(device_index, p_fragment);
+ break;
+
+ case MessageCategory::SysExBegin:
+ status_byte = p_fragment;
+ skipping_sys_ex = true;
+ break;
+
+ case MessageCategory::SysExEnd:
+ status_byte = 0;
+ skipping_sys_ex = false;
+ break;
+
+ case MessageCategory::Voice:
+ case MessageCategory::SystemCommon:
+ skipping_sys_ex = false; // If we were in SysEx, assume it was aborted.
+ received_data_len = 0;
+ status_byte = 0;
+ ERR_FAIL_COND(expected_data(p_fragment) > DATA_BUFFER_SIZE);
+ if (expected_data(p_fragment) == 0) {
+ // No data bytes needed, post it now.
+ MIDIDriver::send_event(device_index, p_fragment);
+ } else {
+ status_byte = p_fragment;
+ }
+ break;
+
+ case MessageCategory::Data:
+ // We don't currently process SysEx messages, so ignore their data.
+ if (!skipping_sys_ex) {
+ const size_t expected = expected_data(status_byte);
+ if (received_data_len < expected) {
+ data_buffer[received_data_len] = p_fragment;
+ received_data_len++;
+ if (received_data_len == expected) {
+ MIDIDriver::send_event(device_index, status_byte,
+ data_buffer, expected);
+ received_data_len = 0;
+ // Voice messages can use 'running status', sending further
+ // messages without resending their status byte.
+ // For other messages types we clear the cached status byte.
+ if (category(status_byte) != MessageCategory::Voice) {
+ status_byte = 0;
+ }
+ }
+ }
+ }
+ break;
+ }
}
-MIDIDriver::MIDIDriver() {
- set_singleton();
+PackedStringArray MIDIDriver::get_connected_inputs() const {
+ return connected_input_names;
}
diff --git a/core/os/midi_driver.h b/core/os/midi_driver.h
index cad3d8189e..ddce63f9c8 100644
--- a/core/os/midi_driver.h
+++ b/core/os/midi_driver.h
@@ -42,19 +42,73 @@ class MIDIDriver {
static MIDIDriver *singleton;
static uint8_t last_received_message;
+protected:
+ // Categories of message for parser logic.
+ enum class MessageCategory {
+ Data,
+ Voice,
+ SysExBegin,
+ SystemCommon, // excluding System Exclusive Begin/End
+ SysExEnd,
+ RealTime,
+ };
+
+ // Convert midi data to InputEventMIDI and send it to Input.
+ // p_data_len is the length of the buffer passed at p_data, this must be
+ // at least equal to the data required by the passed message type, but
+ // may be larger. Only the required data will be read.
+ static void send_event(int p_device_index, uint8_t p_status,
+ const uint8_t *p_data = nullptr, size_t p_data_len = 0);
+
+ class Parser {
+ public:
+ Parser() = default;
+ Parser(int p_device_index) :
+ device_index{ p_device_index } {}
+ virtual ~Parser() = default;
+
+ // Push a byte of MIDI stream. Any completed messages will be
+ // forwarded to MIDIDriver::send_event.
+ void parse_fragment(uint8_t p_fragment);
+
+ static MessageCategory category(uint8_t p_midi_fragment);
+
+ // If the byte is a Voice Message status byte return the contained
+ // channel number, otherwise zero.
+ static uint8_t channel(uint8_t p_status_byte);
+
+ // If the byte is a status byte for a message with a fixed number of
+ // additional data bytes, return the number expected, otherwise zero.
+ static size_t expected_data(uint8_t p_status_byte);
+ static size_t expected_data(MIDIMessage p_msg_type);
+
+ // If the fragment is a status byte return the message type
+ // represented, otherwise MIDIMessage::NONE.
+ static MIDIMessage status_to_msg_enum(uint8_t p_status_byte);
+
+ private:
+ int device_index = 0;
+
+ static constexpr size_t DATA_BUFFER_SIZE = 2;
+
+ uint8_t status_byte = 0;
+ uint8_t data_buffer[DATA_BUFFER_SIZE] = { 0 };
+ size_t received_data_len = 0;
+ bool skipping_sys_ex = false;
+ };
+
+ PackedStringArray connected_input_names;
+
public:
static MIDIDriver *get_singleton();
- void set_singleton();
+
+ MIDIDriver();
+ virtual ~MIDIDriver() = default;
virtual Error open() = 0;
virtual void close() = 0;
- virtual PackedStringArray get_connected_inputs();
-
- static void receive_input_packet(int device_index, uint64_t timestamp, uint8_t *data, uint32_t length);
-
- MIDIDriver();
- virtual ~MIDIDriver() {}
+ PackedStringArray get_connected_inputs() const;
};
#endif // MIDI_DRIVER_H
diff --git a/core/os/os.cpp b/core/os/os.cpp
index fa7f23ded0..40d8601af3 100644
--- a/core/os/os.cpp
+++ b/core/os/os.cpp
@@ -513,6 +513,10 @@ bool OS::has_feature(const String &p_feature) {
if (p_feature == "threads") {
return true;
}
+#else
+ if (p_feature == "nothreads") {
+ return true;
+ }
#endif
if (_check_internal_feature_support(p_feature)) {
diff --git a/core/typedefs.h b/core/typedefs.h
index 2b90a911cd..0de803293d 100644
--- a/core/typedefs.h
+++ b/core/typedefs.h
@@ -71,12 +71,7 @@
#endif
#endif
-// No discard allows the compiler to flag warnings if we don't use the return value of functions / classes
-#ifndef _NO_DISCARD_
-#define _NO_DISCARD_ [[nodiscard]]
-#endif
-
-// In some cases _NO_DISCARD_ will get false positives,
+// In some cases [[nodiscard]] will get false positives,
// we can prevent the warning in specific cases by preceding the call with a cast.
#ifndef _ALLOW_DISCARD_
#define _ALLOW_DISCARD_ (void)
diff --git a/doc/classes/AudioStreamWAV.xml b/doc/classes/AudioStreamWAV.xml
index 3df814cb7f..8a28514ed6 100644
--- a/doc/classes/AudioStreamWAV.xml
+++ b/doc/classes/AudioStreamWAV.xml
@@ -29,10 +29,10 @@
Audio format. See [enum Format] constants for values.
</member>
<member name="loop_begin" type="int" setter="set_loop_begin" getter="get_loop_begin" default="0">
- The loop start point (in number of samples, relative to the beginning of the sample). This information will be imported automatically from the WAV file if present.
+ The loop start point (in number of samples, relative to the beginning of the stream). This information will be imported automatically from the WAV file if present.
</member>
<member name="loop_end" type="int" setter="set_loop_end" getter="get_loop_end" default="0">
- The loop end point (in number of samples, relative to the beginning of the sample). This information will be imported automatically from the WAV file if present.
+ The loop end point (in number of samples, relative to the beginning of the stream). This information will be imported automatically from the WAV file if present.
</member>
<member name="loop_mode" type="int" setter="set_loop_mode" getter="get_loop_mode" enum="AudioStreamWAV.LoopMode" default="0">
The loop mode. This information will be imported automatically from the WAV file if present. See [enum LoopMode] constants for values.
diff --git a/doc/classes/EditorSettings.xml b/doc/classes/EditorSettings.xml
index aa1ac566ef..c5737b235f 100644
--- a/doc/classes/EditorSettings.xml
+++ b/doc/classes/EditorSettings.xml
@@ -1043,6 +1043,9 @@
The indentation style to use (tabs or spaces).
[b]Note:[/b] The [url=$DOCS_URL/tutorials/scripting/gdscript/gdscript_styleguide.html]GDScript style guide[/url] recommends using tabs for indentation. It is advised to change this setting only if you need to work on a project that currently uses spaces for indentation.
</member>
+ <member name="text_editor/behavior/navigation/custom_word_separators" type="String" setter="" getter="">
+ The characters to consider as word delimiters if [member text_editor/behavior/navigation/use_custom_word_separators] is [code]true[/code]. The characters should be defined without separation, for example [code]#_![/code].
+ </member>
<member name="text_editor/behavior/navigation/drag_and_drop_selection" type="bool" setter="" getter="">
If [code]true[/code], allows drag-and-dropping text in the script editor to move text. Disable this if you find yourself accidentally drag-and-dropping text in the script editor.
</member>
@@ -1062,6 +1065,12 @@
<member name="text_editor/behavior/navigation/stay_in_script_editor_on_node_selected" type="bool" setter="" getter="">
If [code]true[/code], prevents automatically switching between the Script and 2D/3D screens when selecting a node in the Scene tree dock.
</member>
+ <member name="text_editor/behavior/navigation/use_custom_word_separators" type="bool" setter="" getter="">
+ If [code]false[/code], using [kbd]Ctrl + Left[/kbd] or [kbd]Ctrl + Right[/kbd] ([kbd]Cmd + Left[/kbd] or [kbd]Cmd + Right[/kbd] on macOS) bindings will use the behavior of [member text_editor/behavior/navigation/use_default_word_separators]. If [code]true[/code], it will also stop the caret if a character within [member text_editor/behavior/navigation/custom_word_separators] is detected. Useful for subword moving. This behavior also will be applied to the behavior of text selection.
+ </member>
+ <member name="text_editor/behavior/navigation/use_default_word_separators" type="bool" setter="" getter="">
+ If [code]false[/code], using [kbd]Ctrl + Left[/kbd] or [kbd]Ctrl + Right[/kbd] ([kbd]Cmd + Left[/kbd] or [kbd]Cmd + Right[/kbd] on macOS) bindings will stop moving caret only if a space or punctuation is detected. If [code]true[/code], it will also stop the caret if a character is [code]´`~$^=+|&lt;&gt;[/code], a General Punctuation, or CJK Punctuation. Useful for subword moving. This behavior also will be applied to the behavior of text selection.
+ </member>
<member name="text_editor/behavior/navigation/v_scroll_speed" type="int" setter="" getter="">
The number of pixels to scroll with every mouse wheel increment. Higher values make the script scroll by faster when using the mouse wheel.
[b]Note:[/b] You can hold down [kbd]Alt[/kbd] while using the mouse wheel to temporarily scroll 5 times faster.
diff --git a/doc/classes/EditorUndoRedoManager.xml b/doc/classes/EditorUndoRedoManager.xml
index 26580bbf06..5ac0d790a2 100644
--- a/doc/classes/EditorUndoRedoManager.xml
+++ b/doc/classes/EditorUndoRedoManager.xml
@@ -88,6 +88,13 @@
The way undo operation are ordered in actions is dictated by [param backward_undo_ops]. When [param backward_undo_ops] is [code]false[/code] undo option are ordered in the same order they were added. Which means the first operation to be added will be the first to be undone.
</description>
</method>
+ <method name="force_fixed_history">
+ <return type="void" />
+ <description>
+ Forces the next operation (e.g. [method add_do_method]) to use the action's history rather than guessing it from the object. This is sometimes needed when a history can't be correctly determined, like for a nested resource that doesn't have a path yet.
+ This method should only be used when absolutely necessary, otherwise it might cause invalid history state. For most of complex cases, the [code]custom_context[/code] parameter of [method create_action] is sufficient.
+ </description>
+ </method>
<method name="get_history_undo_redo" qualifiers="const">
<return type="UndoRedo" />
<param index="0" name="id" type="int" />
diff --git a/doc/classes/InputEvent.xml b/doc/classes/InputEvent.xml
index 6f2e6aac20..a970f63c6e 100644
--- a/doc/classes/InputEvent.xml
+++ b/doc/classes/InputEvent.xml
@@ -77,10 +77,11 @@
Returns [code]true[/code] if this input event has been canceled.
</description>
</method>
- <method name="is_echo" qualifiers="const">
+ <method name="is_echo" qualifiers="const" keywords="is_repeat">
<return type="bool" />
<description>
- Returns [code]true[/code] if this input event is an echo event (only for events of type [InputEventKey]). Any other event type returns [code]false[/code].
+ Returns [code]true[/code] if this input event is an echo event (only for events of type [InputEventKey]). An echo event is a repeated key event sent when the user is holding down the key. Any other event type returns [code]false[/code].
+ [b]Note:[/b] The rate at which echo events are sent is typically around 20 events per second (after holding down the key for roughly half a second). However, the key repeat delay/speed can be changed by the user or disabled entirely in the operating system settings. To ensure your project works correctly on all configurations, do not assume the user has a specific key repeat configuration in your project's behavior.
</description>
</method>
<method name="is_match" qualifiers="const">
diff --git a/doc/classes/InputEventKey.xml b/doc/classes/InputEventKey.xml
index dc4872ba03..69c447ba01 100644
--- a/doc/classes/InputEventKey.xml
+++ b/doc/classes/InputEventKey.xml
@@ -59,8 +59,9 @@
</method>
</methods>
<members>
- <member name="echo" type="bool" setter="set_echo" getter="is_echo" default="false">
- If [code]true[/code], the key was already pressed before this event. It means the user is holding the key down.
+ <member name="echo" type="bool" setter="set_echo" getter="is_echo" default="false" keywords="repeat">
+ If [code]true[/code], the key was already pressed before this event. An echo event is a repeated key event sent when the user is holding down the key.
+ [b]Note:[/b] The rate at which echo events are sent is typically around 20 events per second (after holding down the key for roughly half a second). However, the key repeat delay/speed can be changed by the user or disabled entirely in the operating system settings. To ensure your project works correctly on all configurations, do not assume the user has a specific key repeat configuration in your project's behavior.
</member>
<member name="key_label" type="int" setter="set_key_label" getter="get_key_label" enum="Key" default="0">
Represents the localized label printed on the key in the current keyboard layout, which corresponds to one of the [enum Key] constants or any valid Unicode character.
diff --git a/doc/classes/PhysicalBone3D.xml b/doc/classes/PhysicalBone3D.xml
index bce1a80526..ca1948e8e1 100644
--- a/doc/classes/PhysicalBone3D.xml
+++ b/doc/classes/PhysicalBone3D.xml
@@ -5,6 +5,7 @@
</brief_description>
<description>
The [PhysicalBone3D] node is a physics body that can be used to make bones in a [Skeleton3D] react to physics.
+ [b]Note:[/b] In order to detect physical bones with raycasts, the [member SkeletonModifier3D.active] property of the parent [PhysicalBoneSimulator3D] must be [code]true[/code] and the [Skeleton3D]'s bone must be assigned to [PhysicalBone3D] correctly; it means that [method get_bone_id] should return a valid id ([code]&gt;= 0[/code]).
</description>
<tutorials>
</tutorials>
diff --git a/doc/classes/Skeleton3D.xml b/doc/classes/Skeleton3D.xml
index 1167b70c8d..5829a787a1 100644
--- a/doc/classes/Skeleton3D.xml
+++ b/doc/classes/Skeleton3D.xml
@@ -243,6 +243,8 @@
<return type="void" />
<param index="0" name="enabled" type="bool" />
<description>
+ This method exists for compatibility with old structures in which the [Skeleton3D] does not have a [PhysicalBoneSimulator3D] as a child, but directly has [PhysicalBone3D]s as children.
+ In case you need to raycast to it without running [method physical_bones_start_simulation], call this method with [code]enabled == true[/code].
</description>
</method>
<method name="set_bone_enabled">
diff --git a/doc/classes/TextEdit.xml b/doc/classes/TextEdit.xml
index 75cad4d08b..f82671b533 100644
--- a/doc/classes/TextEdit.xml
+++ b/doc/classes/TextEdit.xml
@@ -1278,6 +1278,9 @@
<member name="context_menu_enabled" type="bool" setter="set_context_menu_enabled" getter="is_context_menu_enabled" default="true">
If [code]true[/code], a right-click displays the context menu.
</member>
+ <member name="custom_word_separators" type="String" setter="set_custom_word_separators" getter="get_custom_word_separators" default="&quot;&quot;">
+ The characters to consider as word delimiters if [member use_custom_word_separators] is [code]true[/code]. The characters should be defined without separation, for example [code]#_![/code].
+ </member>
<member name="deselect_on_focus_loss_enabled" type="bool" setter="set_deselect_on_focus_loss_enabled" getter="is_deselect_on_focus_loss_enabled" default="true">
If [code]true[/code], the selected text will be deselected when focus is lost.
</member>
@@ -1363,6 +1366,12 @@
<member name="text_direction" type="int" setter="set_text_direction" getter="get_text_direction" enum="Control.TextDirection" default="0">
Base text writing direction.
</member>
+ <member name="use_custom_word_separators" type="bool" setter="set_use_custom_word_separators" getter="is_custom_word_separators_enabled" default="false">
+ If [code]false[/code], using [kbd]Ctrl + Left[/kbd] or [kbd]Ctrl + Right[/kbd] ([kbd]Cmd + Left[/kbd] or [kbd]Cmd + Right[/kbd] on macOS) bindings will use the behavior of [member use_default_word_separators]. If [code]true[/code], it will also stop the caret if a character within [member custom_word_separators] is detected. Useful for subword moving. This behavior also will be applied to the behavior of text selection.
+ </member>
+ <member name="use_default_word_separators" type="bool" setter="set_use_default_word_separators" getter="is_default_word_separators_enabled" default="true">
+ If [code]false[/code], using [kbd]Ctrl + Left[/kbd] or [kbd]Ctrl + Right[/kbd] ([kbd]Cmd + Left[/kbd] or [kbd]Cmd + Right[/kbd] on macOS) bindings will stop moving caret only if a space or punctuation is detected. If [code]true[/code], it will also stop the caret if a character is [code]´`~$^=+|&lt;&gt;[/code], a General Punctuation, or CJK Punctuation. Useful for subword moving. This behavior also will be applied to the behavior of text selection.
+ </member>
<member name="virtual_keyboard_enabled" type="bool" setter="set_virtual_keyboard_enabled" getter="is_virtual_keyboard_enabled" default="true">
If [code]true[/code], the native virtual keyboard is shown when focused on platforms that support it.
</member>
diff --git a/drivers/alsamidi/midi_driver_alsamidi.cpp b/drivers/alsamidi/midi_driver_alsamidi.cpp
index b87be69cc5..445fc4a993 100644
--- a/drivers/alsamidi/midi_driver_alsamidi.cpp
+++ b/drivers/alsamidi/midi_driver_alsamidi.cpp
@@ -37,137 +37,36 @@
#include <errno.h>
-MIDIDriverALSAMidi::MessageCategory MIDIDriverALSAMidi::msg_category(uint8_t msg_part) {
- if (msg_part >= 0xf8) {
- return MessageCategory::RealTime;
- } else if (msg_part >= 0xf0) {
- // System Exclusive begin/end are specified as System Common Category messages,
- // but we separate them here and give them their own categories as their
- // behavior is significantly different.
- if (msg_part == 0xf0) {
- return MessageCategory::SysExBegin;
- } else if (msg_part == 0xf7) {
- return MessageCategory::SysExEnd;
- }
- return MessageCategory::SystemCommon;
- } else if (msg_part >= 0x80) {
- return MessageCategory::Voice;
- }
- return MessageCategory::Data;
-}
-
-size_t MIDIDriverALSAMidi::msg_expected_data(uint8_t status_byte) {
- if (msg_category(status_byte) == MessageCategory::Voice) {
- // Voice messages have a channel number in the status byte, mask it out.
- status_byte &= 0xf0;
- }
-
- switch (status_byte) {
- case 0x80: // Note Off
- case 0x90: // Note On
- case 0xA0: // Polyphonic Key Pressure (Aftertouch)
- case 0xB0: // Control Change (CC)
- case 0xE0: // Pitch Bend Change
- case 0xF2: // Song Position Pointer
- return 2;
-
- case 0xC0: // Program Change
- case 0xD0: // Channel Pressure (Aftertouch)
- case 0xF1: // MIDI Time Code Quarter Frame
- case 0xF3: // Song Select
- return 1;
- }
+MIDIDriverALSAMidi::InputConnection::InputConnection(int p_device_index,
+ snd_rawmidi_t *p_rawmidi) :
+ parser(p_device_index), rawmidi_ptr(p_rawmidi) {}
- return 0;
-}
-
-void MIDIDriverALSAMidi::InputConnection::parse_byte(uint8_t byte, MIDIDriverALSAMidi &driver,
- uint64_t timestamp, int device_index) {
- switch (msg_category(byte)) {
- case MessageCategory::RealTime:
- // Real-Time messages are single byte messages that can
- // occur at any point.
- // We pass them straight through.
- driver.receive_input_packet(device_index, timestamp, &byte, 1);
- break;
-
- case MessageCategory::Data:
- // We don't currently forward System Exclusive messages so skip their data.
- // Collect any expected data for other message types.
- if (!skipping_sys_ex && expected_data > received_data) {
- buffer[received_data + 1] = byte;
- received_data++;
-
- // Forward a complete message and reset relevant state.
- if (received_data == expected_data) {
- driver.receive_input_packet(device_index, timestamp, buffer, received_data + 1);
- received_data = 0;
-
- if (msg_category(buffer[0]) != MessageCategory::Voice) {
- // Voice Category messages can be sent with "running status".
- // This means they don't resend the status byte until it changes.
- // For other categories, we reset expected data, to require a new status byte.
- expected_data = 0;
- }
- }
- }
- break;
-
- case MessageCategory::SysExBegin:
- buffer[0] = byte;
- skipping_sys_ex = true;
- break;
-
- case MessageCategory::SysExEnd:
- expected_data = 0;
- skipping_sys_ex = false;
- break;
-
- case MessageCategory::Voice:
- case MessageCategory::SystemCommon:
- buffer[0] = byte;
- received_data = 0;
- expected_data = msg_expected_data(byte);
- skipping_sys_ex = false;
- if (expected_data == 0) {
- driver.receive_input_packet(device_index, timestamp, &byte, 1);
- }
- break;
- }
-}
-
-int MIDIDriverALSAMidi::InputConnection::read_in(MIDIDriverALSAMidi &driver, uint64_t timestamp, int device_index) {
- int ret;
+void MIDIDriverALSAMidi::InputConnection::read() {
+ int read_count;
do {
- uint8_t byte = 0;
- ret = snd_rawmidi_read(rawmidi_ptr, &byte, 1);
+ uint8_t buffer[32];
+ read_count = snd_rawmidi_read(rawmidi_ptr, buffer, sizeof(buffer));
- if (ret < 0) {
- if (ret != -EAGAIN) {
- ERR_PRINT("snd_rawmidi_read error: " + String(snd_strerror(ret)));
+ if (read_count < 0) {
+ if (read_count != -EAGAIN) {
+ ERR_PRINT("snd_rawmidi_read error: " + String(snd_strerror(read_count)));
}
} else {
- parse_byte(byte, driver, timestamp, device_index);
+ for (int i = 0; i < read_count; i++) {
+ parser.parse_fragment(buffer[i]);
+ }
}
- } while (ret > 0);
-
- return ret;
+ } while (read_count > 0);
}
void MIDIDriverALSAMidi::thread_func(void *p_udata) {
MIDIDriverALSAMidi *md = static_cast<MIDIDriverALSAMidi *>(p_udata);
- uint64_t timestamp = 0;
while (!md->exit_thread.is_set()) {
md->lock();
-
- InputConnection *connections = md->connected_inputs.ptrw();
- size_t connection_count = md->connected_inputs.size();
-
- for (size_t i = 0; i < connection_count; i++) {
- connections[i].read_in(*md, timestamp, (int)i);
+ for (InputConnection &conn : md->connected_inputs) {
+ conn.read();
}
-
md->unlock();
OS::get_singleton()->delay_usec(1000);
@@ -181,15 +80,25 @@ Error MIDIDriverALSAMidi::open() {
return ERR_CANT_OPEN;
}
- int i = 0;
- for (void **n = hints; *n != nullptr; n++) {
- char *name = snd_device_name_get_hint(*n, "NAME");
+ lock();
+ int device_index = 0;
+ for (void **h = hints; *h != nullptr; h++) {
+ char *name = snd_device_name_get_hint(*h, "NAME");
if (name != nullptr) {
snd_rawmidi_t *midi_in;
int ret = snd_rawmidi_open(&midi_in, nullptr, name, SND_RAWMIDI_NONBLOCK);
if (ret >= 0) {
- connected_inputs.insert(i++, InputConnection(midi_in));
+ // Get display name.
+ snd_rawmidi_info_t *info;
+ snd_rawmidi_info_malloc(&info);
+ snd_rawmidi_info(midi_in, info);
+ connected_input_names.push_back(snd_rawmidi_info_get_name(info));
+ snd_rawmidi_info_free(info);
+
+ connected_inputs.push_back(InputConnection(device_index, midi_in));
+ // Only increment device_index for successfully connected devices.
+ device_index++;
}
}
@@ -198,6 +107,7 @@ Error MIDIDriverALSAMidi::open() {
}
}
snd_device_name_free_hint(hints);
+ unlock();
exit_thread.clear();
thread.start(MIDIDriverALSAMidi::thread_func, this);
@@ -211,11 +121,12 @@ void MIDIDriverALSAMidi::close() {
thread.wait_to_finish();
}
- for (int i = 0; i < connected_inputs.size(); i++) {
- snd_rawmidi_t *midi_in = connected_inputs[i].rawmidi_ptr;
- snd_rawmidi_close(midi_in);
+ for (const InputConnection &conn : connected_inputs) {
+ snd_rawmidi_close(conn.rawmidi_ptr);
}
+
connected_inputs.clear();
+ connected_input_names.clear();
}
void MIDIDriverALSAMidi::lock() const {
@@ -226,24 +137,6 @@ void MIDIDriverALSAMidi::unlock() const {
mutex.unlock();
}
-PackedStringArray MIDIDriverALSAMidi::get_connected_inputs() {
- PackedStringArray list;
-
- lock();
- for (int i = 0; i < connected_inputs.size(); i++) {
- snd_rawmidi_t *midi_in = connected_inputs[i].rawmidi_ptr;
- snd_rawmidi_info_t *info;
-
- snd_rawmidi_info_malloc(&info);
- snd_rawmidi_info(midi_in, info);
- list.push_back(snd_rawmidi_info_get_name(info));
- snd_rawmidi_info_free(info);
- }
- unlock();
-
- return list;
-}
-
MIDIDriverALSAMidi::MIDIDriverALSAMidi() {
exit_thread.clear();
}
diff --git a/drivers/alsamidi/midi_driver_alsamidi.h b/drivers/alsamidi/midi_driver_alsamidi.h
index 95ded3b1c9..45811bec47 100644
--- a/drivers/alsamidi/midi_driver_alsamidi.h
+++ b/drivers/alsamidi/midi_driver_alsamidi.h
@@ -51,24 +51,15 @@ class MIDIDriverALSAMidi : public MIDIDriver {
Thread thread;
Mutex mutex;
- class InputConnection {
- public:
+ struct InputConnection {
InputConnection() = default;
- InputConnection(snd_rawmidi_t *midi_in) :
- rawmidi_ptr{ midi_in } {}
-
- // Read in and parse available data, forwarding any complete messages through the driver.
- int read_in(MIDIDriverALSAMidi &driver, uint64_t timestamp, int device_index);
+ InputConnection(int p_device_index, snd_rawmidi_t *p_rawmidi);
+ Parser parser;
snd_rawmidi_t *rawmidi_ptr = nullptr;
- private:
- static const size_t MSG_BUFFER_SIZE = 3;
- uint8_t buffer[MSG_BUFFER_SIZE] = { 0 };
- size_t expected_data = 0;
- size_t received_data = 0;
- bool skipping_sys_ex = false;
- void parse_byte(uint8_t byte, MIDIDriverALSAMidi &driver, uint64_t timestamp, int device_index);
+ // Read in and parse available data, forwarding complete events to Input.
+ void read();
};
Vector<InputConnection> connected_inputs;
@@ -77,30 +68,12 @@ class MIDIDriverALSAMidi : public MIDIDriver {
static void thread_func(void *p_udata);
- enum class MessageCategory {
- Data,
- Voice,
- SysExBegin,
- SystemCommon, // excluding System Exclusive Begin/End
- SysExEnd,
- RealTime,
- };
-
- // If the passed byte is a status byte, return the associated message category,
- // else return MessageCategory::Data.
- static MessageCategory msg_category(uint8_t msg_part);
-
- // Return the number of data bytes expected for the provided status byte.
- static size_t msg_expected_data(uint8_t status_byte);
-
void lock() const;
void unlock() const;
public:
- virtual Error open();
- virtual void close();
-
- virtual PackedStringArray get_connected_inputs();
+ virtual Error open() override;
+ virtual void close() override;
MIDIDriverALSAMidi();
virtual ~MIDIDriverALSAMidi();
diff --git a/drivers/coremidi/midi_driver_coremidi.cpp b/drivers/coremidi/midi_driver_coremidi.cpp
index 87fc7612f7..f6cc59471e 100644
--- a/drivers/coremidi/midi_driver_coremidi.cpp
+++ b/drivers/coremidi/midi_driver_coremidi.cpp
@@ -37,16 +37,30 @@
#import <CoreAudio/HostTime.h>
#import <CoreServices/CoreServices.h>
+Mutex MIDIDriverCoreMidi::mutex;
+bool MIDIDriverCoreMidi::core_midi_closed = false;
+
+MIDIDriverCoreMidi::InputConnection::InputConnection(int p_device_index, MIDIEndpointRef p_source) :
+ parser(p_device_index), source(p_source) {}
+
void MIDIDriverCoreMidi::read(const MIDIPacketList *packet_list, void *read_proc_ref_con, void *src_conn_ref_con) {
- MIDIPacket *packet = const_cast<MIDIPacket *>(packet_list->packet);
- int *device_index = static_cast<int *>(src_conn_ref_con);
- for (UInt32 i = 0; i < packet_list->numPackets; i++) {
- receive_input_packet(*device_index, packet->timeStamp, packet->data, packet->length);
- packet = MIDIPacketNext(packet);
+ MutexLock lock(mutex);
+ if (!core_midi_closed) {
+ InputConnection *source = static_cast<InputConnection *>(src_conn_ref_con);
+ const MIDIPacket *packet = packet_list->packet;
+ for (UInt32 packet_index = 0; packet_index < packet_list->numPackets; packet_index++) {
+ for (UInt16 data_index = 0; data_index < packet->length; data_index++) {
+ source->parser.parse_fragment(packet->data[data_index]);
+ }
+ packet = MIDIPacketNext(packet);
+ }
}
}
Error MIDIDriverCoreMidi::open() {
+ ERR_FAIL_COND_V_MSG(client || core_midi_closed, FAILED,
+ "MIDIDriverCoreMidi cannot be reopened.");
+
CFStringRef name = CFStringCreateWithCString(nullptr, "Godot", kCFStringEncodingASCII);
OSStatus result = MIDIClientCreate(name, nullptr, nullptr, &client);
CFRelease(name);
@@ -61,12 +75,27 @@ Error MIDIDriverCoreMidi::open() {
return ERR_CANT_OPEN;
}
- int sources = MIDIGetNumberOfSources();
- for (int i = 0; i < sources; i++) {
+ int source_count = MIDIGetNumberOfSources();
+ int connection_index = 0;
+ for (int i = 0; i < source_count; i++) {
MIDIEndpointRef source = MIDIGetSource(i);
if (source) {
- MIDIPortConnectSource(port_in, source, static_cast<void *>(&i));
- connected_sources.insert(i, source);
+ InputConnection *conn = memnew(InputConnection(connection_index, source));
+ const OSStatus res = MIDIPortConnectSource(port_in, source, static_cast<void *>(conn));
+ if (res != noErr) {
+ memdelete(conn);
+ } else {
+ connected_sources.push_back(conn);
+
+ CFStringRef nameRef = nullptr;
+ char name[256];
+ MIDIObjectGetStringProperty(source, kMIDIPropertyDisplayName, &nameRef);
+ CFStringGetCString(nameRef, name, sizeof(name), kCFStringEncodingUTF8);
+ CFRelease(nameRef);
+ connected_input_names.push_back(name);
+
+ connection_index++; // Contiguous index for successfully connected inputs.
+ }
}
}
@@ -74,11 +103,17 @@ Error MIDIDriverCoreMidi::open() {
}
void MIDIDriverCoreMidi::close() {
- for (int i = 0; i < connected_sources.size(); i++) {
- MIDIEndpointRef source = connected_sources[i];
- MIDIPortDisconnectSource(port_in, source);
+ mutex.lock();
+ core_midi_closed = true;
+ mutex.unlock();
+
+ for (InputConnection *conn : connected_sources) {
+ MIDIPortDisconnectSource(port_in, conn->source);
+ memdelete(conn);
}
+
connected_sources.clear();
+ connected_input_names.clear();
if (port_in != 0) {
MIDIPortDispose(port_in);
@@ -91,26 +126,6 @@ void MIDIDriverCoreMidi::close() {
}
}
-PackedStringArray MIDIDriverCoreMidi::get_connected_inputs() {
- PackedStringArray list;
-
- for (int i = 0; i < connected_sources.size(); i++) {
- MIDIEndpointRef source = connected_sources[i];
- CFStringRef ref = nullptr;
- char name[256];
-
- MIDIObjectGetStringProperty(source, kMIDIPropertyDisplayName, &ref);
- CFStringGetCString(ref, name, sizeof(name), kCFStringEncodingUTF8);
- CFRelease(ref);
-
- list.push_back(name);
- }
-
- return list;
-}
-
-MIDIDriverCoreMidi::MIDIDriverCoreMidi() {}
-
MIDIDriverCoreMidi::~MIDIDriverCoreMidi() {
close();
}
diff --git a/drivers/coremidi/midi_driver_coremidi.h b/drivers/coremidi/midi_driver_coremidi.h
index 38fb515664..02cbc6234c 100644
--- a/drivers/coremidi/midi_driver_coremidi.h
+++ b/drivers/coremidi/midi_driver_coremidi.h
@@ -34,6 +34,7 @@
#ifdef COREMIDI_ENABLED
#include "core/os/midi_driver.h"
+#include "core/os/mutex.h"
#include "core/templates/vector.h"
#import <CoreMIDI/CoreMIDI.h>
@@ -43,17 +44,25 @@ class MIDIDriverCoreMidi : public MIDIDriver {
MIDIClientRef client = 0;
MIDIPortRef port_in;
- Vector<MIDIEndpointRef> connected_sources;
+ struct InputConnection {
+ InputConnection() = default;
+ InputConnection(int p_device_index, MIDIEndpointRef p_source);
+ Parser parser;
+ MIDIEndpointRef source;
+ };
+
+ Vector<InputConnection *> connected_sources;
+
+ static Mutex mutex;
+ static bool core_midi_closed;
static void read(const MIDIPacketList *packet_list, void *read_proc_ref_con, void *src_conn_ref_con);
public:
- virtual Error open();
- virtual void close();
-
- PackedStringArray get_connected_inputs();
+ virtual Error open() override;
+ virtual void close() override;
- MIDIDriverCoreMidi();
+ MIDIDriverCoreMidi() = default;
virtual ~MIDIDriverCoreMidi();
};
diff --git a/drivers/gles3/rasterizer_gles3.cpp b/drivers/gles3/rasterizer_gles3.cpp
index ae39c86d44..5ff95391a2 100644
--- a/drivers/gles3/rasterizer_gles3.cpp
+++ b/drivers/gles3/rasterizer_gles3.cpp
@@ -72,7 +72,7 @@
#if !defined(IOS_ENABLED) && !defined(WEB_ENABLED)
// We include EGL below to get debug callback on GLES2 platforms,
-// but EGL is not available on iOS.
+// but EGL is not available on iOS or the web.
#define CAN_DEBUG
#endif
diff --git a/drivers/gles3/rasterizer_scene_gles3.cpp b/drivers/gles3/rasterizer_scene_gles3.cpp
index 9ea030bbd4..8e89889fd1 100644
--- a/drivers/gles3/rasterizer_scene_gles3.cpp
+++ b/drivers/gles3/rasterizer_scene_gles3.cpp
@@ -2247,7 +2247,6 @@ void RasterizerSceneGLES3::render_scene(const Ref<RenderSceneBuffers> &p_render_
bool glow_enabled = false;
if (p_environment.is_valid()) {
glow_enabled = environment_get_glow_enabled(p_environment);
- rb->ensure_internal_buffers(); // Ensure our intermediate buffer is available if glow is enabled
if (glow_enabled) {
// If glow is enabled, we apply tonemapping etc. in post, so disable it during rendering
apply_color_adjustments_in_post = true;
@@ -2339,7 +2338,6 @@ void RasterizerSceneGLES3::render_scene(const Ref<RenderSceneBuffers> &p_render_
if (render_data.environment.is_valid()) {
bool use_bcs = environment_get_adjustments_enabled(render_data.environment);
if (use_bcs) {
- rb->ensure_internal_buffers();
apply_color_adjustments_in_post = true;
}
@@ -2473,6 +2471,7 @@ void RasterizerSceneGLES3::render_scene(const Ref<RenderSceneBuffers> &p_render_
if (is_reflection_probe) {
fbo = GLES3::LightStorage::get_singleton()->reflection_probe_instance_get_framebuffer(render_data.reflection_probe, render_data.reflection_probe_pass);
} else {
+ rb->set_apply_color_adjustments_in_post(apply_color_adjustments_in_post);
fbo = rb->get_render_fbo();
}
diff --git a/drivers/gles3/storage/config.cpp b/drivers/gles3/storage/config.cpp
index 1a14902c7c..a28b050bf8 100644
--- a/drivers/gles3/storage/config.cpp
+++ b/drivers/gles3/storage/config.cpp
@@ -35,6 +35,10 @@
#include "../rasterizer_gles3.h"
#include "texture_storage.h"
+#ifdef WEB_ENABLED
+#include <emscripten/html5_webgl.h>
+#endif
+
using namespace GLES3;
#define _GL_MAX_TEXTURE_MAX_ANISOTROPY_EXT 0x84FF
@@ -44,6 +48,23 @@ Config *Config::singleton = nullptr;
Config::Config() {
singleton = this;
+#ifdef WEB_ENABLED
+ // Starting with Emscripten 3.1.51, glGetStringi(GL_EXTENSIONS, i) will only ever return
+ // a fixed list of extensions, regardless of what additional extensions are enabled. This
+ // isn't very useful for us in determining which extensions we can rely on here. So, instead
+ // we use emscripten_webgl_get_supported_extensions() to get all supported extensions, which
+ // is what Emscripten 3.1.50 and earlier do.
+ {
+ char *extension_array_string = emscripten_webgl_get_supported_extensions();
+ PackedStringArray extension_array = String((const char *)extension_array_string).split(" ");
+ extensions.reserve(extension_array.size() * 2);
+ for (const String &s : extension_array) {
+ extensions.insert(s);
+ extensions.insert("GL_" + s);
+ }
+ free(extension_array_string);
+ }
+#else
{
GLint max_extensions = 0;
glGetIntegerv(GL_NUM_EXTENSIONS, &max_extensions);
@@ -55,6 +76,7 @@ Config::Config() {
extensions.insert((const char *)s);
}
}
+#endif
bptc_supported = extensions.has("GL_ARB_texture_compression_bptc") || extensions.has("EXT_texture_compression_bptc");
astc_supported = extensions.has("GL_KHR_texture_compression_astc") || extensions.has("GL_OES_texture_compression_astc") || extensions.has("GL_KHR_texture_compression_astc_ldr") || extensions.has("GL_KHR_texture_compression_astc_hdr");
diff --git a/drivers/gles3/storage/render_scene_buffers_gles3.cpp b/drivers/gles3/storage/render_scene_buffers_gles3.cpp
index e4f1a01f68..c91547d2b1 100644
--- a/drivers/gles3/storage/render_scene_buffers_gles3.cpp
+++ b/drivers/gles3/storage/render_scene_buffers_gles3.cpp
@@ -194,7 +194,7 @@ void RenderSceneBuffersGLES3::_check_render_buffers() {
ERR_FAIL_COND(view_count == 0);
- bool use_internal_buffer = scaling_3d_mode != RS::VIEWPORT_SCALING_3D_MODE_OFF || needs_internal_buffers;
+ bool use_internal_buffer = scaling_3d_mode != RS::VIEWPORT_SCALING_3D_MODE_OFF || apply_color_adjustments_in_post;
uint32_t depth_format_size = 3;
bool use_multiview = view_count > 1;
@@ -558,8 +558,8 @@ void RenderSceneBuffersGLES3::_clear_back_buffers() {
}
}
-void RenderSceneBuffersGLES3::ensure_internal_buffers() {
- needs_internal_buffers = true;
+void RenderSceneBuffersGLES3::set_apply_color_adjustments_in_post(bool p_apply_in_post) {
+ apply_color_adjustments_in_post = p_apply_in_post;
}
void RenderSceneBuffersGLES3::check_glow_buffers() {
diff --git a/drivers/gles3/storage/render_scene_buffers_gles3.h b/drivers/gles3/storage/render_scene_buffers_gles3.h
index 8273c18b8e..a7a676ad33 100644
--- a/drivers/gles3/storage/render_scene_buffers_gles3.h
+++ b/drivers/gles3/storage/render_scene_buffers_gles3.h
@@ -50,7 +50,7 @@ public:
//bool use_taa = false;
//bool use_debanding = false;
uint32_t view_count = 1;
- bool needs_internal_buffers = false;
+ bool apply_color_adjustments_in_post = false;
RID render_target;
@@ -106,12 +106,12 @@ public:
virtual void set_fsr_sharpness(float p_fsr_sharpness) override{};
virtual void set_texture_mipmap_bias(float p_texture_mipmap_bias) override{};
virtual void set_use_debanding(bool p_use_debanding) override{};
+ void set_apply_color_adjustments_in_post(bool p_apply_in_post);
void free_render_buffer_data();
void check_backbuffer(bool p_need_color, bool p_need_depth); // Check if we need to initialize our backbuffer.
void check_glow_buffers(); // Check if we need to initialize our glow buffers.
- void ensure_internal_buffers();
GLuint get_render_fbo();
GLuint get_msaa3d_fbo() {
diff --git a/drivers/winmidi/midi_driver_winmidi.cpp b/drivers/winmidi/midi_driver_winmidi.cpp
index 07f0226c5d..0f37f63ccd 100644
--- a/drivers/winmidi/midi_driver_winmidi.cpp
+++ b/drivers/winmidi/midi_driver_winmidi.cpp
@@ -36,26 +36,42 @@
void MIDIDriverWinMidi::read(HMIDIIN hMidiIn, UINT wMsg, DWORD_PTR dwInstance, DWORD_PTR dwParam1, DWORD_PTR dwParam2) {
if (wMsg == MIM_DATA) {
- receive_input_packet((int)dwInstance, (uint64_t)dwParam2, (uint8_t *)&dwParam1, 3);
+ // For MIM_DATA: dwParam1 = wMidiMessage, dwParam2 = dwTimestamp.
+ // Windows implementation has already unpacked running status and dropped any SysEx,
+ // so we can just forward straight to the event.
+ const uint8_t *midi_msg = (uint8_t *)&dwParam1;
+ send_event((int)dwInstance, midi_msg[0], &midi_msg[1], 2);
}
}
Error MIDIDriverWinMidi::open() {
+ int device_index = 0;
for (UINT i = 0; i < midiInGetNumDevs(); i++) {
HMIDIIN midi_in;
+ MIDIINCAPS caps;
- MMRESULT res = midiInOpen(&midi_in, i, (DWORD_PTR)read, (DWORD_PTR)i, CALLBACK_FUNCTION);
- if (res == MMSYSERR_NOERROR) {
+ MMRESULT open_res = midiInOpen(&midi_in, i, (DWORD_PTR)read,
+ (DWORD_PTR)device_index, CALLBACK_FUNCTION);
+ MMRESULT caps_res = midiInGetDevCaps(i, &caps, sizeof(MIDIINCAPS));
+
+ if (open_res == MMSYSERR_NOERROR) {
midiInStart(midi_in);
- connected_sources.insert(i, midi_in);
+ connected_sources.push_back(midi_in);
+ if (caps_res == MMSYSERR_NOERROR) {
+ connected_input_names.push_back(caps.szPname);
+ } else {
+ // Should push something even if we don't get a name,
+ // so that the IDs line up correctly on the script side.
+ connected_input_names.push_back("ERROR");
+ }
+ // Only increment device index for successfully connected devices.
+ device_index++;
} else {
char err[256];
- midiInGetErrorText(res, err, 256);
+ midiInGetErrorText(open_res, err, 256);
ERR_PRINT("midiInOpen error: " + String(err));
- MIDIINCAPS caps;
- res = midiInGetDevCaps(i, &caps, sizeof(MIDIINCAPS));
- if (res == MMSYSERR_NOERROR) {
+ if (caps_res == MMSYSERR_NOERROR) {
ERR_PRINT("Can't open MIDI device \"" + String(caps.szPname) + "\", is it being used by another application?");
}
}
@@ -64,25 +80,6 @@ Error MIDIDriverWinMidi::open() {
return OK;
}
-PackedStringArray MIDIDriverWinMidi::get_connected_inputs() {
- PackedStringArray list;
-
- for (int i = 0; i < connected_sources.size(); i++) {
- HMIDIIN midi_in = connected_sources[i];
- UINT id = 0;
- MMRESULT res = midiInGetID(midi_in, &id);
- if (res == MMSYSERR_NOERROR) {
- MIDIINCAPS caps;
- res = midiInGetDevCaps(i, &caps, sizeof(MIDIINCAPS));
- if (res == MMSYSERR_NOERROR) {
- list.push_back(caps.szPname);
- }
- }
- }
-
- return list;
-}
-
void MIDIDriverWinMidi::close() {
for (int i = 0; i < connected_sources.size(); i++) {
HMIDIIN midi_in = connected_sources[i];
@@ -90,9 +87,7 @@ void MIDIDriverWinMidi::close() {
midiInClose(midi_in);
}
connected_sources.clear();
-}
-
-MIDIDriverWinMidi::MIDIDriverWinMidi() {
+ connected_input_names.clear();
}
MIDIDriverWinMidi::~MIDIDriverWinMidi() {
diff --git a/drivers/winmidi/midi_driver_winmidi.h b/drivers/winmidi/midi_driver_winmidi.h
index f3e016f378..7a75252233 100644
--- a/drivers/winmidi/midi_driver_winmidi.h
+++ b/drivers/winmidi/midi_driver_winmidi.h
@@ -48,12 +48,10 @@ class MIDIDriverWinMidi : public MIDIDriver {
static void CALLBACK read(HMIDIIN hMidiIn, UINT wMsg, DWORD_PTR dwInstance, DWORD_PTR dwParam1, DWORD_PTR dwParam2);
public:
- virtual Error open();
- virtual void close();
+ virtual Error open() override;
+ virtual void close() override;
- virtual PackedStringArray get_connected_inputs();
-
- MIDIDriverWinMidi();
+ MIDIDriverWinMidi() = default;
virtual ~MIDIDriverWinMidi();
};
diff --git a/editor/code_editor.cpp b/editor/code_editor.cpp
index 595aff3bd0..7e07e626b1 100644
--- a/editor/code_editor.cpp
+++ b/editor/code_editor.cpp
@@ -1049,6 +1049,9 @@ void CodeTextEditor::update_editor_settings() {
text_editor->set_smooth_scroll_enabled(EDITOR_GET("text_editor/behavior/navigation/smooth_scrolling"));
text_editor->set_v_scroll_speed(EDITOR_GET("text_editor/behavior/navigation/v_scroll_speed"));
text_editor->set_drag_and_drop_selection_enabled(EDITOR_GET("text_editor/behavior/navigation/drag_and_drop_selection"));
+ text_editor->set_use_default_word_separators(EDITOR_GET("text_editor/behavior/navigation/use_default_word_separators"));
+ text_editor->set_use_custom_word_separators(EDITOR_GET("text_editor/behavior/navigation/use_custom_word_separators"));
+ text_editor->set_custom_word_separators(EDITOR_GET("text_editor/behavior/navigation/custom_word_separators"));
// Behavior: Indent
set_indent_using_spaces(EDITOR_GET("text_editor/behavior/indent/type"));
diff --git a/editor/create_dialog.cpp b/editor/create_dialog.cpp
index 56500c71e2..204636e128 100644
--- a/editor/create_dialog.cpp
+++ b/editor/create_dialog.cpp
@@ -160,8 +160,13 @@ bool CreateDialog::_should_hide_type(const StringName &p_type) const {
String script_path = ScriptServer::get_global_class_path(p_type);
if (script_path.begins_with("res://addons/")) {
- if (!EditorNode::get_singleton()->is_addon_plugin_enabled(script_path.get_slicec('/', 3))) {
- return true; // Plugin is not enabled.
+ int i = script_path.find("/", 13); // 13 is length of "res://addons/".
+ while (i > -1) {
+ const String plugin_path = script_path.substr(0, i).path_join("plugin.cfg");
+ if (FileAccess::exists(plugin_path)) {
+ return !EditorNode::get_singleton()->is_addon_plugin_enabled(plugin_path);
+ }
+ i = script_path.find("/", i + 1);
}
}
}
diff --git a/editor/editor_audio_buses.cpp b/editor/editor_audio_buses.cpp
index 74f911f07d..3b337997e0 100644
--- a/editor/editor_audio_buses.cpp
+++ b/editor/editor_audio_buses.cpp
@@ -799,6 +799,7 @@ EditorAudioBus::EditorAudioBus(EditorAudioBuses *p_buses, bool p_is_master) {
set_tooltip_text(TTR("Drag & drop to rearrange."));
VBoxContainer *vb = memnew(VBoxContainer);
+ vb->add_theme_constant_override("separation", 4 * EDSCALE);
add_child(vb);
set_v_size_flags(SIZE_EXPAND_FILL);
@@ -854,8 +855,17 @@ EditorAudioBus::EditorAudioBus(EditorAudioBuses *p_buses, bool p_is_master) {
separator->set_mouse_filter(MOUSE_FILTER_PASS);
vb->add_child(separator);
+ Control *spacer_top = memnew(Control);
+ spacer_top->set_custom_minimum_size(Size2(0, 6 * EDSCALE));
+ vb->add_child(spacer_top);
+
HBoxContainer *hb = memnew(HBoxContainer);
vb->add_child(hb);
+
+ Control *spacer_bottom = memnew(Control);
+ spacer_bottom->set_custom_minimum_size(Size2(0, 2 * EDSCALE));
+ vb->add_child(spacer_bottom);
+
slider = memnew(VSlider);
slider->set_min(0.0);
slider->set_max(1.0);
diff --git a/editor/editor_inspector.cpp b/editor/editor_inspector.cpp
index 6c26231a4b..81d9510033 100644
--- a/editor/editor_inspector.cpp
+++ b/editor/editor_inspector.cpp
@@ -2598,6 +2598,10 @@ int EditorInspector::inspector_plugin_count = 0;
EditorProperty *EditorInspector::instantiate_property_editor(Object *p_object, const Variant::Type p_type, const String &p_path, PropertyHint p_hint, const String &p_hint_text, const uint32_t p_usage, const bool p_wide) {
for (int i = inspector_plugin_count - 1; i >= 0; i--) {
+ if (!inspector_plugins[i]->can_handle(p_object)) {
+ continue;
+ }
+
inspector_plugins[i]->parse_property(p_object, p_type, p_path, p_hint, p_hint_text, p_usage, p_wide);
if (inspector_plugins[i]->added_editors.size()) {
for (List<EditorInspectorPlugin::AddedEditor>::Element *E = inspector_plugins[i]->added_editors.front()->next(); E; E = E->next()) { //only keep first one
diff --git a/editor/editor_node.cpp b/editor/editor_node.cpp
index 1560fe5636..ae7066fea9 100644
--- a/editor/editor_node.cpp
+++ b/editor/editor_node.cpp
@@ -679,6 +679,8 @@ void EditorNode::_notification(int p_what) {
} break;
case NOTIFICATION_EXIT_TREE: {
+ singleton->active_plugins.clear();
+
if (progress_dialog) {
progress_dialog->queue_free();
}
@@ -1735,7 +1737,7 @@ bool EditorNode::_validate_scene_recursive(const String &p_filename, Node *p_nod
return false;
}
-int EditorNode::_save_external_resources() {
+int EditorNode::_save_external_resources(bool p_also_save_external_data) {
// Save external resources and its subresources if any was modified.
int flg = 0;
@@ -1781,6 +1783,16 @@ int EditorNode::_save_external_resources() {
saved++;
}
+ if (p_also_save_external_data) {
+ for (int i = 0; i < editor_data.get_editor_plugin_count(); i++) {
+ EditorPlugin *plugin = editor_data.get_editor_plugin(i);
+ if (!plugin->get_unsaved_status().is_empty()) {
+ plugin->save_external_data();
+ saved++;
+ }
+ }
+ }
+
EditorUndoRedoManager::get_singleton()->set_history_as_saved(EditorUndoRedoManager::GLOBAL_HISTORY);
return saved;
@@ -1810,9 +1822,7 @@ static void _reset_animation_mixers(Node *p_node, List<Pair<AnimationMixer *, Re
}
void EditorNode::_save_scene(String p_file, int idx) {
- if (!saving_scene.is_empty() && saving_scene == p_file) {
- return;
- }
+ ERR_FAIL_COND_MSG(!saving_scene.is_empty() && saving_scene == p_file, "Scene saved while already being saved!");
Node *scene = editor_data.get_edited_scene_root(idx);
@@ -2726,10 +2736,10 @@ void EditorNode::_menu_option_confirm(int p_option, bool p_confirmed) {
ScriptEditor::get_singleton()->save_current_script();
}
- const int saved = _save_external_resources();
+ const int saved = _save_external_resources(true);
if (saved > 0) {
show_accept(
- vformat(TTR("The current scene has no root node, but %d modified external resource(s) were saved anyway."), saved),
+ vformat(TTR("The current scene has no root node, but %d modified external resource(s) and/or plugin data were saved anyway."), saved),
TTR("OK"));
} else if (p_option == FILE_SAVE_AS_SCENE) {
// Don't show this dialog when pressing Ctrl + S to avoid interfering with script saving.
@@ -6446,6 +6456,7 @@ EditorNode::EditorNode() {
ED_SHORTCUT("editor/ungroup_selected_nodes", TTR("Ungroup Selected Node(s)"), KeyModifierMask::CMD_OR_CTRL | KeyModifierMask::SHIFT | Key::G);
// Used in the GPUParticles/CPUParticles 2D/3D editor plugins.
+ // The shortcut is Ctrl + R even on macOS, as Cmd + R is used to run the current scene on macOS.
ED_SHORTCUT("particles/restart_emission", TTR("Restart Emission"), KeyModifierMask::CTRL | Key::R);
FileAccess::set_backup_save(EDITOR_GET("filesystem/on_save/safe_save_on_backup_then_rename"));
diff --git a/editor/editor_node.h b/editor/editor_node.h
index b7b4dff74e..49674dd1c1 100644
--- a/editor/editor_node.h
+++ b/editor/editor_node.h
@@ -573,7 +573,7 @@ private:
void _update_undo_redo_allowed();
- int _save_external_resources();
+ int _save_external_resources(bool p_also_save_external_data = false);
void _set_current_scene(int p_idx);
void _set_current_scene_nocheck(int p_idx);
diff --git a/editor/editor_properties_array_dict.cpp b/editor/editor_properties_array_dict.cpp
index 127bca9bbf..633f6abad9 100644
--- a/editor/editor_properties_array_dict.cpp
+++ b/editor/editor_properties_array_dict.cpp
@@ -435,7 +435,7 @@ void EditorPropertyArray::update_property() {
editor->setup("Object");
new_prop = editor;
} else {
- new_prop = EditorInspector::instantiate_property_editor(nullptr, value_type, "", subtype_hint, subtype_hint_string, PROPERTY_USAGE_NONE);
+ new_prop = EditorInspector::instantiate_property_editor(this, value_type, "", subtype_hint, subtype_hint_string, PROPERTY_USAGE_NONE);
}
new_prop->set_selectable(false);
new_prop->set_use_folding(is_using_folding());
@@ -1064,7 +1064,7 @@ void EditorPropertyDictionary::update_property() {
editor->setup("Object");
new_prop = editor;
} else {
- new_prop = EditorInspector::instantiate_property_editor(nullptr, value_type, "", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NONE);
+ new_prop = EditorInspector::instantiate_property_editor(this, value_type, "", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NONE);
}
new_prop->set_selectable(false);
new_prop->set_use_folding(is_using_folding());
diff --git a/editor/editor_settings.cpp b/editor/editor_settings.cpp
index a431cf93ba..e021be9668 100644
--- a/editor/editor_settings.cpp
+++ b/editor/editor_settings.cpp
@@ -639,6 +639,9 @@ void EditorSettings::_load_defaults(Ref<ConfigFile> p_extra_config) {
_initial_set("text_editor/behavior/navigation/drag_and_drop_selection", true);
_initial_set("text_editor/behavior/navigation/stay_in_script_editor_on_node_selected", true);
_initial_set("text_editor/behavior/navigation/open_script_when_connecting_signal_to_existing_method", true);
+ _initial_set("text_editor/behavior/navigation/use_default_word_separators", true); // Includes ´`~$^=+|<> General punctuation and CJK punctuation.
+ _initial_set("text_editor/behavior/navigation/use_custom_word_separators", false);
+ _initial_set("text_editor/behavior/navigation/custom_word_separators", ""); // Custom word separators.
// Behavior: Indent
EDITOR_SETTING(Variant::INT, PROPERTY_HINT_ENUM, "text_editor/behavior/indent/type", 0, "Tabs,Spaces")
diff --git a/editor/editor_undo_redo_manager.cpp b/editor/editor_undo_redo_manager.cpp
index 94f76dbc41..55bc198dfb 100644
--- a/editor/editor_undo_redo_manager.cpp
+++ b/editor/editor_undo_redo_manager.cpp
@@ -104,8 +104,13 @@ int EditorUndoRedoManager::get_history_id_for_object(Object *p_object) const {
}
EditorUndoRedoManager::History &EditorUndoRedoManager::get_history_for_object(Object *p_object) {
- int history_id = get_history_id_for_object(p_object);
- ERR_FAIL_COND_V_MSG(pending_action.history_id != INVALID_HISTORY && history_id != pending_action.history_id, get_or_create_history(pending_action.history_id), vformat("UndoRedo history mismatch: expected %d, got %d.", pending_action.history_id, history_id));
+ int history_id;
+ if (!forced_history) {
+ history_id = get_history_id_for_object(p_object);
+ ERR_FAIL_COND_V_MSG(pending_action.history_id != INVALID_HISTORY && history_id != pending_action.history_id, get_or_create_history(pending_action.history_id), vformat("UndoRedo history mismatch: expected %d, got %d.", pending_action.history_id, history_id));
+ } else {
+ history_id = pending_action.history_id;
+ }
History &history = get_or_create_history(history_id);
if (pending_action.history_id == INVALID_HISTORY) {
@@ -116,6 +121,11 @@ EditorUndoRedoManager::History &EditorUndoRedoManager::get_history_for_object(Ob
return history;
}
+void EditorUndoRedoManager::force_fixed_history() {
+ ERR_FAIL_COND_MSG(pending_action.history_id == INVALID_HISTORY, "The current action has no valid history assigned.");
+ forced_history = true;
+}
+
void EditorUndoRedoManager::create_action_for_history(const String &p_name, int p_history_id, UndoRedo::MergeMode p_mode, bool p_backward_undo_ops) {
if (pending_action.history_id != INVALID_HISTORY) {
// Nested action.
@@ -236,6 +246,7 @@ void EditorUndoRedoManager::commit_action(bool p_execute) {
return; // Empty action, do nothing.
}
+ forced_history = false;
is_committing = true;
History &history = get_or_create_history(pending_action.history_id);
@@ -469,6 +480,7 @@ void EditorUndoRedoManager::_bind_methods() {
ClassDB::bind_method(D_METHOD("create_action", "name", "merge_mode", "custom_context", "backward_undo_ops"), &EditorUndoRedoManager::create_action, DEFVAL(UndoRedo::MERGE_DISABLE), DEFVAL((Object *)nullptr), DEFVAL(false));
ClassDB::bind_method(D_METHOD("commit_action", "execute"), &EditorUndoRedoManager::commit_action, DEFVAL(true));
ClassDB::bind_method(D_METHOD("is_committing_action"), &EditorUndoRedoManager::is_committing_action);
+ ClassDB::bind_method(D_METHOD("force_fixed_history"), &EditorUndoRedoManager::force_fixed_history);
{
MethodInfo mi;
diff --git a/editor/editor_undo_redo_manager.h b/editor/editor_undo_redo_manager.h
index e8c782871c..219d5e0702 100644
--- a/editor/editor_undo_redo_manager.h
+++ b/editor/editor_undo_redo_manager.h
@@ -67,6 +67,7 @@ private:
HashMap<int, History> history_map;
Action pending_action;
+ bool forced_history = false;
bool is_committing = false;
History *_get_newest_undo();
@@ -79,6 +80,7 @@ public:
UndoRedo *get_history_undo_redo(int p_idx) const;
int get_history_id_for_object(Object *p_object) const;
History &get_history_for_object(Object *p_object);
+ void force_fixed_history();
void create_action_for_history(const String &p_name, int p_history_id, UndoRedo::MergeMode p_mode = UndoRedo::MERGE_DISABLE, bool p_backward_undo_ops = false);
void create_action(const String &p_name = "", UndoRedo::MergeMode p_mode = UndoRedo::MERGE_DISABLE, Object *p_custom_context = nullptr, bool p_backward_undo_ops = false);
diff --git a/editor/plugins/cast_2d_editor_plugin.cpp b/editor/plugins/cast_2d_editor_plugin.cpp
index c9d7ff8e08..3da7d4a7dc 100644
--- a/editor/plugins/cast_2d_editor_plugin.cpp
+++ b/editor/plugins/cast_2d_editor_plugin.cpp
@@ -102,7 +102,6 @@ bool Cast2DEditor::forward_canvas_gui_input(const Ref<InputEvent> &p_event) {
node->set("target_position", point);
canvas_item_editor->update_viewport();
- node->notify_property_list_changed();
return true;
}
diff --git a/editor/plugins/editor_plugin.cpp b/editor/plugins/editor_plugin.cpp
index 2b3c187352..005407e188 100644
--- a/editor/plugins/editor_plugin.cpp
+++ b/editor/plugins/editor_plugin.cpp
@@ -420,7 +420,9 @@ void EditorPlugin::add_import_plugin(const Ref<EditorImportPlugin> &p_importer,
void EditorPlugin::remove_import_plugin(const Ref<EditorImportPlugin> &p_importer) {
ERR_FAIL_COND(!p_importer.is_valid());
ResourceFormatImporter::get_singleton()->remove_importer(p_importer);
- callable_mp(EditorFileSystem::get_singleton(), &EditorFileSystem::scan).call_deferred();
+ if (!EditorNode::get_singleton()->is_exiting()) {
+ callable_mp(EditorFileSystem::get_singleton(), &EditorFileSystem::scan).call_deferred();
+ }
}
void EditorPlugin::add_export_plugin(const Ref<EditorExportPlugin> &p_exporter) {
diff --git a/editor/plugins/packed_scene_translation_parser_plugin.cpp b/editor/plugins/packed_scene_translation_parser_plugin.cpp
index e266a3241b..118cba53d2 100644
--- a/editor/plugins/packed_scene_translation_parser_plugin.cpp
+++ b/editor/plugins/packed_scene_translation_parser_plugin.cpp
@@ -200,6 +200,7 @@ PackedSceneEditorTranslationParserPlugin::PackedSceneEditorTranslationParserPlug
lookup_properties.insert("title");
lookup_properties.insert("filters");
lookup_properties.insert("script");
+ lookup_properties.insert("item_*/text");
// Exception list (to prevent false positives).
exception_list.insert("LineEdit", { "text" });
diff --git a/editor/plugins/script_editor_plugin.cpp b/editor/plugins/script_editor_plugin.cpp
index 3d043909b2..9d5dbb4a4f 100644
--- a/editor/plugins/script_editor_plugin.cpp
+++ b/editor/plugins/script_editor_plugin.cpp
@@ -1012,8 +1012,6 @@ void ScriptEditor::_resave_scripts(const String &p_str) {
se->trim_final_newlines();
}
- se->insert_final_newline();
-
if (convert_indent_on_save) {
se->convert_indent();
}
@@ -1410,8 +1408,6 @@ void ScriptEditor::_menu_option(int p_option) {
current->trim_final_newlines();
}
- current->insert_final_newline();
-
if (convert_indent_on_save) {
current->convert_indent();
}
@@ -2614,8 +2610,6 @@ void ScriptEditor::save_current_script() {
current->trim_final_newlines();
}
- current->insert_final_newline();
-
if (convert_indent_on_save) {
current->convert_indent();
}
@@ -2662,8 +2656,6 @@ void ScriptEditor::save_all_scripts() {
se->trim_final_newlines();
}
- se->insert_final_newline();
-
if (!se->is_unsaved()) {
continue;
}
@@ -2713,6 +2705,7 @@ void ScriptEditor::apply_scripts() const {
if (!se) {
continue;
}
+ se->insert_final_newline();
se->apply_code();
}
}
diff --git a/editor/plugins/sprite_frames_editor_plugin.cpp b/editor/plugins/sprite_frames_editor_plugin.cpp
index b33250bcb5..27056a6cc4 100644
--- a/editor/plugins/sprite_frames_editor_plugin.cpp
+++ b/editor/plugins/sprite_frames_editor_plugin.cpp
@@ -1056,10 +1056,12 @@ void SpriteFramesEditor::_rename_node_animation(EditorUndoRedoManager *undo_redo
for (Node *E : nodes) {
String current_name = E->call("get_animation");
if (current_name == p_filter) {
+ undo_redo->force_fixed_history(); // Fixes corner-case when editing SpriteFrames stored as separate file.
undo_redo->add_undo_method(E, "set_animation", p_new_animation);
}
String autoplay_name = E->call("get_autoplay");
if (autoplay_name == p_filter) {
+ undo_redo->force_fixed_history();
undo_redo->add_undo_method(E, "set_autoplay", p_new_autoplay);
}
}
@@ -1067,10 +1069,12 @@ void SpriteFramesEditor::_rename_node_animation(EditorUndoRedoManager *undo_redo
for (Node *E : nodes) {
String current_name = E->call("get_animation");
if (current_name == p_filter) {
+ undo_redo->force_fixed_history();
undo_redo->add_do_method(E, "set_animation", p_new_animation);
}
String autoplay_name = E->call("get_autoplay");
if (autoplay_name == p_filter) {
+ undo_redo->force_fixed_history();
undo_redo->add_do_method(E, "set_autoplay", p_new_autoplay);
}
}
diff --git a/editor/plugins/visual_shader_editor_plugin.cpp b/editor/plugins/visual_shader_editor_plugin.cpp
index 3f6927c02a..607c446e1b 100644
--- a/editor/plugins/visual_shader_editor_plugin.cpp
+++ b/editor/plugins/visual_shader_editor_plugin.cpp
@@ -3649,12 +3649,15 @@ void VisualShaderEditor::_add_node(int p_idx, const Vector<Variant> &p_ops, cons
if (output_port_type == VisualShaderNode::PORT_TYPE_SAMPLER) {
if (is_texture2d) {
+ undo_redo->force_fixed_history(); // vsnode is freshly created and has no path, so history can't be correctly determined.
undo_redo->add_do_method(vsnode.ptr(), "set_source", VisualShaderNodeTexture::SOURCE_PORT);
}
if (is_texture3d || is_texture2d_array) {
+ undo_redo->force_fixed_history();
undo_redo->add_do_method(vsnode.ptr(), "set_source", VisualShaderNodeSample3D::SOURCE_PORT);
}
if (is_cubemap) {
+ undo_redo->force_fixed_history();
undo_redo->add_do_method(vsnode.ptr(), "set_source", VisualShaderNodeCubemap::SOURCE_PORT);
}
}
@@ -3754,16 +3757,19 @@ void VisualShaderEditor::_add_node(int p_idx, const Vector<Variant> &p_ops, cons
//post-initialization
if (is_texture2d || is_texture3d || is_curve || is_curve_xyz) {
+ undo_redo->force_fixed_history();
undo_redo->add_do_method(vsnode.ptr(), "set_texture", ResourceLoader::load(p_resource_path));
return;
}
if (is_cubemap) {
+ undo_redo->force_fixed_history();
undo_redo->add_do_method(vsnode.ptr(), "set_cube_map", ResourceLoader::load(p_resource_path));
return;
}
if (is_texture2d_array) {
+ undo_redo->force_fixed_history();
undo_redo->add_do_method(vsnode.ptr(), "set_texture_array", ResourceLoader::load(p_resource_path));
}
}
diff --git a/misc/scripts/godot_gdb_pretty_print.py b/misc/utility/godot_gdb_pretty_print.py
index 932831d24e..7edd668016 100755..100644
--- a/misc/scripts/godot_gdb_pretty_print.py
+++ b/misc/utility/godot_gdb_pretty_print.py
@@ -1,28 +1,30 @@
-#!/usr/bin/env python3
-# Load this file to your GDB session to enable pretty-printing
-# of some Godot C++ types.
-# GDB command: source misc/scripts/godot_gdb_pretty_print.py
-#
-# To load these automatically in Visual Studio Code,
-# add the source command to the setupCommands of your configuration
-# in launch.json.
-# "setupCommands": [
-# ...
-# {
-# "description": "Load custom pretty-printers for Godot types.",
-# "text": "source ${workspaceRoot}/misc/scripts/godot_gdb_pretty_print.py"
-# }
-# ]
-# Other UI:s that use GDB under the hood are likely to have their own ways to achieve this.
-#
-# To debug this script it's easiest to use the interactive python from a command-line
-# GDB session. Stop at a breakpoint, then use
-# python-interactive to enter the python shell and
-# acquire a Value object using gdb.selected_frame().read_var("variable name").
-# From there you can figure out how to print it nicely.
+"""
+Load this file to your GDB session to enable pretty-printing of some Godot C++ types.
+
+GDB command: `source misc/utility/godot_gdb_pretty_print.py`.
+
+To load these automatically in Visual Studio Code, add the source command to
+the `setupCommands` of your configuration in `launch.json`:
+```json
+"setupCommands": [
+...
+ {
+ "description": "Load custom pretty-printers for Godot types.",
+ "text": "source ${workspaceFolder}/misc/utility/godot_gdb_pretty_print.py"
+ }
+]
+```
+Other UIs that use GDB under the hood are likely to have their own ways to achieve this.
+
+To debug this script it's easiest to use the interactive python from a command-line
+GDB session. Stop at a breakpoint, then use python-interactive to enter the python shell
+and acquire a `Value` object using `gdb.selected_frame().read_var("variable name")`.
+From there you can figure out how to print it nicely.
+"""
+
import re
-import gdb
+import gdb # type: ignore
# Printer for Godot StringName variables.
diff --git a/modules/gdscript/gdscript_analyzer.cpp b/modules/gdscript/gdscript_analyzer.cpp
index aa26bb222d..c60672d3f4 100644
--- a/modules/gdscript/gdscript_analyzer.cpp
+++ b/modules/gdscript/gdscript_analyzer.cpp
@@ -5527,6 +5527,9 @@ bool GDScriptAnalyzer::check_type_compatibility(const GDScriptParser::DataType &
// A script type cannot be a subtype of a GDScript class.
return false;
}
+ if (p_source.script_type.is_null()) {
+ return false;
+ }
if (p_source.is_meta_type) {
src_native = p_source.script_type->get_class_name();
} else {
diff --git a/modules/gridmap/editor/grid_map_editor_plugin.cpp b/modules/gridmap/editor/grid_map_editor_plugin.cpp
index b93b7c1a8c..f402e2a583 100644
--- a/modules/gridmap/editor/grid_map_editor_plugin.cpp
+++ b/modules/gridmap/editor/grid_map_editor_plugin.cpp
@@ -59,10 +59,18 @@ void GridMapEditor::_menu_option(int p_option) {
switch (p_option) {
case MENU_OPTION_PREV_LEVEL: {
floor->set_value(floor->get_value() - 1);
+ if (selection.active && input_action == INPUT_SELECT) {
+ selection.current[edit_axis]--;
+ _validate_selection();
+ }
} break;
case MENU_OPTION_NEXT_LEVEL: {
floor->set_value(floor->get_value() + 1);
+ if (selection.active && input_action == INPUT_SELECT) {
+ selection.current[edit_axis]++;
+ _validate_selection();
+ }
} break;
case MENU_OPTION_X_AXIS:
@@ -754,19 +762,6 @@ EditorPlugin::AfterGUIInput GridMapEditor::forward_spatial_input_event(Camera3D
}
}
}
-
- if (k->is_shift_pressed() && selection.active && input_action != INPUT_PASTE) {
- if (k->get_keycode() == (Key)options->get_popup()->get_item_accelerator(options->get_popup()->get_item_index(MENU_OPTION_PREV_LEVEL))) {
- selection.click[edit_axis]--;
- _validate_selection();
- return EditorPlugin::AFTER_GUI_INPUT_STOP;
- }
- if (k->get_keycode() == (Key)options->get_popup()->get_item_accelerator(options->get_popup()->get_item_index(MENU_OPTION_NEXT_LEVEL))) {
- selection.click[edit_axis]++;
- _validate_selection();
- return EditorPlugin::AFTER_GUI_INPUT_STOP;
- }
- }
}
}
diff --git a/modules/webxr/native/library_godot_webxr.js b/modules/webxr/native/library_godot_webxr.js
index 031530a047..581da4d56f 100644
--- a/modules/webxr/native/library_godot_webxr.js
+++ b/modules/webxr/native/library_godot_webxr.js
@@ -320,7 +320,8 @@ const GodotWebXR = {
// next reference space.
window.setTimeout(function () {
const reference_space_c_str = GodotRuntime.allocString(reference_space_type);
- const enabled_features_c_str = GodotRuntime.allocString(Array.from(session.enabledFeatures).join(','));
+ const enabled_features = 'enabledFeatures' in session ? Array.from(session.enabledFeatures) : [];
+ const enabled_features_c_str = GodotRuntime.allocString(enabled_features.join(','));
onstarted(reference_space_c_str, enabled_features_c_str);
GodotRuntime.free(reference_space_c_str);
GodotRuntime.free(enabled_features_c_str);
diff --git a/modules/webxr/native/webxr.externs.js b/modules/webxr/native/webxr.externs.js
index 35ad33fa93..80a7f8d2de 100644
--- a/modules/webxr/native/webxr.externs.js
+++ b/modules/webxr/native/webxr.externs.js
@@ -78,6 +78,11 @@ XRSession.prototype.frameRate;
XRSession.prototype.supportedFrameRates;
/**
+ * @type {Array<string>}
+ */
+XRSession.prototype.enabledFeatures;
+
+/**
* @type {?function (Event)}
*/
XRSession.prototype.onend;
diff --git a/modules/webxr/webxr_interface_js.cpp b/modules/webxr/webxr_interface_js.cpp
index 916566fc1b..6c18b650ee 100644
--- a/modules/webxr/webxr_interface_js.cpp
+++ b/modules/webxr/webxr_interface_js.cpp
@@ -740,12 +740,20 @@ void WebXRInterfaceJS::_update_input_source(int p_input_source_id) {
// WebXR doesn't have a palm joint, so we calculate it by finding the middle of the middle finger metacarpal bone.
{
- // 10 is the WebXR middle finger metacarpal joint, and 12 is the offset to the transform origin.
- const float *start_pos = hand_joints + (10 * 16) + 12;
- // 11 is the WebXR middle finger phalanx proximal joint, and 12 is the offset to the transform origin.
- const float *end_pos = hand_joints + (11 * 16) + 12;
- Transform3D palm_transform;
- palm_transform.origin = (Vector3(start_pos[0], start_pos[1], start_pos[2]) + Vector3(end_pos[0], end_pos[1], end_pos[2])) / 2.0;
+ // Start by getting the middle finger metacarpal joint.
+ // Note: 10 is the WebXR middle finger metacarpal joint.
+ Transform3D palm_transform = _js_matrix_to_transform(hand_joints + (10 * 16));
+ palm_transform.basis *= bone_adjustment;
+
+ // Get the middle finger phalanx position.
+ // Note: 11 is the WebXR middle finger phalanx proximal joint and 12 is the origin offset.
+ const float *phalanx_pos = hand_joints + (11 * 16) + 12;
+ Vector3 phalanx(phalanx_pos[0], phalanx_pos[1], phalanx_pos[2]);
+
+ // Offset the palm half-way towards the phalanx joint.
+ palm_transform.origin = (palm_transform.origin + phalanx) / 2.0;
+
+ // Set the palm joint and the pose.
hand_tracker->set_hand_joint_transform(XRHandTracker::HAND_JOINT_PALM, palm_transform);
hand_tracker->set_pose("default", palm_transform, Vector3(), Vector3());
}
diff --git a/platform/web/detect.py b/platform/web/detect.py
index 1b15ff8e2e..cb4dac1125 100644
--- a/platform/web/detect.py
+++ b/platform/web/detect.py
@@ -207,11 +207,10 @@ def configure(env: "SConsEnvironment"):
env.Append(LINKFLAGS=["-sMAX_WEBGL_VERSION=2"])
# Allow use to take control of swapping WebGL buffers.
env.Append(LINKFLAGS=["-sOFFSCREEN_FRAMEBUFFER=1"])
- # Breaking change since emscripten 3.1.51
- # https://github.com/emscripten-core/emscripten/blob/main/ChangeLog.md#3151---121323
+ # Disables the use of *glGetProcAddress() which is inefficient.
+ # See https://emscripten.org/docs/tools_reference/settings_reference.html#gl-enable-get-proc-address
if cc_semver >= (3, 1, 51):
- # Enables the use of *glGetProcAddress()
- env.Append(LINKFLAGS=["-sGL_ENABLE_GET_PROC_ADDRESS=1"])
+ env.Append(LINKFLAGS=["-sGL_ENABLE_GET_PROC_ADDRESS=0"])
if env["javascript_eval"]:
env.Append(CPPDEFINES=["JAVASCRIPT_EVAL_ENABLED"])
diff --git a/platform/web/export/export_plugin.cpp b/platform/web/export/export_plugin.cpp
index 8941c26ec0..d83e465e8e 100644
--- a/platform/web/export/export_plugin.cpp
+++ b/platform/web/export/export_plugin.cpp
@@ -334,10 +334,14 @@ void EditorExportPlatformWeb::get_preset_features(const Ref<EditorExportPreset>
if (p_preset->get("vram_texture_compression/for_desktop")) {
r_features->push_back("s3tc");
}
-
if (p_preset->get("vram_texture_compression/for_mobile")) {
r_features->push_back("etc2");
}
+ if (p_preset->get("variant/thread_support").operator bool()) {
+ r_features->push_back("threads");
+ } else {
+ r_features->push_back("nothreads");
+ }
r_features->push_back("wasm32");
}
@@ -345,7 +349,7 @@ void EditorExportPlatformWeb::get_export_options(List<ExportOption> *r_options)
r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "custom_template/debug", PROPERTY_HINT_GLOBAL_FILE, "*.zip"), ""));
r_options->push_back(ExportOption(PropertyInfo(Variant::STRING, "custom_template/release", PROPERTY_HINT_GLOBAL_FILE, "*.zip"), ""));
- r_options->push_back(ExportOption(PropertyInfo(Variant::BOOL, "variant/extensions_support"), false)); // Export type.
+ r_options->push_back(ExportOption(PropertyInfo(Variant::BOOL, "variant/extensions_support"), false)); // GDExtension support.
r_options->push_back(ExportOption(PropertyInfo(Variant::BOOL, "variant/thread_support"), false)); // Thread support (i.e. run with or without COEP/COOP headers).
r_options->push_back(ExportOption(PropertyInfo(Variant::BOOL, "vram_texture_compression/for_desktop"), true)); // S3TC
r_options->push_back(ExportOption(PropertyInfo(Variant::BOOL, "vram_texture_compression/for_mobile"), false)); // ETC or ETC2, depending on renderer
@@ -585,35 +589,50 @@ bool EditorExportPlatformWeb::poll_export() {
}
}
- int prev = menu_options;
- menu_options = preset.is_valid();
- HTTPServerState prev_server_state = server_state;
- server_state = HTTP_SERVER_STATE_OFF;
- if (server->is_listening()) {
- if (preset.is_null() || menu_options == 0) {
- server->stop();
- } else {
- server_state = HTTP_SERVER_STATE_ON;
- menu_options += 1;
+ RemoteDebugState prev_remote_debug_state = remote_debug_state;
+ remote_debug_state = REMOTE_DEBUG_STATE_UNAVAILABLE;
+
+ if (preset.is_valid()) {
+ const bool debug = true;
+ // Throwaway variables to pass to `can_export`.
+ String err;
+ bool missing_templates;
+
+ if (can_export(preset, err, missing_templates, debug)) {
+ if (server->is_listening()) {
+ remote_debug_state = REMOTE_DEBUG_STATE_SERVING;
+ } else {
+ remote_debug_state = REMOTE_DEBUG_STATE_AVAILABLE;
+ }
}
}
- return server_state != prev_server_state || menu_options != prev;
+ if (remote_debug_state != REMOTE_DEBUG_STATE_SERVING && server->is_listening()) {
+ server->stop();
+ }
+
+ return remote_debug_state != prev_remote_debug_state;
}
Ref<ImageTexture> EditorExportPlatformWeb::get_option_icon(int p_index) const {
Ref<ImageTexture> play_icon = EditorExportPlatform::get_option_icon(p_index);
- switch (server_state) {
- case HTTP_SERVER_STATE_OFF: {
+ switch (remote_debug_state) {
+ case REMOTE_DEBUG_STATE_UNAVAILABLE: {
+ return nullptr;
+ } break;
+
+ case REMOTE_DEBUG_STATE_AVAILABLE: {
switch (p_index) {
case 0:
case 1:
return play_icon;
+ default:
+ ERR_FAIL_V(nullptr);
}
} break;
- case HTTP_SERVER_STATE_ON: {
+ case REMOTE_DEBUG_STATE_SERVING: {
switch (p_index) {
case 0:
return play_icon;
@@ -621,18 +640,31 @@ Ref<ImageTexture> EditorExportPlatformWeb::get_option_icon(int p_index) const {
return restart_icon;
case 2:
return stop_icon;
+ default:
+ ERR_FAIL_V(nullptr);
}
} break;
}
- ERR_FAIL_V_MSG(nullptr, vformat(R"(EditorExportPlatformWeb option icon index "%s" is invalid.)", p_index));
+ return nullptr;
}
int EditorExportPlatformWeb::get_options_count() const {
- if (server_state == HTTP_SERVER_STATE_ON) {
- return 3;
+ switch (remote_debug_state) {
+ case REMOTE_DEBUG_STATE_UNAVAILABLE: {
+ return 0;
+ } break;
+
+ case REMOTE_DEBUG_STATE_AVAILABLE: {
+ return 2;
+ } break;
+
+ case REMOTE_DEBUG_STATE_SERVING: {
+ return 3;
+ } break;
}
- return 2;
+
+ return 0;
}
String EditorExportPlatformWeb::get_option_label(int p_index) const {
@@ -641,17 +673,22 @@ String EditorExportPlatformWeb::get_option_label(int p_index) const {
String reexport_project = TTR("Re-export Project");
String stop_http_server = TTR("Stop HTTP Server");
- switch (server_state) {
- case HTTP_SERVER_STATE_OFF: {
+ switch (remote_debug_state) {
+ case REMOTE_DEBUG_STATE_UNAVAILABLE:
+ return "";
+
+ case REMOTE_DEBUG_STATE_AVAILABLE: {
switch (p_index) {
case 0:
return run_in_browser;
case 1:
return start_http_server;
+ default:
+ ERR_FAIL_V("");
}
} break;
- case HTTP_SERVER_STATE_ON: {
+ case REMOTE_DEBUG_STATE_SERVING: {
switch (p_index) {
case 0:
return run_in_browser;
@@ -659,11 +696,13 @@ String EditorExportPlatformWeb::get_option_label(int p_index) const {
return reexport_project;
case 2:
return stop_http_server;
+ default:
+ ERR_FAIL_V("");
}
} break;
}
- ERR_FAIL_V_MSG("", vformat(R"(EditorExportPlatformWeb option label index "%s" is invalid.)", p_index));
+ return "";
}
String EditorExportPlatformWeb::get_option_tooltip(int p_index) const {
@@ -672,17 +711,22 @@ String EditorExportPlatformWeb::get_option_tooltip(int p_index) const {
String reexport_project = TTR("Export project again to account for updates.");
String stop_http_server = TTR("Stop the HTTP server.");
- switch (server_state) {
- case HTTP_SERVER_STATE_OFF: {
+ switch (remote_debug_state) {
+ case REMOTE_DEBUG_STATE_UNAVAILABLE:
+ return "";
+
+ case REMOTE_DEBUG_STATE_AVAILABLE: {
switch (p_index) {
case 0:
return run_in_browser;
case 1:
return start_http_server;
+ default:
+ ERR_FAIL_V("");
}
} break;
- case HTTP_SERVER_STATE_ON: {
+ case REMOTE_DEBUG_STATE_SERVING: {
switch (p_index) {
case 0:
return run_in_browser;
@@ -690,11 +734,13 @@ String EditorExportPlatformWeb::get_option_tooltip(int p_index) const {
return reexport_project;
case 2:
return stop_http_server;
+ default:
+ ERR_FAIL_V("");
}
} break;
}
- ERR_FAIL_V_MSG("", vformat(R"(EditorExportPlatformWeb option tooltip index "%s" is invalid.)", p_index));
+ return "";
}
Error EditorExportPlatformWeb::run(const Ref<EditorExportPreset> &p_preset, int p_option, int p_debug_flags) {
@@ -703,8 +749,12 @@ Error EditorExportPlatformWeb::run(const Ref<EditorExportPreset> &p_preset, int
const String bind_host = EDITOR_GET("export/web/http_host");
const bool use_tls = EDITOR_GET("export/web/use_tls");
- switch (server_state) {
- case HTTP_SERVER_STATE_OFF: {
+ switch (remote_debug_state) {
+ case REMOTE_DEBUG_STATE_UNAVAILABLE: {
+ return FAILED;
+ } break;
+
+ case REMOTE_DEBUG_STATE_AVAILABLE: {
switch (p_option) {
// Run in Browser.
case 0: {
@@ -727,10 +777,14 @@ Error EditorExportPlatformWeb::run(const Ref<EditorExportPreset> &p_preset, int
}
return _start_server(bind_host, bind_port, use_tls);
} break;
+
+ default: {
+ ERR_FAIL_V_MSG(FAILED, vformat(R"(Invalid option "%s" for the current state.)", p_option));
+ }
}
} break;
- case HTTP_SERVER_STATE_ON: {
+ case REMOTE_DEBUG_STATE_SERVING: {
switch (p_option) {
// Run in Browser.
case 0: {
@@ -750,11 +804,15 @@ Error EditorExportPlatformWeb::run(const Ref<EditorExportPreset> &p_preset, int
case 2: {
return _stop_server();
} break;
+
+ default: {
+ ERR_FAIL_V_MSG(FAILED, vformat(R"(Invalid option "%s" for the current state.)", p_option));
+ }
}
} break;
}
- ERR_FAIL_V_MSG(ERR_INVALID_PARAMETER, vformat(R"(Trying to run EditorExportPlatformWeb, but option "%s" isn't known.)", p_option));
+ return FAILED;
}
Error EditorExportPlatformWeb::_export_project(const Ref<EditorExportPreset> &p_preset, int p_debug_flags) {
diff --git a/platform/web/export/export_plugin.h b/platform/web/export/export_plugin.h
index d3d2083a23..2f67d8107f 100644
--- a/platform/web/export/export_plugin.h
+++ b/platform/web/export/export_plugin.h
@@ -46,19 +46,19 @@
class EditorExportPlatformWeb : public EditorExportPlatform {
GDCLASS(EditorExportPlatformWeb, EditorExportPlatform);
- enum HTTPServerState {
- HTTP_SERVER_STATE_OFF,
- HTTP_SERVER_STATE_ON,
+ enum RemoteDebugState {
+ REMOTE_DEBUG_STATE_UNAVAILABLE,
+ REMOTE_DEBUG_STATE_AVAILABLE,
+ REMOTE_DEBUG_STATE_SERVING,
};
Ref<ImageTexture> logo;
Ref<ImageTexture> run_icon;
Ref<ImageTexture> stop_icon;
Ref<ImageTexture> restart_icon;
- HTTPServerState server_state = HTTP_SERVER_STATE_OFF;
+ RemoteDebugState remote_debug_state = REMOTE_DEBUG_STATE_UNAVAILABLE;
Ref<EditorHTTPServer> server;
- int menu_options = 0;
String _get_template_name(bool p_extension, bool p_thread_support, bool p_debug) const {
String name = "web";
diff --git a/platform/web/platform_gl.h b/platform/web/platform_gl.h
index be6e1462a7..8aadab81de 100644
--- a/platform/web/platform_gl.h
+++ b/platform/web/platform_gl.h
@@ -35,6 +35,10 @@
#define GLES_API_ENABLED // Allow using GLES.
#endif
+// Make using *glGetProcAddress() an error on the web.
+#define glGetProcAddress(n) static_assert(false, "Usage of glGetProcessAddress() on the web is a bug.")
+#define eglGetProcAddress(n) static_assert(false, "Usage of eglGetProcessAddress() on the web is a bug.")
+
#include "platform/web/godot_webgl2.h"
#endif // PLATFORM_GL_H
diff --git a/scene/2d/animated_sprite_2d.cpp b/scene/2d/animated_sprite_2d.cpp
index 6d380aed3c..3506a0df2b 100644
--- a/scene/2d/animated_sprite_2d.cpp
+++ b/scene/2d/animated_sprite_2d.cpp
@@ -473,9 +473,10 @@ void AnimatedSprite2D::play(const StringName &p_name, float p_custom_scale, bool
playing = true;
custom_speed_scale = p_custom_scale;
- int end_frame = MAX(0, frames->get_frame_count(animation) - 1);
if (name != animation) {
animation = name;
+ int end_frame = MAX(0, frames->get_frame_count(animation) - 1);
+
if (p_from_end) {
set_frame_and_progress(end_frame, 1.0);
} else {
@@ -483,7 +484,9 @@ void AnimatedSprite2D::play(const StringName &p_name, float p_custom_scale, bool
}
emit_signal(SceneStringName(animation_changed));
} else {
+ int end_frame = MAX(0, frames->get_frame_count(animation) - 1);
bool is_backward = signbit(speed_scale * custom_speed_scale);
+
if (p_from_end && is_backward && frame == 0 && frame_progress <= 0.0) {
set_frame_and_progress(end_frame, 1.0);
} else if (!p_from_end && !is_backward && frame == end_frame && frame_progress >= 1.0) {
diff --git a/scene/3d/physics/physical_bone_3d.cpp b/scene/3d/physics/physical_bone_3d.cpp
index c290f16c0d..294690a89a 100644
--- a/scene/3d/physics/physical_bone_3d.cpp
+++ b/scene/3d/physics/physical_bone_3d.cpp
@@ -764,7 +764,7 @@ void PhysicalBone3D::_notification(int p_what) {
case NOTIFICATION_EXIT_TREE: {
PhysicalBoneSimulator3D *simulator = get_simulator();
if (simulator) {
- if (-1 != bone_id) {
+ if (bone_id != -1) {
simulator->unbind_physical_bone_from_bone(bone_id);
bone_id = -1;
}
@@ -816,7 +816,7 @@ void PhysicalBone3D::_body_state_changed(PhysicsDirectBodyState3D *p_state) {
PhysicalBoneSimulator3D *simulator = get_simulator();
Skeleton3D *skeleton = get_skeleton();
if (simulator && skeleton) {
- if (-1 != bone_id) {
+ if (bone_id != -1) {
simulator->set_bone_global_pose(bone_id, skeleton->get_global_transform().affine_inverse() * (global_transform * body_offset_inverse));
}
}
@@ -1293,7 +1293,7 @@ void PhysicalBone3D::update_bone_id() {
const int new_bone_id = simulator->find_bone(bone_name);
if (new_bone_id != bone_id) {
- if (-1 != bone_id) {
+ if (bone_id != -1) {
// Assert the unbind from old node
simulator->unbind_physical_bone_from_bone(bone_id);
}
@@ -1313,7 +1313,7 @@ void PhysicalBone3D::update_offset() {
Skeleton3D *skeleton = get_skeleton();
if (simulator && skeleton) {
Transform3D bone_transform(skeleton->get_global_transform());
- if (-1 != bone_id) {
+ if (bone_id != -1) {
bone_transform *= simulator->get_bone_global_pose(bone_id);
}
@@ -1328,7 +1328,7 @@ void PhysicalBone3D::update_offset() {
}
void PhysicalBone3D::_start_physics_simulation() {
- if (_internal_simulate_physics || !simulator_id.is_valid()) {
+ if (_internal_simulate_physics || !simulator_id.is_valid() || bone_id == -1) {
return;
}
reset_to_rest_position();
@@ -1344,7 +1344,7 @@ void PhysicalBone3D::_start_physics_simulation() {
void PhysicalBone3D::_stop_physics_simulation() {
PhysicalBoneSimulator3D *simulator = get_simulator();
if (simulator) {
- if (simulator->is_simulating_physics()) {
+ if (simulator->is_active() && bone_id != -1) {
set_body_mode(PhysicsServer3D::BODY_MODE_KINEMATIC);
PhysicsServer3D::get_singleton()->body_set_collision_layer(get_rid(), get_collision_layer());
PhysicsServer3D::get_singleton()->body_set_collision_mask(get_rid(), get_collision_mask());
diff --git a/scene/3d/skeleton_3d.cpp b/scene/3d/skeleton_3d.cpp
index 21e82adf47..b5263add48 100644
--- a/scene/3d/skeleton_3d.cpp
+++ b/scene/3d/skeleton_3d.cpp
@@ -308,6 +308,7 @@ void Skeleton3D::_notification(int p_what) {
switch (p_what) {
case NOTIFICATION_ENTER_TREE: {
_process_changed();
+ _make_dirty();
_make_modifiers_dirty();
force_update_all_dirty_bones();
#ifndef DISABLE_DEPRECATED
@@ -333,6 +334,13 @@ void Skeleton3D::_notification(int p_what) {
_process_modifiers();
}
+ // Abort if pose is not changed.
+ if (!(update_flags & UPDATE_FLAG_POSE)) {
+ updating = false;
+ update_flags = UPDATE_FLAG_NONE;
+ return;
+ }
+
emit_signal(SceneStringName(skeleton_updated));
// Update skins.
@@ -400,13 +408,13 @@ void Skeleton3D::_notification(int p_what) {
}
updating = false;
- is_update_needed = false;
+ update_flags = UPDATE_FLAG_NONE;
} break;
case NOTIFICATION_INTERNAL_PROCESS:
case NOTIFICATION_INTERNAL_PHYSICS_PROCESS: {
_find_modifiers();
if (!modifiers.is_empty()) {
- _update_deferred();
+ _update_deferred(UPDATE_FLAG_MODIFIER);
}
} break;
}
@@ -436,7 +444,7 @@ void Skeleton3D::_process_changed() {
void Skeleton3D::_make_modifiers_dirty() {
modifiers_dirty = true;
- _update_deferred();
+ _update_deferred(UPDATE_FLAG_MODIFIER);
}
Transform3D Skeleton3D::get_bone_global_pose(int p_bone) const {
@@ -745,10 +753,12 @@ void Skeleton3D::_make_dirty() {
_update_deferred();
}
-void Skeleton3D::_update_deferred() {
- if (!is_update_needed && !updating && is_inside_tree()) {
- is_update_needed = true;
- notify_deferred_thread_group(NOTIFICATION_UPDATE_SKELETON); // It must never be called more than once in a single frame.
+void Skeleton3D::_update_deferred(UpdateFlag p_update_flag) {
+ if (is_inside_tree()) {
+ if (update_flags == UPDATE_FLAG_NONE && !updating) {
+ notify_deferred_thread_group(NOTIFICATION_UPDATE_SKELETON); // It must never be called more than once in a single frame.
+ }
+ update_flags |= p_update_flag;
}
}
@@ -1130,7 +1140,8 @@ void Skeleton3D::set_animate_physical_bones(bool p_enabled) {
if (!sim) {
return;
}
- sim->set_active(p_enabled);
+ animate_physical_bones = p_enabled;
+ sim->set_active(animate_physical_bones || sim->is_simulating_physics());
}
bool Skeleton3D::get_animate_physical_bones() const {
@@ -1138,7 +1149,7 @@ bool Skeleton3D::get_animate_physical_bones() const {
if (!sim) {
return false;
}
- return sim->is_active();
+ return animate_physical_bones;
}
void Skeleton3D::physical_bones_stop_simulation() {
@@ -1147,7 +1158,7 @@ void Skeleton3D::physical_bones_stop_simulation() {
return;
}
sim->physical_bones_stop_simulation();
- sim->set_active(false);
+ sim->set_active(animate_physical_bones || sim->is_simulating_physics());
}
void Skeleton3D::physical_bones_start_simulation_on(const TypedArray<StringName> &p_bones) {
diff --git a/scene/3d/skeleton_3d.h b/scene/3d/skeleton_3d.h
index b8e38242b9..2d70aafcad 100644
--- a/scene/3d/skeleton_3d.h
+++ b/scene/3d/skeleton_3d.h
@@ -67,6 +67,7 @@ class Skeleton3D : public Node3D {
GDCLASS(Skeleton3D, Node3D);
#ifndef DISABLE_DEPRECATED
+ bool animate_physical_bones = false;
Node *simulator = nullptr;
void setup_simulator();
#endif // _DISABLE_DEPRECATED
@@ -80,8 +81,14 @@ public:
private:
friend class SkinReference;
- void _update_deferred();
- bool is_update_needed = false; // Is updating reserved?
+ enum UpdateFlag {
+ UPDATE_FLAG_NONE = 1,
+ UPDATE_FLAG_MODIFIER = 2,
+ UPDATE_FLAG_POSE = 4,
+ };
+
+ void _update_deferred(UpdateFlag p_update_flag = UPDATE_FLAG_POSE);
+ uint8_t update_flags = UPDATE_FLAG_NONE;
bool updating = false; // Is updating now?
struct Bone {
diff --git a/scene/3d/sprite_3d.cpp b/scene/3d/sprite_3d.cpp
index ba3b32a031..d08aeb1de2 100644
--- a/scene/3d/sprite_3d.cpp
+++ b/scene/3d/sprite_3d.cpp
@@ -1334,9 +1334,10 @@ void AnimatedSprite3D::play(const StringName &p_name, float p_custom_scale, bool
playing = true;
custom_speed_scale = p_custom_scale;
- int end_frame = MAX(0, frames->get_frame_count(animation) - 1);
if (name != animation) {
animation = name;
+ int end_frame = MAX(0, frames->get_frame_count(animation) - 1);
+
if (p_from_end) {
set_frame_and_progress(end_frame, 1.0);
} else {
@@ -1344,7 +1345,9 @@ void AnimatedSprite3D::play(const StringName &p_name, float p_custom_scale, bool
}
emit_signal(SceneStringName(animation_changed));
} else {
+ int end_frame = MAX(0, frames->get_frame_count(animation) - 1);
bool is_backward = signbit(speed_scale * custom_speed_scale);
+
if (p_from_end && is_backward && frame == 0 && frame_progress <= 0.0) {
set_frame_and_progress(end_frame, 1.0);
} else if (!p_from_end && !is_backward && frame == end_frame && frame_progress >= 1.0) {
diff --git a/scene/animation/animation_mixer.cpp b/scene/animation/animation_mixer.cpp
index 1c4a5ff20e..c74348c2e7 100644
--- a/scene/animation/animation_mixer.cpp
+++ b/scene/animation/animation_mixer.cpp
@@ -150,6 +150,7 @@ void AnimationMixer::_animation_set_cache_update() {
ad.name = key;
ad.last_update = animation_set_update_pass;
animation_set.insert(ad.name, ad);
+ cache_valid = false; // No need to delete the cache, but it must be updated to add track caches.
} else {
AnimationData &ad = animation_set[key];
if (ad.last_update != animation_set_update_pass) {
diff --git a/scene/gui/text_edit.cpp b/scene/gui/text_edit.cpp
index 9b7c6249e6..233c7200ff 100644
--- a/scene/gui/text_edit.cpp
+++ b/scene/gui/text_edit.cpp
@@ -207,6 +207,8 @@ void TextEdit::Text::invalidate_cache(int p_line, int p_column, bool p_text_chan
text.write[p_line].data_buf->set_direction((TextServer::Direction)direction);
text.write[p_line].data_buf->set_break_flags(flags);
text.write[p_line].data_buf->set_preserve_control(draw_control_chars);
+ text.write[p_line].data_buf->set_custom_punctuation(get_enabled_word_separators());
+
if (p_ime_text.length() > 0) {
if (p_text_changed) {
text.write[p_line].data_buf->add_string(p_ime_text, font, font_size, language);
@@ -275,6 +277,8 @@ void TextEdit::Text::invalidate_all_lines() {
}
text.write[i].data_buf->set_width(width);
text.write[i].data_buf->set_break_flags(flags);
+ text.write[i].data_buf->set_custom_punctuation(get_enabled_word_separators());
+
if (tab_size_dirty) {
if (tab_size > 0) {
Vector<float> tabs;
@@ -439,6 +443,57 @@ void TextEdit::Text::move_gutters(int p_from_line, int p_to_line) {
text.write[p_from_line].gutters.resize(gutter_count);
}
+void TextEdit::Text::set_use_default_word_separators(bool p_enabled) {
+ if (use_default_word_separators == p_enabled) {
+ return;
+ }
+ use_default_word_separators = p_enabled;
+ invalidate_all_lines();
+}
+
+void TextEdit::Text::set_use_custom_word_separators(bool p_enabled) {
+ if (use_custom_word_separators == p_enabled) {
+ return;
+ }
+ use_custom_word_separators = p_enabled;
+ invalidate_all_lines();
+}
+
+bool TextEdit::Text::is_default_word_separators_enabled() const {
+ return use_default_word_separators;
+}
+
+bool TextEdit::Text::is_custom_word_separators_enabled() const {
+ return use_custom_word_separators;
+}
+
+String TextEdit::Text::get_custom_word_separators() const {
+ return custom_word_separators;
+}
+
+String TextEdit::Text::get_default_word_separators() const {
+ String concat_separators = "´`~$^=+|<>";
+ for (char32_t ch = 0x2000; ch <= 0x206F; ++ch) { // General punctuation block.
+ concat_separators += ch;
+ }
+ for (char32_t ch = 0x3000; ch <= 0x303F; ++ch) { // CJK punctuation block.
+ concat_separators += ch;
+ }
+ return concat_separators;
+}
+
+// Get default and/or custom word separators depending on the option enabled.
+String TextEdit::Text::get_enabled_word_separators() const {
+ String all_separators;
+ if (use_default_word_separators) {
+ all_separators += get_default_word_separators();
+ }
+ if (use_custom_word_separators) {
+ all_separators += get_custom_word_separators();
+ }
+ return all_separators;
+}
+
///////////////////////////////////////////////////////////////////////////////
/// TEXT EDIT ///
///////////////////////////////////////////////////////////////////////////////
@@ -6277,6 +6332,44 @@ bool TextEdit::is_highlight_all_occurrences_enabled() const {
return highlight_all_occurrences;
}
+void TextEdit::set_use_default_word_separators(bool p_enabled) {
+ text.set_use_default_word_separators(p_enabled);
+}
+
+bool TextEdit::is_default_word_separators_enabled() const {
+ return text.is_default_word_separators_enabled();
+}
+
+// Set word separators. Combine default separators with custom separators if those options are enabled.
+void TextEdit::set_custom_word_separators(const String &p_separators) {
+ text.set_custom_word_separators(p_separators);
+}
+
+void TextEdit::Text::set_custom_word_separators(const String &p_separators) {
+ if (custom_word_separators == p_separators) {
+ return;
+ }
+ custom_word_separators = p_separators;
+ invalidate_all_lines();
+}
+
+bool TextEdit::is_custom_word_separators_enabled() const {
+ return text.is_custom_word_separators_enabled();
+}
+
+String TextEdit::get_custom_word_separators() const {
+ return text.get_custom_word_separators();
+}
+
+// Enable or disable custom word separators.
+void TextEdit::set_use_custom_word_separators(bool p_enabled) {
+ text.set_use_custom_word_separators(p_enabled);
+}
+
+String TextEdit::get_default_word_separators() const {
+ return text.get_default_word_separators();
+}
+
void TextEdit::set_draw_control_chars(bool p_enabled) {
if (draw_control_chars != p_enabled) {
draw_control_chars = p_enabled;
@@ -6551,6 +6644,14 @@ void TextEdit::_bind_methods() {
ClassDB::bind_method(D_METHOD("get_word_under_caret", "caret_index"), &TextEdit::get_word_under_caret, DEFVAL(-1));
+ ClassDB::bind_method(D_METHOD("set_use_default_word_separators", "enabled"), &TextEdit::set_use_default_word_separators);
+ ClassDB::bind_method(D_METHOD("is_default_word_separators_enabled"), &TextEdit::is_default_word_separators_enabled);
+
+ ClassDB::bind_method(D_METHOD("set_use_custom_word_separators", "enabled"), &TextEdit::set_use_custom_word_separators);
+ ClassDB::bind_method(D_METHOD("is_custom_word_separators_enabled"), &TextEdit::is_custom_word_separators_enabled);
+ ClassDB::bind_method(D_METHOD("set_custom_word_separators", "custom_word_separators"), &TextEdit::set_custom_word_separators);
+ ClassDB::bind_method(D_METHOD("get_custom_word_separators"), &TextEdit::get_custom_word_separators);
+
/* Selection. */
BIND_ENUM_CONSTANT(SELECTION_MODE_NONE);
BIND_ENUM_CONSTANT(SELECTION_MODE_SHIFT);
@@ -6774,6 +6875,10 @@ void TextEdit::_bind_methods() {
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "caret_mid_grapheme"), "set_caret_mid_grapheme_enabled", "is_caret_mid_grapheme_enabled");
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "caret_multiple"), "set_multiple_carets_enabled", "is_multiple_carets_enabled");
+ ADD_PROPERTY(PropertyInfo(Variant::BOOL, "use_default_word_separators"), "set_use_default_word_separators", "is_default_word_separators_enabled");
+ ADD_PROPERTY(PropertyInfo(Variant::BOOL, "use_custom_word_separators"), "set_use_custom_word_separators", "is_custom_word_separators_enabled");
+ ADD_PROPERTY(PropertyInfo(Variant::STRING, "custom_word_separators"), "set_custom_word_separators", "get_custom_word_separators");
+
ADD_GROUP("Highlighting", "");
ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "syntax_highlighter", PROPERTY_HINT_RESOURCE_TYPE, "SyntaxHighlighter", PROPERTY_USAGE_DEFAULT | PROPERTY_USAGE_ALWAYS_DUPLICATE), "set_syntax_highlighter", "get_syntax_highlighter");
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "highlight_all_occurrences"), "set_highlight_all_occurrences", "is_highlight_all_occurrences_enabled");
diff --git a/scene/gui/text_edit.h b/scene/gui/text_edit.h
index efade39876..6ed5cf4bdc 100644
--- a/scene/gui/text_edit.h
+++ b/scene/gui/text_edit.h
@@ -174,6 +174,9 @@ private:
TextServer::Direction direction = TextServer::DIRECTION_AUTO;
BitField<TextServer::LineBreakFlag> brk_flags = TextServer::BREAK_MANDATORY;
bool draw_control_chars = false;
+ String custom_word_separators;
+ bool use_default_word_separators = true;
+ bool use_custom_word_separators = false;
int line_height = -1;
int max_width = -1;
@@ -201,6 +204,18 @@ private:
int get_line_width(int p_line, int p_wrap_index = -1) const;
int get_max_width() const;
+ void set_use_default_word_separators(bool p_enabled);
+ bool is_default_word_separators_enabled() const;
+
+ void set_use_custom_word_separators(bool p_enabled);
+ bool is_custom_word_separators_enabled() const;
+
+ void set_word_separators(const String &p_separators);
+ void set_custom_word_separators(const String &p_separators);
+ String get_enabled_word_separators() const;
+ String get_custom_word_separators() const;
+ String get_default_word_separators() const;
+
void set_width(float p_width);
float get_width() const;
void set_brk_flags(BitField<TextServer::LineBreakFlag> p_flags);
@@ -1068,6 +1083,19 @@ public:
Color get_font_color() const;
+ /* Behavior */
+
+ String get_default_word_separators() const;
+
+ void set_use_default_word_separators(bool p_enabled);
+ bool is_default_word_separators_enabled() const;
+
+ void set_custom_word_separators(const String &p_separators);
+ void set_use_custom_word_separators(bool p_enabled);
+ bool is_custom_word_separators_enabled() const;
+
+ String get_custom_word_separators() const;
+
/* Deprecated. */
#ifndef DISABLE_DEPRECATED
Vector<int> get_caret_index_edit_order();
diff --git a/scene/main/viewport.cpp b/scene/main/viewport.cpp
index 4d75e06ff9..1302e3c53e 100644
--- a/scene/main/viewport.cpp
+++ b/scene/main/viewport.cpp
@@ -309,7 +309,8 @@ void Viewport::_sub_window_update(Window *p_window) {
int index = _sub_window_find(p_window);
ERR_FAIL_COND(index == -1);
- const SubWindow &sw = gui.sub_windows[index];
+ SubWindow &sw = gui.sub_windows.write[index];
+ sw.pending_window_update = false;
Transform2D pos;
pos.set_origin(p_window->get_position());
@@ -972,6 +973,14 @@ void Viewport::update_canvas_items() {
return;
}
+ if (is_embedding_subwindows()) {
+ for (Viewport::SubWindow w : gui.sub_windows) {
+ if (w.window && !w.pending_window_update) {
+ w.pending_window_update = true;
+ callable_mp(this, &Viewport::_sub_window_update).call_deferred(w.window);
+ }
+ }
+ }
_update_canvas_items(this);
}
diff --git a/scene/main/viewport.h b/scene/main/viewport.h
index df09755d4f..0d31c07e57 100644
--- a/scene/main/viewport.h
+++ b/scene/main/viewport.h
@@ -340,6 +340,7 @@ private:
Window *window = nullptr;
RID canvas_item;
Rect2i parent_safe_rect;
+ bool pending_window_update = false;
};
// VRS
diff --git a/servers/rendering/renderer_rd/forward_clustered/render_forward_clustered.cpp b/servers/rendering/renderer_rd/forward_clustered/render_forward_clustered.cpp
index aae32f0b3e..536fc7a04a 100644
--- a/servers/rendering/renderer_rd/forward_clustered/render_forward_clustered.cpp
+++ b/servers/rendering/renderer_rd/forward_clustered/render_forward_clustered.cpp
@@ -3585,22 +3585,31 @@ RID RenderForwardClustered::_render_buffers_get_velocity_texture(Ref<RenderScene
}
void RenderForwardClustered::environment_set_ssao_quality(RS::EnvironmentSSAOQuality p_quality, bool p_half_size, float p_adaptive_target, int p_blur_passes, float p_fadeout_from, float p_fadeout_to) {
+ ERR_FAIL_NULL(ss_effects);
+ ERR_FAIL_COND(p_quality < RS::EnvironmentSSAOQuality::ENV_SSAO_QUALITY_VERY_LOW || p_quality > RS::EnvironmentSSAOQuality::ENV_SSAO_QUALITY_ULTRA);
ss_effects->ssao_set_quality(p_quality, p_half_size, p_adaptive_target, p_blur_passes, p_fadeout_from, p_fadeout_to);
}
void RenderForwardClustered::environment_set_ssil_quality(RS::EnvironmentSSILQuality p_quality, bool p_half_size, float p_adaptive_target, int p_blur_passes, float p_fadeout_from, float p_fadeout_to) {
+ ERR_FAIL_NULL(ss_effects);
+ ERR_FAIL_COND(p_quality < RS::EnvironmentSSILQuality::ENV_SSIL_QUALITY_VERY_LOW || p_quality > RS::EnvironmentSSILQuality::ENV_SSIL_QUALITY_ULTRA);
ss_effects->ssil_set_quality(p_quality, p_half_size, p_adaptive_target, p_blur_passes, p_fadeout_from, p_fadeout_to);
}
void RenderForwardClustered::environment_set_ssr_roughness_quality(RS::EnvironmentSSRRoughnessQuality p_quality) {
+ ERR_FAIL_NULL(ss_effects);
+ ERR_FAIL_COND(p_quality < RS::EnvironmentSSRRoughnessQuality::ENV_SSR_ROUGHNESS_QUALITY_DISABLED || p_quality > RS::EnvironmentSSRRoughnessQuality::ENV_SSR_ROUGHNESS_QUALITY_HIGH);
ss_effects->ssr_set_roughness_quality(p_quality);
}
void RenderForwardClustered::sub_surface_scattering_set_quality(RS::SubSurfaceScatteringQuality p_quality) {
+ ERR_FAIL_NULL(ss_effects);
+ ERR_FAIL_COND(p_quality < RS::SubSurfaceScatteringQuality::SUB_SURFACE_SCATTERING_QUALITY_DISABLED || p_quality > RS::SubSurfaceScatteringQuality::SUB_SURFACE_SCATTERING_QUALITY_HIGH);
ss_effects->sss_set_quality(p_quality);
}
void RenderForwardClustered::sub_surface_scattering_set_scale(float p_scale, float p_depth_scale) {
+ ERR_FAIL_NULL(ss_effects);
ss_effects->sss_set_scale(p_scale, p_depth_scale);
}
diff --git a/servers/rendering/renderer_rd/shaders/particles.glsl b/servers/rendering/renderer_rd/shaders/particles.glsl
index 60c49bacae..7c5291038f 100644
--- a/servers/rendering/renderer_rd/shaders/particles.glsl
+++ b/servers/rendering/renderer_rd/shaders/particles.glsl
@@ -292,6 +292,24 @@ void main() {
PARTICLE.velocity = particles.data[src_idx].velocity;
PARTICLE.flags = PARTICLE_FLAG_TRAILED | ((frame_history.data[0].frame & PARTICLE_FRAME_MASK) << PARTICLE_FRAME_SHIFT); //mark it as trailed, save in which frame it will start
PARTICLE.xform = particles.data[src_idx].xform;
+#ifdef USERDATA1_USED
+ PARTICLE.userdata1 = particles.data[src_idx].userdata1;
+#endif
+#ifdef USERDATA2_USED
+ PARTICLE.userdata2 = particles.data[src_idx].userdata2;
+#endif
+#ifdef USERDATA3_USED
+ PARTICLE.userdata3 = particles.data[src_idx].userdata3;
+#endif
+#ifdef USERDATA4_USED
+ PARTICLE.userdata4 = particles.data[src_idx].userdata4;
+#endif
+#ifdef USERDATA5_USED
+ PARTICLE.userdata5 = particles.data[src_idx].userdata5;
+#endif
+#ifdef USERDATA6_USED
+ PARTICLE.userdata6 = particles.data[src_idx].userdata6;
+#endif
}
if (!bool(particles.data[src_idx].flags & PARTICLE_FLAG_ACTIVE)) {
// Disable the entire trail if the parent is no longer active.
diff --git a/servers/rendering/shader_language.cpp b/servers/rendering/shader_language.cpp
index 9aa54d0bb7..1e9690a8ae 100644
--- a/servers/rendering/shader_language.cpp
+++ b/servers/rendering/shader_language.cpp
@@ -1238,6 +1238,7 @@ void ShaderLanguage::clear() {
include_positions.push_back(FilePosition());
include_markers_handled.clear();
+ calls_info.clear();
#ifdef DEBUG_ENABLED
keyword_completion_context = CF_UNSPECIFIED;
@@ -1445,8 +1446,12 @@ bool ShaderLanguage::_find_identifier(const BlockNode *p_block, bool p_allow_rea
*r_struct_name = shader->constants[p_identifier].struct_name;
}
if (r_constant_value) {
- if (shader->constants[p_identifier].initializer && shader->constants[p_identifier].initializer->values.size() == 1) {
- *r_constant_value = shader->constants[p_identifier].initializer->values[0];
+ if (shader->constants[p_identifier].initializer && shader->constants[p_identifier].initializer->type == Node::NODE_TYPE_CONSTANT) {
+ ConstantNode *cnode = static_cast<ConstantNode *>(shader->constants[p_identifier].initializer);
+
+ if (cnode->values.size() == 1) {
+ *r_constant_value = cnode->values[0];
+ }
}
}
if (r_type) {
@@ -3085,6 +3090,19 @@ const ShaderLanguage::BuiltinFuncConstArgs ShaderLanguage::builtin_func_const_ar
{ nullptr, 0, 0, 0 }
};
+const ShaderLanguage::BuiltinEntry ShaderLanguage::frag_only_func_defs[] = {
+ { "dFdx" },
+ { "dFdxCoarse" },
+ { "dFdxFine" },
+ { "dFdy" },
+ { "dFdyCoarse" },
+ { "dFdyFine" },
+ { "fwidth" },
+ { "fwidthCoarse" },
+ { "fwidthFine" },
+ { nullptr }
+};
+
bool ShaderLanguage::is_const_suffix_lut_initialized = false;
bool ShaderLanguage::_validate_function_call(BlockNode *p_block, const FunctionInfo &p_function_info, OperatorNode *p_func, DataType *r_ret_type, StringName *r_ret_type_str, bool *r_is_custom_function) {
@@ -4610,6 +4628,58 @@ bool ShaderLanguage::_check_node_constness(const Node *p_node) const {
return true;
}
+bool ShaderLanguage::_check_restricted_func(const StringName &p_name, const StringName &p_current_function) const {
+ int idx = 0;
+
+ while (frag_only_func_defs[idx].name) {
+ if (StringName(frag_only_func_defs[idx].name) == p_name) {
+ if (is_supported_frag_only_funcs) {
+ if (p_current_function == "vertex" && stages->has(p_current_function)) {
+ return true;
+ }
+ } else {
+ return true;
+ }
+ break;
+ }
+ idx++;
+ }
+
+ return false;
+}
+
+bool ShaderLanguage::_validate_restricted_func(const StringName &p_name, const CallInfo *p_func_info, bool p_is_builtin_hint) {
+ const bool is_in_restricted_function = p_func_info->name == "vertex";
+
+ // No need to check up the hierarchy if it's a built-in.
+ if (!p_is_builtin_hint) {
+ for (const CallInfo *func_info : p_func_info->calls) {
+ if (is_in_restricted_function && func_info->name != p_name) {
+ // Skips check for non-called method.
+ continue;
+ }
+
+ if (!_validate_restricted_func(p_name, func_info)) {
+ return false;
+ }
+ }
+ }
+
+ if (!p_func_info->uses_restricted_functions.is_empty()) {
+ const Pair<StringName, TkPos> &first_element = p_func_info->uses_restricted_functions.get(0);
+ _set_tkpos(first_element.second);
+
+ if (is_in_restricted_function) {
+ _set_error(vformat(RTR("'%s' cannot be used within the '%s' processor function."), first_element.first, "vertex"));
+ } else {
+ _set_error(vformat(RTR("'%s' cannot be used here, because '%s' is called by the '%s' processor function (which is not allowed)."), first_element.first, p_func_info->name, "vertex"));
+ }
+ return false;
+ }
+
+ return true;
+}
+
bool ShaderLanguage::_validate_assign(Node *p_node, const FunctionInfo &p_function_info, String *r_message) {
if (p_node->type == Node::NODE_TYPE_OPERATOR) {
OperatorNode *op = static_cast<OperatorNode *>(p_node);
@@ -5266,6 +5336,36 @@ ShaderLanguage::Node *ShaderLanguage::_parse_expression(BlockNode *p_block, cons
const StringName &name = identifier;
+ if (name != current_function) { // Recursion is not allowed.
+ // Register call.
+ if (calls_info.has(name)) {
+ calls_info[current_function].calls.push_back(&calls_info[name]);
+ }
+
+ int idx = 0;
+ bool is_builtin = false;
+
+ while (frag_only_func_defs[idx].name) {
+ if (frag_only_func_defs[idx].name == name) {
+ // If a built-in function not found for the current shader type, then it shouldn't be parsed further.
+ if (!is_supported_frag_only_funcs) {
+ _set_error(vformat(RTR("Built-in function '%s' is not supported for the '%s' shader type."), name, shader_type_identifier));
+ return nullptr;
+ }
+ // Register usage of the restricted function.
+ calls_info[current_function].uses_restricted_functions.push_back(Pair<StringName, TkPos>(name, _get_tkpos()));
+ is_builtin = true;
+ break;
+ }
+ idx++;
+ }
+
+ // Recursively checks for the restricted function call.
+ if (is_supported_frag_only_funcs && current_function == "vertex" && stages->has(current_function) && !_validate_restricted_func(name, &calls_info[current_function], is_builtin)) {
+ return nullptr;
+ }
+ }
+
OperatorNode *func = alloc_node<OperatorNode>();
func->op = OP_CALL;
VariableNode *funcname = alloc_node<VariableNode>();
@@ -8099,6 +8199,8 @@ Error ShaderLanguage::_parse_shader(const HashMap<StringName, FunctionInfo> &p_f
ShaderNode::Uniform::Scope uniform_scope = ShaderNode::Uniform::SCOPE_LOCAL;
stages = &p_functions;
+ is_supported_frag_only_funcs = shader_type_identifier == "canvas_item" || shader_type_identifier == "spatial" || shader_type_identifier == "sky";
+
const FunctionInfo &constants = p_functions.has("constants") ? p_functions["constants"] : FunctionInfo();
HashMap<String, String> defined_modes;
@@ -9443,7 +9545,7 @@ Error ShaderLanguage::_parse_shader(const HashMap<StringName, FunctionInfo> &p_f
}
}
- constant.initializer = static_cast<ConstantNode *>(expr);
+ constant.initializer = expr;
if (!_compare_datatypes(type, struct_name, 0, expr->get_datatype(), expr->get_datatype_name(), expr->get_array_size())) {
return ERR_PARSE_ERROR;
@@ -9541,6 +9643,11 @@ Error ShaderLanguage::_parse_shader(const HashMap<StringName, FunctionInfo> &p_f
shader->functions.insert(name, function);
shader->vfunctions.push_back(function);
+ CallInfo call_info;
+ call_info.name = name;
+
+ calls_info.insert(name, call_info);
+
func_node->name = name;
func_node->return_type = type;
func_node->return_struct_name = struct_name;
@@ -10325,10 +10432,11 @@ Error ShaderLanguage::complete(const String &p_code, const ShaderCompileInfo &p_
}
while (builtin_func_defs[idx].name) {
- if (low_end && builtin_func_defs[idx].high_end) {
+ if ((low_end && builtin_func_defs[idx].high_end) || _check_restricted_func(builtin_func_defs[idx].name, skip_function)) {
idx++;
continue;
}
+
matches.insert(String(builtin_func_defs[idx].name), ScriptLanguage::CODE_COMPLETION_KIND_FUNCTION);
idx++;
}
@@ -10490,7 +10598,7 @@ Error ShaderLanguage::complete(const String &p_code, const ShaderCompileInfo &p_
}
while (builtin_func_defs[idx].name) {
- if (low_end && builtin_func_defs[idx].high_end) {
+ if ((low_end && builtin_func_defs[idx].high_end) || _check_restricted_func(builtin_func_defs[idx].name, block_function)) {
idx++;
continue;
}
diff --git a/servers/rendering/shader_language.h b/servers/rendering/shader_language.h
index 5615d7f457..edac819a1e 100644
--- a/servers/rendering/shader_language.h
+++ b/servers/rendering/shader_language.h
@@ -620,7 +620,7 @@ public:
DataType type;
StringName struct_name;
DataPrecision precision;
- ConstantNode *initializer = nullptr;
+ Node *initializer = nullptr;
int array_size;
};
@@ -913,6 +913,15 @@ private:
Vector<FilePosition> include_positions;
HashSet<String> include_markers_handled;
+ // Additional function information (eg. call hierarchy). No need to expose it to compiler.
+ struct CallInfo {
+ StringName name;
+ List<Pair<StringName, TkPos>> uses_restricted_functions;
+ List<CallInfo *> calls;
+ };
+
+ RBMap<StringName, CallInfo> calls_info;
+
#ifdef DEBUG_ENABLED
struct Usage {
int decl_line;
@@ -1036,6 +1045,10 @@ private:
bool _validate_assign(Node *p_node, const FunctionInfo &p_function_info, String *r_message = nullptr);
bool _validate_operator(OperatorNode *p_op, DataType *r_ret_type = nullptr, int *r_ret_size = nullptr);
+ struct BuiltinEntry {
+ const char *name;
+ };
+
struct BuiltinFuncDef {
enum { MAX_ARGS = 5 };
const char *name;
@@ -1078,11 +1091,13 @@ private:
#endif // DEBUG_ENABLED
const HashMap<StringName, FunctionInfo> *stages = nullptr;
+ bool is_supported_frag_only_funcs = false;
bool _get_completable_identifier(BlockNode *p_block, CompletionType p_type, StringName &identifier);
static const BuiltinFuncDef builtin_func_defs[];
static const BuiltinFuncOutArgs builtin_func_out_args[];
static const BuiltinFuncConstArgs builtin_func_const_args[];
+ static const BuiltinEntry frag_only_func_defs[];
static bool is_const_suffix_lut_initialized;
@@ -1097,6 +1112,9 @@ private:
bool _validate_varying_assign(ShaderNode::Varying &p_varying, String *r_message);
bool _check_node_constness(const Node *p_node) const;
+ bool _check_restricted_func(const StringName &p_name, const StringName &p_current_function) const;
+ bool _validate_restricted_func(const StringName &p_call_name, const CallInfo *p_func_info, bool p_is_builtin_hint = false);
+
Node *_parse_expression(BlockNode *p_block, const FunctionInfo &p_function_info);
Error _parse_array_size(BlockNode *p_block, const FunctionInfo &p_function_info, bool p_forbid_unknown_size, Node **r_size_expression, int *r_array_size, bool *r_unknown_size);
Node *_parse_array_constructor(BlockNode *p_block, const FunctionInfo &p_function_info);