summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--core/io/resource_format_binary.cpp1
-rw-r--r--doc/classes/Object.xml4
-rw-r--r--doc/classes/ProjectSettings.xml2
-rw-r--r--doc/classes/RenderingDevice.xml9
-rw-r--r--editor/editor_properties.cpp3
-rw-r--r--editor/filesystem_dock.cpp2
-rw-r--r--editor/plugins/node_3d_editor_plugin.cpp9
-rw-r--r--editor/plugins/texture_region_editor_plugin.cpp70
-rw-r--r--editor/plugins/texture_region_editor_plugin.h11
-rw-r--r--modules/gdscript/editor/gdscript_highlighter.cpp41
-rw-r--r--modules/gdscript/editor/gdscript_highlighter.h5
-rw-r--r--modules/gdscript/gdscript_analyzer.cpp4
-rw-r--r--modules/gdscript/gdscript_warning.cpp2
-rw-r--r--modules/gdscript/gdscript_warning.h2
-rw-r--r--modules/gdscript/tests/scripts/analyzer/errors/cast_int_to_array.gd3
-rw-r--r--modules/gdscript/tests/scripts/analyzer/errors/cast_int_to_array.out2
-rw-r--r--modules/gdscript/tests/scripts/analyzer/errors/cast_int_to_object.gd3
-rw-r--r--modules/gdscript/tests/scripts/analyzer/errors/cast_int_to_object.out2
-rw-r--r--modules/gdscript/tests/scripts/analyzer/errors/cast_object_to_int.gd3
-rw-r--r--modules/gdscript/tests/scripts/analyzer/errors/cast_object_to_int.out2
-rw-r--r--modules/gdscript/tests/scripts/analyzer/warnings/unsafe_cast.gd24
-rw-r--r--modules/gdscript/tests/scripts/analyzer/warnings/unsafe_cast.out33
-rw-r--r--modules/gdscript/tests/scripts/runtime/errors/cast_freed_object.gd4
-rw-r--r--modules/gdscript/tests/scripts/runtime/errors/cast_freed_object.out6
-rw-r--r--modules/gdscript/tests/scripts/runtime/errors/cast_int_to_array.gd4
-rw-r--r--modules/gdscript/tests/scripts/runtime/errors/cast_int_to_array.out6
-rw-r--r--modules/gdscript/tests/scripts/runtime/errors/cast_int_to_object.gd4
-rw-r--r--modules/gdscript/tests/scripts/runtime/errors/cast_int_to_object.out6
-rw-r--r--modules/gdscript/tests/scripts/runtime/errors/cast_object_to_int.gd4
-rw-r--r--modules/gdscript/tests/scripts/runtime/errors/cast_object_to_int.out6
-rw-r--r--modules/gdscript/tests/scripts/runtime/features/type_casting.gd24
-rw-r--r--modules/gdscript/tests/scripts/runtime/features/type_casting.out10
-rw-r--r--scene/gui/tab_container.cpp27
-rw-r--r--scene/gui/tab_container.h1
-rw-r--r--scene/resources/resource_format_text.cpp1
-rw-r--r--servers/rendering/rendering_device.cpp1
36 files changed, 276 insertions, 65 deletions
diff --git a/core/io/resource_format_binary.cpp b/core/io/resource_format_binary.cpp
index 17cffb878e..5c8f4746b3 100644
--- a/core/io/resource_format_binary.cpp
+++ b/core/io/resource_format_binary.cpp
@@ -2016,6 +2016,7 @@ void ResourceFormatSaverBinaryInstance::_find_resources(const Variant &p_variant
case Variant::ARRAY: {
Array varray = p_variant;
+ _find_resources(varray.get_typed_script());
int len = varray.size();
for (int i = 0; i < len; i++) {
const Variant &v = varray.get(i);
diff --git a/doc/classes/Object.xml b/doc/classes/Object.xml
index b69326b6e0..c508591093 100644
--- a/doc/classes/Object.xml
+++ b/doc/classes/Object.xml
@@ -556,7 +556,7 @@
While all options have the same outcome ([code]button[/code]'s [signal BaseButton.button_down] signal will be connected to [code]_on_button_down[/code]), [b]option 3[/b] offers the best validation: it will print a compile-time error if either the [code]button_down[/code] [Signal] or the [code]_on_button_down[/code] [Callable] are not defined. On the other hand, [b]option 2[/b] only relies on string names and will only be able to validate either names at runtime: it will print a runtime error if [code]"button_down"[/code] doesn't correspond to a signal, or if [code]"_on_button_down"[/code] is not a registered method in the object [code]self[/code]. The main reason for using options 1, 2, or 4 would be if you actually need to use strings (e.g. to connect signals programmatically based on strings read from a configuration file). Otherwise, option 3 is the recommended (and fastest) method.
[b]Binding and passing parameters:[/b]
The syntax to bind parameters is through [method Callable.bind], which returns a copy of the [Callable] with its parameters bound.
- When calling [method emit_signal], the signal parameters can be also passed. The examples below show the relationship between these signal parameters and bound parameters.
+ When calling [method emit_signal] or [method Signal.emit], the signal parameters can be also passed. The examples below show the relationship between these signal parameters and bound parameters.
[codeblocks]
[gdscript]
func _ready():
@@ -566,7 +566,7 @@
player.hit.connect(_on_player_hit.bind("sword", 100))
# Parameters added when emitting the signal are passed first.
- player.emit_signal("hit", "Dark lord", 5)
+ player.hit.emit("Dark lord", 5)
# We pass two arguments when emitting (`hit_by`, `level`),
# and bind two more arguments when connecting (`weapon_type`, `damage`).
diff --git a/doc/classes/ProjectSettings.xml b/doc/classes/ProjectSettings.xml
index 3f4a9575be..a5aeee5bc4 100644
--- a/doc/classes/ProjectSettings.xml
+++ b/doc/classes/ProjectSettings.xml
@@ -574,7 +574,7 @@
When set to [code]warn[/code] or [code]error[/code], produces a warning or an error respectively when using an expression whose type may not be compatible with the function parameter expected.
</member>
<member name="debug/gdscript/warnings/unsafe_cast" type="int" setter="" getter="" default="0">
- When set to [code]warn[/code] or [code]error[/code], produces a warning or an error respectively when performing an unsafe cast.
+ When set to [code]warn[/code] or [code]error[/code], produces a warning or an error respectively when a [Variant] value is cast to a non-Variant.
</member>
<member name="debug/gdscript/warnings/unsafe_method_access" type="int" setter="" getter="" default="0">
When set to [code]warn[/code] or [code]error[/code], produces a warning or an error respectively when calling a method whose presence is not guaranteed at compile-time in the class.
diff --git a/doc/classes/RenderingDevice.xml b/doc/classes/RenderingDevice.xml
index 36abde36b4..3c1061dee9 100644
--- a/doc/classes/RenderingDevice.xml
+++ b/doc/classes/RenderingDevice.xml
@@ -138,6 +138,15 @@
Submits the compute list for processing on the GPU. This is the compute equivalent to [method draw_list_draw].
</description>
</method>
+ <method name="compute_list_dispatch_indirect">
+ <return type="void" />
+ <param index="0" name="compute_list" type="int" />
+ <param index="1" name="buffer" type="RID" />
+ <param index="2" name="offset" type="int" />
+ <description>
+ Submits the compute list for processing on the GPU with the given group counts stored in the [param buffer] at [param offset]. Buffer must have been created with [constant STORAGE_BUFFER_USAGE_DISPATCH_INDIRECT] flag.
+ </description>
+ </method>
<method name="compute_list_end">
<return type="void" />
<description>
diff --git a/editor/editor_properties.cpp b/editor/editor_properties.cpp
index 4858fcf78f..ea364d8a0d 100644
--- a/editor/editor_properties.cpp
+++ b/editor/editor_properties.cpp
@@ -2755,7 +2755,8 @@ void EditorPropertyNodePath::_node_assign() {
Variant val = get_edited_property_value();
Node *n = nullptr;
if (val.get_type() == Variant::Type::NODE_PATH) {
- n = get_base_node()->get_node_or_null(val);
+ Node *base_node = get_base_node();
+ n = base_node == nullptr ? nullptr : base_node->get_node_or_null(val);
} else {
n = Object::cast_to<Node>(val);
}
diff --git a/editor/filesystem_dock.cpp b/editor/filesystem_dock.cpp
index 233f20a8b3..2087c8cee6 100644
--- a/editor/filesystem_dock.cpp
+++ b/editor/filesystem_dock.cpp
@@ -2250,6 +2250,8 @@ void FileSystemDock::_file_option(int p_option, const Vector<String> &p_selected
terminal_emulator_args.push_back("--working-directory");
} else if (chosen_terminal_emulator.ends_with("urxvt")) {
terminal_emulator_args.push_back("-cd");
+ } else if (chosen_terminal_emulator.ends_with("xfce4-terminal")) {
+ terminal_emulator_args.push_back("--working-directory");
}
}
#endif
diff --git a/editor/plugins/node_3d_editor_plugin.cpp b/editor/plugins/node_3d_editor_plugin.cpp
index d09e4fd3db..88250ea3c3 100644
--- a/editor/plugins/node_3d_editor_plugin.cpp
+++ b/editor/plugins/node_3d_editor_plugin.cpp
@@ -6639,8 +6639,13 @@ void fragment() {
for (int j = 0; j < 4; j++) {
Transform3D t = Transform3D();
- t = t.scaled(axis * distances[j + 1]);
- t = t.translated(axis * distances[j]);
+ if (distances[j] > 0.0) {
+ t = t.scaled(axis * distances[j + 1]);
+ t = t.translated(axis * distances[j]);
+ } else {
+ t = t.scaled(axis * distances[j]);
+ t = t.translated(axis * distances[j + 1]);
+ }
RenderingServer::get_singleton()->multimesh_instance_set_transform(origin_multimesh, i * 4 + j, t);
RenderingServer::get_singleton()->multimesh_instance_set_color(origin_multimesh, i * 4 + j, origin_color);
}
diff --git a/editor/plugins/texture_region_editor_plugin.cpp b/editor/plugins/texture_region_editor_plugin.cpp
index 6c7a3f776e..9498e980ec 100644
--- a/editor/plugins/texture_region_editor_plugin.cpp
+++ b/editor/plugins/texture_region_editor_plugin.cpp
@@ -37,13 +37,16 @@
#include "editor/editor_string_names.h"
#include "editor/editor_undo_redo_manager.h"
#include "editor/themes/editor_scale.h"
-#include "scene/gui/check_box.h"
+#include "scene/2d/sprite_2d.h"
+#include "scene/3d/sprite_3d.h"
+#include "scene/gui/nine_patch_rect.h"
#include "scene/gui/option_button.h"
#include "scene/gui/panel_container.h"
#include "scene/gui/separator.h"
#include "scene/gui/spin_box.h"
#include "scene/gui/view_panner.h"
#include "scene/resources/atlas_texture.h"
+#include "scene/resources/style_box_texture.h"
Transform2D TextureRegionEditor::_get_offset_transform() const {
Transform2D mtx;
@@ -94,7 +97,7 @@ void TextureRegionEditor::_texture_overlay_draw() {
last_cell = cell;
}
} else {
- for (int i = 0; i < s.width; i++) {
+ for (int i = 0; i < s.width + snap_separation.x; i++) {
int cell = Math::fast_ftoi(Math::floor((mtx.affine_inverse().xform(Vector2(i, 0)).x - snap_offset.x) / (snap_step.x + snap_separation.x)));
if (i == 0) {
last_cell = cell;
@@ -120,7 +123,7 @@ void TextureRegionEditor::_texture_overlay_draw() {
last_cell = cell;
}
} else {
- for (int i = 0; i < s.height; i++) {
+ for (int i = 0; i < s.height + snap_separation.y; i++) {
int cell = Math::fast_ftoi(Math::floor((mtx.affine_inverse().xform(Vector2(0, i)).y - snap_offset.y) / (snap_step.y + snap_separation.y)));
if (i == 0) {
last_cell = cell;
@@ -281,6 +284,17 @@ void TextureRegionEditor::_draw_margin_line(Vector2 p_from, Vector2 p_to) {
}
}
+void TextureRegionEditor::_set_grid_parameters_clamping(bool p_enabled) {
+ sb_off_x->set_allow_lesser(!p_enabled);
+ sb_off_x->set_allow_greater(!p_enabled);
+ sb_off_y->set_allow_lesser(!p_enabled);
+ sb_off_y->set_allow_greater(!p_enabled);
+ sb_step_x->set_allow_greater(!p_enabled);
+ sb_step_y->set_allow_greater(!p_enabled);
+ sb_sep_x->set_allow_greater(!p_enabled);
+ sb_sep_y->set_allow_greater(!p_enabled);
+}
+
void TextureRegionEditor::_texture_overlay_input(const Ref<InputEvent> &p_input) {
if (panner->gui_input(p_input)) {
return;
@@ -842,6 +856,7 @@ void TextureRegionEditor::_notification(int p_what) {
}
if (!is_visible()) {
+ EditorSettings::get_singleton()->set_project_metadata("texture_region_editor", "snap_offset", snap_offset);
EditorSettings::get_singleton()->set_project_metadata("texture_region_editor", "snap_step", snap_step);
EditorSettings::get_singleton()->set_project_metadata("texture_region_editor", "snap_separation", snap_separation);
EditorSettings::get_singleton()->set_project_metadata("texture_region_editor", "snap_mode", snap_mode);
@@ -973,6 +988,7 @@ void TextureRegionEditor::_texture_changed() {
void TextureRegionEditor::_edit_region() {
const Ref<Texture2D> object_texture = _get_edited_object_texture();
if (object_texture.is_null()) {
+ _set_grid_parameters_clamping(false);
_zoom_reset();
hscroll->hide();
vscroll->hide();
@@ -1057,6 +1073,26 @@ void TextureRegionEditor::_edit_region() {
}
}
+ // Avoiding clamping with mismatched min/max.
+ _set_grid_parameters_clamping(false);
+ const Size2 tex_size = object_texture->get_size();
+ sb_off_x->set_min(-tex_size.x);
+ sb_off_x->set_max(tex_size.x);
+ sb_off_y->set_min(-tex_size.y);
+ sb_off_y->set_max(tex_size.y);
+ sb_step_x->set_max(tex_size.x);
+ sb_step_y->set_max(tex_size.y);
+ sb_sep_x->set_max(tex_size.x);
+ sb_sep_y->set_max(tex_size.y);
+
+ _set_grid_parameters_clamping(true);
+ sb_off_x->set_value(snap_offset.x);
+ sb_off_y->set_value(snap_offset.y);
+ sb_step_x->set_value(snap_step.x);
+ sb_step_y->set_value(snap_step.y);
+ sb_sep_x->set_value(snap_separation.x);
+ sb_sep_y->set_value(snap_separation.y);
+
_update_rect();
texture_preview->queue_redraw();
texture_overlay->queue_redraw();
@@ -1080,6 +1116,7 @@ TextureRegionEditor::TextureRegionEditor() {
set_ok_button_text(TTR("Close"));
// A power-of-two value works better as a default grid size.
+ snap_offset = EditorSettings::get_singleton()->get_project_metadata("texture_region_editor", "snap_offset", Vector2());
snap_step = EditorSettings::get_singleton()->get_project_metadata("texture_region_editor", "snap_step", Vector2(8, 8));
snap_separation = EditorSettings::get_singleton()->get_project_metadata("texture_region_editor", "snap_separation", Vector2());
snap_mode = (SnapMode)(int)EditorSettings::get_singleton()->get_project_metadata("texture_region_editor", "snap_mode", SNAP_NONE);
@@ -1110,19 +1147,13 @@ TextureRegionEditor::TextureRegionEditor() {
hb_grid->add_child(memnew(Label(TTR("Offset:"))));
sb_off_x = memnew(SpinBox);
- sb_off_x->set_min(-256);
- sb_off_x->set_max(256);
sb_off_x->set_step(1);
- sb_off_x->set_value(snap_offset.x);
sb_off_x->set_suffix("px");
sb_off_x->connect("value_changed", callable_mp(this, &TextureRegionEditor::_set_snap_off_x));
hb_grid->add_child(sb_off_x);
sb_off_y = memnew(SpinBox);
- sb_off_y->set_min(-256);
- sb_off_y->set_max(256);
sb_off_y->set_step(1);
- sb_off_y->set_value(snap_offset.y);
sb_off_y->set_suffix("px");
sb_off_y->connect("value_changed", callable_mp(this, &TextureRegionEditor::_set_snap_off_y));
hb_grid->add_child(sb_off_y);
@@ -1131,19 +1162,15 @@ TextureRegionEditor::TextureRegionEditor() {
hb_grid->add_child(memnew(Label(TTR("Step:"))));
sb_step_x = memnew(SpinBox);
- sb_step_x->set_min(-256);
- sb_step_x->set_max(256);
+ sb_step_x->set_min(0);
sb_step_x->set_step(1);
- sb_step_x->set_value(snap_step.x);
sb_step_x->set_suffix("px");
sb_step_x->connect("value_changed", callable_mp(this, &TextureRegionEditor::_set_snap_step_x));
hb_grid->add_child(sb_step_x);
sb_step_y = memnew(SpinBox);
- sb_step_y->set_min(-256);
- sb_step_y->set_max(256);
+ sb_step_y->set_min(0);
sb_step_y->set_step(1);
- sb_step_y->set_value(snap_step.y);
sb_step_y->set_suffix("px");
sb_step_y->connect("value_changed", callable_mp(this, &TextureRegionEditor::_set_snap_step_y));
hb_grid->add_child(sb_step_y);
@@ -1153,24 +1180,29 @@ TextureRegionEditor::TextureRegionEditor() {
sb_sep_x = memnew(SpinBox);
sb_sep_x->set_min(0);
- sb_sep_x->set_max(256);
sb_sep_x->set_step(1);
- sb_sep_x->set_value(snap_separation.x);
sb_sep_x->set_suffix("px");
sb_sep_x->connect("value_changed", callable_mp(this, &TextureRegionEditor::_set_snap_sep_x));
hb_grid->add_child(sb_sep_x);
sb_sep_y = memnew(SpinBox);
sb_sep_y->set_min(0);
- sb_sep_y->set_max(256);
sb_sep_y->set_step(1);
- sb_sep_y->set_value(snap_separation.y);
sb_sep_y->set_suffix("px");
sb_sep_y->connect("value_changed", callable_mp(this, &TextureRegionEditor::_set_snap_sep_y));
hb_grid->add_child(sb_sep_y);
hb_grid->hide();
+ // Restore grid snap parameters.
+ _set_grid_parameters_clamping(false);
+ sb_off_x->set_value(snap_offset.x);
+ sb_off_y->set_value(snap_offset.y);
+ sb_step_x->set_value(snap_step.x);
+ sb_step_y->set_value(snap_step.y);
+ sb_sep_x->set_value(snap_separation.x);
+ sb_sep_y->set_value(snap_separation.y);
+
// Default the zoom to match the editor scale, but don't dezoom on editor scales below 100% to prevent pixel art from looking bad.
draw_zoom = MAX(1.0f, EDSCALE);
max_draw_zoom = 128.0f * MAX(1.0f, EDSCALE);
diff --git a/editor/plugins/texture_region_editor_plugin.h b/editor/plugins/texture_region_editor_plugin.h
index fb2b547bed..59a1b56c19 100644
--- a/editor/plugins/texture_region_editor_plugin.h
+++ b/editor/plugins/texture_region_editor_plugin.h
@@ -31,18 +31,17 @@
#ifndef TEXTURE_REGION_EDITOR_PLUGIN_H
#define TEXTURE_REGION_EDITOR_PLUGIN_H
-#include "canvas_item_editor_plugin.h"
#include "editor/editor_inspector.h"
#include "editor/editor_plugin.h"
-#include "scene/2d/sprite_2d.h"
-#include "scene/3d/sprite_3d.h"
#include "scene/gui/dialogs.h"
-#include "scene/gui/nine_patch_rect.h"
-#include "scene/resources/style_box_texture.h"
class AtlasTexture;
+class NinePatchRect;
class OptionButton;
class PanelContainer;
+class Sprite2D;
+class Sprite3D;
+class StyleBoxTexture;
class ViewPanner;
class TextureRegionEditor : public AcceptDialog {
@@ -138,6 +137,8 @@ class TextureRegionEditor : public AcceptDialog {
void _draw_margin_line(Vector2 p_from, Vector2 p_to);
+ void _set_grid_parameters_clamping(bool p_enabled);
+
protected:
void _notification(int p_what);
static void _bind_methods();
diff --git a/modules/gdscript/editor/gdscript_highlighter.cpp b/modules/gdscript/editor/gdscript_highlighter.cpp
index b4f4e879d0..f83b784f85 100644
--- a/modules/gdscript/editor/gdscript_highlighter.cpp
+++ b/modules/gdscript/editor/gdscript_highlighter.cpp
@@ -53,7 +53,6 @@ Dictionary GDScriptSyntaxHighlighter::_get_line_syntax_highlighting_impl(int p_l
bool in_keyword = false;
bool in_word = false;
bool in_number = false;
- bool in_raw_string = false;
bool in_node_path = false;
bool in_node_ref = false;
bool in_annotation = false;
@@ -127,6 +126,7 @@ Dictionary GDScriptSyntaxHighlighter::_get_line_syntax_highlighting_impl(int p_l
if (from != line_length) {
// Check if we are in entering a region.
if (in_region == -1) {
+ const bool r_prefix = from > 0 && str[from - 1] == 'r';
for (int c = 0; c < color_regions.size(); c++) {
// Check there is enough room.
int chars_left = line_length - from;
@@ -136,6 +136,10 @@ Dictionary GDScriptSyntaxHighlighter::_get_line_syntax_highlighting_impl(int p_l
continue;
}
+ if (color_regions[c].is_string && color_regions[c].r_prefix != r_prefix) {
+ continue;
+ }
+
// Search the line.
bool match = true;
const char32_t *start_key = color_regions[c].start_key.get_data();
@@ -154,7 +158,7 @@ Dictionary GDScriptSyntaxHighlighter::_get_line_syntax_highlighting_impl(int p_l
// Check if it's the whole line.
if (end_key_length == 0 || color_regions[c].line_only || from + end_key_length > line_length) {
// Don't skip comments, for highlighting markers.
- if (color_regions[in_region].type == ColorRegion::TYPE_COMMENT) {
+ if (color_regions[in_region].is_comment) {
break;
}
if (from + end_key_length > line_length) {
@@ -176,7 +180,7 @@ Dictionary GDScriptSyntaxHighlighter::_get_line_syntax_highlighting_impl(int p_l
}
// Don't skip comments, for highlighting markers.
- if (j == line_length && color_regions[in_region].type != ColorRegion::TYPE_COMMENT) {
+ if (j == line_length && !color_regions[in_region].is_comment) {
continue;
}
}
@@ -198,7 +202,7 @@ Dictionary GDScriptSyntaxHighlighter::_get_line_syntax_highlighting_impl(int p_l
highlighter_info["color"] = region_color;
color_map[j] = highlighter_info;
- if (color_regions[in_region].type == ColorRegion::TYPE_COMMENT) {
+ if (color_regions[in_region].is_comment) {
int marker_start_pos = from;
int marker_len = 0;
while (from <= line_length) {
@@ -242,7 +246,7 @@ Dictionary GDScriptSyntaxHighlighter::_get_line_syntax_highlighting_impl(int p_l
}
if (str[from] == '\\') {
- if (!in_raw_string) {
+ if (!color_regions[in_region].r_prefix) {
Dictionary escape_char_highlighter_info;
escape_char_highlighter_info["color"] = symbol_color;
color_map[from] = escape_char_highlighter_info;
@@ -250,7 +254,7 @@ Dictionary GDScriptSyntaxHighlighter::_get_line_syntax_highlighting_impl(int p_l
from++;
- if (!in_raw_string) {
+ if (!color_regions[in_region].r_prefix) {
int esc_len = 0;
if (str[from] == 'u') {
esc_len = 4;
@@ -556,12 +560,6 @@ Dictionary GDScriptSyntaxHighlighter::_get_line_syntax_highlighting_impl(int p_l
}
}
- if (!in_raw_string && in_region == -1 && str[j] == 'r' && j < line_length - 1 && (str[j + 1] == '"' || str[j + 1] == '\'')) {
- in_raw_string = true;
- } else if (in_raw_string && in_region == -1) {
- in_raw_string = false;
- }
-
// Keep symbol color for binary '&&'. In the case of '&&&' use StringName color for the last ampersand.
if (!in_string_name && in_region == -1 && str[j] == '&' && !is_binary_op) {
if (j >= 2 && str[j - 1] == '&' && str[j - 2] != '&' && prev_is_binary_op) {
@@ -593,7 +591,9 @@ Dictionary GDScriptSyntaxHighlighter::_get_line_syntax_highlighting_impl(int p_l
in_annotation = false;
}
- if (in_raw_string) {
+ const bool in_raw_string_prefix = in_region == -1 && str[j] == 'r' && j + 1 < line_length && (str[j + 1] == '"' || str[j + 1] == '\'');
+
+ if (in_raw_string_prefix) {
color = string_color;
} else if (in_node_ref) {
next_type = NODE_REF;
@@ -795,6 +795,10 @@ void GDScriptSyntaxHighlighter::_update_cache() {
add_color_region(ColorRegion::TYPE_STRING, "'", "'", string_color);
add_color_region(ColorRegion::TYPE_MULTILINE_STRING, "\"\"\"", "\"\"\"", string_color);
add_color_region(ColorRegion::TYPE_MULTILINE_STRING, "'''", "'''", string_color);
+ add_color_region(ColorRegion::TYPE_STRING, "\"", "\"", string_color, false, true);
+ add_color_region(ColorRegion::TYPE_STRING, "'", "'", string_color, false, true);
+ add_color_region(ColorRegion::TYPE_MULTILINE_STRING, "\"\"\"", "\"\"\"", string_color, false, true);
+ add_color_region(ColorRegion::TYPE_MULTILINE_STRING, "'''", "'''", string_color, false, true);
const Ref<Script> scr = _get_edited_resource();
if (scr.is_valid()) {
@@ -927,7 +931,7 @@ void GDScriptSyntaxHighlighter::_update_cache() {
}
}
-void GDScriptSyntaxHighlighter::add_color_region(ColorRegion::Type p_type, const String &p_start_key, const String &p_end_key, const Color &p_color, bool p_line_only) {
+void GDScriptSyntaxHighlighter::add_color_region(ColorRegion::Type p_type, const String &p_start_key, const String &p_end_key, const Color &p_color, bool p_line_only, bool p_r_prefix) {
ERR_FAIL_COND_MSG(p_start_key.is_empty(), "Color region start key cannot be empty.");
ERR_FAIL_COND_MSG(!is_symbol(p_start_key[0]), "Color region start key must start with a symbol.");
@@ -936,9 +940,9 @@ void GDScriptSyntaxHighlighter::add_color_region(ColorRegion::Type p_type, const
}
int at = 0;
- for (int i = 0; i < color_regions.size(); i++) {
- ERR_FAIL_COND_MSG(color_regions[i].start_key == p_start_key, "Color region with start key '" + p_start_key + "' already exists.");
- if (p_start_key.length() < color_regions[i].start_key.length()) {
+ for (const ColorRegion &region : color_regions) {
+ ERR_FAIL_COND_MSG(region.start_key == p_start_key && region.r_prefix == p_r_prefix, "Color region with start key '" + p_start_key + "' already exists.");
+ if (p_start_key.length() < region.start_key.length()) {
at++;
} else {
break;
@@ -951,6 +955,9 @@ void GDScriptSyntaxHighlighter::add_color_region(ColorRegion::Type p_type, const
color_region.start_key = p_start_key;
color_region.end_key = p_end_key;
color_region.line_only = p_line_only;
+ color_region.r_prefix = p_r_prefix;
+ color_region.is_string = p_type == ColorRegion::TYPE_STRING || p_type == ColorRegion::TYPE_MULTILINE_STRING;
+ color_region.is_comment = p_type == ColorRegion::TYPE_COMMENT || p_type == ColorRegion::TYPE_CODE_REGION;
color_regions.insert(at, color_region);
clear_highlighting_cache();
}
diff --git a/modules/gdscript/editor/gdscript_highlighter.h b/modules/gdscript/editor/gdscript_highlighter.h
index eb7bb7d801..655557b3d9 100644
--- a/modules/gdscript/editor/gdscript_highlighter.h
+++ b/modules/gdscript/editor/gdscript_highlighter.h
@@ -52,6 +52,9 @@ private:
String start_key;
String end_key;
bool line_only = false;
+ bool r_prefix = false;
+ bool is_string = false; // `TYPE_STRING` or `TYPE_MULTILINE_STRING`.
+ bool is_comment = false; // `TYPE_COMMENT` or `TYPE_CODE_REGION`.
};
Vector<ColorRegion> color_regions;
HashMap<int, int> color_region_cache;
@@ -103,7 +106,7 @@ private:
Color comment_marker_colors[COMMENT_MARKER_MAX];
HashMap<String, CommentMarkerLevel> comment_markers;
- void add_color_region(ColorRegion::Type p_type, const String &p_start_key, const String &p_end_key, const Color &p_color, bool p_line_only = false);
+ void add_color_region(ColorRegion::Type p_type, const String &p_start_key, const String &p_end_key, const Color &p_color, bool p_line_only = false, bool p_r_prefix = false);
public:
virtual void _update_cache() override;
diff --git a/modules/gdscript/gdscript_analyzer.cpp b/modules/gdscript/gdscript_analyzer.cpp
index f67f4913c3..dd9171c670 100644
--- a/modules/gdscript/gdscript_analyzer.cpp
+++ b/modules/gdscript/gdscript_analyzer.cpp
@@ -3439,9 +3439,7 @@ void GDScriptAnalyzer::reduce_cast(GDScriptParser::CastNode *p_cast) {
if (op_type.is_variant() || !op_type.is_hard_type()) {
mark_node_unsafe(p_cast);
#ifdef DEBUG_ENABLED
- if (op_type.is_variant() && !op_type.is_hard_type()) {
- parser->push_warning(p_cast, GDScriptWarning::UNSAFE_CAST, cast_type.to_string());
- }
+ parser->push_warning(p_cast, GDScriptWarning::UNSAFE_CAST, cast_type.to_string());
#endif
} else {
bool valid = false;
diff --git a/modules/gdscript/gdscript_warning.cpp b/modules/gdscript/gdscript_warning.cpp
index e7d9787eab..ca84c7fff6 100644
--- a/modules/gdscript/gdscript_warning.cpp
+++ b/modules/gdscript/gdscript_warning.cpp
@@ -96,7 +96,7 @@ String GDScriptWarning::get_message() const {
return vformat(R"*(The method "%s()" is not present on the inferred type "%s" (but may be present on a subtype).)*", symbols[0], symbols[1]);
case UNSAFE_CAST:
CHECK_SYMBOLS(1);
- return vformat(R"(The value is cast to "%s" but has an unknown type.)", symbols[0]);
+ return vformat(R"(Casting "Variant" to "%s" is unsafe.)", symbols[0]);
case UNSAFE_CALL_ARGUMENT:
CHECK_SYMBOLS(5);
return vformat(R"*(The argument %s of the %s "%s()" requires the subtype "%s" but the supertype "%s" was provided.)*", symbols[0], symbols[1], symbols[2], symbols[3], symbols[4]);
diff --git a/modules/gdscript/gdscript_warning.h b/modules/gdscript/gdscript_warning.h
index 69cc8c179f..93c232a0f8 100644
--- a/modules/gdscript/gdscript_warning.h
+++ b/modules/gdscript/gdscript_warning.h
@@ -65,7 +65,7 @@ public:
INFERRED_DECLARATION, // Variable/constant/parameter has an implicitly inferred static type.
UNSAFE_PROPERTY_ACCESS, // Property not found in the detected type (but can be in subtypes).
UNSAFE_METHOD_ACCESS, // Function not found in the detected type (but can be in subtypes).
- UNSAFE_CAST, // Cast used in an unknown type.
+ UNSAFE_CAST, // Casting a `Variant` value to non-`Variant`.
UNSAFE_CALL_ARGUMENT, // Function call argument is of a supertype of the required type.
UNSAFE_VOID_RETURN, // Function returns void but returned a call to a function that can't be type checked.
RETURN_VALUE_DISCARDED, // Function call returns something but the value isn't used.
diff --git a/modules/gdscript/tests/scripts/analyzer/errors/cast_int_to_array.gd b/modules/gdscript/tests/scripts/analyzer/errors/cast_int_to_array.gd
new file mode 100644
index 0000000000..b53e814eea
--- /dev/null
+++ b/modules/gdscript/tests/scripts/analyzer/errors/cast_int_to_array.gd
@@ -0,0 +1,3 @@
+func test():
+ var integer := 1
+ print(integer as Array)
diff --git a/modules/gdscript/tests/scripts/analyzer/errors/cast_int_to_array.out b/modules/gdscript/tests/scripts/analyzer/errors/cast_int_to_array.out
new file mode 100644
index 0000000000..e3e82c2b7e
--- /dev/null
+++ b/modules/gdscript/tests/scripts/analyzer/errors/cast_int_to_array.out
@@ -0,0 +1,2 @@
+GDTEST_ANALYZER_ERROR
+Invalid cast. Cannot convert from "int" to "Array".
diff --git a/modules/gdscript/tests/scripts/analyzer/errors/cast_int_to_object.gd b/modules/gdscript/tests/scripts/analyzer/errors/cast_int_to_object.gd
new file mode 100644
index 0000000000..323e367f8e
--- /dev/null
+++ b/modules/gdscript/tests/scripts/analyzer/errors/cast_int_to_object.gd
@@ -0,0 +1,3 @@
+func test():
+ var integer := 1
+ print(integer as Node)
diff --git a/modules/gdscript/tests/scripts/analyzer/errors/cast_int_to_object.out b/modules/gdscript/tests/scripts/analyzer/errors/cast_int_to_object.out
new file mode 100644
index 0000000000..7de40418bf
--- /dev/null
+++ b/modules/gdscript/tests/scripts/analyzer/errors/cast_int_to_object.out
@@ -0,0 +1,2 @@
+GDTEST_ANALYZER_ERROR
+Invalid cast. Cannot convert from "int" to "Node".
diff --git a/modules/gdscript/tests/scripts/analyzer/errors/cast_object_to_int.gd b/modules/gdscript/tests/scripts/analyzer/errors/cast_object_to_int.gd
new file mode 100644
index 0000000000..f6cd5e217e
--- /dev/null
+++ b/modules/gdscript/tests/scripts/analyzer/errors/cast_object_to_int.gd
@@ -0,0 +1,3 @@
+func test():
+ var object := RefCounted.new()
+ print(object as int)
diff --git a/modules/gdscript/tests/scripts/analyzer/errors/cast_object_to_int.out b/modules/gdscript/tests/scripts/analyzer/errors/cast_object_to_int.out
new file mode 100644
index 0000000000..8af0847577
--- /dev/null
+++ b/modules/gdscript/tests/scripts/analyzer/errors/cast_object_to_int.out
@@ -0,0 +1,2 @@
+GDTEST_ANALYZER_ERROR
+Invalid cast. Cannot convert from "RefCounted" to "int".
diff --git a/modules/gdscript/tests/scripts/analyzer/warnings/unsafe_cast.gd b/modules/gdscript/tests/scripts/analyzer/warnings/unsafe_cast.gd
new file mode 100644
index 0000000000..1a6d10f8f7
--- /dev/null
+++ b/modules/gdscript/tests/scripts/analyzer/warnings/unsafe_cast.gd
@@ -0,0 +1,24 @@
+# We don't want to execute it because of errors, just analyze.
+func no_exec_test():
+ var weak_int = 1
+ print(weak_int as Variant) # No warning.
+ print(weak_int as int)
+ print(weak_int as Node)
+
+ var weak_node = Node.new()
+ print(weak_node as Variant) # No warning.
+ print(weak_node as int)
+ print(weak_node as Node)
+
+ var weak_variant = null
+ print(weak_variant as Variant) # No warning.
+ print(weak_variant as int)
+ print(weak_variant as Node)
+
+ var hard_variant: Variant = null
+ print(hard_variant as Variant) # No warning.
+ print(hard_variant as int)
+ print(hard_variant as Node)
+
+func test():
+ pass
diff --git a/modules/gdscript/tests/scripts/analyzer/warnings/unsafe_cast.out b/modules/gdscript/tests/scripts/analyzer/warnings/unsafe_cast.out
new file mode 100644
index 0000000000..c1e683d942
--- /dev/null
+++ b/modules/gdscript/tests/scripts/analyzer/warnings/unsafe_cast.out
@@ -0,0 +1,33 @@
+GDTEST_OK
+>> WARNING
+>> Line: 5
+>> UNSAFE_CAST
+>> Casting "Variant" to "int" is unsafe.
+>> WARNING
+>> Line: 6
+>> UNSAFE_CAST
+>> Casting "Variant" to "Node" is unsafe.
+>> WARNING
+>> Line: 10
+>> UNSAFE_CAST
+>> Casting "Variant" to "int" is unsafe.
+>> WARNING
+>> Line: 11
+>> UNSAFE_CAST
+>> Casting "Variant" to "Node" is unsafe.
+>> WARNING
+>> Line: 15
+>> UNSAFE_CAST
+>> Casting "Variant" to "int" is unsafe.
+>> WARNING
+>> Line: 16
+>> UNSAFE_CAST
+>> Casting "Variant" to "Node" is unsafe.
+>> WARNING
+>> Line: 20
+>> UNSAFE_CAST
+>> Casting "Variant" to "int" is unsafe.
+>> WARNING
+>> Line: 21
+>> UNSAFE_CAST
+>> Casting "Variant" to "Node" is unsafe.
diff --git a/modules/gdscript/tests/scripts/runtime/errors/cast_freed_object.gd b/modules/gdscript/tests/scripts/runtime/errors/cast_freed_object.gd
new file mode 100644
index 0000000000..6b766f4d3d
--- /dev/null
+++ b/modules/gdscript/tests/scripts/runtime/errors/cast_freed_object.gd
@@ -0,0 +1,4 @@
+func test():
+ var node := Node.new()
+ node.free()
+ print(node as Node2D)
diff --git a/modules/gdscript/tests/scripts/runtime/errors/cast_freed_object.out b/modules/gdscript/tests/scripts/runtime/errors/cast_freed_object.out
new file mode 100644
index 0000000000..90d81dd9a1
--- /dev/null
+++ b/modules/gdscript/tests/scripts/runtime/errors/cast_freed_object.out
@@ -0,0 +1,6 @@
+GDTEST_RUNTIME_ERROR
+>> SCRIPT ERROR
+>> on function: test()
+>> runtime/errors/cast_freed_object.gd
+>> 4
+>> Trying to cast a freed object.
diff --git a/modules/gdscript/tests/scripts/runtime/errors/cast_int_to_array.gd b/modules/gdscript/tests/scripts/runtime/errors/cast_int_to_array.gd
new file mode 100644
index 0000000000..00817c588f
--- /dev/null
+++ b/modules/gdscript/tests/scripts/runtime/errors/cast_int_to_array.gd
@@ -0,0 +1,4 @@
+func test():
+ var integer: Variant = 1
+ @warning_ignore("unsafe_cast")
+ print(integer as Array)
diff --git a/modules/gdscript/tests/scripts/runtime/errors/cast_int_to_array.out b/modules/gdscript/tests/scripts/runtime/errors/cast_int_to_array.out
new file mode 100644
index 0000000000..545d7a4906
--- /dev/null
+++ b/modules/gdscript/tests/scripts/runtime/errors/cast_int_to_array.out
@@ -0,0 +1,6 @@
+GDTEST_RUNTIME_ERROR
+>> SCRIPT ERROR
+>> on function: test()
+>> runtime/errors/cast_int_to_array.gd
+>> 4
+>> Invalid cast: could not convert value to 'Array'.
diff --git a/modules/gdscript/tests/scripts/runtime/errors/cast_int_to_object.gd b/modules/gdscript/tests/scripts/runtime/errors/cast_int_to_object.gd
new file mode 100644
index 0000000000..44673a4513
--- /dev/null
+++ b/modules/gdscript/tests/scripts/runtime/errors/cast_int_to_object.gd
@@ -0,0 +1,4 @@
+func test():
+ var integer: Variant = 1
+ @warning_ignore("unsafe_cast")
+ print(integer as Node)
diff --git a/modules/gdscript/tests/scripts/runtime/errors/cast_int_to_object.out b/modules/gdscript/tests/scripts/runtime/errors/cast_int_to_object.out
new file mode 100644
index 0000000000..7c39b46396
--- /dev/null
+++ b/modules/gdscript/tests/scripts/runtime/errors/cast_int_to_object.out
@@ -0,0 +1,6 @@
+GDTEST_RUNTIME_ERROR
+>> SCRIPT ERROR
+>> on function: test()
+>> runtime/errors/cast_int_to_object.gd
+>> 4
+>> Invalid cast: can't convert a non-object value to an object type.
diff --git a/modules/gdscript/tests/scripts/runtime/errors/cast_object_to_int.gd b/modules/gdscript/tests/scripts/runtime/errors/cast_object_to_int.gd
new file mode 100644
index 0000000000..830d0c0c4a
--- /dev/null
+++ b/modules/gdscript/tests/scripts/runtime/errors/cast_object_to_int.gd
@@ -0,0 +1,4 @@
+func test():
+ var object: Variant = RefCounted.new()
+ @warning_ignore("unsafe_cast")
+ print(object as int)
diff --git a/modules/gdscript/tests/scripts/runtime/errors/cast_object_to_int.out b/modules/gdscript/tests/scripts/runtime/errors/cast_object_to_int.out
new file mode 100644
index 0000000000..f922199fb3
--- /dev/null
+++ b/modules/gdscript/tests/scripts/runtime/errors/cast_object_to_int.out
@@ -0,0 +1,6 @@
+GDTEST_RUNTIME_ERROR
+>> SCRIPT ERROR
+>> on function: test()
+>> runtime/errors/cast_object_to_int.gd
+>> 4
+>> Invalid cast: could not convert value to 'int'.
diff --git a/modules/gdscript/tests/scripts/runtime/features/type_casting.gd b/modules/gdscript/tests/scripts/runtime/features/type_casting.gd
new file mode 100644
index 0000000000..c63ea16c32
--- /dev/null
+++ b/modules/gdscript/tests/scripts/runtime/features/type_casting.gd
@@ -0,0 +1,24 @@
+func print_value(value: Variant) -> void:
+ if value is Object:
+ @warning_ignore("unsafe_method_access")
+ print("<%s>" % value.get_class())
+ else:
+ print(var_to_str(value))
+
+func test():
+ var int_value := 1
+ print_value(int_value as Variant)
+ print_value(int_value as int)
+ print_value(int_value as float)
+
+ var node_value := Node.new()
+ print_value(node_value as Variant)
+ print_value(node_value as Object)
+ print_value(node_value as Node)
+ print_value(node_value as Node2D)
+ node_value.free()
+
+ var null_value = null
+ print_value(null_value as Variant)
+ @warning_ignore("unsafe_cast")
+ print_value(null_value as Node)
diff --git a/modules/gdscript/tests/scripts/runtime/features/type_casting.out b/modules/gdscript/tests/scripts/runtime/features/type_casting.out
new file mode 100644
index 0000000000..7da5a4c0a4
--- /dev/null
+++ b/modules/gdscript/tests/scripts/runtime/features/type_casting.out
@@ -0,0 +1,10 @@
+GDTEST_OK
+1
+1
+1.0
+<Node>
+<Node>
+<Node>
+null
+null
+null
diff --git a/scene/gui/tab_container.cpp b/scene/gui/tab_container.cpp
index df3c9631f9..105f4484b2 100644
--- a/scene/gui/tab_container.cpp
+++ b/scene/gui/tab_container.cpp
@@ -500,6 +500,13 @@ void TabContainer::_on_tab_visibility_changed(Control *p_child) {
updating_visibility = false;
}
+void TabContainer::_refresh_tab_indices() {
+ Vector<Control *> controls = _get_tab_controls();
+ for (int i = 0; i < controls.size(); i++) {
+ controls[i]->set_meta("_tab_index", i);
+ }
+}
+
void TabContainer::_refresh_tab_names() {
Vector<Control *> controls = _get_tab_controls();
for (int i = 0; i < controls.size(); i++) {
@@ -523,6 +530,7 @@ void TabContainer::add_child_notify(Node *p_child) {
c->hide();
tab_bar->add_tab(p_child->get_name());
+ c->set_meta("_tab_index", tab_bar->get_tab_count() - 1);
_update_margins();
if (get_tab_count() == 1) {
@@ -547,19 +555,10 @@ void TabContainer::move_child_notify(Node *p_child) {
Control *c = Object::cast_to<Control>(p_child);
if (c && !c->is_set_as_top_level()) {
- int old_idx = -1;
- String tab_name = String(c->get_meta("_tab_name", c->get_name()));
-
- // Find the previous tab index of the control.
- for (int i = 0; i < get_tab_count(); i++) {
- if (get_tab_title(i) == tab_name) {
- old_idx = i;
- break;
- }
- }
-
- tab_bar->move_tab(old_idx, get_tab_idx_from_control(c));
+ tab_bar->move_tab(c->get_meta("_tab_index"), get_tab_idx_from_control(c));
}
+
+ _refresh_tab_indices();
}
void TabContainer::remove_child_notify(Node *p_child) {
@@ -578,7 +577,10 @@ void TabContainer::remove_child_notify(Node *p_child) {
// As the child hasn't been removed yet, keep track of it so when the "tab_changed" signal is fired it can be ignored.
children_removing.push_back(c);
+
tab_bar->remove_tab(idx);
+ _refresh_tab_indices();
+
children_removing.erase(c);
_update_margins();
@@ -586,6 +588,7 @@ void TabContainer::remove_child_notify(Node *p_child) {
queue_redraw();
}
+ p_child->remove_meta("_tab_index");
p_child->remove_meta("_tab_name");
p_child->disconnect("renamed", callable_mp(this, &TabContainer::_refresh_tab_names));
p_child->disconnect(SNAME("visibility_changed"), callable_mp(this, &TabContainer::_on_tab_visibility_changed));
diff --git a/scene/gui/tab_container.h b/scene/gui/tab_container.h
index 03eff5d944..8645a6d14e 100644
--- a/scene/gui/tab_container.h
+++ b/scene/gui/tab_container.h
@@ -101,6 +101,7 @@ private:
Vector<Control *> _get_tab_controls() const;
void _on_theme_changed();
void _repaint();
+ void _refresh_tab_indices();
void _refresh_tab_names();
void _update_margins();
void _on_mouse_exited();
diff --git a/scene/resources/resource_format_text.cpp b/scene/resources/resource_format_text.cpp
index 6d0796f1b9..ea8408a594 100644
--- a/scene/resources/resource_format_text.cpp
+++ b/scene/resources/resource_format_text.cpp
@@ -1951,6 +1951,7 @@ void ResourceFormatSaverTextInstance::_find_resources(const Variant &p_variant,
} break;
case Variant::ARRAY: {
Array varray = p_variant;
+ _find_resources(varray.get_typed_script());
int len = varray.size();
for (int i = 0; i < len; i++) {
const Variant &v = varray.get(i);
diff --git a/servers/rendering/rendering_device.cpp b/servers/rendering/rendering_device.cpp
index 00291d2ac4..2b6644e893 100644
--- a/servers/rendering/rendering_device.cpp
+++ b/servers/rendering/rendering_device.cpp
@@ -5554,6 +5554,7 @@ void RenderingDevice::_bind_methods() {
ClassDB::bind_method(D_METHOD("compute_list_set_push_constant", "compute_list", "buffer", "size_bytes"), &RenderingDevice::_compute_list_set_push_constant);
ClassDB::bind_method(D_METHOD("compute_list_bind_uniform_set", "compute_list", "uniform_set", "set_index"), &RenderingDevice::compute_list_bind_uniform_set);
ClassDB::bind_method(D_METHOD("compute_list_dispatch", "compute_list", "x_groups", "y_groups", "z_groups"), &RenderingDevice::compute_list_dispatch);
+ ClassDB::bind_method(D_METHOD("compute_list_dispatch_indirect", "compute_list", "buffer", "offset"), &RenderingDevice::compute_list_dispatch_indirect);
ClassDB::bind_method(D_METHOD("compute_list_add_barrier", "compute_list"), &RenderingDevice::compute_list_add_barrier);
ClassDB::bind_method(D_METHOD("compute_list_end"), &RenderingDevice::compute_list_end);