diff options
108 files changed, 1956 insertions, 213 deletions
diff --git a/core/input/input_map.cpp b/core/input/input_map.cpp index fe4ee99204..816d9d1082 100644 --- a/core/input/input_map.cpp +++ b/core/input/input_map.cpp @@ -746,3 +746,7 @@ InputMap::InputMap() { ERR_FAIL_COND_MSG(singleton, "Singleton in InputMap already exist."); singleton = this; } + +InputMap::~InputMap() { + singleton = nullptr; +} diff --git a/core/input/input_map.h b/core/input/input_map.h index a2d3952f94..c724fdb142 100644 --- a/core/input/input_map.h +++ b/core/input/input_map.h @@ -95,6 +95,7 @@ public: const OrderedHashMap<String, List<Ref<InputEvent>>> &get_builtins(); InputMap(); + ~InputMap(); }; #endif // INPUT_MAP_H diff --git a/core/math/convex_hull.h b/core/math/convex_hull.h index a860d60b02..806c6cc3fb 100644 --- a/core/math/convex_hull.h +++ b/core/math/convex_hull.h @@ -49,7 +49,7 @@ subject to the following restrictions: #include "core/templates/vector.h" /// Convex hull implementation based on Preparata and Hong -/// See https://code.google.com/p/bullet/issues/detail?id=275 +/// See https://code.google.com/archive/p/bullet/issues/275 /// Ole Kniemeyer, MAXON Computer GmbH class ConvexHullComputer { public: diff --git a/core/math/dynamic_bvh.h b/core/math/dynamic_bvh.h index d63132b4da..0b6286cd9d 100644 --- a/core/math/dynamic_bvh.h +++ b/core/math/dynamic_bvh.h @@ -41,7 +41,7 @@ /* Bullet Continuous Collision Detection and Physics Library -Copyright (c) 2003-2013 Erwin Coumans https://bulletphysics.org +Copyright (c) 2003-2013 Erwin Coumans http://bulletphysics.org This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held liable for any damages arising from the use of this software. diff --git a/core/string/ustring.cpp b/core/string/ustring.cpp index ed6fd13cc8..8416ff929e 100644 --- a/core/string/ustring.cpp +++ b/core/string/ustring.cpp @@ -4329,23 +4329,39 @@ bool String::is_relative_path() const { } String String::get_base_dir() const { - int basepos = find(":/"); - if (basepos == -1) { - basepos = find(":\\"); + int end = 0; + + // url scheme style base + int basepos = find("://"); + if (basepos != -1) { + end = basepos + 3; } + + // windows top level directory base + if (end == 0) { + basepos = find(":/"); + if (basepos == -1) { + basepos = find(":\\"); + } + if (basepos != -1) { + end = basepos + 2; + } + } + + // unix root directory base + if (end == 0) { + if (begins_with("/")) { + end = 1; + } + } + String rs; String base; - if (basepos != -1) { - int end = basepos + 3; + if (end != 0) { rs = substr(end, length()); base = substr(0, end); } else { - if (begins_with("/")) { - rs = substr(1, length()); - base = "/"; - } else { - rs = *this; - } + rs = *this; } int sep = MAX(rs.rfind("/"), rs.rfind("\\")); diff --git a/doc/classes/HScrollBar.xml b/doc/classes/HScrollBar.xml index 3bdd739cdf..36ff070a37 100644 --- a/doc/classes/HScrollBar.xml +++ b/doc/classes/HScrollBar.xml @@ -19,6 +19,9 @@ <theme_item name="decrement_highlight" data_type="icon" type="Texture2D"> Displayed when the mouse cursor hovers over the decrement button. </theme_item> + <theme_item name="decrement_pressed" data_type="icon" type="Texture2D"> + Displayed when the decrement button is being pressed. + </theme_item> <theme_item name="grabber" data_type="style" type="StyleBox"> Used as texture for the grabber, the draggable element representing current scroll. </theme_item> @@ -34,6 +37,9 @@ <theme_item name="increment_highlight" data_type="icon" type="Texture2D"> Displayed when the mouse cursor hovers over the increment button. </theme_item> + <theme_item name="increment_pressed" data_type="icon" type="Texture2D"> + Displayed when the increment button is being pressed. + </theme_item> <theme_item name="scroll" data_type="style" type="StyleBox"> Used as background of this [ScrollBar]. </theme_item> diff --git a/doc/classes/MeshLibrary.xml b/doc/classes/MeshLibrary.xml index 9e0292f946..1d07647ea7 100644 --- a/doc/classes/MeshLibrary.xml +++ b/doc/classes/MeshLibrary.xml @@ -45,6 +45,13 @@ Returns the item's mesh. </description> </method> + <method name="get_item_mesh_transform" qualifiers="const"> + <return type="Transform3D" /> + <argument index="0" name="id" type="int" /> + <description> + Returns the transform applied to the item's mesh. + </description> + </method> <method name="get_item_name" qualifiers="const"> <return type="String" /> <argument index="0" name="id" type="int" /> @@ -102,6 +109,14 @@ Sets the item's mesh. </description> </method> + <method name="set_item_mesh_transform"> + <return type="void" /> + <argument index="0" name="id" type="int" /> + <argument index="1" name="mesh_transform" type="Transform3D" /> + <description> + Sets the transform to apply to the item's mesh. + </description> + </method> <method name="set_item_name"> <return type="void" /> <argument index="0" name="id" type="int" /> diff --git a/doc/classes/ProjectSettings.xml b/doc/classes/ProjectSettings.xml index 21d974e233..6fdce591ec 100644 --- a/doc/classes/ProjectSettings.xml +++ b/doc/classes/ProjectSettings.xml @@ -328,6 +328,9 @@ <member name="debug/gdscript/warnings/deprecated_keyword" type="bool" setter="" getter="" default="true"> If [code]true[/code], enables warnings when deprecated keywords are used. </member> + <member name="debug/gdscript/warnings/empty_file" type="bool" setter="" getter="" default="true"> + If [code]true[/code], enables warnings when an empty file is parsed. + </member> <member name="debug/gdscript/warnings/enable" type="bool" setter="" getter="" default="true"> If [code]true[/code], enables specific GDScript warnings (see [code]debug/gdscript/warnings/*[/code] settings). If [code]false[/code], disables all GDScript warnings. </member> diff --git a/doc/classes/RenderingServer.xml b/doc/classes/RenderingServer.xml index c0d7cca840..b5be04fb01 100644 --- a/doc/classes/RenderingServer.xml +++ b/doc/classes/RenderingServer.xml @@ -3801,21 +3801,18 @@ <constant name="VIEWPORT_SDF_SCALE_MAX" value="3" enum="ViewportSDFScale"> </constant> <constant name="VIEWPORT_MSAA_DISABLED" value="0" enum="ViewportMSAA"> - Multisample antialiasing is disabled. + Multisample antialiasing for 3D is disabled. This is the default value, and also the fastest setting. </constant> <constant name="VIEWPORT_MSAA_2X" value="1" enum="ViewportMSAA"> - Multisample antialiasing uses 2 samples per pixel. + Multisample antialiasing uses 2 samples per pixel for 3D. This has a moderate impact on performance. </constant> <constant name="VIEWPORT_MSAA_4X" value="2" enum="ViewportMSAA"> - Multisample antialiasing uses 4 samples per pixel. + Multisample antialiasing uses 4 samples per pixel for 3D. This has a high impact on performance. </constant> <constant name="VIEWPORT_MSAA_8X" value="3" enum="ViewportMSAA"> - Multisample antialiasing uses 8 samples per pixel. + Multisample antialiasing uses 8 samples per pixel for 3D. This has a very high impact on performance. Likely unsupported on low-end and older hardware. </constant> - <constant name="VIEWPORT_MSAA_16X" value="4" enum="ViewportMSAA"> - Multisample antialiasing uses 16 samples per pixel. - </constant> - <constant name="VIEWPORT_MSAA_MAX" value="5" enum="ViewportMSAA"> + <constant name="VIEWPORT_MSAA_MAX" value="4" enum="ViewportMSAA"> </constant> <constant name="VIEWPORT_SCREEN_SPACE_AA_DISABLED" value="0" enum="ViewportScreenSpaceAA"> </constant> diff --git a/doc/classes/VScrollBar.xml b/doc/classes/VScrollBar.xml index 98a0aea0c7..519cc9c137 100644 --- a/doc/classes/VScrollBar.xml +++ b/doc/classes/VScrollBar.xml @@ -23,6 +23,9 @@ <theme_item name="decrement_highlight" data_type="icon" type="Texture2D"> Displayed when the mouse cursor hovers over the decrement button. </theme_item> + <theme_item name="decrement_pressed" data_type="icon" type="Texture2D"> + Displayed when the decrement button is being pressed. + </theme_item> <theme_item name="grabber" data_type="style" type="StyleBox"> Used as texture for the grabber, the draggable element representing current scroll. </theme_item> @@ -38,6 +41,9 @@ <theme_item name="increment_highlight" data_type="icon" type="Texture2D"> Displayed when the mouse cursor hovers over the increment button. </theme_item> + <theme_item name="increment_pressed" data_type="icon" type="Texture2D"> + Displayed when the increment button is being pressed. + </theme_item> <theme_item name="scroll" data_type="style" type="StyleBox"> Used as background of this [ScrollBar]. </theme_item> diff --git a/doc/classes/Viewport.xml b/doc/classes/Viewport.xml index a02a23517f..06a7177bfc 100644 --- a/doc/classes/Viewport.xml +++ b/doc/classes/Viewport.xml @@ -321,10 +321,7 @@ <constant name="MSAA_8X" value="3" enum="MSAA"> Use 8× Multisample Antialiasing. This has a very high performance cost. The difference between 4× and 8× MSAA may not always be visible in real gameplay conditions. Likely unsupported on low-end and older hardware. </constant> - <constant name="MSAA_16X" value="4" enum="MSAA"> - Use 16× Multisample Antialiasing. This has a very high performance cost. The difference between 8× and 16× MSAA may not always be visible in real gameplay conditions. Likely unsupported on medium and low-end hardware. - </constant> - <constant name="MSAA_MAX" value="5" enum="MSAA"> + <constant name="MSAA_MAX" value="4" enum="MSAA"> Represents the size of the [enum MSAA] enum. </constant> <constant name="SCREEN_SPACE_AA_DISABLED" value="0" enum="ScreenSpaceAA"> diff --git a/drivers/wasapi/audio_driver_wasapi.cpp b/drivers/wasapi/audio_driver_wasapi.cpp index 0b5cfceadc..276fda2a8f 100644 --- a/drivers/wasapi/audio_driver_wasapi.cpp +++ b/drivers/wasapi/audio_driver_wasapi.cpp @@ -499,7 +499,7 @@ Error AudioDriverWASAPI::finish_capture_device() { Error AudioDriverWASAPI::init() { mix_rate = GLOBAL_GET("audio/driver/mix_rate"); - target_latency_ms = GLOBAL_GET("audio/output_latency"); + target_latency_ms = GLOBAL_GET("audio/driver/output_latency"); Error err = init_render_device(); if (err != OK) { diff --git a/editor/editor_autoload_settings.cpp b/editor/editor_autoload_settings.cpp index fad76682b5..362c4c3457 100644 --- a/editor/editor_autoload_settings.cpp +++ b/editor/editor_autoload_settings.cpp @@ -78,6 +78,14 @@ bool EditorAutoloadSettings::_autoload_name_is_valid(const String &p_name, Strin return false; } + if (ScriptServer::is_global_class(p_name)) { + if (r_error) { + *r_error = TTR("Invalid name.") + "\n" + TTR("Must not collide with an existing global script class name."); + } + + return false; + } + for (int i = 0; i < Variant::VARIANT_MAX; i++) { if (Variant::get_type_name(Variant::Type(i)) == p_name) { if (r_error) { diff --git a/editor/editor_node.cpp b/editor/editor_node.cpp index f86a36df2f..cf4f8c0b7d 100644 --- a/editor/editor_node.cpp +++ b/editor/editor_node.cpp @@ -1856,7 +1856,7 @@ void EditorNode::_dialog_action(String p_file) { ml = Ref<MeshLibrary>(memnew(MeshLibrary)); } - MeshLibraryEditor::update_library_file(editor_data.get_edited_scene_root(), ml, true); + MeshLibraryEditor::update_library_file(editor_data.get_edited_scene_root(), ml, true, file_export_lib_apply_xforms->is_pressed()); Error err = ResourceSaver::save(p_file, ml); if (err) { @@ -6811,6 +6811,10 @@ EditorNode::EditorNode() { file_export_lib_merge->set_text(TTR("Merge With Existing")); file_export_lib_merge->set_pressed(true); file_export_lib->get_vbox()->add_child(file_export_lib_merge); + file_export_lib_apply_xforms = memnew(CheckBox); + file_export_lib_apply_xforms->set_text(TTR("Apply MeshInstance Transforms")); + file_export_lib_apply_xforms->set_pressed(false); + file_export_lib->get_vbox()->add_child(file_export_lib_apply_xforms); gui_base->add_child(file_export_lib); file_script = memnew(EditorFileDialog); diff --git a/editor/editor_node.h b/editor/editor_node.h index 03c18a8972..488957b1df 100644 --- a/editor/editor_node.h +++ b/editor/editor_node.h @@ -335,6 +335,7 @@ private: EditorFileDialog *file_script; EditorFileDialog *file_android_build_source; CheckBox *file_export_lib_merge; + CheckBox *file_export_lib_apply_xforms; String current_path; MenuButton *update_spinner; diff --git a/editor/editor_themes.cpp b/editor/editor_themes.cpp index 8a08f4e450..6e5b94dc07 100644 --- a/editor/editor_themes.cpp +++ b/editor/editor_themes.cpp @@ -1154,8 +1154,10 @@ Ref<Theme> create_editor_theme(const Ref<Theme> p_theme) { theme->set_icon("increment", "HScrollBar", empty_icon); theme->set_icon("increment_highlight", "HScrollBar", empty_icon); + theme->set_icon("increment_pressed", "HScrollBar", empty_icon); theme->set_icon("decrement", "HScrollBar", empty_icon); theme->set_icon("decrement_highlight", "HScrollBar", empty_icon); + theme->set_icon("decrement_pressed", "HScrollBar", empty_icon); // VScrollBar theme->set_stylebox("scroll", "VScrollBar", make_stylebox(theme->get_icon("GuiScrollBg", "EditorIcons"), 5, 5, 5, 5, 0, 0, 0, 0)); @@ -1166,8 +1168,10 @@ Ref<Theme> create_editor_theme(const Ref<Theme> p_theme) { theme->set_icon("increment", "VScrollBar", empty_icon); theme->set_icon("increment_highlight", "VScrollBar", empty_icon); + theme->set_icon("increment_pressed", "VScrollBar", empty_icon); theme->set_icon("decrement", "VScrollBar", empty_icon); theme->set_icon("decrement_highlight", "VScrollBar", empty_icon); + theme->set_icon("decrement_pressed", "VScrollBar", empty_icon); // HSlider theme->set_icon("grabber_highlight", "HSlider", theme->get_icon("GuiSliderGrabberHl", "EditorIcons")); diff --git a/editor/multi_node_edit.cpp b/editor/multi_node_edit.cpp index fd4a4334fc..1e707c1a60 100644 --- a/editor/multi_node_edit.cpp +++ b/editor/multi_node_edit.cpp @@ -49,6 +49,11 @@ bool MultiNodeEdit::_set_impl(const StringName &p_name, const Variant &p_value, name = "script"; } + Node *node_path_target = nullptr; + if (p_value.get_type() == Variant::NODE_PATH && p_value != NodePath()) { + node_path_target = es->get_node(p_value); + } + UndoRedo *ur = EditorNode::get_undo_redo(); ur->create_action(TTR("MultiNode Set") + " " + String(name), UndoRedo::MERGE_ENDS); @@ -63,9 +68,11 @@ bool MultiNodeEdit::_set_impl(const StringName &p_name, const Variant &p_value, } if (p_value.get_type() == Variant::NODE_PATH) { - Node *tonode = n->get_node(p_value); - NodePath p_path = n->get_path_to(tonode); - ur->add_do_property(n, name, p_path); + NodePath path; + if (node_path_target) { + path = n->get_path_to(node_path_target); + } + ur->add_do_property(n, name, path); } else { Variant new_value; if (p_field == "") { diff --git a/editor/plugins/canvas_item_editor_plugin.cpp b/editor/plugins/canvas_item_editor_plugin.cpp index d96cc1cd18..f11e51960c 100644 --- a/editor/plugins/canvas_item_editor_plugin.cpp +++ b/editor/plugins/canvas_item_editor_plugin.cpp @@ -5204,7 +5204,9 @@ CanvasItemEditor::CanvasItemEditor(EditorNode *p_editor) { snap_rotation = false; snap_scale = false; snap_relative = false; - snap_pixel = false; + // Enable pixel snapping even if pixel snap rendering is disabled in the Project Settings. + // This results in crisper visuals by preventing 2D nodes from being placed at subpixel coordinates. + snap_pixel = true; snap_target[0] = SNAP_TARGET_NONE; snap_target[1] = SNAP_TARGET_NONE; diff --git a/editor/plugins/mesh_library_editor_plugin.cpp b/editor/plugins/mesh_library_editor_plugin.cpp index b3f92c9d95..18e7480287 100644 --- a/editor/plugins/mesh_library_editor_plugin.cpp +++ b/editor/plugins/mesh_library_editor_plugin.cpp @@ -47,23 +47,25 @@ void MeshLibraryEditor::edit(const Ref<MeshLibrary> &p_mesh_library) { } } -void MeshLibraryEditor::_menu_confirm() { +void MeshLibraryEditor::_menu_remove_confirm() { switch (option) { case MENU_OPTION_REMOVE_ITEM: { mesh_library->remove_item(to_erase); } break; - case MENU_OPTION_UPDATE_FROM_SCENE: { - String existing = mesh_library->get_meta("_editor_source_scene"); - ERR_FAIL_COND(existing == ""); - _import_scene_cbk(existing); - - } break; default: { }; } } -void MeshLibraryEditor::_import_scene(Node *p_scene, Ref<MeshLibrary> p_library, bool p_merge) { +void MeshLibraryEditor::_menu_update_confirm(bool p_apply_xforms) { + cd_update->hide(); + apply_xforms = p_apply_xforms; + String existing = mesh_library->get_meta("_editor_source_scene"); + ERR_FAIL_COND(existing == ""); + _import_scene_cbk(existing); +} + +void MeshLibraryEditor::_import_scene(Node *p_scene, Ref<MeshLibrary> p_library, bool p_merge, bool p_apply_xforms) { if (!p_merge) { p_library->clear(); } @@ -108,6 +110,13 @@ void MeshLibraryEditor::_import_scene(Node *p_scene, Ref<MeshLibrary> p_library, } p_library->set_item_mesh(id, mesh); + + if (p_apply_xforms) { + p_library->set_item_mesh_transform(id, mi->get_transform()); + } else { + p_library->set_item_mesh_transform(id, Transform3D()); + } + mesh_instances[id] = mi; Vector<MeshLibrary::ShapeData> collisions; @@ -197,15 +206,16 @@ void MeshLibraryEditor::_import_scene_cbk(const String &p_str) { ERR_FAIL_COND_MSG(!scene, "Cannot create an instance from PackedScene '" + p_str + "'."); - _import_scene(scene, mesh_library, option == MENU_OPTION_UPDATE_FROM_SCENE); + _import_scene(scene, mesh_library, option == MENU_OPTION_UPDATE_FROM_SCENE, apply_xforms); memdelete(scene); mesh_library->set_meta("_editor_source_scene", p_str); + menu->get_popup()->set_item_disabled(menu->get_popup()->get_item_index(MENU_OPTION_UPDATE_FROM_SCENE), false); } -Error MeshLibraryEditor::update_library_file(Node *p_base_scene, Ref<MeshLibrary> ml, bool p_merge) { - _import_scene(p_base_scene, ml, p_merge); +Error MeshLibraryEditor::update_library_file(Node *p_base_scene, Ref<MeshLibrary> ml, bool p_merge, bool p_apply_xforms) { + _import_scene(p_base_scene, ml, p_merge, p_apply_xforms); return OK; } @@ -219,16 +229,21 @@ void MeshLibraryEditor::_menu_cbk(int p_option) { String p = editor->get_inspector()->get_selected_path(); if (p.begins_with("/MeshLibrary/item") && p.get_slice_count("/") >= 3) { to_erase = p.get_slice("/", 3).to_int(); - cd->set_text(vformat(TTR("Remove item %d?"), to_erase)); - cd->popup_centered(Size2(300, 60)); + cd_remove->set_text(vformat(TTR("Remove item %d?"), to_erase)); + cd_remove->popup_centered(Size2(300, 60)); } } break; case MENU_OPTION_IMPORT_FROM_SCENE: { + apply_xforms = false; + file->popup_file_dialog(); + } break; + case MENU_OPTION_IMPORT_FROM_SCENE_APPLY_XFORMS: { + apply_xforms = true; file->popup_file_dialog(); } break; case MENU_OPTION_UPDATE_FROM_SCENE: { - cd->set_text(vformat(TTR("Update from existing scene?:\n%s"), String(mesh_library->get_meta("_editor_source_scene")))); - cd->popup_centered(Size2(500, 60)); + cd_update->set_text(vformat(TTR("Update from existing scene?:\n%s"), String(mesh_library->get_meta("_editor_source_scene")))); + cd_update->popup_centered(Size2(500, 60)); } break; } } @@ -258,16 +273,22 @@ MeshLibraryEditor::MeshLibraryEditor(EditorNode *p_editor) { menu->get_popup()->add_item(TTR("Add Item"), MENU_OPTION_ADD_ITEM); menu->get_popup()->add_item(TTR("Remove Selected Item"), MENU_OPTION_REMOVE_ITEM); menu->get_popup()->add_separator(); - menu->get_popup()->add_item(TTR("Import from Scene"), MENU_OPTION_IMPORT_FROM_SCENE); + menu->get_popup()->add_item(TTR("Import from Scene (Ignore Transforms)"), MENU_OPTION_IMPORT_FROM_SCENE); + menu->get_popup()->add_item(TTR("Import from Scene (Apply Transforms)"), MENU_OPTION_IMPORT_FROM_SCENE_APPLY_XFORMS); menu->get_popup()->add_item(TTR("Update from Scene"), MENU_OPTION_UPDATE_FROM_SCENE); menu->get_popup()->set_item_disabled(menu->get_popup()->get_item_index(MENU_OPTION_UPDATE_FROM_SCENE), true); menu->get_popup()->connect("id_pressed", callable_mp(this, &MeshLibraryEditor::_menu_cbk)); menu->hide(); editor = p_editor; - cd = memnew(ConfirmationDialog); - add_child(cd); - cd->get_ok_button()->connect("pressed", callable_mp(this, &MeshLibraryEditor::_menu_confirm)); + cd_remove = memnew(ConfirmationDialog); + add_child(cd_remove); + cd_remove->get_ok_button()->connect("pressed", callable_mp(this, &MeshLibraryEditor::_menu_remove_confirm)); + cd_update = memnew(ConfirmationDialog); + add_child(cd_update); + cd_update->get_ok_button()->set_text("Apply without Transforms"); + cd_update->get_ok_button()->connect("pressed", callable_mp(this, &MeshLibraryEditor::_menu_update_confirm), varray(false)); + cd_update->add_button("Apply with Transforms")->connect("pressed", callable_mp(this, &MeshLibraryEditor::_menu_update_confirm), varray(true)); } void MeshLibraryEditorPlugin::edit(Object *p_node) { diff --git a/editor/plugins/mesh_library_editor_plugin.h b/editor/plugins/mesh_library_editor_plugin.h index 6c33c8bb9e..9e225ffb9b 100644 --- a/editor/plugins/mesh_library_editor_plugin.h +++ b/editor/plugins/mesh_library_editor_plugin.h @@ -41,23 +41,27 @@ class MeshLibraryEditor : public Control { EditorNode *editor; MenuButton *menu; - ConfirmationDialog *cd; + ConfirmationDialog *cd_remove; + ConfirmationDialog *cd_update; EditorFileDialog *file; + bool apply_xforms; int to_erase; enum { MENU_OPTION_ADD_ITEM, MENU_OPTION_REMOVE_ITEM, MENU_OPTION_UPDATE_FROM_SCENE, - MENU_OPTION_IMPORT_FROM_SCENE + MENU_OPTION_IMPORT_FROM_SCENE, + MENU_OPTION_IMPORT_FROM_SCENE_APPLY_XFORMS }; int option; void _import_scene_cbk(const String &p_str); void _menu_cbk(int p_option); - void _menu_confirm(); + void _menu_remove_confirm(); + void _menu_update_confirm(bool p_apply_xforms); - static void _import_scene(Node *p_scene, Ref<MeshLibrary> p_library, bool p_merge); + static void _import_scene(Node *p_scene, Ref<MeshLibrary> p_library, bool p_merge, bool p_apply_xforms); protected: static void _bind_methods(); @@ -66,7 +70,7 @@ public: MenuButton *get_menu_button() const { return menu; } void edit(const Ref<MeshLibrary> &p_mesh_library); - static Error update_library_file(Node *p_base_scene, Ref<MeshLibrary> ml, bool p_merge = true); + static Error update_library_file(Node *p_base_scene, Ref<MeshLibrary> ml, bool p_merge = true, bool p_apply_xforms = false); MeshLibraryEditor(EditorNode *p_editor); }; diff --git a/editor/translations/el.po b/editor/translations/el.po index e773b011a4..93b5941f64 100644 --- a/editor/translations/el.po +++ b/editor/translations/el.po @@ -6,7 +6,7 @@ # Georgios Katsanakis <geo.elgeo@gmail.com>, 2019. # Overloaded <manoschool@yahoo.gr>, 2019. # Eternal Death <eternaldeath0001@gmail.com>, 2019. -# Overloaded @ Orama Interactive https://orama-interactive.com/ <manoschool@yahoo.gr>, 2020. +# Overloaded @ Orama Interactive http://orama-interactive.com/ <manoschool@yahoo.gr>, 2020. # pandektis <pandektis@gmail.com>, 2020. # KostasMSC <kargyris@athtech.gr>, 2020. # lawfulRobot <czavantias@gmail.com>, 2020, 2021. diff --git a/main/main.cpp b/main/main.cpp index ece194b0f1..fe6df43364 100644 --- a/main/main.cpp +++ b/main/main.cpp @@ -403,6 +403,7 @@ Error Main::test_setup() { GLOBAL_DEF("debug/settings/crash_handler/message", String("Please include this when reporting the bug on https://github.com/godotengine/godot/issues")); + GLOBAL_DEF_RST("rendering/occlusion_culling/bvh_build_quality", 2); translation_server = memnew(TranslationServer); diff --git a/modules/csg/csg_shape.cpp b/modules/csg/csg_shape.cpp index bf11cc7f68..452fb32d9d 100644 --- a/modules/csg/csg_shape.cpp +++ b/modules/csg/csg_shape.cpp @@ -280,7 +280,7 @@ void CSGShape3D::mikktSetTSpaceDefault(const SMikkTSpaceContext *pContext, const } void CSGShape3D::_update_shape() { - if (parent) { + if (parent || !is_inside_tree()) { return; } diff --git a/modules/gdscript/gdscript_analyzer.cpp b/modules/gdscript/gdscript_analyzer.cpp index fc0bef3ba2..06db46173c 100644 --- a/modules/gdscript/gdscript_analyzer.cpp +++ b/modules/gdscript/gdscript_analyzer.cpp @@ -171,7 +171,7 @@ Error GDScriptAnalyzer::check_native_member_name_conflict(const StringName &p_me } if (class_exists(p_member_name)) { - push_error(vformat(R"(The class "%s" shadows a native class.)", p_member_name), p_member_node); + push_error(vformat(R"(The member "%s" shadows a native class.)", p_member_name), p_member_node); return ERR_PARSE_ERROR; } @@ -218,6 +218,17 @@ Error GDScriptAnalyzer::resolve_inheritance(GDScriptParser::ClassNode *p_class, p_class->fqcn = p_class->outer->fqcn + "::" + String(p_class->identifier->name); } + if (p_class->identifier) { + StringName class_name = p_class->identifier->name; + if (class_exists(class_name)) { + push_error(vformat(R"(Class "%s" hides a native class.)", class_name), p_class->identifier); + } else if (ScriptServer::is_global_class(class_name) && (ScriptServer::get_global_class_path(class_name) != parser->script_path || p_class != parser->head)) { + push_error(vformat(R"(Class "%s" hides a global script class.)", class_name), p_class->identifier); + } else if (ProjectSettings::get_singleton()->has_autoload(class_name) && ProjectSettings::get_singleton()->get_autoload(class_name).is_singleton) { + push_error(vformat(R"(Class "%s" hides an autoload singleton.)", class_name), p_class->identifier); + } + } + GDScriptParser::DataType result; // Set datatype for class. @@ -681,8 +692,9 @@ void GDScriptAnalyzer::resolve_class_interface(GDScriptParser::ClassNode *p_clas specified_type.is_meta_type = false; } - GDScriptParser::DataType datatype = member.constant->get_datatype(); + GDScriptParser::DataType datatype; if (member.constant->initializer) { + datatype = member.constant->initializer->get_datatype(); if (member.constant->initializer->type == GDScriptParser::Node::ARRAY) { GDScriptParser::ArrayNode *array = static_cast<GDScriptParser::ArrayNode *>(member.constant->initializer); const_fold_array(array); @@ -2516,14 +2528,29 @@ void GDScriptAnalyzer::reduce_identifier_from_base(GDScriptParser::IdentifierNod while (outer != nullptr) { if (outer->has_member(name)) { const GDScriptParser::ClassNode::Member &member = outer->get_member(name); - if (member.type == GDScriptParser::ClassNode::Member::CONSTANT) { - // TODO: Make sure loops won't cause problem. And make special error message for those. - // For out-of-order resolution: - reduce_expression(member.constant->initializer); - p_identifier->set_datatype(member.get_datatype()); - p_identifier->is_constant = true; - p_identifier->reduced_value = member.constant->initializer->reduced_value; - return; + switch (member.type) { + case GDScriptParser::ClassNode::Member::CONSTANT: { + // TODO: Make sure loops won't cause problem. And make special error message for those. + // For out-of-order resolution: + reduce_expression(member.constant->initializer); + p_identifier->set_datatype(member.get_datatype()); + p_identifier->is_constant = true; + p_identifier->reduced_value = member.constant->initializer->reduced_value; + return; + } break; + case GDScriptParser::ClassNode::Member::ENUM_VALUE: { + p_identifier->set_datatype(member.get_datatype()); + p_identifier->is_constant = true; + p_identifier->reduced_value = member.enum_value.value; + return; + } break; + case GDScriptParser::ClassNode::Member::ENUM: { + p_identifier->set_datatype(member.get_datatype()); + p_identifier->is_constant = false; + return; + } break; + default: + break; } } outer = outer->outer; diff --git a/modules/gdscript/gdscript_byte_codegen.cpp b/modules/gdscript/gdscript_byte_codegen.cpp index bed67b55f0..1127488db8 100644 --- a/modules/gdscript/gdscript_byte_codegen.cpp +++ b/modules/gdscript/gdscript_byte_codegen.cpp @@ -864,6 +864,12 @@ void GDScriptByteCodeGenerator::write_assign_default_parameter(const Address &p_ function->default_arguments.push_back(opcodes.size()); } +void GDScriptByteCodeGenerator::write_store_global(const Address &p_dst, int p_global_index) { + append(GDScriptFunction::OPCODE_STORE_GLOBAL, 1); + append(p_dst); + append(p_global_index); +} + void GDScriptByteCodeGenerator::write_store_named_global(const Address &p_dst, const StringName &p_global) { append(GDScriptFunction::OPCODE_STORE_NAMED_GLOBAL, 1); append(p_dst); diff --git a/modules/gdscript/gdscript_byte_codegen.h b/modules/gdscript/gdscript_byte_codegen.h index ce1a043b28..dcc11ebdce 100644 --- a/modules/gdscript/gdscript_byte_codegen.h +++ b/modules/gdscript/gdscript_byte_codegen.h @@ -454,6 +454,7 @@ public: virtual void write_assign_true(const Address &p_target) override; virtual void write_assign_false(const Address &p_target) override; virtual void write_assign_default_parameter(const Address &p_dst, const Address &p_src) override; + virtual void write_store_global(const Address &p_dst, int p_global_index) override; virtual void write_store_named_global(const Address &p_dst, const StringName &p_global) override; virtual void write_cast(const Address &p_target, const Address &p_source, const GDScriptDataType &p_type) override; virtual void write_call(const Address &p_target, const Address &p_base, const StringName &p_function_name, const Vector<Address> &p_arguments) override; diff --git a/modules/gdscript/gdscript_codegen.h b/modules/gdscript/gdscript_codegen.h index 7713d13bc8..e6ecc92d55 100644 --- a/modules/gdscript/gdscript_codegen.h +++ b/modules/gdscript/gdscript_codegen.h @@ -115,6 +115,7 @@ public: virtual void write_assign_true(const Address &p_target) = 0; virtual void write_assign_false(const Address &p_target) = 0; virtual void write_assign_default_parameter(const Address &dst, const Address &src) = 0; + virtual void write_store_global(const Address &p_dst, int p_global_index) = 0; virtual void write_store_named_global(const Address &p_dst, const StringName &p_global) = 0; virtual void write_cast(const Address &p_target, const Address &p_source, const GDScriptDataType &p_type) = 0; virtual void write_call(const Address &p_target, const Address &p_base, const StringName &p_function_name, const Vector<Address> &p_arguments) = 0; diff --git a/modules/gdscript/gdscript_compiler.cpp b/modules/gdscript/gdscript_compiler.cpp index 736f6eae79..b0d0b02443 100644 --- a/modules/gdscript/gdscript_compiler.cpp +++ b/modules/gdscript/gdscript_compiler.cpp @@ -35,6 +35,8 @@ #include "gdscript_cache.h" #include "gdscript_utility_functions.h" +#include "core/config/project_settings.h" + bool GDScriptCompiler::_is_class_member_property(CodeGen &codegen, const StringName &p_name) { if (codegen.function_node && codegen.function_node->is_static) { return false; @@ -316,10 +318,21 @@ GDScriptCodeGenerator::Address GDScriptCompiler::_parse_expression(CodeGen &code } } + // Try globals. if (GDScriptLanguage::get_singleton()->get_global_map().has(identifier)) { - int idx = GDScriptLanguage::get_singleton()->get_global_map()[identifier]; - Variant global = GDScriptLanguage::get_singleton()->get_global_array()[idx]; - return codegen.add_constant(global); // TODO: Get type. + // If it's an autoload singleton, we postpone to load it at runtime. + // This is so one autoload doesn't try to load another before it's compiled. + OrderedHashMap<StringName, ProjectSettings::AutoloadInfo> autoloads = ProjectSettings::get_singleton()->get_autoload_list(); + if (autoloads.has(identifier) && autoloads[identifier].is_singleton) { + GDScriptCodeGenerator::Address global = codegen.add_temporary(_gdtype_from_datatype(in->get_datatype())); + int idx = GDScriptLanguage::get_singleton()->get_global_map()[identifier]; + gen->write_store_global(global, idx); + return global; + } else { + int idx = GDScriptLanguage::get_singleton()->get_global_map()[identifier]; + Variant global = GDScriptLanguage::get_singleton()->get_global_array()[idx]; + return codegen.add_constant(global); + } } // Try global classes. diff --git a/modules/gdscript/gdscript_disassembler.cpp b/modules/gdscript/gdscript_disassembler.cpp index 1acb9ceddc..9287df2ea0 100644 --- a/modules/gdscript/gdscript_disassembler.cpp +++ b/modules/gdscript/gdscript_disassembler.cpp @@ -914,6 +914,14 @@ void GDScriptFunction::disassemble(const Vector<String> &p_code_lines) const { incr += 5; } break; DISASSEMBLE_ITERATE_TYPES(DISASSEMBLE_ITERATE); + case OPCODE_STORE_GLOBAL: { + text += "store global "; + text += DADDR(1); + text += " = "; + text += String::num_int64(_code_ptr[ip + 2]); + + incr += 3; + } break; case OPCODE_STORE_NAMED_GLOBAL: { text += "store named global "; text += DADDR(1); diff --git a/modules/gdscript/gdscript_editor.cpp b/modules/gdscript/gdscript_editor.cpp index f809a4dab8..f79e5726ce 100644 --- a/modules/gdscript/gdscript_editor.cpp +++ b/modules/gdscript/gdscript_editor.cpp @@ -1733,7 +1733,7 @@ static bool _guess_identifier_type(GDScriptParser::CompletionContext &p_context, } } - if (is_function_parameter && p_context.current_function && p_context.current_class) { + if (is_function_parameter && p_context.current_function && p_context.current_function->source_lambda == nullptr && p_context.current_class) { // Check if it's override of native function, then we can assume the type from the signature. GDScriptParser::DataType base_type = p_context.current_class->base_type; while (base_type.is_set()) { diff --git a/modules/gdscript/gdscript_function.h b/modules/gdscript/gdscript_function.h index b21cb47910..9d076a8e4c 100644 --- a/modules/gdscript/gdscript_function.h +++ b/modules/gdscript/gdscript_function.h @@ -348,6 +348,7 @@ public: OPCODE_ITERATE_PACKED_VECTOR3_ARRAY, OPCODE_ITERATE_PACKED_COLOR_ARRAY, OPCODE_ITERATE_OBJECT, + OPCODE_STORE_GLOBAL, OPCODE_STORE_NAMED_GLOBAL, OPCODE_TYPE_ADJUST_BOOL, OPCODE_TYPE_ADJUST_INT, diff --git a/modules/gdscript/gdscript_parser.cpp b/modules/gdscript/gdscript_parser.cpp index 6c3d4367e4..19584ce194 100644 --- a/modules/gdscript/gdscript_parser.cpp +++ b/modules/gdscript/gdscript_parser.cpp @@ -337,12 +337,29 @@ Error GDScriptParser::parse(const String &p_source_code, const String &p_script_ tokenizer.set_cursor_position(cursor_line, cursor_column); script_path = p_script_path; current = tokenizer.scan(); - // Avoid error as the first token. - while (current.type == GDScriptTokenizer::Token::ERROR) { - push_error(current.literal); + // Avoid error or newline as the first token. + // The latter can mess with the parser when opening files filled exclusively with comments and newlines. + while (current.type == GDScriptTokenizer::Token::ERROR || current.type == GDScriptTokenizer::Token::NEWLINE) { + if (current.type == GDScriptTokenizer::Token::ERROR) { + push_error(current.literal); + } current = tokenizer.scan(); } +#ifdef DEBUG_ENABLED + // Warn about parsing an empty script file: + if (current.type == GDScriptTokenizer::Token::TK_EOF) { + // Create a dummy Node for the warning, pointing to the very beginning of the file + Node *nd = alloc_node<PassNode>(); + nd->start_line = 1; + nd->start_column = 0; + nd->end_line = 1; + nd->leftmost_column = 0; + nd->rightmost_column = 0; + push_warning(nd, GDScriptWarning::EMPTY_FILE); + } +#endif + push_multiline(false); // Keep one for the whole parsing. parse_program(); pop_multiline(); @@ -2369,8 +2386,12 @@ GDScriptParser::ExpressionNode *GDScriptParser::parse_assignment(ExpressionNode } #ifdef DEBUG_ENABLED - if (has_operator && source_variable != nullptr && source_variable->assignments == 0) { - push_warning(assignment, GDScriptWarning::UNASSIGNED_VARIABLE_OP_ASSIGN, source_variable->identifier->name); + if (source_variable != nullptr) { + if (has_operator && source_variable->assignments == 0) { + push_warning(assignment, GDScriptWarning::UNASSIGNED_VARIABLE_OP_ASSIGN, source_variable->identifier->name); + } + + source_variable->assignments += 1; } #endif diff --git a/modules/gdscript/gdscript_vm.cpp b/modules/gdscript/gdscript_vm.cpp index 882256b7e3..64fd7eca8a 100644 --- a/modules/gdscript/gdscript_vm.cpp +++ b/modules/gdscript/gdscript_vm.cpp @@ -322,6 +322,7 @@ void (*type_init_function_table[])(Variant *) = { &&OPCODE_ITERATE_PACKED_VECTOR3_ARRAY, \ &&OPCODE_ITERATE_PACKED_COLOR_ARRAY, \ &&OPCODE_ITERATE_OBJECT, \ + &&OPCODE_STORE_GLOBAL, \ &&OPCODE_STORE_NAMED_GLOBAL, \ &&OPCODE_TYPE_ADJUST_BOOL, \ &&OPCODE_TYPE_ADJUST_INT, \ @@ -3116,6 +3117,18 @@ Variant GDScriptFunction::call(GDScriptInstance *p_instance, const Variant **p_a } DISPATCH_OPCODE; + OPCODE(OPCODE_STORE_GLOBAL) { + CHECK_SPACE(3); + int global_idx = _code_ptr[ip + 2]; + GD_ERR_BREAK(global_idx < 0 || global_idx >= GDScriptLanguage::get_singleton()->get_global_array_size()); + + GET_INSTRUCTION_ARG(dst, 0); + *dst = GDScriptLanguage::get_singleton()->get_global_array()[global_idx]; + + ip += 3; + } + DISPATCH_OPCODE; + OPCODE(OPCODE_STORE_NAMED_GLOBAL) { CHECK_SPACE(3); int globalname_idx = _code_ptr[ip + 2]; diff --git a/modules/gdscript/gdscript_warning.cpp b/modules/gdscript/gdscript_warning.cpp index ad41b60a4e..7a483a16ba 100644 --- a/modules/gdscript/gdscript_warning.cpp +++ b/modules/gdscript/gdscript_warning.cpp @@ -145,6 +145,9 @@ String GDScriptWarning::get_message() const { case REDUNDANT_AWAIT: { return R"("await" keyword not needed in this case, because the expression isn't a coroutine nor a signal.)"; } + case EMPTY_FILE: { + return "Empty script file."; + } case WARNING_MAX: break; // Can't happen, but silences warning } @@ -190,6 +193,7 @@ String GDScriptWarning::get_name_from_code(Code p_code) { "ASSERT_ALWAYS_TRUE", "ASSERT_ALWAYS_FALSE", "REDUNDANT_AWAIT", + "EMPTY_FILE", }; static_assert((sizeof(names) / sizeof(*names)) == WARNING_MAX, "Amount of warning types don't match the amount of warning names."); diff --git a/modules/gdscript/gdscript_warning.h b/modules/gdscript/gdscript_warning.h index 4b295b5eb8..8de46b08c1 100644 --- a/modules/gdscript/gdscript_warning.h +++ b/modules/gdscript/gdscript_warning.h @@ -68,6 +68,7 @@ public: ASSERT_ALWAYS_TRUE, // Expression for assert argument is always true. ASSERT_ALWAYS_FALSE, // Expression for assert argument is always false. REDUNDANT_AWAIT, // await is used but expression is synchronous (not a signal nor a coroutine). + EMPTY_FILE, // A script file is empty. WARNING_MAX, }; diff --git a/modules/gdscript/tests/gdscript_test_runner.cpp b/modules/gdscript/tests/gdscript_test_runner.cpp index 6225e5d1eb..c383830c82 100644 --- a/modules/gdscript/tests/gdscript_test_runner.cpp +++ b/modules/gdscript/tests/gdscript_test_runner.cpp @@ -415,6 +415,7 @@ GDScriptTest::TestResult GDScriptTest::execute_test_code(bool p_is_generating) { TestResult result; result.status = GDTEST_OK; result.output = String(); + result.passed = false; Error err = OK; @@ -496,7 +497,12 @@ GDScriptTest::TestResult GDScriptTest::execute_test_code(bool p_is_generating) { } return result; } - + // Script files matching this pattern are allowed to not contain a test() function. + if (source_file.match("*.notest.gd")) { + enable_stdout(); + result.passed = check_output(result.output); + return result; + } // Test running. const Map<StringName, GDScriptFunction *>::Element *test_function_element = script->get_member_functions().find(GDScriptTestRunner::test_function_name); if (test_function_element == nullptr) { diff --git a/modules/gdscript/tests/scripts/analyzer/features/constants_from_parent.gd b/modules/gdscript/tests/scripts/analyzer/features/constants_from_parent.gd new file mode 100644 index 0000000000..135b6c3d85 --- /dev/null +++ b/modules/gdscript/tests/scripts/analyzer/features/constants_from_parent.gd @@ -0,0 +1,16 @@ +extends Node + +const NO_TYPE_CONST = 0 +const TYPE_CONST: int = 1 +const GUESS_TYPE_CONST := 2 + +class Test: + var a = NO_TYPE_CONST + var b = TYPE_CONST + var c = GUESS_TYPE_CONST + +func test(): + var test_instance = Test.new() + prints("a", test_instance.a, test_instance.a == NO_TYPE_CONST) + prints("b", test_instance.b, test_instance.b == TYPE_CONST) + prints("c", test_instance.c, test_instance.c == GUESS_TYPE_CONST) diff --git a/modules/gdscript/tests/scripts/analyzer/features/constants_from_parent.out b/modules/gdscript/tests/scripts/analyzer/features/constants_from_parent.out new file mode 100644 index 0000000000..a96bb84246 --- /dev/null +++ b/modules/gdscript/tests/scripts/analyzer/features/constants_from_parent.out @@ -0,0 +1,4 @@ +GDTEST_OK +a 0 true +b 1 true +c 2 true diff --git a/modules/gdscript/tests/scripts/analyzer/features/enum_from_parent.gd b/modules/gdscript/tests/scripts/analyzer/features/enum_from_parent.gd new file mode 100644 index 0000000000..5f57c5b8c2 --- /dev/null +++ b/modules/gdscript/tests/scripts/analyzer/features/enum_from_parent.gd @@ -0,0 +1,14 @@ +extends Node + +enum Named { VALUE_A, VALUE_B, VALUE_C = 42 } + +class Test: + var a = Named.VALUE_A + var b = Named.VALUE_B + var c = Named.VALUE_C + +func test(): + var test_instance = Test.new() + prints("a", test_instance.a, test_instance.a == Named.VALUE_A) + prints("b", test_instance.b, test_instance.b == Named.VALUE_B) + prints("c", test_instance.c, test_instance.c == Named.VALUE_C) diff --git a/modules/gdscript/tests/scripts/analyzer/features/enum_from_parent.out b/modules/gdscript/tests/scripts/analyzer/features/enum_from_parent.out new file mode 100644 index 0000000000..c160839da3 --- /dev/null +++ b/modules/gdscript/tests/scripts/analyzer/features/enum_from_parent.out @@ -0,0 +1,4 @@ +GDTEST_OK +a 0 true +b 1 true +c 42 true diff --git a/modules/gdscript/tests/scripts/analyzer/features/enum_value_from_parent.gd b/modules/gdscript/tests/scripts/analyzer/features/enum_value_from_parent.gd new file mode 100644 index 0000000000..26edce353d --- /dev/null +++ b/modules/gdscript/tests/scripts/analyzer/features/enum_value_from_parent.gd @@ -0,0 +1,14 @@ +extends Node + +enum { VALUE_A, VALUE_B, VALUE_C = 42 } + +class Test: + var a = VALUE_A + var b = VALUE_B + var c = VALUE_C + +func test(): + var test_instance = Test.new() + prints("a", test_instance.a, test_instance.a == VALUE_A) + prints("b", test_instance.b, test_instance.b == VALUE_B) + prints("c", test_instance.c, test_instance.c == VALUE_C) diff --git a/modules/gdscript/tests/scripts/analyzer/features/enum_value_from_parent.out b/modules/gdscript/tests/scripts/analyzer/features/enum_value_from_parent.out new file mode 100644 index 0000000000..c160839da3 --- /dev/null +++ b/modules/gdscript/tests/scripts/analyzer/features/enum_value_from_parent.out @@ -0,0 +1,4 @@ +GDTEST_OK +a 0 true +b 1 true +c 42 true diff --git a/modules/gdscript/tests/scripts/parser/features/variable_declaration.gd b/modules/gdscript/tests/scripts/parser/features/variable_declaration.gd index 3b48f10ca7..38567d35c6 100644 --- a/modules/gdscript/tests/scripts/parser/features/variable_declaration.gd +++ b/modules/gdscript/tests/scripts/parser/features/variable_declaration.gd @@ -1,12 +1,19 @@ -var a # No init. -var b = 42 # Init. +var m1 # No init. +var m2 = 22 # Init. +var m3: String # No init, typed. +var m4: String = "44" # Init, typed. func test(): - var c # No init, local. - var d = 23 # Init, local. + var loc5 # No init, local. + var loc6 = 66 # Init, local. + var loc7: String # No init, typed. + var loc8: String = "88" # Init, typed. - a = 1 - c = 2 + m1 = 11 + m3 = "33" - prints(a, b, c, d) + loc5 = 55 + loc7 = "77" + + prints(m1, m2, m3, m4, loc5, loc6, loc7, loc8) print("OK") diff --git a/modules/gdscript/tests/scripts/parser/features/variable_declaration.out b/modules/gdscript/tests/scripts/parser/features/variable_declaration.out index 2e0a63c024..7817dd3169 100644 --- a/modules/gdscript/tests/scripts/parser/features/variable_declaration.out +++ b/modules/gdscript/tests/scripts/parser/features/variable_declaration.out @@ -1,7 +1,3 @@ GDTEST_OK ->> WARNING ->> Line: 5 ->> UNASSIGNED_VARIABLE ->> The variable 'c' was used but never assigned a value. -1 42 2 23 +11 22 33 44 55 66 77 88 OK diff --git a/modules/gdscript/tests/scripts/parser/warnings/empty_file.notest.gd b/modules/gdscript/tests/scripts/parser/warnings/empty_file.notest.gd new file mode 100644 index 0000000000..8b13789179 --- /dev/null +++ b/modules/gdscript/tests/scripts/parser/warnings/empty_file.notest.gd @@ -0,0 +1 @@ + diff --git a/modules/gdscript/tests/scripts/parser/warnings/empty_file.notest.out b/modules/gdscript/tests/scripts/parser/warnings/empty_file.notest.out new file mode 100644 index 0000000000..20eec212ba --- /dev/null +++ b/modules/gdscript/tests/scripts/parser/warnings/empty_file.notest.out @@ -0,0 +1,4 @@ +>> WARNING +>> Line: 1 +>> EMPTY_FILE +>> Empty script file. diff --git a/modules/gdscript/tests/scripts/parser/warnings/empty_file_comment.notest.gd b/modules/gdscript/tests/scripts/parser/warnings/empty_file_comment.notest.gd new file mode 100644 index 0000000000..15cd95ff2b --- /dev/null +++ b/modules/gdscript/tests/scripts/parser/warnings/empty_file_comment.notest.gd @@ -0,0 +1 @@ +#a comment diff --git a/modules/gdscript/tests/scripts/parser/warnings/empty_file_comment.notest.out b/modules/gdscript/tests/scripts/parser/warnings/empty_file_comment.notest.out new file mode 100644 index 0000000000..20eec212ba --- /dev/null +++ b/modules/gdscript/tests/scripts/parser/warnings/empty_file_comment.notest.out @@ -0,0 +1,4 @@ +>> WARNING +>> Line: 1 +>> EMPTY_FILE +>> Empty script file. diff --git a/modules/gdscript/tests/scripts/parser/warnings/empty_file_newline.notest.gd b/modules/gdscript/tests/scripts/parser/warnings/empty_file_newline.notest.gd new file mode 100644 index 0000000000..b28b04f643 --- /dev/null +++ b/modules/gdscript/tests/scripts/parser/warnings/empty_file_newline.notest.gd @@ -0,0 +1,3 @@ + + + diff --git a/modules/gdscript/tests/scripts/parser/warnings/empty_file_newline.notest.out b/modules/gdscript/tests/scripts/parser/warnings/empty_file_newline.notest.out new file mode 100644 index 0000000000..20eec212ba --- /dev/null +++ b/modules/gdscript/tests/scripts/parser/warnings/empty_file_newline.notest.out @@ -0,0 +1,4 @@ +>> WARNING +>> Line: 1 +>> EMPTY_FILE +>> Empty script file. diff --git a/modules/gdscript/tests/scripts/parser/warnings/empty_file_newline_comment.notest.gd b/modules/gdscript/tests/scripts/parser/warnings/empty_file_newline_comment.notest.gd new file mode 100644 index 0000000000..ecdba44d21 --- /dev/null +++ b/modules/gdscript/tests/scripts/parser/warnings/empty_file_newline_comment.notest.gd @@ -0,0 +1,4 @@ +#a comment, followed by a bunch of newlines + + + diff --git a/modules/gdscript/tests/scripts/parser/warnings/empty_file_newline_comment.notest.out b/modules/gdscript/tests/scripts/parser/warnings/empty_file_newline_comment.notest.out new file mode 100644 index 0000000000..20eec212ba --- /dev/null +++ b/modules/gdscript/tests/scripts/parser/warnings/empty_file_newline_comment.notest.out @@ -0,0 +1,4 @@ +>> WARNING +>> Line: 1 +>> EMPTY_FILE +>> Empty script file. diff --git a/modules/gridmap/grid_map.cpp b/modules/gridmap/grid_map.cpp index 8e8b6f14ad..487e6deac0 100644 --- a/modules/gridmap/grid_map.cpp +++ b/modules/gridmap/grid_map.cpp @@ -475,7 +475,7 @@ bool GridMap::_octant_update(const OctantKey &p_key) { } Pair<Transform3D, IndexKey> p; - p.first = xform; + p.first = xform * mesh_library->get_item_mesh_transform(c.item); p.second = E->get(); multimesh_items[c.item].push_back(p); } diff --git a/modules/gridmap/grid_map_editor_plugin.cpp b/modules/gridmap/grid_map_editor_plugin.cpp index 2331a12d0f..c170bb107e 100644 --- a/modules/gridmap/grid_map_editor_plugin.cpp +++ b/modules/gridmap/grid_map_editor_plugin.cpp @@ -255,6 +255,12 @@ void GridMapEditor::_update_cursor_transform() { cursor_transform.basis *= node->get_cell_scale(); cursor_transform = node->get_global_transform() * cursor_transform; + if (selected_palette >= 0) { + if (node && !node->get_mesh_library().is_null()) { + cursor_transform *= node->get_mesh_library()->get_item_mesh_transform(selected_palette); + } + } + if (cursor_instance.is_valid()) { RenderingServer::get_singleton()->instance_set_transform(cursor_instance, cursor_transform); RenderingServer::get_singleton()->instance_set_visible(cursor_instance, cursor_visible); diff --git a/modules/mono/csharp_script.cpp b/modules/mono/csharp_script.cpp index 3437dbd194..fab950019f 100644 --- a/modules/mono/csharp_script.cpp +++ b/modules/mono/csharp_script.cpp @@ -1861,6 +1861,28 @@ Variant::Type CSharpInstance::get_property_type(const StringName &p_name, bool * return Variant::NIL; } +void CSharpInstance::get_method_list(List<MethodInfo> *p_list) const { + if (!script->is_valid() || !script->script_class) + return; + + GD_MONO_SCOPE_THREAD_ATTACH; + + // TODO: We're filtering out constructors but there may be other methods unsuitable for explicit calls. + GDMonoClass *top = script->script_class; + + while (top && top != script->native) { + const Vector<GDMonoMethod *> &methods = top->get_all_methods(); + for (int i = 0; i < methods.size(); ++i) { + MethodInfo minfo = methods[i]->get_method_info(); + if (minfo.name != CACHED_STRING_NAME(dotctor)) { + p_list->push_back(minfo); + } + } + + top = top->get_parent_class(); + } +} + bool CSharpInstance::has_method(const StringName &p_method) const { if (!script.is_valid()) { return false; @@ -3280,10 +3302,19 @@ void CSharpScript::get_script_method_list(List<MethodInfo> *p_list) const { GD_MONO_SCOPE_THREAD_ATTACH; - // TODO: Filter out things unsuitable for explicit calls, like constructors. - const Vector<GDMonoMethod *> &methods = script_class->get_all_methods(); - for (int i = 0; i < methods.size(); ++i) { - p_list->push_back(methods[i]->get_method_info()); + // TODO: We're filtering out constructors but there may be other methods unsuitable for explicit calls. + GDMonoClass *top = script_class; + + while (top && top != native) { + const Vector<GDMonoMethod *> &methods = top->get_all_methods(); + for (int i = 0; i < methods.size(); ++i) { + MethodInfo minfo = methods[i]->get_method_info(); + if (minfo.name != CACHED_STRING_NAME(dotctor)) { + p_list->push_back(methods[i]->get_method_info()); + } + } + + top = top->get_parent_class(); } } diff --git a/modules/mono/csharp_script.h b/modules/mono/csharp_script.h index e3bbb20dec..afc17f694a 100644 --- a/modules/mono/csharp_script.h +++ b/modules/mono/csharp_script.h @@ -293,7 +293,7 @@ public: void get_property_list(List<PropertyInfo> *p_properties) const override; Variant::Type get_property_type(const StringName &p_name, bool *r_is_valid) const override; - /* TODO */ void get_method_list(List<MethodInfo> *p_list) const override {} + void get_method_list(List<MethodInfo> *p_list) const override; bool has_method(const StringName &p_method) const override; Variant call(const StringName &p_method, const Variant **p_args, int p_argcount, Callable::CallError &r_error) override; diff --git a/modules/mono/glue/GodotSharp/GodotSharp/Core/GD.cs b/modules/mono/glue/GodotSharp/GodotSharp/Core/GD.cs index 53c02feaa2..ef42374041 100644 --- a/modules/mono/glue/GodotSharp/GodotSharp/Core/GD.cs +++ b/modules/mono/glue/GodotSharp/GodotSharp/Core/GD.cs @@ -376,7 +376,7 @@ namespace Godot /// <example> /// <code> /// GD.Print(GD.RandRange(0, 1)); // Prints 0 or 1 - /// GD.Print(GD.RangeRange(-10, 1000)); // Prints any number from -10 to 1000 + /// GD.Print(GD.RandRange(-10, 1000)); // Prints any number from -10 to 1000 /// </code> /// </example> /// <returns>A random <see langword="int"/> number inside the given range.</returns> diff --git a/modules/text_server_adv/text_server_adv.cpp b/modules/text_server_adv/text_server_adv.cpp index 19e94adf68..7beb0cdf6c 100644 --- a/modules/text_server_adv/text_server_adv.cpp +++ b/modules/text_server_adv/text_server_adv.cpp @@ -1225,7 +1225,7 @@ _FORCE_INLINE_ bool TextServerAdvanced::_ensure_cache_for_size(FontDataAdvanced int error = 0; if (!library) { error = FT_Init_FreeType(&library); - ERR_FAIL_COND_V_MSG(error != 0, false, TTR("FreeType: Error initializing library:") + " '" + String(FT_Error_String(error)) + "'."); + ERR_FAIL_COND_V_MSG(error != 0, false, RTR("FreeType: Error initializing library:") + " '" + String(FT_Error_String(error)) + "'."); } memset(&fd->stream, 0, sizeof(FT_StreamRec)); @@ -1243,13 +1243,7 @@ _FORCE_INLINE_ bool TextServerAdvanced::_ensure_cache_for_size(FontDataAdvanced if (error) { FT_Done_Face(fd->face); fd->face = nullptr; - ERR_FAIL_V_MSG(false, TTR("FreeType: Error loading font:") + " '" + String(FT_Error_String(error)) + "'."); - } - fd->hb_handle = hb_ft_font_create(fd->face, nullptr); - if (fd->hb_handle == nullptr) { - FT_Done_Face(fd->face); - fd->face = nullptr; - ERR_FAIL_V_MSG(false, TTR("HarfBuzz: Error creating FreeType font object.")); + ERR_FAIL_V_MSG(false, RTR("FreeType: Error loading font:") + " '" + String(FT_Error_String(error)) + "'."); } if (p_font_data->msdf) { @@ -1278,6 +1272,8 @@ _FORCE_INLINE_ bool TextServerAdvanced::_ensure_cache_for_size(FontDataAdvanced FT_Set_Pixel_Sizes(fd->face, 0, fd->size.x * fd->oversampling); } + fd->hb_handle = hb_ft_font_create(fd->face, nullptr); + fd->ascent = (fd->face->size->metrics.ascender / 64.0) / fd->oversampling * fd->scale; fd->descent = (-fd->face->size->metrics.descender / 64.0) / fd->oversampling * fd->scale; fd->underline_position = (-FT_MulFix(fd->face->underline_position, fd->face->size->metrics.y_scale) / 64.0) / fd->oversampling * fd->scale; @@ -1592,14 +1588,11 @@ _FORCE_INLINE_ bool TextServerAdvanced::_ensure_cache_for_size(FontDataAdvanced FT_Done_MM_Var(library, amaster); } #else - ERR_FAIL_V_MSG(false, TTR("FreeType: Can't load dynamic font, engine is compiled without FreeType support!"); + ERR_FAIL_V_MSG(false, RTR("FreeType: Can't load dynamic font, engine is compiled without FreeType support!"); #endif } else { // Init bitmap font. fd->hb_handle = hb_bmp_font_create(fd, nullptr); - if (!fd->hb_handle) { - ERR_FAIL_V_MSG(false, TTR("HarfBuzz: Error creating bitmap font object.")); - } } p_font_data->cache[p_size] = fd; return true; diff --git a/modules/text_server_fb/text_server_fb.cpp b/modules/text_server_fb/text_server_fb.cpp index e4e6797f92..236495ee12 100644 --- a/modules/text_server_fb/text_server_fb.cpp +++ b/modules/text_server_fb/text_server_fb.cpp @@ -686,7 +686,7 @@ _FORCE_INLINE_ bool TextServerFallback::_ensure_cache_for_size(FontDataFallback int error = 0; if (!library) { error = FT_Init_FreeType(&library); - ERR_FAIL_COND_V_MSG(error != 0, false, TTR("FreeType: Error initializing library:") + " '" + String(FT_Error_String(error)) + "'."); + ERR_FAIL_COND_V_MSG(error != 0, false, RTR("FreeType: Error initializing library:") + " '" + String(FT_Error_String(error)) + "'."); } memset(&fd->stream, 0, sizeof(FT_StreamRec)); @@ -704,7 +704,7 @@ _FORCE_INLINE_ bool TextServerFallback::_ensure_cache_for_size(FontDataFallback if (error) { FT_Done_Face(fd->face); fd->face = nullptr; - ERR_FAIL_V_MSG(false, TTR("FreeType: Error loading font:") + " '" + String(FT_Error_String(error)) + "'."); + ERR_FAIL_V_MSG(false, RTR("FreeType: Error loading font:") + " '" + String(FT_Error_String(error)) + "'."); } if (p_font_data->msdf) { @@ -784,7 +784,7 @@ _FORCE_INLINE_ bool TextServerFallback::_ensure_cache_for_size(FontDataFallback FT_Done_MM_Var(library, amaster); } #else - ERR_FAIL_V_MSG(false, TTR("FreeType: Can't load dynamic font, engine is compiled without FreeType support!"); + ERR_FAIL_V_MSG(false, RTR("FreeType: Can't load dynamic font, engine is compiled without FreeType support!"); #endif } p_font_data->cache[p_size] = fd; diff --git a/modules/visual_script/doc_classes/VisualScriptCustomNode.xml b/modules/visual_script/doc_classes/VisualScriptCustomNode.xml index b574576856..2c6313c80a 100644 --- a/modules/visual_script/doc_classes/VisualScriptCustomNode.xml +++ b/modules/visual_script/doc_classes/VisualScriptCustomNode.xml @@ -131,7 +131,7 @@ The [code]inputs[/code] array contains the values of the input ports. [code]outputs[/code] is an array whose indices should be set to the respective outputs. The [code]start_mode[/code] is usually [constant START_MODE_BEGIN_SEQUENCE], unless you have used the [code]STEP_*[/code] constants. - [code]working_mem[/code] is an array which can be used to persist information between runs of the custom node. + [code]working_mem[/code] is an array which can be used to persist information between runs of the custom node. The size needs to be predefined using [method _get_working_memory_size]. When returning, you can mask the returned value with one of the [code]STEP_*[/code] constants. </description> </method> diff --git a/modules/visual_script/visual_script_func_nodes.cpp b/modules/visual_script/visual_script_func_nodes.cpp index 6ba5ad4fd6..205918a5f0 100644 --- a/modules/visual_script/visual_script_func_nodes.cpp +++ b/modules/visual_script/visual_script_func_nodes.cpp @@ -1010,7 +1010,7 @@ PropertyInfo VisualScriptPropertySet::get_input_value_port_info(int p_idx) const if (index != StringName()) { detail_prop_name += "." + String(index); } - PropertyInfo pinfo = PropertyInfo(E.type, detail_prop_name, PROPERTY_HINT_TYPE_STRING, E.hint_string); + PropertyInfo pinfo = PropertyInfo(E.type, detail_prop_name, E.hint, E.hint_string); _adjust_input_index(pinfo); return pinfo; } diff --git a/platform/javascript/api/javascript_tools_editor_plugin.cpp b/platform/javascript/api/javascript_tools_editor_plugin.cpp index c50195639c..45a2cd595a 100644 --- a/platform/javascript/api/javascript_tools_editor_plugin.cpp +++ b/platform/javascript/api/javascript_tools_editor_plugin.cpp @@ -71,8 +71,8 @@ void JavaScriptToolsEditorPlugin::_download_zip(Variant p_v) { // Replace characters not allowed (or risky) in Windows file names with safe characters. // In the project name, all invalid characters become an empty string so that a name // like "Platformer 2: Godette's Revenge" becomes "platformer_2-_godette-s_revenge". - const String project_name_safe = - GLOBAL_GET("application/config/name").to_lower().replace(" ", "_"); + const String project_name = GLOBAL_GET("application/config/name"); + const String project_name_safe = project_name.to_lower().replace(" ", "_"); const String datetime_safe = Time::get_singleton()->get_datetime_string_from_system(false, true).replace(" ", "_"); const String output_name = OS::get_singleton()->get_safe_dir_name(vformat("%s_%s.zip")); diff --git a/platform/javascript/display_server_javascript.cpp b/platform/javascript/display_server_javascript.cpp index fda18a5c19..b43614eb99 100644 --- a/platform/javascript/display_server_javascript.cpp +++ b/platform/javascript/display_server_javascript.cpp @@ -158,6 +158,10 @@ EM_BOOL DisplayServerJavaScript::keydown_callback(int p_event_type, const Emscri return false; } Input::get_singleton()->parse_input_event(ev); + + // Make sure to flush all events so we can call restricted APIs inside the event. + Input::get_singleton()->flush_buffered_events(); + return true; } @@ -165,6 +169,10 @@ EM_BOOL DisplayServerJavaScript::keypress_callback(int p_event_type, const Emscr DisplayServerJavaScript *display = get_singleton(); display->deferred_key_event->set_unicode(p_event->charCode); Input::get_singleton()->parse_input_event(display->deferred_key_event); + + // Make sure to flush all events so we can call restricted APIs inside the event. + Input::get_singleton()->flush_buffered_events(); + return true; } @@ -172,6 +180,10 @@ EM_BOOL DisplayServerJavaScript::keyup_callback(int p_event_type, const Emscript Ref<InputEventKey> ev = setup_key_event(p_event); ev->set_pressed(false); Input::get_singleton()->parse_input_event(ev); + + // Make sure to flush all events so we can call restricted APIs inside the event. + Input::get_singleton()->flush_buffered_events(); + return ev->get_keycode() != KEY_UNKNOWN && ev->get_keycode() != (Key)0; } @@ -245,6 +257,10 @@ EM_BOOL DisplayServerJavaScript::mouse_button_callback(int p_event_type, const E ev->set_button_mask(mask); input->parse_input_event(ev); + + // Make sure to flush all events so we can call restricted APIs inside the event. + Input::get_singleton()->flush_buffered_events(); + // Prevent multi-click text selection and wheel-click scrolling anchor. // Context menu is prevented through contextmenu event. return true; @@ -507,6 +523,10 @@ EM_BOOL DisplayServerJavaScript::touch_press_callback(int p_event_type, const Em Input::get_singleton()->parse_input_event(ev); } + + // Make sure to flush all events so we can call restricted APIs inside the event. + Input::get_singleton()->flush_buffered_events(); + // Resume audio context after input in case autoplay was denied. return true; } @@ -1019,6 +1039,7 @@ bool DisplayServerJavaScript::can_any_window_draw() const { } void DisplayServerJavaScript::process_events() { + Input::get_singleton()->flush_buffered_events(); if (godot_js_display_gamepad_sample() == OK) { process_joypads(); } diff --git a/scene/animation/tween.cpp b/scene/animation/tween.cpp index 542011618d..3018fd3ae6 100644 --- a/scene/animation/tween.cpp +++ b/scene/animation/tween.cpp @@ -36,6 +36,10 @@ void Tweener::set_tween(Ref<Tween> p_tween) { tween = p_tween; } +void Tweener::clear_tween() { + tween.unref(); +} + void Tweener::_bind_methods() { ADD_SIGNAL(MethodInfo("finished")); } @@ -53,7 +57,7 @@ void Tween::start_tweeners() { Ref<PropertyTweener> Tween::tween_property(Object *p_target, NodePath p_property, Variant p_to, float p_duration) { ERR_FAIL_NULL_V(p_target, nullptr); - ERR_FAIL_COND_V_MSG(invalid, nullptr, "Tween was created outside the scene tree, can't use Tweeners."); + ERR_FAIL_COND_V_MSG(!valid, nullptr, "Tween invalid. Either finished or created outside scene tree."); ERR_FAIL_COND_V_MSG(started, nullptr, "Can't append to a Tween that has started. Use stop() first."); Ref<PropertyTweener> tweener = memnew(PropertyTweener(p_target, p_property, p_to, p_duration)); @@ -62,7 +66,7 @@ Ref<PropertyTweener> Tween::tween_property(Object *p_target, NodePath p_property } Ref<IntervalTweener> Tween::tween_interval(float p_time) { - ERR_FAIL_COND_V_MSG(invalid, nullptr, "Tween was created outside the scene tree, can't use Tweeners."); + ERR_FAIL_COND_V_MSG(!valid, nullptr, "Tween invalid. Either finished or created outside scene tree."); ERR_FAIL_COND_V_MSG(started, nullptr, "Can't append to a Tween that has started. Use stop() first."); Ref<IntervalTweener> tweener = memnew(IntervalTweener(p_time)); @@ -71,7 +75,7 @@ Ref<IntervalTweener> Tween::tween_interval(float p_time) { } Ref<CallbackTweener> Tween::tween_callback(Callable p_callback) { - ERR_FAIL_COND_V_MSG(invalid, nullptr, "Tween was created outside the scene tree, can't use Tweeners."); + ERR_FAIL_COND_V_MSG(!valid, nullptr, "Tween invalid. Either finished or created outside scene tree."); ERR_FAIL_COND_V_MSG(started, nullptr, "Can't append to a Tween that has started. Use stop() first."); Ref<CallbackTweener> tweener = memnew(CallbackTweener(p_callback)); @@ -80,7 +84,7 @@ Ref<CallbackTweener> Tween::tween_callback(Callable p_callback) { } Ref<MethodTweener> Tween::tween_method(Callable p_callback, float p_from, float p_to, float p_duration) { - ERR_FAIL_COND_V_MSG(invalid, nullptr, "Tween was created outside the scene tree, can't use Tweeners."); + ERR_FAIL_COND_V_MSG(!valid, nullptr, "Tween invalid. Either finished or created outside scene tree."); ERR_FAIL_COND_V_MSG(started, nullptr, "Can't append to a Tween that has started. Use stop() first."); Ref<MethodTweener> tweener = memnew(MethodTweener(p_callback, p_from, p_to, p_duration)); @@ -88,9 +92,7 @@ Ref<MethodTweener> Tween::tween_method(Callable p_callback, float p_from, float return tweener; } -Ref<Tween> Tween::append(Ref<Tweener> p_tweener) { - ERR_FAIL_COND_V_MSG(invalid, nullptr, "Tween was created outside the scene tree, can't use Tweeners."); - ERR_FAIL_COND_V_MSG(started, nullptr, "Can't append to a Tween that has started. Use stop() first."); +void Tween::append(Ref<Tweener> p_tweener) { p_tweener->set_tween(this); if (parallel_enabled) { @@ -102,8 +104,6 @@ Ref<Tween> Tween::append(Ref<Tweener> p_tweener) { tweeners.resize(current_step + 1); tweeners.write[current_step].push_back(p_tweener); - - return this; } void Tween::stop() { @@ -117,7 +117,7 @@ void Tween::pause() { } void Tween::play() { - ERR_FAIL_COND_MSG(invalid, "Tween invalid, can't play."); + ERR_FAIL_COND_MSG(!valid, "Tween invalid. Either finished or created outside scene tree."); ERR_FAIL_COND_MSG(dead, "Can't play finished Tween, use stop() first to reset its state."); running = true; } @@ -132,11 +132,22 @@ bool Tween::is_running() { } void Tween::set_valid(bool p_valid) { - invalid = !p_valid; + valid = p_valid; } bool Tween::is_valid() { - return invalid; + return valid; +} + +void Tween::clear() { + valid = false; + + for (List<Ref<Tweener>> &step : tweeners) { + for (Ref<Tweener> &tweener : step) { + tweener->clear_tween(); + } + } + tweeners.clear(); } Ref<Tween> Tween::bind_node(Node *p_node) { diff --git a/scene/animation/tween.h b/scene/animation/tween.h index 947cdb7c2d..953d573539 100644 --- a/scene/animation/tween.h +++ b/scene/animation/tween.h @@ -43,6 +43,7 @@ public: virtual void set_tween(Ref<Tween> p_tween); virtual void start() = 0; virtual bool step(float &r_delta) = 0; + void clear_tween(); protected: static void _bind_methods(); @@ -111,7 +112,7 @@ private: bool started = false; bool running = true; bool dead = false; - bool invalid = true; + bool valid = false; bool default_parallel = false; bool parallel_enabled = false; @@ -128,7 +129,7 @@ public: Ref<IntervalTweener> tween_interval(float p_time); Ref<CallbackTweener> tween_callback(Callable p_callback); Ref<MethodTweener> tween_method(Callable p_callback, float p_from, float p_to, float p_duration); - Ref<Tween> append(Ref<Tweener> p_tweener); + void append(Ref<Tweener> p_tweener); bool custom_step(float p_delta); void stop(); @@ -139,6 +140,7 @@ public: bool is_running(); void set_valid(bool p_valid); bool is_valid(); + void clear(); Ref<Tween> bind_node(Node *p_node); Ref<Tween> set_process_mode(TweenProcessMode p_mode); diff --git a/scene/gui/code_edit.cpp b/scene/gui/code_edit.cpp index d05762b6c0..3beff57027 100644 --- a/scene/gui/code_edit.cpp +++ b/scene/gui/code_edit.cpp @@ -654,7 +654,7 @@ void CodeEdit::_backspace_internal() { // For space indentation we need to do a simple unindent if there are no chars to the left, acting in the // same way as tabs. if (indent_using_spaces && cc != 0) { - if (get_first_non_whitespace_column(cl) > cc) { + if (get_first_non_whitespace_column(cl) >= cc) { prev_column = cc - _calculate_spaces_till_next_left_indent(cc); prev_line = cl; } @@ -987,10 +987,10 @@ void CodeEdit::_new_line(bool p_split_current_line, bool p_above) { /* No need to move the brace below if we are not taking the text with us. */ if (p_split_current_line) { brace_indent = true; - ins += "\n" + ins.substr(1, ins.length() - 2); + ins += "\n" + ins.substr(indent_text.size(), ins.length() - 2); } else { brace_indent = false; - ins = "\n" + ins.substr(1, ins.length() - 2); + ins = "\n" + ins.substr(indent_text.size(), ins.length() - 2); } } } @@ -1407,9 +1407,14 @@ void CodeEdit::fold_line(int p_line) { int in_string = (in_comment == -1) ? is_in_string(p_line) : -1; if (in_string != -1 || in_comment != -1) { end_line = get_delimiter_end_position(p_line, get_line(p_line).size() - 1).y; - /* End line is the same therefore we have a block. */ + /* End line is the same therefore we have a block of single line delimiters. */ if (end_line == p_line) { for (int i = p_line + 1; i <= line_count; i++) { + if (i == line_count) { + end_line = line_count; + break; + } + if ((in_string != -1 && is_in_string(i) == -1) || (in_comment != -1 && is_in_comment(i) == -1)) { end_line = i - 1; break; @@ -1617,7 +1622,8 @@ Point2 CodeEdit::get_delimiter_start_position(int p_line, int p_column) const { } /* Region was found on this line and is not a multiline continuation. */ - if (start_position.x != -1 && start_position.x != get_line(p_line).length() + 1) { + int line_length = get_line(p_line).length(); + if (start_position.x != -1 && line_length > 0 && start_position.x != line_length + 1) { start_position.y = p_line; return start_position; } @@ -1636,7 +1642,8 @@ Point2 CodeEdit::get_delimiter_start_position(int p_line, int p_column) const { start_position.x = delimiter_cache[i].back()->key(); /* Make sure it's not a multiline continuation. */ - if (start_position.x != get_line(i).length() + 1) { + line_length = get_line(i).length(); + if (line_length > 0 && start_position.x != line_length + 1) { break; } } @@ -2562,7 +2569,10 @@ int CodeEdit::_is_in_delimiter(int p_line, int p_column, DelimiterType p_type) c } void CodeEdit::_add_delimiter(const String &p_start_key, const String &p_end_key, bool p_line_only, DelimiterType p_type) { - if (p_start_key.length() > 0) { + // If we are the editor allow "null" as a valid start key, otherwise users cannot add delimiters via the inspector. + if (!(Engine::get_singleton()->is_editor_hint() && p_start_key == "null")) { + ERR_FAIL_COND_MSG(p_start_key.is_empty(), "delimiter start key cannot be empty"); + for (int i = 0; i < p_start_key.length(); i++) { ERR_FAIL_COND_MSG(!is_symbol(p_start_key[i]), "delimiter must start with a symbol"); } @@ -2627,7 +2637,11 @@ void CodeEdit::_set_delimiters(const TypedArray<String> &p_delimiters, Delimiter _clear_delimiters(p_type); for (int i = 0; i < p_delimiters.size(); i++) { - String key = p_delimiters[i].is_null() ? "" : p_delimiters[i]; + String key = p_delimiters[i]; + + if (key.is_empty()) { + continue; + } const String start_key = key.get_slice(" ", 0); const String end_key = key.get_slice_count(" ") > 1 ? key.get_slice(" ", 1) : String(); diff --git a/scene/gui/code_edit.h b/scene/gui/code_edit.h index 4fbb5194e6..740548d559 100644 --- a/scene/gui/code_edit.h +++ b/scene/gui/code_edit.h @@ -248,7 +248,6 @@ private: void _text_changed(); protected: - void gui_input(const Ref<InputEvent> &p_gui_input) override; void _notification(int p_what); static void _bind_methods(); @@ -265,6 +264,7 @@ protected: public: /* General overrides */ + virtual void gui_input(const Ref<InputEvent> &p_gui_input) override; virtual CursorShape get_cursor_shape(const Point2 &p_pos = Point2i()) const override; /* Indent management */ diff --git a/scene/gui/label.cpp b/scene/gui/label.cpp index 3f87003423..5600816b2d 100644 --- a/scene/gui/label.cpp +++ b/scene/gui/label.cpp @@ -227,7 +227,15 @@ void Label::_update_visible() { } } -inline void draw_glyph(const TextServer::Glyph &p_gl, const RID &p_canvas, const Color &p_font_color, const Color &p_font_shadow_color, const Color &p_font_outline_color, const int &p_shadow_outline_size, const int &p_outline_size, const Vector2 &p_ofs, const Vector2 &shadow_ofs) { +inline void draw_glyph(const TextServer::Glyph &p_gl, const RID &p_canvas, const Color &p_font_color, const Vector2 &p_ofs) { + if (p_gl.font_rid != RID()) { + TS->font_draw_glyph(p_gl.font_rid, p_canvas, p_gl.font_size, p_ofs + Vector2(p_gl.x_off, p_gl.y_off), p_gl.index, p_font_color); + } else { + TS->draw_hex_code_box(p_canvas, p_gl.font_size, p_ofs + Vector2(p_gl.x_off, p_gl.y_off), p_gl.index, p_font_color); + } +} + +inline void draw_glyph_outline(const TextServer::Glyph &p_gl, const RID &p_canvas, const Color &p_font_color, const Color &p_font_shadow_color, const Color &p_font_outline_color, const int &p_shadow_outline_size, const int &p_outline_size, const Vector2 &p_ofs, const Vector2 &shadow_ofs) { if (p_gl.font_rid != RID()) { if (p_font_shadow_color.a > 0) { TS->font_draw_glyph(p_gl.font_rid, p_canvas, p_gl.font_size, p_ofs + Vector2(p_gl.x_off, p_gl.y_off) + shadow_ofs, p_gl.index, p_font_shadow_color); @@ -240,9 +248,6 @@ inline void draw_glyph(const TextServer::Glyph &p_gl, const RID &p_canvas, const if (p_font_outline_color.a != 0.0 && p_outline_size > 0) { TS->font_draw_glyph_outline(p_gl.font_rid, p_canvas, p_gl.font_size, p_outline_size, p_ofs + Vector2(p_gl.x_off, p_gl.y_off), p_gl.index, p_font_outline_color); } - TS->font_draw_glyph(p_gl.font_rid, p_canvas, p_gl.font_size, p_ofs + Vector2(p_gl.x_off, p_gl.y_off), p_gl.index, p_font_color); - } else { - TS->draw_hex_code_box(p_canvas, p_gl.font_size, p_ofs + Vector2(p_gl.x_off, p_gl.y_off), p_gl.index, p_font_color); } } @@ -385,12 +390,70 @@ void Label::_notification(int p_what) { int gl_size = visual.size(); TextServer::TrimData trim_data = TS->shaped_text_get_trim_data(lines_rid[i]); + // Draw outline. Note: Do not merge this into the single loop with the main text, to prevent overlaps. + if (font_shadow_color.a > 0 || (font_outline_color.a != 0.0 && outline_size > 0)) { + Vector2 offset = ofs; + // Draw RTL ellipsis string when necessary. + if (rtl && trim_data.ellipsis_pos >= 0) { + for (int gl_idx = trim_data.ellipsis_glyph_buf.size() - 1; gl_idx >= 0; gl_idx--) { + for (int j = 0; j < trim_data.ellipsis_glyph_buf[gl_idx].repeat; j++) { + //Draw glyph outlines and shadow. + draw_glyph_outline(trim_data.ellipsis_glyph_buf[gl_idx], ci, font_color, font_shadow_color, font_outline_color, shadow_outline_size, outline_size, offset, shadow_ofs); + offset.x += trim_data.ellipsis_glyph_buf[gl_idx].advance; + } + } + } + + // Draw main text. + for (int j = 0; j < gl_size; j++) { + for (int k = 0; k < glyphs[j].repeat; k++) { + if (visible_glyphs != -1) { + if ((glyphs[j].flags & TextServer::GRAPHEME_IS_VIRTUAL) != TextServer::GRAPHEME_IS_VIRTUAL) { + if (glyhps_drawn >= visible_glyphs) { + return; + } + } + } + + // Trim when necessary. + if (trim_data.trim_pos >= 0) { + if (rtl) { + if (j < trim_data.trim_pos && (glyphs[j].flags & TextServer::GRAPHEME_IS_VIRTUAL) != TextServer::GRAPHEME_IS_VIRTUAL) { + continue; + } + } else { + if (j >= trim_data.trim_pos && (glyphs[j].flags & TextServer::GRAPHEME_IS_VIRTUAL) != TextServer::GRAPHEME_IS_VIRTUAL) { + break; + } + } + } + + // Draw glyph outlines and shadow. + draw_glyph_outline(glyphs[j], ci, font_color, font_shadow_color, font_outline_color, shadow_outline_size, outline_size, offset, shadow_ofs); + offset.x += glyphs[j].advance; + glyhps_drawn++; + } + } + // Draw LTR ellipsis string when necessary. + if (!rtl && trim_data.ellipsis_pos >= 0) { + for (int gl_idx = 0; gl_idx < trim_data.ellipsis_glyph_buf.size(); gl_idx++) { + for (int j = 0; j < trim_data.ellipsis_glyph_buf[gl_idx].repeat; j++) { + //Draw glyph outlines and shadow. + draw_glyph_outline(trim_data.ellipsis_glyph_buf[gl_idx], ci, font_color, font_shadow_color, font_outline_color, shadow_outline_size, outline_size, offset, shadow_ofs); + offset.x += trim_data.ellipsis_glyph_buf[gl_idx].advance; + } + } + } + } + + // Draw main text. Note: Do not merge this into the single loop with the outline, to prevent overlaps. + // Draw RTL ellipsis string when necessary. if (rtl && trim_data.ellipsis_pos >= 0) { for (int gl_idx = trim_data.ellipsis_glyph_buf.size() - 1; gl_idx >= 0; gl_idx--) { for (int j = 0; j < trim_data.ellipsis_glyph_buf[gl_idx].repeat; j++) { //Draw glyph outlines and shadow. - draw_glyph(trim_data.ellipsis_glyph_buf[gl_idx], ci, font_color, font_shadow_color, font_outline_color, shadow_outline_size, outline_size, ofs, shadow_ofs); + draw_glyph(trim_data.ellipsis_glyph_buf[gl_idx], ci, font_color, ofs); ofs.x += trim_data.ellipsis_glyph_buf[gl_idx].advance; } } @@ -421,7 +484,7 @@ void Label::_notification(int p_what) { } // Draw glyph outlines and shadow. - draw_glyph(glyphs[j], ci, font_color, font_shadow_color, font_outline_color, shadow_outline_size, outline_size, ofs, shadow_ofs); + draw_glyph(glyphs[j], ci, font_color, ofs); ofs.x += glyphs[j].advance; glyhps_drawn++; } @@ -431,7 +494,7 @@ void Label::_notification(int p_what) { for (int gl_idx = 0; gl_idx < trim_data.ellipsis_glyph_buf.size(); gl_idx++) { for (int j = 0; j < trim_data.ellipsis_glyph_buf[gl_idx].repeat; j++) { //Draw glyph outlines and shadow. - draw_glyph(trim_data.ellipsis_glyph_buf[gl_idx], ci, font_color, font_shadow_color, font_outline_color, shadow_outline_size, outline_size, ofs, shadow_ofs); + draw_glyph(trim_data.ellipsis_glyph_buf[gl_idx], ci, font_color, ofs); ofs.x += trim_data.ellipsis_glyph_buf[gl_idx].advance; } } diff --git a/scene/gui/scroll_bar.cpp b/scene/gui/scroll_bar.cpp index 08bcb0bdda..4edf373fbf 100644 --- a/scene/gui/scroll_bar.cpp +++ b/scene/gui/scroll_bar.cpp @@ -80,12 +80,16 @@ void ScrollBar::gui_input(const Ref<InputEvent> &p_event) { double total = orientation == VERTICAL ? get_size().height : get_size().width; if (ofs < decr_size) { + decr_active = true; set_value(get_value() - (custom_step >= 0 ? custom_step : get_step())); + update(); return; } if (ofs > total - incr_size) { + incr_active = true; set_value(get_value() + (custom_step >= 0 ? custom_step : get_step())); + update(); return; } @@ -130,6 +134,8 @@ void ScrollBar::gui_input(const Ref<InputEvent> &p_event) { } } else { + incr_active = false; + decr_active = false; drag.active = false; update(); } @@ -215,8 +221,24 @@ void ScrollBar::_notification(int p_what) { if (p_what == NOTIFICATION_DRAW) { RID ci = get_canvas_item(); - Ref<Texture2D> decr = highlight == HIGHLIGHT_DECR ? get_theme_icon(SNAME("decrement_highlight")) : get_theme_icon(SNAME("decrement")); - Ref<Texture2D> incr = highlight == HIGHLIGHT_INCR ? get_theme_icon(SNAME("increment_highlight")) : get_theme_icon(SNAME("increment")); + Ref<Texture2D> decr, incr; + + if (decr_active) { + decr = get_theme_icon(SNAME("decrement_pressed")); + } else if (highlight == HIGHLIGHT_DECR) { + decr = get_theme_icon(SNAME("decrement_highlight")); + } else { + decr = get_theme_icon(SNAME("decrement")); + } + + if (incr_active) { + incr = get_theme_icon(SNAME("increment_pressed")); + } else if (highlight == HIGHLIGHT_INCR) { + incr = get_theme_icon(SNAME("increment_highlight")); + } else { + incr = get_theme_icon(SNAME("increment")); + } + Ref<StyleBox> bg = has_focus() ? get_theme_stylebox(SNAME("scroll_focus")) : get_theme_stylebox(SNAME("scroll")); Ref<StyleBox> grabber; diff --git a/scene/gui/scroll_bar.h b/scene/gui/scroll_bar.h index fbc035397f..574d17ee20 100644 --- a/scene/gui/scroll_bar.h +++ b/scene/gui/scroll_bar.h @@ -51,6 +51,9 @@ class ScrollBar : public Range { HighlightStatus highlight = HIGHLIGHT_NONE; + bool incr_active = false; + bool decr_active = false; + struct Drag { bool active = false; float pos_at_click = 0.0; diff --git a/scene/gui/text_edit.cpp b/scene/gui/text_edit.cpp index f64c07df76..06dfc31621 100644 --- a/scene/gui/text_edit.cpp +++ b/scene/gui/text_edit.cpp @@ -2610,10 +2610,10 @@ void TextEdit::set_text(const String &p_text) { set_caret_column(0); begin_complex_operation(); + deselect(); _remove_text(0, 0, MAX(0, get_line_count() - 1), MAX(get_line(MAX(get_line_count() - 1, 0)).size() - 1, 0)); insert_text_at_caret(p_text); end_complex_operation(); - selection.active = false; } set_caret_line(0); @@ -2731,6 +2731,8 @@ void TextEdit::insert_line_at(int p_at, const String &p_text) { } void TextEdit::insert_text_at_caret(const String &p_text) { + begin_complex_operation(); + delete_selection(); int new_column, new_line; @@ -2740,6 +2742,8 @@ void TextEdit::insert_text_at_caret(const String &p_text) { set_caret_line(new_line, false); set_caret_column(new_column); update(); + + end_complex_operation(); } void TextEdit::remove_text(int p_from_line, int p_from_column, int p_to_line, int p_to_column) { @@ -3024,13 +3028,20 @@ void TextEdit::menu_option(int p_option) { /* Versioning */ void TextEdit::begin_complex_operation() { _push_current_op(); - next_operation_is_complex = true; + if (complex_operation_count == 0) { + next_operation_is_complex = true; + } + complex_operation_count++; } void TextEdit::end_complex_operation() { _push_current_op(); ERR_FAIL_COND(undo_stack.size() == 0); + complex_operation_count = MAX(complex_operation_count - 1, 0); + if (complex_operation_count > 0) { + return; + } if (undo_stack.back()->get().chain_forward) { undo_stack.back()->get().chain_forward = false; return; diff --git a/scene/gui/text_edit.h b/scene/gui/text_edit.h index e996bba983..b1226f2aff 100644 --- a/scene/gui/text_edit.h +++ b/scene/gui/text_edit.h @@ -304,6 +304,7 @@ private: bool undo_enabled = true; int undo_stack_max_size = 50; + int complex_operation_count = 0; bool next_operation_is_complex = false; TextOperation current_op; @@ -545,7 +546,6 @@ private: protected: void _notification(int p_what); - virtual void gui_input(const Ref<InputEvent> &p_gui_input) override; static void _bind_methods(); @@ -594,6 +594,7 @@ protected: public: /* General overrides. */ + virtual void gui_input(const Ref<InputEvent> &p_gui_input) override; virtual Size2 get_minimum_size() const override; virtual bool is_text_field() const override; virtual CursorShape get_cursor_shape(const Point2 &p_pos = Point2i()) const override; diff --git a/scene/gui/tree.cpp b/scene/gui/tree.cpp index cb990892ed..c4dfbc0d4e 100644 --- a/scene/gui/tree.cpp +++ b/scene/gui/tree.cpp @@ -2339,13 +2339,22 @@ int Tree::propagate_mouse_event(const Point2i &p_pos, int x_ofs, int y_ofs, int cache.click_type = Cache::CLICK_NONE; return -1; } + + // Make sure the click is correct. + Point2 click_pos = get_global_mouse_position() - get_global_position(); + if (!get_item_at_position(click_pos)) { + pressed_button = -1; + cache.click_type = Cache::CLICK_NONE; + return -1; + } + pressed_button = j; cache.click_type = Cache::CLICK_BUTTON; cache.click_index = j; cache.click_id = c.buttons[j].id; cache.click_item = p_item; cache.click_column = col; - cache.click_pos = get_global_mouse_position() - get_global_position(); + cache.click_pos = click_pos; update(); //emit_signal(SNAME("button_pressed")); return -1; diff --git a/scene/main/scene_tree.cpp b/scene/main/scene_tree.cpp index bef7ecb462..e1b1b356a9 100644 --- a/scene/main/scene_tree.cpp +++ b/scene/main/scene_tree.cpp @@ -544,7 +544,7 @@ void SceneTree::process_tweens(float p_delta, bool p_physics) { } if (!E->get()->step(p_delta)) { - E->get()->set_valid(false); + E->get()->clear(); tweens.erase(E); } if (E == L) { @@ -1337,7 +1337,7 @@ SceneTree::SceneTree() { current_scene = nullptr; const int msaa_mode = GLOBAL_DEF("rendering/anti_aliasing/quality/msaa", 0); - ProjectSettings::get_singleton()->set_custom_property_info("rendering/anti_aliasing/quality/msaa", PropertyInfo(Variant::INT, "rendering/anti_aliasing/quality/msaa", PROPERTY_HINT_ENUM, String::utf8("Disabled (Fastest),2× (Fast),4× (Average),8× (Slow),16× (Slower)"))); + ProjectSettings::get_singleton()->set_custom_property_info("rendering/anti_aliasing/quality/msaa", PropertyInfo(Variant::INT, "rendering/anti_aliasing/quality/msaa", PROPERTY_HINT_ENUM, String::utf8("Disabled (Fastest),2× (Average),4× (Slow),8× (Slowest)"))); root->set_msaa(Viewport::MSAA(msaa_mode)); const int ssaa_mode = GLOBAL_DEF("rendering/anti_aliasing/quality/screen_space_aa", 0); diff --git a/scene/main/viewport.cpp b/scene/main/viewport.cpp index eea5ca9895..fb86d37280 100644 --- a/scene/main/viewport.cpp +++ b/scene/main/viewport.cpp @@ -823,7 +823,9 @@ Rect2 Viewport::get_visible_rect() const { } void Viewport::_update_listener_2d() { - AudioServer::get_singleton()->notify_listener_changed(); + if (AudioServer::get_singleton()) { + AudioServer::get_singleton()->notify_listener_changed(); + } } void Viewport::set_as_audio_listener_2d(bool p_enable) { @@ -3063,7 +3065,9 @@ bool Viewport::is_audio_listener_3d() const { } void Viewport::_update_listener_3d() { - AudioServer::get_singleton()->notify_listener_changed(); + if (AudioServer::get_singleton()) { + AudioServer::get_singleton()->notify_listener_changed(); + } } void Viewport::_listener_transform_3d_changed_notify() { @@ -3594,7 +3598,7 @@ void Viewport::_bind_methods() { ADD_PROPERTY(PropertyInfo(Variant::BOOL, "snap_2d_transforms_to_pixel"), "set_snap_2d_transforms_to_pixel", "is_snap_2d_transforms_to_pixel_enabled"); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "snap_2d_vertices_to_pixel"), "set_snap_2d_vertices_to_pixel", "is_snap_2d_vertices_to_pixel_enabled"); ADD_GROUP("Rendering", ""); - ADD_PROPERTY(PropertyInfo(Variant::INT, "msaa", PROPERTY_HINT_ENUM, String::utf8("Disabled (Fastest),2× (Fast),4× (Average),8× (Slow),16× (Slower)")), "set_msaa", "get_msaa"); + ADD_PROPERTY(PropertyInfo(Variant::INT, "msaa", PROPERTY_HINT_ENUM, String::utf8("Disabled (Fastest),2× (Average),4× (Slow),8× (Slowest)")), "set_msaa", "get_msaa"); ADD_PROPERTY(PropertyInfo(Variant::INT, "screen_space_aa", PROPERTY_HINT_ENUM, "Disabled (Fastest),FXAA (Fast)"), "set_screen_space_aa", "get_screen_space_aa"); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "use_debanding"), "set_use_debanding", "is_using_debanding"); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "use_occlusion_culling"), "set_use_occlusion_culling", "is_using_occlusion_culling"); @@ -3646,7 +3650,6 @@ void Viewport::_bind_methods() { BIND_ENUM_CONSTANT(MSAA_2X); BIND_ENUM_CONSTANT(MSAA_4X); BIND_ENUM_CONSTANT(MSAA_8X); - BIND_ENUM_CONSTANT(MSAA_16X); BIND_ENUM_CONSTANT(MSAA_MAX); BIND_ENUM_CONSTANT(SCREEN_SPACE_AA_DISABLED); diff --git a/scene/main/viewport.h b/scene/main/viewport.h index 06efd27073..bfb52c4b98 100644 --- a/scene/main/viewport.h +++ b/scene/main/viewport.h @@ -113,7 +113,7 @@ public: MSAA_2X, MSAA_4X, MSAA_8X, - MSAA_16X, + // 16x MSAA is not supported due to its high cost and driver bugs. MSAA_MAX }; diff --git a/scene/resources/default_theme/default_theme.cpp b/scene/resources/default_theme/default_theme.cpp index 4845c556c6..fa3824e6eb 100644 --- a/scene/resources/default_theme/default_theme.cpp +++ b/scene/resources/default_theme/default_theme.cpp @@ -502,8 +502,10 @@ void fill_default_theme(Ref<Theme> &theme, const Ref<Font> &default_font, const theme->set_icon("increment", "HScrollBar", empty_icon); theme->set_icon("increment_highlight", "HScrollBar", empty_icon); + theme->set_icon("increment_pressed", "HScrollBar", empty_icon); theme->set_icon("decrement", "HScrollBar", empty_icon); theme->set_icon("decrement_highlight", "HScrollBar", empty_icon); + theme->set_icon("decrement_pressed", "HScrollBar", empty_icon); // VScrollBar @@ -515,8 +517,10 @@ void fill_default_theme(Ref<Theme> &theme, const Ref<Font> &default_font, const theme->set_icon("increment", "VScrollBar", empty_icon); theme->set_icon("increment_highlight", "VScrollBar", empty_icon); + theme->set_icon("increment_pressed", "VScrollBar", empty_icon); theme->set_icon("decrement", "VScrollBar", empty_icon); theme->set_icon("decrement_highlight", "VScrollBar", empty_icon); + theme->set_icon("decrement_pressed", "VScrollBar", empty_icon); // HSlider diff --git a/scene/resources/mesh_library.cpp b/scene/resources/mesh_library.cpp index 33c9ca6d1e..cfb7c3e037 100644 --- a/scene/resources/mesh_library.cpp +++ b/scene/resources/mesh_library.cpp @@ -43,6 +43,8 @@ bool MeshLibrary::_set(const StringName &p_name, const Variant &p_value) { set_item_name(idx, p_value); } else if (what == "mesh") { set_item_mesh(idx, p_value); + } else if (what == "mesh_transform") { + set_item_mesh_transform(idx, p_value); } else if (what == "shape") { Vector<ShapeData> shapes; ShapeData sd; @@ -77,6 +79,8 @@ bool MeshLibrary::_get(const StringName &p_name, Variant &r_ret) const { r_ret = get_item_name(idx); } else if (what == "mesh") { r_ret = get_item_mesh(idx); + } else if (what == "mesh_transform") { + r_ret = get_item_mesh_transform(idx); } else if (what == "shapes") { r_ret = _get_item_shapes(idx); } else if (what == "navmesh") { @@ -127,6 +131,14 @@ void MeshLibrary::set_item_mesh(int p_item, const Ref<Mesh> &p_mesh) { notify_property_list_changed(); } +void MeshLibrary::set_item_mesh_transform(int p_item, const Transform3D &p_transform) { + ERR_FAIL_COND_MSG(!item_map.has(p_item), "Requested for nonexistent MeshLibrary item '" + itos(p_item) + "'."); + item_map[p_item].mesh_transform = p_transform; + notify_change_to_owners(); + emit_changed(); + notify_property_list_changed(); +} + void MeshLibrary::set_item_shapes(int p_item, const Vector<ShapeData> &p_shapes) { ERR_FAIL_COND_MSG(!item_map.has(p_item), "Requested for nonexistent MeshLibrary item '" + itos(p_item) + "'."); item_map[p_item].shapes = p_shapes; @@ -170,6 +182,11 @@ Ref<Mesh> MeshLibrary::get_item_mesh(int p_item) const { return item_map[p_item].mesh; } +Transform3D MeshLibrary::get_item_mesh_transform(int p_item) const { + ERR_FAIL_COND_V_MSG(!item_map.has(p_item), Transform3D(), "Requested for nonexistent MeshLibrary item '" + itos(p_item) + "'."); + return item_map[p_item].mesh_transform; +} + Vector<MeshLibrary::ShapeData> MeshLibrary::get_item_shapes(int p_item) const { ERR_FAIL_COND_V_MSG(!item_map.has(p_item), Vector<ShapeData>(), "Requested for nonexistent MeshLibrary item '" + itos(p_item) + "'."); return item_map[p_item].shapes; @@ -271,12 +288,14 @@ void MeshLibrary::_bind_methods() { ClassDB::bind_method(D_METHOD("create_item", "id"), &MeshLibrary::create_item); ClassDB::bind_method(D_METHOD("set_item_name", "id", "name"), &MeshLibrary::set_item_name); ClassDB::bind_method(D_METHOD("set_item_mesh", "id", "mesh"), &MeshLibrary::set_item_mesh); + ClassDB::bind_method(D_METHOD("set_item_mesh_transform", "id", "mesh_transform"), &MeshLibrary::set_item_mesh_transform); ClassDB::bind_method(D_METHOD("set_item_navmesh", "id", "navmesh"), &MeshLibrary::set_item_navmesh); ClassDB::bind_method(D_METHOD("set_item_navmesh_transform", "id", "navmesh"), &MeshLibrary::set_item_navmesh_transform); ClassDB::bind_method(D_METHOD("set_item_shapes", "id", "shapes"), &MeshLibrary::_set_item_shapes); ClassDB::bind_method(D_METHOD("set_item_preview", "id", "texture"), &MeshLibrary::set_item_preview); ClassDB::bind_method(D_METHOD("get_item_name", "id"), &MeshLibrary::get_item_name); ClassDB::bind_method(D_METHOD("get_item_mesh", "id"), &MeshLibrary::get_item_mesh); + ClassDB::bind_method(D_METHOD("get_item_mesh_transform", "id"), &MeshLibrary::get_item_mesh_transform); ClassDB::bind_method(D_METHOD("get_item_navmesh", "id"), &MeshLibrary::get_item_navmesh); ClassDB::bind_method(D_METHOD("get_item_navmesh_transform", "id"), &MeshLibrary::get_item_navmesh_transform); ClassDB::bind_method(D_METHOD("get_item_shapes", "id"), &MeshLibrary::_get_item_shapes); diff --git a/scene/resources/mesh_library.h b/scene/resources/mesh_library.h index 1e8a6bf3ff..c25df757e9 100644 --- a/scene/resources/mesh_library.h +++ b/scene/resources/mesh_library.h @@ -52,6 +52,7 @@ public: Vector<ShapeData> shapes; Ref<Texture2D> preview; Transform3D navmesh_transform; + Transform3D mesh_transform; Ref<NavigationMesh> navmesh; }; @@ -72,12 +73,14 @@ public: void create_item(int p_item); void set_item_name(int p_item, const String &p_name); void set_item_mesh(int p_item, const Ref<Mesh> &p_mesh); + void set_item_mesh_transform(int p_item, const Transform3D &p_transform); void set_item_navmesh(int p_item, const Ref<NavigationMesh> &p_navmesh); void set_item_navmesh_transform(int p_item, const Transform3D &p_transform); void set_item_shapes(int p_item, const Vector<ShapeData> &p_shapes); void set_item_preview(int p_item, const Ref<Texture2D> &p_preview); String get_item_name(int p_item) const; Ref<Mesh> get_item_mesh(int p_item) const; + Transform3D get_item_mesh_transform(int p_item) const; Ref<NavigationMesh> get_item_navmesh(int p_item) const; Transform3D get_item_navmesh_transform(int p_item) const; Vector<ShapeData> get_item_shapes(int p_item) const; diff --git a/scene/resources/texture.cpp b/scene/resources/texture.cpp index 063a13efc0..3dc32632cc 100644 --- a/scene/resources/texture.cpp +++ b/scene/resources/texture.cpp @@ -131,25 +131,6 @@ void ImageTexture::_get_property_list(List<PropertyInfo> *p_list) const { p_list->push_back(PropertyInfo(Variant::VECTOR2, "size", PROPERTY_HINT_NONE, "")); } -void ImageTexture::_reload_hook(const RID &p_hook) { - String path = get_path(); - if (!path.is_resource_file()) { - return; - } - - Ref<Image> img; - img.instantiate(); - Error err = ImageLoader::load_image(path, img); - - ERR_FAIL_COND_MSG(err != OK, "Cannot load image from path '" + path + "'."); - - RID new_texture = RenderingServer::get_singleton()->texture_2d_create(img); - RenderingServer::get_singleton()->texture_replace(texture, new_texture); - - notify_property_list_changed(); - emit_changed(); -} - void ImageTexture::create_from_image(const Ref<Image> &p_image) { ERR_FAIL_COND_MSG(p_image.is_null() || p_image->is_empty(), "Invalid image"); w = p_image->get_width(); @@ -192,10 +173,6 @@ void ImageTexture::update(const Ref<Image> &p_image) { image_stored = true; } -void ImageTexture::_resource_path_changed() { - String path = get_path(); -} - Ref<Image> ImageTexture::get_image() const { if (image_stored) { return RenderingServer::get_singleton()->texture_2d_get(texture); @@ -303,7 +280,6 @@ void ImageTexture::_bind_methods() { ClassDB::bind_method(D_METHOD("update", "image"), &ImageTexture::update); ClassDB::bind_method(D_METHOD("set_size_override", "size"), &ImageTexture::set_size_override); - ClassDB::bind_method(D_METHOD("_reload_hook", "rid"), &ImageTexture::_reload_hook); } ImageTexture::ImageTexture() {} diff --git a/scene/resources/texture.h b/scene/resources/texture.h index f6b991c335..93f4e2de5a 100644 --- a/scene/resources/texture.h +++ b/scene/resources/texture.h @@ -98,8 +98,6 @@ protected: bool _get(const StringName &p_name, Variant &r_ret) const; void _get_property_list(List<PropertyInfo> *p_list) const; - void _reload_hook(const RID &p_hook); - virtual void _resource_path_changed() override; static void _bind_methods(); public: diff --git a/servers/display_server.cpp b/servers/display_server.cpp index 3d44484033..cdf892094d 100644 --- a/servers/display_server.cpp +++ b/servers/display_server.cpp @@ -605,4 +605,5 @@ DisplayServer::DisplayServer() { } DisplayServer::~DisplayServer() { + singleton = nullptr; } diff --git a/servers/physics_3d/gjk_epa.cpp b/servers/physics_3d/gjk_epa.cpp index f2f712193a..2df991563d 100644 --- a/servers/physics_3d/gjk_epa.cpp +++ b/servers/physics_3d/gjk_epa.cpp @@ -37,7 +37,7 @@ /* Bullet Continuous Collision Detection and Physics Library -Copyright (c) 2003-2008 Erwin Coumans https://bulletphysics.org +Copyright (c) 2003-2008 Erwin Coumans http://continuousphysics.com/Bullet/ This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held liable for any damages arising from the diff --git a/servers/physics_3d/joints/generic_6dof_joint_3d_sw.cpp b/servers/physics_3d/joints/generic_6dof_joint_3d_sw.cpp index d2b64ce6e3..56aba24b42 100644 --- a/servers/physics_3d/joints/generic_6dof_joint_3d_sw.cpp +++ b/servers/physics_3d/joints/generic_6dof_joint_3d_sw.cpp @@ -34,7 +34,7 @@ Adapted to Godot from the Bullet library. /* Bullet Continuous Collision Detection and Physics Library -Copyright (c) 2003-2006 Erwin Coumans https://bulletphysics.org +Copyright (c) 2003-2006 Erwin Coumans http://continuousphysics.com/Bullet/ This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held liable for any damages arising from the use of this software. diff --git a/servers/physics_3d/joints/generic_6dof_joint_3d_sw.h b/servers/physics_3d/joints/generic_6dof_joint_3d_sw.h index c2a0443aff..d0f3dbbd35 100644 --- a/servers/physics_3d/joints/generic_6dof_joint_3d_sw.h +++ b/servers/physics_3d/joints/generic_6dof_joint_3d_sw.h @@ -40,7 +40,7 @@ Adapted to Godot from the Bullet library. /* Bullet Continuous Collision Detection and Physics Library -Copyright (c) 2003-2006 Erwin Coumans https://bulletphysics.org +Copyright (c) 2003-2006 Erwin Coumans http://continuousphysics.com/Bullet/ This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held liable for any damages arising from the use of this software. diff --git a/servers/physics_3d/joints/hinge_joint_3d_sw.cpp b/servers/physics_3d/joints/hinge_joint_3d_sw.cpp index e2bf2845fe..b928f18231 100644 --- a/servers/physics_3d/joints/hinge_joint_3d_sw.cpp +++ b/servers/physics_3d/joints/hinge_joint_3d_sw.cpp @@ -34,7 +34,7 @@ Adapted to Godot from the Bullet library. /* Bullet Continuous Collision Detection and Physics Library -Copyright (c) 2003-2006 Erwin Coumans https://bulletphysics.org +Copyright (c) 2003-2006 Erwin Coumans http://continuousphysics.com/Bullet/ This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held liable for any damages arising from the use of this software. diff --git a/servers/physics_3d/joints/hinge_joint_3d_sw.h b/servers/physics_3d/joints/hinge_joint_3d_sw.h index 572c35266f..22eb2f4660 100644 --- a/servers/physics_3d/joints/hinge_joint_3d_sw.h +++ b/servers/physics_3d/joints/hinge_joint_3d_sw.h @@ -40,7 +40,7 @@ Adapted to Godot from the Bullet library. /* Bullet Continuous Collision Detection and Physics Library -Copyright (c) 2003-2006 Erwin Coumans https://bulletphysics.org +Copyright (c) 2003-2006 Erwin Coumans http://continuousphysics.com/Bullet/ This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held liable for any damages arising from the use of this software. diff --git a/servers/physics_3d/joints/jacobian_entry_3d_sw.h b/servers/physics_3d/joints/jacobian_entry_3d_sw.h index 30c80db23f..6afa70c816 100644 --- a/servers/physics_3d/joints/jacobian_entry_3d_sw.h +++ b/servers/physics_3d/joints/jacobian_entry_3d_sw.h @@ -37,7 +37,7 @@ Adapted to Godot from the Bullet library. /* Bullet Continuous Collision Detection and Physics Library -Copyright (c) 2003-2006 Erwin Coumans https://bulletphysics.org +Copyright (c) 2003-2006 Erwin Coumans http://continuousphysics.com/Bullet/ This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held liable for any damages arising from the use of this software. diff --git a/servers/physics_3d/joints/pin_joint_3d_sw.cpp b/servers/physics_3d/joints/pin_joint_3d_sw.cpp index 7a713c1161..8eb84d1c2f 100644 --- a/servers/physics_3d/joints/pin_joint_3d_sw.cpp +++ b/servers/physics_3d/joints/pin_joint_3d_sw.cpp @@ -34,7 +34,7 @@ Adapted to Godot from the Bullet library. /* Bullet Continuous Collision Detection and Physics Library -Copyright (c) 2003-2006 Erwin Coumans https://bulletphysics.org +Copyright (c) 2003-2006 Erwin Coumans http://continuousphysics.com/Bullet/ This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held liable for any damages arising from the use of this software. diff --git a/servers/physics_3d/joints/pin_joint_3d_sw.h b/servers/physics_3d/joints/pin_joint_3d_sw.h index 09deefc5c4..3d91452850 100644 --- a/servers/physics_3d/joints/pin_joint_3d_sw.h +++ b/servers/physics_3d/joints/pin_joint_3d_sw.h @@ -40,7 +40,7 @@ Adapted to Godot from the Bullet library. /* Bullet Continuous Collision Detection and Physics Library -Copyright (c) 2003-2006 Erwin Coumans https://bulletphysics.org +Copyright (c) 2003-2006 Erwin Coumans http://continuousphysics.com/Bullet/ This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held liable for any damages arising from the use of this software. diff --git a/servers/physics_3d/joints/slider_joint_3d_sw.cpp b/servers/physics_3d/joints/slider_joint_3d_sw.cpp index 9f01196c30..1895fe1e2e 100644 --- a/servers/physics_3d/joints/slider_joint_3d_sw.cpp +++ b/servers/physics_3d/joints/slider_joint_3d_sw.cpp @@ -34,7 +34,7 @@ Adapted to Godot from the Bullet library. /* Bullet Continuous Collision Detection and Physics Library -Copyright (c) 2003-2006 Erwin Coumans https://bulletphysics.org +Copyright (c) 2003-2006 Erwin Coumans http://continuousphysics.com/Bullet/ This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held liable for any damages arising from the use of this software. diff --git a/servers/physics_3d/joints/slider_joint_3d_sw.h b/servers/physics_3d/joints/slider_joint_3d_sw.h index f09476f570..f357bbd67a 100644 --- a/servers/physics_3d/joints/slider_joint_3d_sw.h +++ b/servers/physics_3d/joints/slider_joint_3d_sw.h @@ -40,7 +40,7 @@ Adapted to Godot from the Bullet library. /* Bullet Continuous Collision Detection and Physics Library -Copyright (c) 2003-2006 Erwin Coumans https://bulletphysics.org +Copyright (c) 2003-2006 Erwin Coumans http://continuousphysics.com/Bullet/ This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held liable for any damages arising from the use of this software. diff --git a/servers/physics_3d/shape_3d_sw.cpp b/servers/physics_3d/shape_3d_sw.cpp index 945d0120be..60703c4e2d 100644 --- a/servers/physics_3d/shape_3d_sw.cpp +++ b/servers/physics_3d/shape_3d_sw.cpp @@ -39,7 +39,7 @@ /* Bullet Continuous Collision Detection and Physics Library -Copyright (c) 2003-2009 Erwin Coumans https://bulletphysics.org +Copyright (c) 2003-2009 Erwin Coumans http://bulletphysics.org This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held liable for any damages arising from the use of this software. diff --git a/servers/physics_3d/soft_body_3d_sw.cpp b/servers/physics_3d/soft_body_3d_sw.cpp index d7e13867bf..5f6e202c73 100644 --- a/servers/physics_3d/soft_body_3d_sw.cpp +++ b/servers/physics_3d/soft_body_3d_sw.cpp @@ -38,7 +38,7 @@ /* Bullet Continuous Collision Detection and Physics Library -Copyright (c) 2003-2006 Erwin Coumans https://bulletphysics.org +Copyright (c) 2003-2006 Erwin Coumans http://continuousphysics.com/Bullet/ This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held liable for any damages arising from the use of this software. diff --git a/servers/rendering/rasterizer_dummy.h b/servers/rendering/rasterizer_dummy.h index f58d124140..35bb7722e7 100644 --- a/servers/rendering/rasterizer_dummy.h +++ b/servers/rendering/rasterizer_dummy.h @@ -197,7 +197,7 @@ public: TypedArray<Image> bake_render_uv2(RID p_base, const Vector<RID> &p_material_overrides, const Size2i &p_image_size) override { return TypedArray<Image>(); } - bool free(RID p_rid) override { return true; } + bool free(RID p_rid) override { return false; } void update() override {} void sdfgi_set_debug_probe_select(const Vector3 &p_position, const Vector3 &p_dir) override {} @@ -664,8 +664,9 @@ public: DummyTexture *texture = texture_owner.getornull(p_rid); texture_owner.free(p_rid); memdelete(texture); + return true; } - return true; + return false; } virtual void update_memory_info() override {} 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 1b730567d9..9201f917db 100644 --- a/servers/rendering/renderer_rd/forward_clustered/render_forward_clustered.cpp +++ b/servers/rendering/renderer_rd/forward_clustered/render_forward_clustered.cpp @@ -223,7 +223,6 @@ void RenderForwardClustered::RenderBufferDataForwardClustered::configure(RID p_c RD::TEXTURE_SAMPLES_2, RD::TEXTURE_SAMPLES_4, RD::TEXTURE_SAMPLES_8, - RD::TEXTURE_SAMPLES_16 }; texture_samples = ts[p_msaa]; @@ -1163,7 +1162,7 @@ void RenderForwardClustered::_render_scene(RenderDataRD *p_render_data, const Co render_buffer = (RenderBufferDataForwardClustered *)render_buffers_get_data(p_render_data->render_buffers); } RendererSceneEnvironmentRD *env = get_environment(p_render_data->environment); - static const int texture_multisamples[RS::VIEWPORT_MSAA_MAX] = { 1, 2, 4, 8, 16 }; + static const int texture_multisamples[RS::VIEWPORT_MSAA_MAX] = { 1, 2, 4, 8 }; //first of all, make a new render pass //fill up ubo diff --git a/servers/rendering/renderer_rd/forward_mobile/render_forward_mobile.cpp b/servers/rendering/renderer_rd/forward_mobile/render_forward_mobile.cpp index 276a44bc27..a5cc2db48f 100644 --- a/servers/rendering/renderer_rd/forward_mobile/render_forward_mobile.cpp +++ b/servers/rendering/renderer_rd/forward_mobile/render_forward_mobile.cpp @@ -159,7 +159,6 @@ void RenderForwardMobile::RenderBufferDataForwardMobile::configure(RID p_color_b RD::TEXTURE_SAMPLES_2, RD::TEXTURE_SAMPLES_4, RD::TEXTURE_SAMPLES_8, - RD::TEXTURE_SAMPLES_16 }; texture_samples = ts[p_msaa]; diff --git a/servers/rendering/renderer_rd/shaders/scene_forward_clustered.glsl b/servers/rendering/renderer_rd/shaders/scene_forward_clustered.glsl index edbe1031b7..8cb56fbc83 100644 --- a/servers/rendering/renderer_rd/shaders/scene_forward_clustered.glsl +++ b/servers/rendering/renderer_rd/shaders/scene_forward_clustered.glsl @@ -903,6 +903,7 @@ void main() { if (scene_data.use_reflection_cubemap) { vec3 ref_vec = reflect(-view, normal); + float horizon = min(1.0 + dot(ref_vec, normal), 1.0); ref_vec = scene_data.radiance_inverse_xform * ref_vec; #ifdef USE_RADIANCE_CUBEMAP_ARRAY @@ -915,7 +916,6 @@ void main() { specular_light = textureLod(samplerCube(radiance_cubemap, material_samplers[SAMPLER_LINEAR_WITH_MIPMAPS_CLAMP]), ref_vec, roughness * MAX_ROUGHNESS_LOD).rgb; #endif //USE_RADIANCE_CUBEMAP_ARRAY - float horizon = min(1.0 + dot(ref_vec, normal), 1.0); specular_light *= horizon * horizon; specular_light *= scene_data.ambient_light_color_energy.a; } diff --git a/servers/rendering/renderer_rd/shaders/scene_forward_mobile.glsl b/servers/rendering/renderer_rd/shaders/scene_forward_mobile.glsl index 518b0a6c7f..c3c4139450 100644 --- a/servers/rendering/renderer_rd/shaders/scene_forward_mobile.glsl +++ b/servers/rendering/renderer_rd/shaders/scene_forward_mobile.glsl @@ -868,6 +868,7 @@ void main() { if (scene_data.use_reflection_cubemap) { vec3 ref_vec = reflect(-view, normal); + float horizon = min(1.0 + dot(ref_vec, normal), 1.0); ref_vec = scene_data.radiance_inverse_xform * ref_vec; #ifdef USE_RADIANCE_CUBEMAP_ARRAY @@ -880,7 +881,6 @@ void main() { specular_light = textureLod(samplerCube(radiance_cubemap, material_samplers[SAMPLER_LINEAR_WITH_MIPMAPS_CLAMP]), ref_vec, roughness * MAX_ROUGHNESS_LOD).rgb; #endif //USE_RADIANCE_CUBEMAP_ARRAY - float horizon = min(1.0 + dot(ref_vec, normal), 1.0); specular_light *= horizon * horizon; specular_light *= scene_data.ambient_light_color_energy.a; } diff --git a/servers/rendering_server.cpp b/servers/rendering_server.cpp index 5f349e5e33..1b10e4dcbe 100644 --- a/servers/rendering_server.cpp +++ b/servers/rendering_server.cpp @@ -2225,7 +2225,6 @@ void RenderingServer::_bind_methods() { BIND_ENUM_CONSTANT(VIEWPORT_MSAA_2X); BIND_ENUM_CONSTANT(VIEWPORT_MSAA_4X); BIND_ENUM_CONSTANT(VIEWPORT_MSAA_8X); - BIND_ENUM_CONSTANT(VIEWPORT_MSAA_16X); BIND_ENUM_CONSTANT(VIEWPORT_MSAA_MAX); BIND_ENUM_CONSTANT(VIEWPORT_SCREEN_SPACE_AA_DISABLED); diff --git a/servers/rendering_server.h b/servers/rendering_server.h index b79aaefab4..1b04a6e5e2 100644 --- a/servers/rendering_server.h +++ b/servers/rendering_server.h @@ -836,7 +836,6 @@ public: VIEWPORT_MSAA_2X, VIEWPORT_MSAA_4X, VIEWPORT_MSAA_8X, - VIEWPORT_MSAA_16X, VIEWPORT_MSAA_MAX, }; diff --git a/tests/test_array.h b/tests/test_array.h index 52da256860..3bd476fd27 100644 --- a/tests/test_array.h +++ b/tests/test_array.h @@ -39,6 +39,7 @@ #include "core/variant/container_type_validate.h" #include "core/variant/variant.h" #include "tests/test_macros.h" +#include "tests/test_tools.h" namespace TestArray { @@ -170,6 +171,56 @@ TEST_CASE("[Array] push_front(), pop_front(), pop_back()") { CHECK(arr.size() == 2); } +TEST_CASE("[Array] pop_at()") { + ErrorDetector ed; + + Array arr; + arr.push_back(2); + arr.push_back(4); + arr.push_back(6); + arr.push_back(8); + arr.push_back(10); + + REQUIRE(int(arr.pop_at(2)) == 6); + REQUIRE(arr.size() == 4); + CHECK(int(arr[0]) == 2); + CHECK(int(arr[1]) == 4); + CHECK(int(arr[2]) == 8); + CHECK(int(arr[3]) == 10); + + REQUIRE(int(arr.pop_at(2)) == 8); + REQUIRE(arr.size() == 3); + CHECK(int(arr[0]) == 2); + CHECK(int(arr[1]) == 4); + CHECK(int(arr[2]) == 10); + + // Negative index. + REQUIRE(int(arr.pop_at(-1)) == 10); + REQUIRE(arr.size() == 2); + CHECK(int(arr[0]) == 2); + CHECK(int(arr[1]) == 4); + + // Invalid pop. + ed.clear(); + ERR_PRINT_OFF; + const Variant ret = arr.pop_at(-15); + ERR_PRINT_ON; + REQUIRE(ret.is_null()); + CHECK(ed.has_error); + + REQUIRE(int(arr.pop_at(0)) == 2); + REQUIRE(arr.size() == 1); + CHECK(int(arr[0]) == 4); + + REQUIRE(int(arr.pop_at(0)) == 4); + REQUIRE(arr.is_empty()); + + // Pop from empty array. + ed.clear(); + REQUIRE(arr.pop_at(24).is_null()); + CHECK_FALSE(ed.has_error); +} + TEST_CASE("[Array] max() and min()") { Array arr; arr.push_back(3); diff --git a/tests/test_code_edit.h b/tests/test_code_edit.h new file mode 100644 index 0000000000..9579d8ebef --- /dev/null +++ b/tests/test_code_edit.h @@ -0,0 +1,813 @@ +/*************************************************************************/ +/* test_code_edit.h */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md). */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/*************************************************************************/ + +#ifndef TEST_CODE_EDIT_H +#define TEST_CODE_EDIT_H + +#include "core/input/input_map.h" +#include "core/object/message_queue.h" +#include "core/os/keyboard.h" +#include "core/string/string_builder.h" +#include "scene/gui/code_edit.h" +#include "scene/resources/default_theme/default_theme.h" + +#include "tests/test_macros.h" + +namespace TestCodeEdit { + +TEST_CASE("[SceneTree][CodeEdit] line gutters") { + CodeEdit *code_edit = memnew(CodeEdit); + SceneTree::get_singleton()->get_root()->add_child(code_edit); + + SUBCASE("[CodeEdit] breakpoints") { + SIGNAL_WATCH(code_edit, "breakpoint_toggled"); + + SUBCASE("[CodeEdit] draw breakpoints gutter") { + code_edit->set_draw_breakpoints_gutter(false); + CHECK_FALSE(code_edit->is_drawing_breakpoints_gutter()); + + code_edit->set_draw_breakpoints_gutter(true); + CHECK(code_edit->is_drawing_breakpoints_gutter()); + } + + SUBCASE("[CodeEdit] set line as breakpoint") { + /* Out of bounds. */ + ERR_PRINT_OFF; + + code_edit->set_line_as_breakpoint(-1, true); + CHECK_FALSE(code_edit->is_line_breakpointed(-1)); + SIGNAL_CHECK_FALSE("breakpoint_toggled"); + + code_edit->set_line_as_breakpoint(1, true); + CHECK_FALSE(code_edit->is_line_breakpointed(1)); + SIGNAL_CHECK_FALSE("breakpoint_toggled"); + + ERR_PRINT_ON; + + Array arg1; + arg1.push_back(0); + Array args; + args.push_back(arg1); + + code_edit->set_line_as_breakpoint(0, true); + CHECK(code_edit->is_line_breakpointed(0)); + CHECK(code_edit->get_breakpointed_lines()[0] == Variant(0)); + SIGNAL_CHECK("breakpoint_toggled", args); + + code_edit->set_line_as_breakpoint(0, false); + CHECK_FALSE(code_edit->is_line_breakpointed(0)); + SIGNAL_CHECK("breakpoint_toggled", args); + } + + SUBCASE("[CodeEdit] clear breakpointed lines") { + code_edit->clear_breakpointed_lines(); + SIGNAL_CHECK_FALSE("breakpoint_toggled"); + + Array arg1; + arg1.push_back(0); + Array args; + args.push_back(arg1); + + code_edit->set_line_as_breakpoint(0, true); + CHECK(code_edit->is_line_breakpointed(0)); + SIGNAL_CHECK("breakpoint_toggled", args); + + code_edit->clear_breakpointed_lines(); + CHECK_FALSE(code_edit->is_line_breakpointed(0)); + SIGNAL_CHECK("breakpoint_toggled", args); + } + + SUBCASE("[CodeEdit] breakpoints and set text") { + Array arg1; + arg1.push_back(0); + Array args; + args.push_back(arg1); + + code_edit->set_text("test\nline"); + code_edit->set_line_as_breakpoint(0, true); + CHECK(code_edit->is_line_breakpointed(0)); + SIGNAL_CHECK("breakpoint_toggled", args); + + /* breakpoint on lines that still exist are kept. */ + code_edit->set_text(""); + MessageQueue::get_singleton()->flush(); + CHECK(code_edit->is_line_breakpointed(0)); + SIGNAL_CHECK_FALSE("breakpoint_toggled"); + + /* breakpoint on lines that are removed should also be removed. */ + code_edit->clear_breakpointed_lines(); + SIGNAL_DISCARD("breakpoint_toggled") + + ((Array)args[0])[0] = 1; + code_edit->set_text("test\nline"); + code_edit->set_line_as_breakpoint(1, true); + CHECK(code_edit->is_line_breakpointed(1)); + SIGNAL_CHECK("breakpoint_toggled", args); + + code_edit->set_text(""); + MessageQueue::get_singleton()->flush(); + CHECK_FALSE(code_edit->is_line_breakpointed(0)); + ERR_PRINT_OFF; + CHECK_FALSE(code_edit->is_line_breakpointed(1)); + ERR_PRINT_ON; + SIGNAL_CHECK("breakpoint_toggled", args); + } + + SUBCASE("[CodeEdit] breakpoints and clear") { + Array arg1; + arg1.push_back(0); + Array args; + args.push_back(arg1); + + code_edit->set_text("test\nline"); + code_edit->set_line_as_breakpoint(0, true); + CHECK(code_edit->is_line_breakpointed(0)); + SIGNAL_CHECK("breakpoint_toggled", args); + + /* breakpoint on lines that still exist are removed. */ + code_edit->clear(); + MessageQueue::get_singleton()->flush(); + CHECK_FALSE(code_edit->is_line_breakpointed(0)); + SIGNAL_CHECK("breakpoint_toggled", args); + + /* breakpoint on lines that are removed should also be removed. */ + code_edit->clear_breakpointed_lines(); + SIGNAL_DISCARD("breakpoint_toggled") + + ((Array)args[0])[0] = 1; + code_edit->set_text("test\nline"); + code_edit->set_line_as_breakpoint(1, true); + CHECK(code_edit->is_line_breakpointed(1)); + SIGNAL_CHECK("breakpoint_toggled", args); + + code_edit->clear(); + MessageQueue::get_singleton()->flush(); + CHECK_FALSE(code_edit->is_line_breakpointed(0)); + ERR_PRINT_OFF; + CHECK_FALSE(code_edit->is_line_breakpointed(1)); + ERR_PRINT_ON; + SIGNAL_CHECK("breakpoint_toggled", args); + } + + SUBCASE("[CodeEdit] breakpoints and new lines no text") { + Array arg1; + arg1.push_back(0); + Array args; + args.push_back(arg1); + + /* No text moves breakpoint. */ + code_edit->set_line_as_breakpoint(0, true); + CHECK(code_edit->is_line_breakpointed(0)); + SIGNAL_CHECK("breakpoint_toggled", args); + + /* Normal. */ + ((Array)args[0])[0] = 0; + Array arg2; + arg2.push_back(1); + args.push_back(arg2); + + SEND_GUI_ACTION(code_edit, "ui_text_newline"); + CHECK(code_edit->get_line_count() == 2); + CHECK_FALSE(code_edit->is_line_breakpointed(0)); + CHECK(code_edit->is_line_breakpointed(1)); + SIGNAL_CHECK("breakpoint_toggled", args); + + /* Non-Breaking. */ + ((Array)args[0])[0] = 1; + ((Array)args[1])[0] = 2; + SEND_GUI_ACTION(code_edit, "ui_text_newline_blank"); + CHECK(code_edit->get_line_count() == 3); + CHECK_FALSE(code_edit->is_line_breakpointed(1)); + CHECK(code_edit->is_line_breakpointed(2)); + SIGNAL_CHECK("breakpoint_toggled", args); + + /* Above. */ + ((Array)args[0])[0] = 2; + ((Array)args[1])[0] = 3; + SEND_GUI_ACTION(code_edit, "ui_text_newline_above"); + CHECK(code_edit->get_line_count() == 4); + CHECK_FALSE(code_edit->is_line_breakpointed(2)); + CHECK(code_edit->is_line_breakpointed(3)); + SIGNAL_CHECK("breakpoint_toggled", args); + } + + SUBCASE("[CodeEdit] breakpoints and new lines with text") { + Array arg1; + arg1.push_back(0); + Array args; + args.push_back(arg1); + + /* Having text does not move breakpoint. */ + code_edit->insert_text_at_caret("text"); + code_edit->set_line_as_breakpoint(0, true); + CHECK(code_edit->is_line_breakpointed(0)); + SIGNAL_CHECK("breakpoint_toggled", args); + + /* Normal. */ + SEND_GUI_ACTION(code_edit, "ui_text_newline"); + CHECK(code_edit->get_line_count() == 2); + CHECK(code_edit->is_line_breakpointed(0)); + CHECK_FALSE(code_edit->is_line_breakpointed(1)); + SIGNAL_CHECK_FALSE("breakpoint_toggled"); + + /* Non-Breaking. */ + code_edit->set_caret_line(0); + SEND_GUI_ACTION(code_edit, "ui_text_newline_blank"); + CHECK(code_edit->get_line_count() == 3); + CHECK(code_edit->is_line_breakpointed(0)); + CHECK_FALSE(code_edit->is_line_breakpointed(1)); + SIGNAL_CHECK_FALSE("breakpoint_toggled"); + + /* Above does move. */ + ((Array)args[0])[0] = 0; + Array arg2; + arg2.push_back(1); + args.push_back(arg2); + + code_edit->set_caret_line(0); + SEND_GUI_ACTION(code_edit, "ui_text_newline_above"); + CHECK(code_edit->get_line_count() == 4); + CHECK_FALSE(code_edit->is_line_breakpointed(0)); + CHECK(code_edit->is_line_breakpointed(1)); + SIGNAL_CHECK("breakpoint_toggled", args); + } + + SUBCASE("[CodeEdit] breakpoints and backspace") { + Array arg1; + arg1.push_back(1); + Array args; + args.push_back(arg1); + + code_edit->set_text("\n\n"); + code_edit->set_line_as_breakpoint(1, true); + CHECK(code_edit->is_line_breakpointed(1)); + SIGNAL_CHECK("breakpoint_toggled", args); + + code_edit->set_caret_line(2); + + /* backspace onto line does not remove breakpoint */ + SEND_GUI_ACTION(code_edit, "ui_text_backspace"); + CHECK(code_edit->is_line_breakpointed(1)); + SIGNAL_CHECK_FALSE("breakpoint_toggled"); + + /* backspace on breakpointed line removes it */ + SEND_GUI_ACTION(code_edit, "ui_text_backspace"); + CHECK_FALSE(code_edit->is_line_breakpointed(0)); + ERR_PRINT_OFF; + CHECK_FALSE(code_edit->is_line_breakpointed(1)); + ERR_PRINT_ON; + SIGNAL_CHECK("breakpoint_toggled", args); + } + + SUBCASE("[CodeEdit] breakpoints and delete") { + Array arg1; + arg1.push_back(1); + Array args; + args.push_back(arg1); + + code_edit->set_text("\n\n"); + code_edit->set_line_as_breakpoint(1, true); + CHECK(code_edit->is_line_breakpointed(1)); + SIGNAL_CHECK("breakpoint_toggled", args); + code_edit->set_caret_line(1); + + /* Delete onto breakpointed lines does not remove it. */ + SEND_GUI_ACTION(code_edit, "ui_text_delete"); + CHECK(code_edit->get_line_count() == 2); + CHECK(code_edit->is_line_breakpointed(1)); + SIGNAL_CHECK_FALSE("breakpoint_toggled"); + + /* Delete moving breakpointed line up removes it. */ + code_edit->set_caret_line(0); + SEND_GUI_ACTION(code_edit, "ui_text_delete"); + CHECK(code_edit->get_line_count() == 1); + ERR_PRINT_OFF; + CHECK_FALSE(code_edit->is_line_breakpointed(1)); + ERR_PRINT_ON; + SIGNAL_CHECK("breakpoint_toggled", args); + } + + SUBCASE("[CodeEdit] breakpoints and delete selection") { + Array arg1; + arg1.push_back(1); + Array args; + args.push_back(arg1); + + code_edit->set_text("\n\n"); + code_edit->set_line_as_breakpoint(1, true); + CHECK(code_edit->is_line_breakpointed(1)); + SIGNAL_CHECK("breakpoint_toggled", args); + + code_edit->select(0, 0, 2, 0); + code_edit->delete_selection(); + MessageQueue::get_singleton()->flush(); + CHECK_FALSE(code_edit->is_line_breakpointed(0)); + SIGNAL_CHECK("breakpoint_toggled", args); + } + + SUBCASE("[CodeEdit] breakpoints and undo") { + Array arg1; + arg1.push_back(1); + Array args; + args.push_back(arg1); + + code_edit->set_text("\n\n"); + code_edit->set_line_as_breakpoint(1, true); + CHECK(code_edit->is_line_breakpointed(1)); + SIGNAL_CHECK("breakpoint_toggled", args); + + code_edit->select(0, 0, 2, 0); + code_edit->delete_selection(); + MessageQueue::get_singleton()->flush(); + CHECK_FALSE(code_edit->is_line_breakpointed(0)); + SIGNAL_CHECK("breakpoint_toggled", args); + + /* Undo does not restore breakpoint. */ + code_edit->undo(); + CHECK_FALSE(code_edit->is_line_breakpointed(1)); + SIGNAL_CHECK_FALSE("breakpoint_toggled"); + } + + SIGNAL_UNWATCH(code_edit, "breakpoint_toggled"); + } + + SUBCASE("[CodeEdit] bookmarks") { + SUBCASE("[CodeEdit] draw bookmarks gutter") { + code_edit->set_draw_bookmarks_gutter(false); + CHECK_FALSE(code_edit->is_drawing_bookmarks_gutter()); + + code_edit->set_draw_bookmarks_gutter(true); + CHECK(code_edit->is_drawing_bookmarks_gutter()); + } + + SUBCASE("[CodeEdit] set line as bookmarks") { + /* Out of bounds. */ + ERR_PRINT_OFF; + + code_edit->set_line_as_bookmarked(-1, true); + CHECK_FALSE(code_edit->is_line_bookmarked(-1)); + + code_edit->set_line_as_bookmarked(1, true); + CHECK_FALSE(code_edit->is_line_bookmarked(1)); + + ERR_PRINT_ON; + + code_edit->set_line_as_bookmarked(0, true); + CHECK(code_edit->get_bookmarked_lines()[0] == Variant(0)); + CHECK(code_edit->is_line_bookmarked(0)); + + code_edit->set_line_as_bookmarked(0, false); + CHECK_FALSE(code_edit->is_line_bookmarked(0)); + } + + SUBCASE("[CodeEdit] clear bookmarked lines") { + code_edit->clear_bookmarked_lines(); + + code_edit->set_line_as_bookmarked(0, true); + CHECK(code_edit->is_line_bookmarked(0)); + + code_edit->clear_bookmarked_lines(); + CHECK_FALSE(code_edit->is_line_bookmarked(0)); + } + + SUBCASE("[CodeEdit] bookmarks and set text") { + code_edit->set_text("test\nline"); + code_edit->set_line_as_bookmarked(0, true); + CHECK(code_edit->is_line_bookmarked(0)); + + /* bookmarks on lines that still exist are kept. */ + code_edit->set_text(""); + MessageQueue::get_singleton()->flush(); + CHECK(code_edit->is_line_bookmarked(0)); + + /* bookmarks on lines that are removed should also be removed. */ + code_edit->clear_bookmarked_lines(); + + code_edit->set_text("test\nline"); + code_edit->set_line_as_bookmarked(1, true); + CHECK(code_edit->is_line_bookmarked(1)); + + code_edit->set_text(""); + MessageQueue::get_singleton()->flush(); + CHECK_FALSE(code_edit->is_line_bookmarked(0)); + ERR_PRINT_OFF; + CHECK_FALSE(code_edit->is_line_bookmarked(1)); + ERR_PRINT_ON; + } + + SUBCASE("[CodeEdit] bookmarks and clear") { + code_edit->set_text("test\nline"); + code_edit->set_line_as_bookmarked(0, true); + CHECK(code_edit->is_line_bookmarked(0)); + + /* bookmarks on lines that still exist are removed. */ + code_edit->clear(); + MessageQueue::get_singleton()->flush(); + CHECK_FALSE(code_edit->is_line_bookmarked(0)); + + /* bookmarks on lines that are removed should also be removed. */ + code_edit->clear_bookmarked_lines(); + + code_edit->set_text("test\nline"); + code_edit->set_line_as_bookmarked(1, true); + CHECK(code_edit->is_line_bookmarked(1)); + + code_edit->clear(); + MessageQueue::get_singleton()->flush(); + CHECK_FALSE(code_edit->is_line_bookmarked(0)); + ERR_PRINT_OFF; + CHECK_FALSE(code_edit->is_line_bookmarked(1)); + ERR_PRINT_ON; + } + + SUBCASE("[CodeEdit] bookmarks and new lines no text") { + /* No text moves bookmarks. */ + code_edit->set_line_as_bookmarked(0, true); + CHECK(code_edit->is_line_bookmarked(0)); + + /* Normal. */ + SEND_GUI_ACTION(code_edit, "ui_text_newline"); + CHECK(code_edit->get_line_count() == 2); + CHECK_FALSE(code_edit->is_line_bookmarked(0)); + CHECK(code_edit->is_line_bookmarked(1)); + + /* Non-Breaking. */ + SEND_GUI_ACTION(code_edit, "ui_text_newline_blank"); + CHECK(code_edit->get_line_count() == 3); + CHECK_FALSE(code_edit->is_line_bookmarked(1)); + CHECK(code_edit->is_line_bookmarked(2)); + + /* Above. */ + SEND_GUI_ACTION(code_edit, "ui_text_newline_above"); + CHECK(code_edit->get_line_count() == 4); + CHECK_FALSE(code_edit->is_line_bookmarked(2)); + CHECK(code_edit->is_line_bookmarked(3)); + } + + SUBCASE("[CodeEdit] bookmarks and new lines with text") { + /* Having text does not move bookmark. */ + code_edit->insert_text_at_caret("text"); + code_edit->set_line_as_bookmarked(0, true); + CHECK(code_edit->is_line_bookmarked(0)); + + /* Normal. */ + SEND_GUI_ACTION(code_edit, "ui_text_newline"); + CHECK(code_edit->get_line_count() == 2); + CHECK(code_edit->is_line_bookmarked(0)); + CHECK_FALSE(code_edit->is_line_bookmarked(1)); + + /* Non-Breaking. */ + code_edit->set_caret_line(0); + SEND_GUI_ACTION(code_edit, "ui_text_newline_blank"); + CHECK(code_edit->get_line_count() == 3); + CHECK(code_edit->is_line_bookmarked(0)); + CHECK_FALSE(code_edit->is_line_bookmarked(1)); + + /* Above does move. */ + code_edit->set_caret_line(0); + SEND_GUI_ACTION(code_edit, "ui_text_newline_above"); + CHECK(code_edit->get_line_count() == 4); + CHECK_FALSE(code_edit->is_line_bookmarked(0)); + CHECK(code_edit->is_line_bookmarked(1)); + } + + SUBCASE("[CodeEdit] bookmarks and backspace") { + code_edit->set_text("\n\n"); + code_edit->set_line_as_bookmarked(1, true); + CHECK(code_edit->is_line_bookmarked(1)); + + code_edit->set_caret_line(2); + + /* backspace onto line does not remove bookmark */ + SEND_GUI_ACTION(code_edit, "ui_text_backspace"); + CHECK(code_edit->is_line_bookmarked(1)); + + /* backspace on bookmarked line removes it */ + SEND_GUI_ACTION(code_edit, "ui_text_backspace"); + CHECK_FALSE(code_edit->is_line_bookmarked(0)); + ERR_PRINT_OFF; + CHECK_FALSE(code_edit->is_line_bookmarked(1)); + ERR_PRINT_ON; + } + + SUBCASE("[CodeEdit] bookmarks and delete") { + code_edit->set_text("\n\n"); + code_edit->set_line_as_bookmarked(1, true); + CHECK(code_edit->is_line_bookmarked(1)); + code_edit->set_caret_line(1); + + /* Delete onto bookmarked lines does not remove it. */ + SEND_GUI_ACTION(code_edit, "ui_text_delete"); + CHECK(code_edit->get_line_count() == 2); + CHECK(code_edit->is_line_bookmarked(1)); + + /* Delete moving bookmarked line up removes it. */ + code_edit->set_caret_line(0); + SEND_GUI_ACTION(code_edit, "ui_text_delete"); + CHECK(code_edit->get_line_count() == 1); + ERR_PRINT_OFF; + CHECK_FALSE(code_edit->is_line_bookmarked(1)); + ERR_PRINT_ON; + } + + SUBCASE("[CodeEdit] bookmarks and delete selection") { + code_edit->set_text("\n\n"); + code_edit->set_line_as_bookmarked(1, true); + CHECK(code_edit->is_line_bookmarked(1)); + + code_edit->select(0, 0, 2, 0); + code_edit->delete_selection(); + MessageQueue::get_singleton()->flush(); + CHECK_FALSE(code_edit->is_line_bookmarked(0)); + } + + SUBCASE("[CodeEdit] bookmarks and undo") { + code_edit->set_text("\n\n"); + code_edit->set_line_as_bookmarked(1, true); + CHECK(code_edit->is_line_bookmarked(1)); + + code_edit->select(0, 0, 2, 0); + code_edit->delete_selection(); + MessageQueue::get_singleton()->flush(); + CHECK_FALSE(code_edit->is_line_bookmarked(0)); + + /* Undo does not restore bookmark. */ + code_edit->undo(); + CHECK_FALSE(code_edit->is_line_bookmarked(1)); + } + } + + SUBCASE("[CodeEdit] executing lines") { + SUBCASE("[CodeEdit] draw executing lines gutter") { + code_edit->set_draw_executing_lines_gutter(false); + CHECK_FALSE(code_edit->is_drawing_executing_lines_gutter()); + + code_edit->set_draw_executing_lines_gutter(true); + CHECK(code_edit->is_drawing_executing_lines_gutter()); + } + + SUBCASE("[CodeEdit] set line as executing lines") { + /* Out of bounds. */ + ERR_PRINT_OFF; + + code_edit->set_line_as_executing(-1, true); + CHECK_FALSE(code_edit->is_line_executing(-1)); + + code_edit->set_line_as_executing(1, true); + CHECK_FALSE(code_edit->is_line_executing(1)); + + ERR_PRINT_ON; + + code_edit->set_line_as_executing(0, true); + CHECK(code_edit->get_executing_lines()[0] == Variant(0)); + CHECK(code_edit->is_line_executing(0)); + + code_edit->set_line_as_executing(0, false); + CHECK_FALSE(code_edit->is_line_executing(0)); + } + + SUBCASE("[CodeEdit] clear executing lines lines") { + code_edit->clear_executing_lines(); + + code_edit->set_line_as_executing(0, true); + CHECK(code_edit->is_line_executing(0)); + + code_edit->clear_executing_lines(); + CHECK_FALSE(code_edit->is_line_executing(0)); + } + + SUBCASE("[CodeEdit] executing lines and set text") { + code_edit->set_text("test\nline"); + code_edit->set_line_as_executing(0, true); + CHECK(code_edit->is_line_executing(0)); + + /* executing on lines that still exist are kept. */ + code_edit->set_text(""); + MessageQueue::get_singleton()->flush(); + CHECK(code_edit->is_line_executing(0)); + + /* executing on lines that are removed should also be removed. */ + code_edit->clear_executing_lines(); + + code_edit->set_text("test\nline"); + code_edit->set_line_as_executing(1, true); + CHECK(code_edit->is_line_executing(1)); + + code_edit->set_text(""); + MessageQueue::get_singleton()->flush(); + CHECK_FALSE(code_edit->is_line_executing(0)); + ERR_PRINT_OFF; + CHECK_FALSE(code_edit->is_line_executing(1)); + ERR_PRINT_ON; + } + + SUBCASE("[CodeEdit] executing lines and clear") { + code_edit->set_text("test\nline"); + code_edit->set_line_as_executing(0, true); + CHECK(code_edit->is_line_executing(0)); + + /* executing on lines that still exist are removed. */ + code_edit->clear(); + MessageQueue::get_singleton()->flush(); + CHECK_FALSE(code_edit->is_line_executing(0)); + + /* executing on lines that are removed should also be removed. */ + code_edit->clear_executing_lines(); + + code_edit->set_text("test\nline"); + code_edit->set_line_as_executing(1, true); + CHECK(code_edit->is_line_executing(1)); + + code_edit->clear(); + MessageQueue::get_singleton()->flush(); + CHECK_FALSE(code_edit->is_line_executing(0)); + ERR_PRINT_OFF; + CHECK_FALSE(code_edit->is_line_executing(1)); + ERR_PRINT_ON; + } + + SUBCASE("[CodeEdit] executing lines and new lines no text") { + /* No text moves executing lines. */ + code_edit->set_line_as_executing(0, true); + CHECK(code_edit->is_line_executing(0)); + + /* Normal. */ + SEND_GUI_ACTION(code_edit, "ui_text_newline"); + CHECK(code_edit->get_line_count() == 2); + CHECK_FALSE(code_edit->is_line_executing(0)); + CHECK(code_edit->is_line_executing(1)); + + /* Non-Breaking. */ + SEND_GUI_ACTION(code_edit, "ui_text_newline_blank"); + CHECK(code_edit->get_line_count() == 3); + CHECK_FALSE(code_edit->is_line_executing(1)); + CHECK(code_edit->is_line_executing(2)); + + /* Above. */ + SEND_GUI_ACTION(code_edit, "ui_text_newline_above"); + CHECK(code_edit->get_line_count() == 4); + CHECK_FALSE(code_edit->is_line_executing(2)); + CHECK(code_edit->is_line_executing(3)); + } + + SUBCASE("[CodeEdit] executing lines and new lines with text") { + /* Having text does not move executing lines. */ + code_edit->insert_text_at_caret("text"); + code_edit->set_line_as_executing(0, true); + CHECK(code_edit->is_line_executing(0)); + + /* Normal. */ + SEND_GUI_ACTION(code_edit, "ui_text_newline"); + CHECK(code_edit->get_line_count() == 2); + CHECK(code_edit->is_line_executing(0)); + CHECK_FALSE(code_edit->is_line_executing(1)); + + /* Non-Breaking. */ + code_edit->set_caret_line(0); + SEND_GUI_ACTION(code_edit, "ui_text_newline_blank"); + CHECK(code_edit->get_line_count() == 3); + CHECK(code_edit->is_line_executing(0)); + CHECK_FALSE(code_edit->is_line_executing(1)); + + /* Above does move. */ + code_edit->set_caret_line(0); + SEND_GUI_ACTION(code_edit, "ui_text_newline_above"); + CHECK(code_edit->get_line_count() == 4); + CHECK_FALSE(code_edit->is_line_executing(0)); + CHECK(code_edit->is_line_executing(1)); + } + + SUBCASE("[CodeEdit] executing lines and backspace") { + code_edit->set_text("\n\n"); + code_edit->set_line_as_executing(1, true); + CHECK(code_edit->is_line_executing(1)); + + code_edit->set_caret_line(2); + + /* backspace onto line does not remove executing lines. */ + SEND_GUI_ACTION(code_edit, "ui_text_backspace"); + CHECK(code_edit->is_line_executing(1)); + + /* backspace on executing line removes it */ + SEND_GUI_ACTION(code_edit, "ui_text_backspace"); + CHECK_FALSE(code_edit->is_line_executing(0)); + ERR_PRINT_OFF; + CHECK_FALSE(code_edit->is_line_executing(1)); + ERR_PRINT_ON; + } + + SUBCASE("[CodeEdit] executing lines and delete") { + code_edit->set_text("\n\n"); + code_edit->set_line_as_executing(1, true); + CHECK(code_edit->is_line_executing(1)); + code_edit->set_caret_line(1); + + /* Delete onto executing lines does not remove it. */ + SEND_GUI_ACTION(code_edit, "ui_text_delete"); + CHECK(code_edit->get_line_count() == 2); + CHECK(code_edit->is_line_executing(1)); + + /* Delete moving executing line up removes it. */ + code_edit->set_caret_line(0); + SEND_GUI_ACTION(code_edit, "ui_text_delete"); + CHECK(code_edit->get_line_count() == 1); + ERR_PRINT_OFF; + CHECK_FALSE(code_edit->is_line_executing(1)); + ERR_PRINT_ON; + } + + SUBCASE("[CodeEdit] executing lines and delete selection") { + code_edit->set_text("\n\n"); + code_edit->set_line_as_executing(1, true); + CHECK(code_edit->is_line_executing(1)); + + code_edit->select(0, 0, 2, 0); + code_edit->delete_selection(); + MessageQueue::get_singleton()->flush(); + CHECK_FALSE(code_edit->is_line_executing(0)); + } + + SUBCASE("[CodeEdit] executing lines and undo") { + code_edit->set_text("\n\n"); + code_edit->set_line_as_executing(1, true); + CHECK(code_edit->is_line_executing(1)); + + code_edit->select(0, 0, 2, 0); + code_edit->delete_selection(); + MessageQueue::get_singleton()->flush(); + CHECK_FALSE(code_edit->is_line_executing(0)); + + /* Undo does not restore executing lines. */ + code_edit->undo(); + CHECK_FALSE(code_edit->is_line_executing(1)); + } + } + + SUBCASE("[CodeEdit] line numbers") { + SUBCASE("[CodeEdit] draw line numbers gutter and padding") { + code_edit->set_draw_line_numbers(false); + CHECK_FALSE(code_edit->is_draw_line_numbers_enabled()); + + code_edit->set_draw_line_numbers(true); + CHECK(code_edit->is_draw_line_numbers_enabled()); + + code_edit->set_line_numbers_zero_padded(false); + CHECK_FALSE(code_edit->is_line_numbers_zero_padded()); + + code_edit->set_line_numbers_zero_padded(true); + CHECK(code_edit->is_line_numbers_zero_padded()); + + code_edit->set_line_numbers_zero_padded(false); + CHECK_FALSE(code_edit->is_line_numbers_zero_padded()); + + code_edit->set_draw_line_numbers(false); + CHECK_FALSE(code_edit->is_draw_line_numbers_enabled()); + + code_edit->set_line_numbers_zero_padded(true); + CHECK(code_edit->is_line_numbers_zero_padded()); + } + } + + SUBCASE("[CodeEdit] line folding") { + SUBCASE("[CodeEdit] draw line folding gutter") { + code_edit->set_draw_fold_gutter(false); + CHECK_FALSE(code_edit->is_drawing_fold_gutter()); + + code_edit->set_draw_fold_gutter(true); + CHECK(code_edit->is_drawing_fold_gutter()); + } + } + + memdelete(code_edit); +} + +} // namespace TestCodeEdit + +#endif // TEST_CODE_EDIT_H diff --git a/tests/test_macros.h b/tests/test_macros.h index a1f1932db4..bf1001fa67 100644 --- a/tests/test_macros.h +++ b/tests/test_macros.h @@ -31,6 +31,8 @@ #ifndef TEST_MACROS_H #define TEST_MACROS_H +#include "core/object/callable_method_pointer.h" +#include "core/object/class_db.h" #include "core/templates/map.h" #include "core/variant/variant.h" @@ -129,4 +131,186 @@ int register_test_command(String p_command, TestFunc p_function); register_test_command(m_command, m_function); \ DOCTEST_GLOBAL_NO_WARNINGS_END() +// Utility macro to send an action event to a given object +// Requires Message Queue and InputMap to be setup. + +#define SEND_GUI_ACTION(m_object, m_action) \ + { \ + const List<Ref<InputEvent>> *events = InputMap::get_singleton()->action_get_events(m_action); \ + const List<Ref<InputEvent>>::Element *first_event = events->front(); \ + Ref<InputEventKey> event = first_event->get(); \ + event->set_pressed(true); \ + m_object->gui_input(event); \ + MessageQueue::get_singleton()->flush(); \ + } + +// Utility class / macros for testing signals +// +// Use SIGNAL_WATCH(*object, "signal_name") to start watching +// Makes sure to call SIGNAL_UNWATCH(*object, "signal_name") to stop watching in cleanup, this is not done automatically. +// +// The SignalWatcher will capture all signals and their args sent between checks. +// +// Use SIGNAL_CHECK("signal_name"), Vector<Vector<Variant>>), to check the arguments of all fired signals. +// The outer vector is each fired signal, the inner vector the list of arguments for that signal. Order does matter. +// +// Use SIGNAL_CHECK_FALSE("signal_name") to check if a signal was not fired. +// +// Use SIGNAL_DISCARD("signal_name") to discard records all of the given signal, use only in placed you don't need to check. +// +// All signals are automaticaly discared between test/sub test cases. + +class SignalWatcher : public Object { +private: + inline static SignalWatcher *singleton; + + /* Equal to: Map<String, Vector<Vector<Variant>>> */ + Map<String, Array> _signals; + void _add_signal_entry(const Array &p_args, const String &p_name) { + if (!_signals.has(p_name)) { + _signals[p_name] = Array(); + } + _signals[p_name].push_back(p_args); + } + + void _signal_callback_zero(const String &p_name) { + Array args; + _add_signal_entry(args, p_name); + } + + void _signal_callback_one(Variant p_arg1, const String &p_name) { + Array args; + args.push_back(p_arg1); + _add_signal_entry(args, p_name); + } + + void _signal_callback_two(Variant p_arg1, Variant p_arg2, const String &p_name) { + Array args; + args.push_back(p_arg1); + args.push_back(p_arg2); + _add_signal_entry(args, p_name); + } + + void _signal_callback_three(Variant p_arg1, Variant p_arg2, Variant p_arg3, const String &p_name) { + Array args; + args.push_back(p_arg1); + args.push_back(p_arg2); + args.push_back(p_arg3); + _add_signal_entry(args, p_name); + } + +public: + static SignalWatcher *get_singleton() { return singleton; } + + void watch_signal(Object *p_object, const String &p_signal) { + Vector<Variant> args; + args.push_back(p_signal); + MethodInfo method_info; + ClassDB::get_signal(p_object->get_class(), p_signal, &method_info); + switch (method_info.arguments.size()) { + case 0: { + p_object->connect(p_signal, callable_mp(this, &SignalWatcher::_signal_callback_zero), args); + } break; + case 1: { + p_object->connect(p_signal, callable_mp(this, &SignalWatcher::_signal_callback_one), args); + } break; + case 2: { + p_object->connect(p_signal, callable_mp(this, &SignalWatcher::_signal_callback_two), args); + } break; + case 3: { + p_object->connect(p_signal, callable_mp(this, &SignalWatcher::_signal_callback_three), args); + } break; + default: { + MESSAGE("Signal ", p_signal, " arg count not supported."); + } break; + } + } + + void unwatch_signal(Object *p_object, const String &p_signal) { + MethodInfo method_info; + ClassDB::get_signal(p_object->get_class(), p_signal, &method_info); + switch (method_info.arguments.size()) { + case 0: { + p_object->disconnect(p_signal, callable_mp(this, &SignalWatcher::_signal_callback_zero)); + } break; + case 1: { + p_object->disconnect(p_signal, callable_mp(this, &SignalWatcher::_signal_callback_one)); + } break; + case 2: { + p_object->disconnect(p_signal, callable_mp(this, &SignalWatcher::_signal_callback_two)); + } break; + case 3: { + p_object->disconnect(p_signal, callable_mp(this, &SignalWatcher::_signal_callback_three)); + } break; + default: { + MESSAGE("Signal ", p_signal, " arg count not supported."); + } break; + } + } + + bool check(const String &p_name, const Array &p_args) { + if (!_signals.has(p_name)) { + MESSAGE("Signal ", p_name, " not emitted"); + return false; + } + + if (p_args.size() != _signals[p_name].size()) { + MESSAGE("Signal has " << _signals[p_name] << " expected " << p_args); + discard_signal(p_name); + return false; + } + + bool match = true; + for (int i = 0; i < p_args.size(); i++) { + if (((Array)p_args[i]).size() != ((Array)_signals[p_name][i]).size()) { + MESSAGE("Signal has " << _signals[p_name][i] << " expected " << p_args[i]); + match = false; + continue; + } + + for (int j = 0; j < ((Array)p_args[i]).size(); j++) { + if (((Array)p_args[i])[j] != ((Array)_signals[p_name][i])[j]) { + MESSAGE("Signal has " << _signals[p_name][i] << " expected " << p_args[i]); + match = false; + break; + } + } + } + + discard_signal(p_name); + return match; + } + + bool check_false(const String &p_name) { + bool has = _signals.has(p_name); + discard_signal(p_name); + return !has; + } + + void discard_signal(const String &p_name) { + if (_signals.has(p_name)) { + _signals.erase(p_name); + } + } + + void _clear_signals() { + _signals.clear(); + } + + SignalWatcher() { + singleton = this; + } + + ~SignalWatcher() { + singleton = nullptr; + } +}; + +#define SIGNAL_WATCH(m_object, m_signal) SignalWatcher::get_singleton()->watch_signal(m_object, m_signal); +#define SIGNAL_UNWATCH(m_object, m_signal) SignalWatcher::get_singleton()->unwatch_signal(m_object, m_signal); + +#define SIGNAL_CHECK(m_signal, m_args) CHECK(SignalWatcher::get_singleton()->check(m_signal, m_args)); +#define SIGNAL_CHECK_FALSE(m_signal) CHECK(SignalWatcher::get_singleton()->check_false(m_signal)); +#define SIGNAL_DISCARD(m_signal) SignalWatcher::get_singleton()->discard_signal(m_signal); + #endif // TEST_MACROS_H diff --git a/tests/test_main.cpp b/tests/test_main.cpp index d0466d1e2d..e4aa4c38ff 100644 --- a/tests/test_main.cpp +++ b/tests/test_main.cpp @@ -37,6 +37,7 @@ #include "test_astar.h" #include "test_basis.h" #include "test_class_db.h" +#include "test_code_edit.h" #include "test_color.h" #include "test_command_queue.h" #include "test_config_file.h" @@ -146,3 +147,153 @@ int test_main(int argc, char *argv[]) { return test_context.run(); } + +//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + +#include "servers/navigation_server_2d.h" +#include "servers/navigation_server_3d.h" +#include "servers/rendering/rendering_server_default.h" + +struct GodotTestCaseListener : public doctest::IReporter { + GodotTestCaseListener(const doctest::ContextOptions &p_in) {} + + SignalWatcher *signal_watcher = nullptr; + + PhysicsServer3D *physics_3d_server = nullptr; + PhysicsServer2D *physics_2d_server = nullptr; + NavigationServer3D *navigation_3d_server = nullptr; + NavigationServer2D *navigation_2d_server = nullptr; + + void test_case_start(const doctest::TestCaseData &p_in) override { + SignalWatcher::get_singleton()->_clear_signals(); + + String name = String(p_in.m_name); + + if (name.find("[SceneTree]") != -1) { + GLOBAL_DEF("memory/limits/multithreaded_server/rid_pool_prealloc", 60); + memnew(MessageQueue); + + GLOBAL_DEF("internationalization/rendering/force_right_to_left_layout_direction", false); + memnew(TextServerManager); + Error err = OK; + TextServerManager::initialize(0, err); + + OS::get_singleton()->set_has_server_feature_callback(nullptr); + for (int i = 0; i < DisplayServer::get_create_function_count(); i++) { + if (String("headless") == DisplayServer::get_create_function_name(i)) { + DisplayServer::create(i, "", DisplayServer::WindowMode::WINDOW_MODE_MINIMIZED, DisplayServer::VSyncMode::VSYNC_ENABLED, 0, Vector2i(0, 0), err); + break; + } + } + memnew(RenderingServerDefault()); + RenderingServerDefault::get_singleton()->init(); + RenderingServerDefault::get_singleton()->set_render_loop_enabled(false); + + physics_3d_server = PhysicsServer3DManager::new_default_server(); + physics_3d_server->init(); + + physics_2d_server = PhysicsServer2DManager::new_default_server(); + physics_2d_server->init(); + + navigation_3d_server = NavigationServer3DManager::new_default_server(); + navigation_2d_server = memnew(NavigationServer2D); + + memnew(InputMap); + InputMap::get_singleton()->load_default(); + + make_default_theme(false, Ref<Font>()); + + memnew(SceneTree); + SceneTree::get_singleton()->initialize(); + return; + } + } + + void test_case_end(const doctest::CurrentTestCaseStats &) override { + if (SceneTree::get_singleton()) { + SceneTree::get_singleton()->finalize(); + } + + if (MessageQueue::get_singleton()) { + MessageQueue::get_singleton()->flush(); + } + + if (SceneTree::get_singleton()) { + memdelete(SceneTree::get_singleton()); + } + + clear_default_theme(); + + if (TextServerManager::get_singleton()) { + memdelete(TextServerManager::get_singleton()); + } + + if (navigation_3d_server) { + memdelete(navigation_3d_server); + navigation_3d_server = nullptr; + } + + if (navigation_2d_server) { + memdelete(navigation_2d_server); + navigation_2d_server = nullptr; + } + + if (physics_3d_server) { + physics_3d_server->finish(); + memdelete(physics_3d_server); + physics_3d_server = nullptr; + } + + if (physics_2d_server) { + physics_2d_server->finish(); + memdelete(physics_2d_server); + physics_2d_server = nullptr; + } + + if (RenderingServer::get_singleton()) { + RenderingServer::get_singleton()->sync(); + RenderingServer::get_singleton()->global_variables_clear(); + RenderingServer::get_singleton()->finish(); + memdelete(RenderingServer::get_singleton()); + } + + if (DisplayServer::get_singleton()) { + memdelete(DisplayServer::get_singleton()); + } + + if (InputMap::get_singleton()) { + memdelete(InputMap::get_singleton()); + } + + if (MessageQueue::get_singleton()) { + MessageQueue::get_singleton()->flush(); + memdelete(MessageQueue::get_singleton()); + } + } + + void test_run_start() override { + signal_watcher = memnew(SignalWatcher); + } + + void test_run_end(const doctest::TestRunStats &) override { + memdelete(signal_watcher); + } + + void test_case_reenter(const doctest::TestCaseData &) override { + SignalWatcher::get_singleton()->_clear_signals(); + } + + void subcase_start(const doctest::SubcaseSignature &) override { + SignalWatcher::get_singleton()->_clear_signals(); + } + + void report_query(const doctest::QueryData &) override {} + void test_case_exception(const doctest::TestCaseException &) override {} + void subcase_end() override {} + + void log_assert(const doctest::AssertData &in) override {} + void log_message(const doctest::MessageData &) override {} + void test_case_skipped(const doctest::TestCaseData &) override {} +}; + +REGISTER_LISTENER("GodotTestCaseListener", 1, GodotTestCaseListener); diff --git a/tests/test_string.h b/tests/test_string.h index 82b23d8a00..bcedaa0db7 100644 --- a/tests/test_string.h +++ b/tests/test_string.h @@ -1142,14 +1142,14 @@ TEST_CASE("[String] dedent") { } TEST_CASE("[String] Path functions") { - static const char *path[4] = { "C:\\Godot\\project\\test.tscn", "/Godot/project/test.xscn", "../Godot/project/test.scn", "Godot\\test.doc" }; - static const char *base_dir[4] = { "C:\\Godot\\project", "/Godot/project", "../Godot/project", "Godot" }; - static const char *base_name[4] = { "C:\\Godot\\project\\test", "/Godot/project/test", "../Godot/project/test", "Godot\\test" }; - static const char *ext[4] = { "tscn", "xscn", "scn", "doc" }; - static const char *file[4] = { "test.tscn", "test.xscn", "test.scn", "test.doc" }; - static const bool abs[4] = { true, true, false, false }; - - for (int i = 0; i < 4; i++) { + static const char *path[7] = { "C:\\Godot\\project\\test.tscn", "/Godot/project/test.xscn", "../Godot/project/test.scn", "Godot\\test.doc", "C:\\test.", "res://test", "/.test" }; + static const char *base_dir[7] = { "C:\\Godot\\project", "/Godot/project", "../Godot/project", "Godot", "C:\\", "res://", "/" }; + static const char *base_name[7] = { "C:\\Godot\\project\\test", "/Godot/project/test", "../Godot/project/test", "Godot\\test", "C:\\test", "res://test", "/" }; + static const char *ext[7] = { "tscn", "xscn", "scn", "doc", "", "", "test" }; + static const char *file[7] = { "test.tscn", "test.xscn", "test.scn", "test.doc", "test.", "test", ".test" }; + static const bool abs[7] = { true, true, false, false, true, true, true }; + + for (int i = 0; i < 7; i++) { CHECK(String(path[i]).get_base_dir() == base_dir[i]); CHECK(String(path[i]).get_basename() == base_name[i]); CHECK(String(path[i]).get_extension() == ext[i]); diff --git a/tests/test_tools.h b/tests/test_tools.h new file mode 100644 index 0000000000..3ea953cb07 --- /dev/null +++ b/tests/test_tools.h @@ -0,0 +1,61 @@ +/*************************************************************************/ +/* test_tools.h */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md). */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/*************************************************************************/ + +#ifndef TEST_TOOLS_H +#define TEST_TOOLS_H + +#include "core/error/error_macros.h" + +struct ErrorDetector { + ErrorDetector() { + eh.errfunc = _detect_error; + eh.userdata = this; + + add_error_handler(&eh); + } + + ~ErrorDetector() { + remove_error_handler(&eh); + } + + void clear() { + has_error = false; + } + + static void _detect_error(void *p_self, const char *p_func, const char *p_file, int p_line, const char *p_error, const char *p_errorexp, ErrorHandlerType p_type) { + ErrorDetector *self = (ErrorDetector *)p_self; + self->has_error = true; + } + + ErrorHandlerList eh; + bool has_error = false; +}; + +#endif // TEST_TOOLS_H diff --git a/tests/test_validate_testing.h b/tests/test_validate_testing.h index f301047509..40b255e18a 100644 --- a/tests/test_validate_testing.h +++ b/tests/test_validate_testing.h @@ -34,6 +34,7 @@ #include "core/os/os.h" #include "tests/test_macros.h" +#include "tests/test_tools.h" TEST_SUITE("Validate tests") { TEST_CASE("Always pass") { @@ -182,6 +183,17 @@ TEST_SUITE("Validate tests") { // doctest string concatenation. CHECK_MESSAGE(true, var, " ", vec2, " ", rect2, " ", color); } + TEST_CASE("Detect error messages") { + ErrorDetector ed; + + REQUIRE_FALSE(ed.has_error); + + ERR_PRINT_OFF; + ERR_PRINT("Still waiting for Godot!"); + ERR_PRINT_ON; + + REQUIRE(ed.has_error); + } } #endif // TEST_VALIDATE_TESTING_H |