diff options
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 ®ion : 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); |
