diff options
38 files changed, 491 insertions, 129 deletions
diff --git a/.clang-tidy b/.clang-tidy index 659b91013d..02aa95c709 100644 --- a/.clang-tidy +++ b/.clang-tidy @@ -2,7 +2,6 @@ Checks: 'clang-diagnostic-*,clang-analyzer-*,-*,cppcoreguidelines-pro-type-member-init,modernize-redundant-void-arg,modernize-use-bool-literals,modernize-use-default-member-init,modernize-use-nullptr,readability-braces-around-statements,readability-redundant-member-init' WarningsAsErrors: '' HeaderFilterRegex: '' -AnalyzeTemporaryDtors: false FormatStyle: none CheckOptions: - key: cert-dcl16-c.NewSuffixes diff --git a/core/config/project_settings.cpp b/core/config/project_settings.cpp index ee20aea35d..a0412e91ff 100644 --- a/core/config/project_settings.cpp +++ b/core/config/project_settings.cpp @@ -253,7 +253,7 @@ bool ProjectSettings::get_ignore_value_in_docs(const String &p_name) const { } void ProjectSettings::add_hidden_prefix(const String &p_prefix) { - ERR_FAIL_COND_MSG(hidden_prefixes.find(p_prefix) > -1, vformat("Hidden prefix '%s' already exists.", p_prefix)); + ERR_FAIL_COND_MSG(hidden_prefixes.has(p_prefix), vformat("Hidden prefix '%s' already exists.", p_prefix)); hidden_prefixes.push_back(p_prefix); } diff --git a/core/input/input.cpp b/core/input/input.cpp index 1eabfacd8e..a8409cc06d 100644 --- a/core/input/input.cpp +++ b/core/input/input.cpp @@ -858,7 +858,7 @@ void Input::warp_mouse(const Vector2 &p_position) { warp_mouse_func(p_position); } -Point2i Input::warp_mouse_motion(const Ref<InputEventMouseMotion> &p_motion, const Rect2 &p_rect) { +Point2 Input::warp_mouse_motion(const Ref<InputEventMouseMotion> &p_motion, const Rect2 &p_rect) { // The relative distance reported for the next event after a warp is in the boundaries of the // size of the rect on that axis, but it may be greater, in which case there's no problem as fmod() // will warp it, but if the pointer has moved in the opposite direction between the pointer relocation @@ -868,14 +868,14 @@ Point2i Input::warp_mouse_motion(const Ref<InputEventMouseMotion> &p_motion, con // detect the warp: if the relative distance is greater than the half of the size of the relevant rect // (checked per each axis), it will be considered as the consequence of a former pointer warp. - const Point2i rel_sign(p_motion->get_relative().x >= 0.0f ? 1 : -1, p_motion->get_relative().y >= 0.0 ? 1 : -1); - const Size2i warp_margin = p_rect.size * 0.5f; - const Point2i rel_warped( + const Point2 rel_sign(p_motion->get_relative().x >= 0.0f ? 1 : -1, p_motion->get_relative().y >= 0.0 ? 1 : -1); + const Size2 warp_margin = p_rect.size * 0.5f; + const Point2 rel_warped( Math::fmod(p_motion->get_relative().x + rel_sign.x * warp_margin.x, p_rect.size.x) - rel_sign.x * warp_margin.x, Math::fmod(p_motion->get_relative().y + rel_sign.y * warp_margin.y, p_rect.size.y) - rel_sign.y * warp_margin.y); - const Point2i pos_local = p_motion->get_global_position() - p_rect.position; - const Point2i pos_warped(Math::fposmod(pos_local.x, p_rect.size.x), Math::fposmod(pos_local.y, p_rect.size.y)); + const Point2 pos_local = p_motion->get_global_position() - p_rect.position; + const Point2 pos_warped(Math::fposmod(pos_local.x, p_rect.size.x), Math::fposmod(pos_local.y, p_rect.size.y)); if (pos_warped != pos_local) { warp_mouse(pos_warped + p_rect.position); } diff --git a/core/input/input.h b/core/input/input.h index 93407da2d9..6e7ab43082 100644 --- a/core/input/input.h +++ b/core/input/input.h @@ -316,7 +316,7 @@ public: BitField<MouseButtonMask> get_mouse_button_mask() const; void warp_mouse(const Vector2 &p_position); - Point2i warp_mouse_motion(const Ref<InputEventMouseMotion> &p_motion, const Rect2 &p_rect); + Point2 warp_mouse_motion(const Ref<InputEventMouseMotion> &p_motion, const Rect2 &p_rect); void parse_input_event(const Ref<InputEvent> &p_event); diff --git a/core/templates/local_vector.h b/core/templates/local_vector.h index e0047e0782..c281d70d92 100644 --- a/core/templates/local_vector.h +++ b/core/templates/local_vector.h @@ -264,6 +264,10 @@ public: return -1; } + bool has(const T &p_val) const { + return find(p_val) != -1; + } + template <typename C> void sort_custom() { U len = count; diff --git a/doc/classes/EditorFileDialog.xml b/doc/classes/EditorFileDialog.xml index 4befcf5e69..d5c2ed55d7 100644 --- a/doc/classes/EditorFileDialog.xml +++ b/doc/classes/EditorFileDialog.xml @@ -90,6 +90,12 @@ Notify the [EditorFileDialog] that its view of the data is no longer accurate. Updates the view contents on next view update. </description> </method> + <method name="popup_file_dialog"> + <return type="void" /> + <description> + Shows the [EditorFileDialog] at the default size and position for file dialogs in the editor, and selects the file name if there is a current file. + </description> + </method> <method name="set_option_default"> <return type="void" /> <param index="0" name="option" type="int" /> diff --git a/doc/classes/FlowContainer.xml b/doc/classes/FlowContainer.xml index 5e767acf7d..2839143960 100644 --- a/doc/classes/FlowContainer.xml +++ b/doc/classes/FlowContainer.xml @@ -21,6 +21,9 @@ <member name="alignment" type="int" setter="set_alignment" getter="get_alignment" enum="FlowContainer.AlignmentMode" default="0"> The alignment of the container's children (must be one of [constant ALIGNMENT_BEGIN], [constant ALIGNMENT_CENTER], or [constant ALIGNMENT_END]). </member> + <member name="last_wrap_alignment" type="int" setter="set_last_wrap_alignment" getter="get_last_wrap_alignment" enum="FlowContainer.LastWrapAlignmentMode" default="0"> + The wrap behavior of the last, partially filled row or column (must be one of [constant LAST_WRAP_ALIGNMENT_INHERIT], [constant LAST_WRAP_ALIGNMENT_BEGIN], [constant LAST_WRAP_ALIGNMENT_CENTER], or [constant LAST_WRAP_ALIGNMENT_END]). + </member> <member name="reverse_fill" type="bool" setter="set_reverse_fill" getter="is_reverse_fill" default="false"> If [code]true[/code], reverses fill direction. Horizontal [FlowContainer]s will fill rows bottom to top, vertical [FlowContainer]s will fill columns right to left. When using a vertical [FlowContainer] with a right to left [member Control.layout_direction], columns will fill left to right instead. @@ -40,6 +43,18 @@ <constant name="ALIGNMENT_END" value="2" enum="AlignmentMode"> The child controls will be arranged at the end of the container, i.e. bottom if orientation is vertical, right if orientation is horizontal (left for RTL layout). </constant> + <constant name="LAST_WRAP_ALIGNMENT_INHERIT" value="0" enum="LastWrapAlignmentMode"> + The last partially filled row or column will wrap aligned to the previous row or column in accordance with [member alignment]. + </constant> + <constant name="LAST_WRAP_ALIGNMENT_BEGIN" value="1" enum="LastWrapAlignmentMode"> + The last partially filled row or column will wrap aligned to the beginning of the previous row or column. + </constant> + <constant name="LAST_WRAP_ALIGNMENT_CENTER" value="2" enum="LastWrapAlignmentMode"> + The last partially filled row or column will wrap aligned to the center of the previous row or column. + </constant> + <constant name="LAST_WRAP_ALIGNMENT_END" value="3" enum="LastWrapAlignmentMode"> + The last partially filled row or column will wrap aligned to the end of the previous row or column. + </constant> </constants> <theme_items> <theme_item name="h_separation" data_type="constant" type="int" default="4"> diff --git a/doc/classes/PhysicsServer2D.xml b/doc/classes/PhysicsServer2D.xml index d40326fa21..c0672cc503 100644 --- a/doc/classes/PhysicsServer2D.xml +++ b/doc/classes/PhysicsServer2D.xml @@ -52,7 +52,8 @@ <method name="area_create"> <return type="RID" /> <description> - Creates a 2D area object in the physics server, and returns the [RID] that identifies it. Use [method area_add_shape] to add shapes to it, use [method area_set_transform] to set its transform, and use [method area_set_space] to add the area to a space. + Creates a 2D area object in the physics server, and returns the [RID] that identifies it. The default settings for the created area include a collision layer and mask set to [code]1[/code], and [code]monitorable[/code] set to [code]false[/code]. + Use [method area_add_shape] to add shapes to it, use [method area_set_transform] to set its transform, and use [method area_set_space] to add the area to a space. If you want the area to be detectable use [method area_set_monitorable]. </description> </method> <method name="area_get_canvas_instance_id" qualifiers="const"> @@ -369,7 +370,8 @@ <method name="body_create"> <return type="RID" /> <description> - Creates a 2D body object in the physics server, and returns the [RID] that identifies it. Use [method body_add_shape] to add shapes to it, use [method body_set_state] to set its transform, and use [method body_set_space] to add the body to a space. + Creates a 2D body object in the physics server, and returns the [RID] that identifies it. The default settings for the created area include a collision layer and mask set to [code]1[/code], and body mode set to [constant BODY_MODE_RIGID]. + Use [method body_add_shape] to add shapes to it, use [method body_set_state] to set its transform, and use [method body_set_space] to add the body to a space. </description> </method> <method name="body_get_canvas_instance_id" qualifiers="const"> diff --git a/doc/classes/PhysicsServer3D.xml b/doc/classes/PhysicsServer3D.xml index 4a4a1ad025..e835724e36 100644 --- a/doc/classes/PhysicsServer3D.xml +++ b/doc/classes/PhysicsServer3D.xml @@ -44,7 +44,8 @@ <method name="area_create"> <return type="RID" /> <description> - Creates an [Area3D]. + Creates a 3D area object in the physics server, and returns the [RID] that identifies it. The default settings for the created area include a collision layer and mask set to [code]1[/code], and [code]monitorable[/code] set to [code]false[/code]. + Use [method area_add_shape] to add shapes to it, use [method area_set_transform] to set its transform, and use [method area_set_space] to add the area to a space. If you want the area to be detectable use [method area_set_monitorable]. </description> </method> <method name="area_get_collision_layer" qualifiers="const"> @@ -351,6 +352,8 @@ <method name="body_create"> <return type="RID" /> <description> + Creates a 3D body object in the physics server, and returns the [RID] that identifies it. The default settings for the created area include a collision layer and mask set to [code]1[/code], and body mode set to [constant BODY_MODE_RIGID]. + Use [method body_add_shape] to add shapes to it, use [method body_set_state] to set its transform, and use [method body_set_space] to add the body to a space. </description> </method> <method name="body_get_collision_layer" qualifiers="const"> diff --git a/doc/classes/ResourceLoader.xml b/doc/classes/ResourceLoader.xml index 885c6f0478..1961ca2b0e 100644 --- a/doc/classes/ResourceLoader.xml +++ b/doc/classes/ResourceLoader.xml @@ -76,7 +76,7 @@ The registered [ResourceFormatLoader]s are queried sequentially to find the first one which can handle the file's extension, and then attempt loading. If loading fails, the remaining ResourceFormatLoaders are also attempted. An optional [param type_hint] can be used to further specify the [Resource] type that should be handled by the [ResourceFormatLoader]. Anything that inherits from [Resource] can be used as a type hint, for example [Image]. The [param cache_mode] property defines whether and how the cache should be used or updated when loading the resource. See [enum CacheMode] for details. - Returns an empty resource if no [ResourceFormatLoader] could handle the file. + Returns an empty resource if no [ResourceFormatLoader] could handle the file, and prints an error if no file is found at the specified path. GDScript has a simplified [method @GDScript.load] built-in method which can be used in most situations, leaving the use of [ResourceLoader] for more advanced scenarios. [b]Note:[/b] If [member ProjectSettings.editor/export/convert_text_resources_to_binary] is [code]true[/code], [method @GDScript.load] will not be able to read converted files in an exported project. If you rely on run-time loading of files present within the PCK, set [member ProjectSettings.editor/export/convert_text_resources_to_binary] to [code]false[/code]. [b]Note:[/b] Relative paths will be prefixed with [code]"res://"[/code] before loading, to avoid unexpected results make sure your paths are absolute. diff --git a/drivers/vulkan/rendering_device_driver_vulkan.cpp b/drivers/vulkan/rendering_device_driver_vulkan.cpp index 339d0782bf..131e0e4a8a 100644 --- a/drivers/vulkan/rendering_device_driver_vulkan.cpp +++ b/drivers/vulkan/rendering_device_driver_vulkan.cpp @@ -2603,7 +2603,7 @@ Error RenderingDeviceDriverVulkan::swap_chain_resize(CommandQueueID p_cmd_queue, break; } - bool present_mode_available = present_modes.find(present_mode) >= 0; + bool present_mode_available = present_modes.has(present_mode); if (present_mode_available) { print_verbose("Using present mode: " + present_mode_name); } else { diff --git a/editor/connections_dialog.cpp b/editor/connections_dialog.cpp index 7714749b66..787cae22cb 100644 --- a/editor/connections_dialog.cpp +++ b/editor/connections_dialog.cpp @@ -282,7 +282,7 @@ List<MethodInfo> ConnectDialog::_filter_method_list(const List<MethodInfo> &p_me List<MethodInfo> ret; for (const MethodInfo &mi : p_methods) { - if (!p_search_string.is_empty() && !mi.name.contains(p_search_string)) { + if (!p_search_string.is_empty() && mi.name.findn(p_search_string) == -1) { continue; } diff --git a/editor/editor_property_name_processor.cpp b/editor/editor_property_name_processor.cpp index 1318b84d60..a00b50c191 100644 --- a/editor/editor_property_name_processor.cpp +++ b/editor/editor_property_name_processor.cpp @@ -75,7 +75,7 @@ String EditorPropertyNameProcessor::_capitalize_name(const String &p_name) const Vector<String> parts = p_name.split("_", false); for (int i = 0; i < parts.size(); i++) { // Articles/conjunctions/prepositions which should only be capitalized when not at beginning and end. - if (i > 0 && i + 1 < parts.size() && stop_words.find(parts[i]) != -1) { + if (i > 0 && i + 1 < parts.size() && stop_words.has(parts[i])) { continue; } HashMap<String, String>::ConstIterator remap = capitalize_string_remaps.find(parts[i]); diff --git a/editor/filesystem_dock.cpp b/editor/filesystem_dock.cpp index 674b20480d..ea76c20a0f 100644 --- a/editor/filesystem_dock.cpp +++ b/editor/filesystem_dock.cpp @@ -1220,8 +1220,10 @@ void FileSystemDock::_select_file(const String &p_path, bool p_select_in_favorit if (is_imported) { SceneImportSettingsDialog::get_singleton()->open_settings(p_path, resource_type == "AnimationLibrary"); - } else { + } else if (resource_type == "PackedScene") { EditorNode::get_singleton()->open_request(fpath); + } else { + EditorNode::get_singleton()->load_resource(fpath); } } else if (ResourceLoader::is_imported(fpath)) { // If the importer has advanced settings, show them. diff --git a/editor/gui/editor_file_dialog.cpp b/editor/gui/editor_file_dialog.cpp index f0ad2946d0..7d26cb21fb 100644 --- a/editor/gui/editor_file_dialog.cpp +++ b/editor/gui/editor_file_dialog.cpp @@ -1929,6 +1929,7 @@ void EditorFileDialog::_bind_methods() { ClassDB::bind_method(D_METHOD("set_disable_overwrite_warning", "disable"), &EditorFileDialog::set_disable_overwrite_warning); ClassDB::bind_method(D_METHOD("is_overwrite_warning_disabled"), &EditorFileDialog::is_overwrite_warning_disabled); ClassDB::bind_method(D_METHOD("add_side_menu", "menu", "title"), &EditorFileDialog::add_side_menu, DEFVAL("")); + ClassDB::bind_method(D_METHOD("popup_file_dialog"), &EditorFileDialog::popup_file_dialog); ClassDB::bind_method(D_METHOD("invalidate"), &EditorFileDialog::invalidate); diff --git a/editor/multi_node_edit.h b/editor/multi_node_edit.h index 000d41c4c1..32fe7402fd 100644 --- a/editor/multi_node_edit.h +++ b/editor/multi_node_edit.h @@ -73,7 +73,7 @@ public: return false; } for (int i = 0; i < get_node_count(); i++) { - if (nodes.find(p_other->get_node(i)) == -1) { + if (!nodes.has(p_other->get_node(i))) { return false; } } diff --git a/editor/plugins/mesh_instance_3d_editor_plugin.cpp b/editor/plugins/mesh_instance_3d_editor_plugin.cpp index 0c922ea070..92c9572a02 100644 --- a/editor/plugins/mesh_instance_3d_editor_plugin.cpp +++ b/editor/plugins/mesh_instance_3d_editor_plugin.cpp @@ -33,6 +33,7 @@ #include "editor/editor_node.h" #include "editor/editor_string_names.h" #include "editor/editor_undo_redo_manager.h" +#include "editor/multi_node_edit.h" #include "editor/plugins/node_3d_editor_plugin.h" #include "editor/themes/editor_scale.h" #include "scene/3d/navigation_region_3d.h" @@ -188,8 +189,7 @@ void MeshInstance3DEditor::_create_collision_shape() { cshape->set_shape(shape); cshape->set_name("CollisionShape3D"); cshape->set_transform(node->get_transform()); - ur->add_do_method(E->get_parent(), "add_child", cshape); - ur->add_do_method(E->get_parent(), "move_child", cshape, E->get_index() + 1); + ur->add_do_method(E, "add_sibling", cshape, true); ur->add_do_method(cshape, "set_owner", owner); ur->add_do_method(Node3DEditor::get_singleton(), SceneStringNames::get_singleton()->_request_gizmo, cshape); ur->add_do_reference(cshape); @@ -612,11 +612,48 @@ MeshInstance3DEditor::MeshInstance3DEditor() { } void MeshInstance3DEditorPlugin::edit(Object *p_object) { - mesh_editor->edit(Object::cast_to<MeshInstance3D>(p_object)); + { + MeshInstance3D *mi = Object::cast_to<MeshInstance3D>(p_object); + if (mi) { + mesh_editor->edit(mi); + return; + } + } + + Ref<MultiNodeEdit> mne = Ref<MultiNodeEdit>(p_object); + Node *edited_scene = EditorNode::get_singleton()->get_edited_scene(); + if (mne.is_valid() && edited_scene) { + for (int i = 0; i < mne->get_node_count(); i++) { + MeshInstance3D *mi = Object::cast_to<MeshInstance3D>(edited_scene->get_node(mne->get_node(i))); + if (mi) { + mesh_editor->edit(mi); + return; + } + } + } + mesh_editor->edit(nullptr); } bool MeshInstance3DEditorPlugin::handles(Object *p_object) const { - return p_object->is_class("MeshInstance3D"); + if (Object::cast_to<MeshInstance3D>(p_object)) { + return true; + } + + Ref<MultiNodeEdit> mne = Ref<MultiNodeEdit>(p_object); + Node *edited_scene = EditorNode::get_singleton()->get_edited_scene(); + if (mne.is_valid() && edited_scene) { + bool has_mesh = false; + for (int i = 0; i < mne->get_node_count(); i++) { + if (Object::cast_to<MeshInstance3D>(edited_scene->get_node(mne->get_node(i)))) { + if (has_mesh) { + return true; + } else { + has_mesh = true; + } + } + } + } + return false; } void MeshInstance3DEditorPlugin::make_visible(bool p_visible) { diff --git a/editor/plugins/node_3d_editor_plugin.cpp b/editor/plugins/node_3d_editor_plugin.cpp index af38f51a25..b50c16c25f 100644 --- a/editor/plugins/node_3d_editor_plugin.cpp +++ b/editor/plugins/node_3d_editor_plugin.cpp @@ -2593,8 +2593,8 @@ void Node3DEditorViewport::scale_freelook_speed(real_t scale) { surface->queue_redraw(); } -Point2i Node3DEditorViewport::_get_warped_mouse_motion(const Ref<InputEventMouseMotion> &p_ev_mouse_motion) const { - Point2i relative; +Point2 Node3DEditorViewport::_get_warped_mouse_motion(const Ref<InputEventMouseMotion> &p_ev_mouse_motion) const { + Point2 relative; if (bool(EDITOR_GET("editors/3d/navigation/warped_mouse_panning"))) { relative = Input::get_singleton()->warp_mouse_motion(p_ev_mouse_motion, surface->get_global_rect()); } else { diff --git a/editor/plugins/node_3d_editor_plugin.h b/editor/plugins/node_3d_editor_plugin.h index 96210de403..a3e1224cb8 100644 --- a/editor/plugins/node_3d_editor_plugin.h +++ b/editor/plugins/node_3d_editor_plugin.h @@ -430,7 +430,7 @@ private: void _selection_result_pressed(int); void _selection_menu_hide(); void _list_select(Ref<InputEventMouseButton> b); - Point2i _get_warped_mouse_motion(const Ref<InputEventMouseMotion> &p_ev_mouse_motion) const; + Point2 _get_warped_mouse_motion(const Ref<InputEventMouseMotion> &p_ev_mouse_motion) const; Vector3 _get_instance_position(const Point2 &p_pos) const; static AABB _calculate_spatial_bounds(const Node3D *p_parent, const Node3D *p_top_level_parent = nullptr); diff --git a/editor/themes/editor_theme_manager.cpp b/editor/themes/editor_theme_manager.cpp index ddacce6270..ee008e5636 100644 --- a/editor/themes/editor_theme_manager.cpp +++ b/editor/themes/editor_theme_manager.cpp @@ -1291,7 +1291,7 @@ void EditorThemeManager::_populate_standard_styles(const Ref<EditorTheme> &p_the // Use a different color for folder icons to make them easier to distinguish from files. // On a light theme, the icon will be dark, so we need to lighten it before blending it with the accent color. p_theme->set_color("folder_icon_color", "FileDialog", (p_config.dark_theme ? Color(1, 1, 1) : Color(4.25, 4.25, 4.25)).lerp(p_config.accent_color, 0.7)); - p_theme->set_color("files_disabled", "FileDialog", p_config.font_disabled_color); + p_theme->set_color("file_disabled_color", "FileDialog", p_config.font_disabled_color); // PopupDialog. p_theme->set_stylebox("panel", "PopupDialog", p_config.popup_style); diff --git a/modules/csg/csg.cpp b/modules/csg/csg.cpp index 878664bb9c..cec03b7246 100644 --- a/modules/csg/csg.cpp +++ b/modules/csg/csg.cpp @@ -1098,7 +1098,7 @@ void CSGBrushOperation::Build2DFaces::_find_edge_intersections(const Vector2 p_s }; // Check if edge has already been processed. - if (processed_edges.find(edge_points_and_uvs) != -1) { + if (processed_edges.has(edge_points_and_uvs)) { continue; } diff --git a/modules/navigation/3d/godot_navigation_server_3d.cpp b/modules/navigation/3d/godot_navigation_server_3d.cpp index 61a128e004..6cbfd93088 100644 --- a/modules/navigation/3d/godot_navigation_server_3d.cpp +++ b/modules/navigation/3d/godot_navigation_server_3d.cpp @@ -136,7 +136,7 @@ bool GodotNavigationServer3D::map_is_active(RID p_map) const { NavMap *map = map_owner.get_or_null(p_map); ERR_FAIL_NULL_V(map, false); - return active_maps.find(map) >= 0; + return active_maps.has(map); } COMMAND_2(map_set_up, RID, p_map, Vector3, p_up) { diff --git a/modules/navigation/nav_map.cpp b/modules/navigation/nav_map.cpp index a3f2ee2e61..dfbc92a919 100644 --- a/modules/navigation/nav_map.cpp +++ b/modules/navigation/nav_map.cpp @@ -734,7 +734,7 @@ void NavMap::remove_link(NavLink *p_link) { } bool NavMap::has_agent(NavAgent *agent) const { - return (agents.find(agent) >= 0); + return agents.has(agent); } void NavMap::add_agent(NavAgent *agent) { @@ -754,7 +754,7 @@ void NavMap::remove_agent(NavAgent *agent) { } bool NavMap::has_obstacle(NavObstacle *obstacle) const { - return (obstacles.find(obstacle) >= 0); + return obstacles.has(obstacle); } void NavMap::add_obstacle(NavObstacle *obstacle) { diff --git a/platform/android/detect.py b/platform/android/detect.py index cbd6144182..6a8c4ed86d 100644 --- a/platform/android/detect.py +++ b/platform/android/detect.py @@ -88,7 +88,7 @@ def install_ndk_if_needed(env: "SConsEnvironment"): else: print_error( f'Cannot find "{sdkmanager}". Please ensure ANDROID_HOME is correct and cmdline-tools' - f'are installed, or install NDK version "{get_ndk_version()}" manually.' + f' are installed, or install NDK version "{get_ndk_version()}" manually.' ) sys.exit(255) env["ANDROID_NDK_ROOT"] = get_android_ndk_root(env) diff --git a/platform/windows/detect.py b/platform/windows/detect.py index 93eb34001e..ccf889f1a3 100644 --- a/platform/windows/detect.py +++ b/platform/windows/detect.py @@ -18,36 +18,30 @@ def get_name(): return "Windows" -def try_cmd(test, prefix, arch): - if arch: +def get_mingw_tool(tool, prefix="", arch="", test="--version"): + if not prefix: + prefix = os.getenv("MINGW_PREFIX", "") + supported_arches = ["x86_64", "x86_32", "arm64", "arm32"] + if arch in supported_arches: + arches = [arch, ""] + else: + arches = ["x86_64", "x86_32", "arm64", "arm32", ""] + for a in arches: try: + path = f"{get_mingw_bin_prefix(prefix, a)}{tool}" out = subprocess.Popen( - get_mingw_bin_prefix(prefix, arch) + test, + f"{path} {test}", shell=True, stderr=subprocess.PIPE, stdout=subprocess.PIPE, ) out.communicate() if out.returncode == 0: - return True + return path except Exception: pass - else: - for a in ["x86_64", "x86_32", "arm64", "arm32"]: - try: - out = subprocess.Popen( - get_mingw_bin_prefix(prefix, a) + test, - shell=True, - stderr=subprocess.PIPE, - stdout=subprocess.PIPE, - ) - out.communicate() - if out.returncode == 0: - return True - except Exception: - pass - return False + return "" def can_build(): @@ -65,9 +59,7 @@ def can_build(): if os.name == "posix": # Cross-compiling with MinGW-w64 (old MinGW32 is not supported) - prefix = os.getenv("MINGW_PREFIX", "") - - if try_cmd("gcc --version", prefix, "") or try_cmd("clang --version", prefix, ""): + if get_mingw_tool("gcc") or get_mingw_tool("clang"): return True return False @@ -255,36 +247,26 @@ def get_flags(): def build_res_file(target, source, env: "SConsEnvironment"): + cmdbase = get_mingw_tool("windres", env["mingw_prefix"], env["arch"]) + if not cmdbase: + return -1 + arch_aliases = { "x86_32": "pe-i386", "x86_64": "pe-x86-64", "arm32": "armv7-w64-mingw32", "arm64": "aarch64-w64-mingw32", } - cmdbase = "windres --include-dir . --target=" + arch_aliases[env["arch"]] - - mingw_bin_prefix = get_mingw_bin_prefix(env["mingw_prefix"], env["arch"]) + cmdbase += " --include-dir . --target=" + arch_aliases[env["arch"]] for x in range(len(source)): - ok = True - # Try prefixed executable (MinGW on Linux). - cmd = mingw_bin_prefix + cmdbase + " -i " + str(source[x]) + " -o " + str(target[x]) + cmd = f"{cmdbase} -i {source[x]} -o {target[x]}" try: out = subprocess.Popen(cmd, shell=True, stderr=subprocess.PIPE).communicate() if len(out[1]): - ok = False - except Exception: - ok = False - - # Try generic executable (MSYS2). - if not ok: - cmd = cmdbase + " -i " + str(source[x]) + " -o " + str(target[x]) - try: - out = subprocess.Popen(cmd, shell=True, stderr=subprocess.PIPE).communicate() - if len(out[1]): - return -1 - except Exception: return -1 + except Exception: + return -1 return 0 @@ -358,8 +340,8 @@ def setup_mingw(env: "SConsEnvironment"): ) sys.exit(255) - if not try_cmd("gcc --version", env["mingw_prefix"], env["arch"]) and not try_cmd( - "clang --version", env["mingw_prefix"], env["arch"] + if not get_mingw_tool("gcc", env["mingw_prefix"], env["arch"]) and not get_mingw_tool( + "clang", env["mingw_prefix"], env["arch"] ): print_error("No valid compilers found, use MINGW_PREFIX environment variable to set MinGW path.") sys.exit(255) @@ -600,10 +582,10 @@ def configure_mingw(env: "SConsEnvironment"): ## Build type - if not env["use_llvm"] and not try_cmd("gcc --version", env["mingw_prefix"], env["arch"]): + if not env["use_llvm"] and not get_mingw_tool("gcc", env["mingw_prefix"], env["arch"]): env["use_llvm"] = True - if env["use_llvm"] and not try_cmd("clang --version", env["mingw_prefix"], env["arch"]): + if env["use_llvm"] and not get_mingw_tool("clang", env["mingw_prefix"], env["arch"]): env["use_llvm"] = False # TODO: Re-evaluate the need for this / streamline with common config. @@ -638,27 +620,26 @@ def configure_mingw(env: "SConsEnvironment"): if env["arch"] in ["x86_32", "x86_64"]: env["x86_libtheora_opt_gcc"] = True - mingw_bin_prefix = get_mingw_bin_prefix(env["mingw_prefix"], env["arch"]) - if env["use_llvm"]: - env["CC"] = mingw_bin_prefix + "clang" - env["CXX"] = mingw_bin_prefix + "clang++" - if try_cmd("as --version", env["mingw_prefix"], env["arch"]): - env["AS"] = mingw_bin_prefix + "as" - if try_cmd("ar --version", env["mingw_prefix"], env["arch"]): - env["AR"] = mingw_bin_prefix + "ar" - if try_cmd("ranlib --version", env["mingw_prefix"], env["arch"]): - env["RANLIB"] = mingw_bin_prefix + "ranlib" + env["CC"] = get_mingw_tool("clang", env["mingw_prefix"], env["arch"]) + env["CXX"] = get_mingw_tool("clang++", env["mingw_prefix"], env["arch"]) + tool_as = get_mingw_tool("as", env["mingw_prefix"], env["arch"]) + tool_ar = get_mingw_tool("ar", env["mingw_prefix"], env["arch"]) + tool_ranlib = get_mingw_tool("ranlib", env["mingw_prefix"], env["arch"]) env.extra_suffix = ".llvm" + env.extra_suffix else: - env["CC"] = mingw_bin_prefix + "gcc" - env["CXX"] = mingw_bin_prefix + "g++" - if try_cmd("as --version", env["mingw_prefix"], env["arch"]): - env["AS"] = mingw_bin_prefix + "as" - if try_cmd("gcc-ar --version", env["mingw_prefix"], env["arch"]): - env["AR"] = mingw_bin_prefix + "gcc-ar" - if try_cmd("gcc-ranlib --version", env["mingw_prefix"], env["arch"]): - env["RANLIB"] = mingw_bin_prefix + "gcc-ranlib" + env["CC"] = get_mingw_tool("gcc", env["mingw_prefix"], env["arch"]) + env["CXX"] = get_mingw_tool("g++", env["mingw_prefix"], env["arch"]) + tool_as = get_mingw_tool("as", env["mingw_prefix"], env["arch"]) + tool_ar = get_mingw_tool("gcc-ar", env["mingw_prefix"], env["arch"]) + tool_ranlib = get_mingw_tool("gcc-ranlib", env["mingw_prefix"], env["arch"]) + + if tool_as: + env["AS"] = tool_as + if tool_ar: + env["AR"] = tool_ar + if tool_ranlib: + env["RANLIB"] = tool_ranlib ## LTO diff --git a/platform/windows/platform_windows_builders.py b/platform/windows/platform_windows_builders.py index 729d55cea6..fc52e39456 100644 --- a/platform/windows/platform_windows_builders.py +++ b/platform/windows/platform_windows_builders.py @@ -1,24 +1,17 @@ """Functions used to generate source files during build time""" import os -from detect import get_mingw_bin_prefix -from detect import try_cmd +from detect import get_mingw_tool def make_debug_mingw(target, source, env): - dst = str(target[0]) - # Force separate debug symbols if executable size is larger than 1.9 GB. - if env["separate_debug_symbols"] or os.stat(dst).st_size >= 2040109465: - mingw_bin_prefix = get_mingw_bin_prefix(env["mingw_prefix"], env["arch"]) - if try_cmd("objcopy --version", env["mingw_prefix"], env["arch"]): - os.system(mingw_bin_prefix + "objcopy --only-keep-debug {0} {0}.debugsymbols".format(dst)) - else: - os.system("objcopy --only-keep-debug {0} {0}.debugsymbols".format(dst)) - if try_cmd("strip --version", env["mingw_prefix"], env["arch"]): - os.system(mingw_bin_prefix + "strip --strip-debug --strip-unneeded {0}".format(dst)) - else: - os.system("strip --strip-debug --strip-unneeded {0}".format(dst)) - if try_cmd("objcopy --version", env["mingw_prefix"], env["arch"]): - os.system(mingw_bin_prefix + "objcopy --add-gnu-debuglink={0}.debugsymbols {0}".format(dst)) - else: - os.system("objcopy --add-gnu-debuglink={0}.debugsymbols {0}".format(dst)) + objcopy = get_mingw_tool("objcopy", env["mingw_prefix"], env["arch"]) + strip = get_mingw_tool("strip", env["mingw_prefix"], env["arch"]) + + if not objcopy or not strip: + print('`separate_debug_symbols` requires both "objcopy" and "strip" to function.') + return + + os.system("{0} --only-keep-debug {1} {1}.debugsymbols".format(objcopy, target[0])) + os.system("{0} --strip-debug --strip-unneeded {1}".format(strip, target[0])) + os.system("{0} --add-gnu-debuglink={1}.debugsymbols {1}".format(objcopy, target[0])) diff --git a/scene/3d/cpu_particles_3d.cpp b/scene/3d/cpu_particles_3d.cpp index db7b80683c..5aa50a4a21 100644 --- a/scene/3d/cpu_particles_3d.cpp +++ b/scene/3d/cpu_particles_3d.cpp @@ -1125,7 +1125,7 @@ void CPUParticles3D::_particles_process(double p_delta) { //turn particle by rotation in Y if (particle_flags[PARTICLE_FLAG_ROTATE_Y]) { Basis rot_y(Vector3(0, 1, 0), p.custom[0]); - p.transform.basis = p.transform.basis * rot_y; + p.transform.basis = rot_y; } } diff --git a/scene/gui/control.cpp b/scene/gui/control.cpp index 7ac7ceb6bc..d78077316a 100644 --- a/scene/gui/control.cpp +++ b/scene/gui/control.cpp @@ -1383,6 +1383,15 @@ void Control::_set_position(const Point2 &p_point) { void Control::set_position(const Point2 &p_point, bool p_keep_offsets) { ERR_MAIN_THREAD_GUARD; + +#ifdef TOOLS_ENABLED + // Can't compute anchors, set position directly and return immediately. + if (saving && !is_inside_tree()) { + data.pos_cache = p_point; + return; + } +#endif + if (p_keep_offsets) { _compute_anchors(Rect2(p_point, data.size_cache), data.offset, data.anchor); } else { @@ -1441,6 +1450,14 @@ void Control::set_size(const Size2 &p_size, bool p_keep_offsets) { new_size.y = min.y; } +#ifdef TOOLS_ENABLED + // Can't compute anchors, set size directly and return immediately. + if (saving && !is_inside_tree()) { + data.size_cache = new_size; + return; + } +#endif + if (p_keep_offsets) { _compute_anchors(Rect2(data.pos_cache, new_size), data.offset, data.anchor); } else { @@ -3140,6 +3157,14 @@ Control *Control::make_custom_tooltip(const String &p_text) const { void Control::_notification(int p_notification) { ERR_MAIN_THREAD_GUARD; switch (p_notification) { +#ifdef TOOLS_ENABLED + case NOTIFICATION_EDITOR_PRE_SAVE: { + saving = true; + } break; + case NOTIFICATION_EDITOR_POST_SAVE: { + saving = false; + } break; +#endif case NOTIFICATION_POSTINITIALIZE: { data.initialized = true; diff --git a/scene/gui/control.h b/scene/gui/control.h index bdb908e089..c784d4330d 100644 --- a/scene/gui/control.h +++ b/scene/gui/control.h @@ -47,6 +47,10 @@ class ThemeContext; class Control : public CanvasItem { GDCLASS(Control, CanvasItem); +#ifdef TOOLS_ENABLED + bool saving = false; +#endif + public: enum Anchor { ANCHOR_BEGIN = 0, diff --git a/scene/gui/flow_container.cpp b/scene/gui/flow_container.cpp index 9f79da2905..f8128a9dc6 100644 --- a/scene/gui/flow_container.cpp +++ b/scene/gui/flow_container.cpp @@ -38,6 +38,7 @@ struct _LineData { int min_line_length = 0; int stretch_avail = 0; float stretch_ratio_total = 0; + bool is_filled = false; }; void FlowContainer::_resort() { @@ -58,6 +59,7 @@ void FlowContainer::_resort() { float line_stretch_ratio_total = 0; int current_container_size = vertical ? get_rect().size.y : get_rect().size.x; int children_in_current_line = 0; + Control *last_child = nullptr; // First pass for line wrapping and minimum size calculation. for (int i = 0; i < get_child_count(); i++) { @@ -77,7 +79,7 @@ void FlowContainer::_resort() { } if (ofs.y + child_msc.y > current_container_size) { line_length = ofs.y - theme_cache.v_separation; - lines_data.push_back(_LineData{ children_in_current_line, line_height, line_length, current_container_size - line_length, line_stretch_ratio_total }); + lines_data.push_back(_LineData{ children_in_current_line, line_height, line_length, current_container_size - line_length, line_stretch_ratio_total, true }); // Move in new column (vertical line). ofs.x += line_height + theme_cache.h_separation; @@ -99,7 +101,7 @@ void FlowContainer::_resort() { } if (ofs.x + child_msc.x > current_container_size) { line_length = ofs.x - theme_cache.h_separation; - lines_data.push_back(_LineData{ children_in_current_line, line_height, line_length, current_container_size - line_length, line_stretch_ratio_total }); + lines_data.push_back(_LineData{ children_in_current_line, line_height, line_length, current_container_size - line_length, line_stretch_ratio_total, true }); // Move in new line. ofs.y += line_height + theme_cache.v_separation; @@ -116,11 +118,16 @@ void FlowContainer::_resort() { ofs.x += child_msc.x; } + last_child = child; children_minsize_cache[child] = child_msc; children_in_current_line++; } line_length = vertical ? (ofs.y) : (ofs.x); - lines_data.push_back(_LineData{ children_in_current_line, line_height, line_length, current_container_size - line_length, line_stretch_ratio_total }); + bool is_filled = false; + if (last_child != nullptr) { + is_filled = vertical ? (ofs.y + last_child->get_combined_minimum_size().y > current_container_size ? true : false) : (ofs.x + last_child->get_combined_minimum_size().x > current_container_size ? true : false); + } + lines_data.push_back(_LineData{ children_in_current_line, line_height, line_length, current_container_size - line_length, line_stretch_ratio_total, is_filled }); // Second pass for in-line expansion and alignment. @@ -158,17 +165,43 @@ void FlowContainer::_resort() { // but only if the line doesn't contain a child that expands. if (child_idx_in_line == 0 && Math::is_equal_approx(line_data.stretch_ratio_total, 0)) { int alignment_ofs = 0; + bool is_not_first_line_and_not_filled = current_line_idx != 0 && !line_data.is_filled; + float prior_stretch_avail = is_not_first_line_and_not_filled ? lines_data[current_line_idx - 1].stretch_avail : 0.0; switch (alignment) { - case ALIGNMENT_CENTER: - alignment_ofs = line_data.stretch_avail / 2; - break; - case ALIGNMENT_END: - alignment_ofs = line_data.stretch_avail; - break; + case ALIGNMENT_BEGIN: { + if (last_wrap_alignment != LAST_WRAP_ALIGNMENT_INHERIT && is_not_first_line_and_not_filled) { + if (last_wrap_alignment == LAST_WRAP_ALIGNMENT_END) { + alignment_ofs = line_data.stretch_avail - prior_stretch_avail; + } else if (last_wrap_alignment == LAST_WRAP_ALIGNMENT_CENTER) { + alignment_ofs = (line_data.stretch_avail - prior_stretch_avail) * 0.5; + } + } + } break; + case ALIGNMENT_CENTER: { + if (last_wrap_alignment != LAST_WRAP_ALIGNMENT_INHERIT && last_wrap_alignment != LAST_WRAP_ALIGNMENT_CENTER && is_not_first_line_and_not_filled) { + if (last_wrap_alignment == LAST_WRAP_ALIGNMENT_END) { + alignment_ofs = line_data.stretch_avail - (prior_stretch_avail * 0.5); + } else { // Is LAST_WRAP_ALIGNMENT_BEGIN + alignment_ofs = prior_stretch_avail * 0.5; + } + } else { + alignment_ofs = line_data.stretch_avail * 0.5; + } + } break; + case ALIGNMENT_END: { + if (last_wrap_alignment != LAST_WRAP_ALIGNMENT_INHERIT && last_wrap_alignment != LAST_WRAP_ALIGNMENT_END && is_not_first_line_and_not_filled) { + if (last_wrap_alignment == LAST_WRAP_ALIGNMENT_BEGIN) { + alignment_ofs = prior_stretch_avail; + } else { // Is LAST_WRAP_ALIGNMENT_CENTER + alignment_ofs = prior_stretch_avail + (line_data.stretch_avail - prior_stretch_avail) * 0.5; + } + } else { + alignment_ofs = line_data.stretch_avail; + } + } break; default: break; } - if (vertical) { /* VERTICAL */ ofs.y += alignment_ofs; } else { /* HORIZONTAL */ @@ -314,6 +347,18 @@ FlowContainer::AlignmentMode FlowContainer::get_alignment() const { return alignment; } +void FlowContainer::set_last_wrap_alignment(LastWrapAlignmentMode p_last_wrap_alignment) { + if (last_wrap_alignment == p_last_wrap_alignment) { + return; + } + last_wrap_alignment = p_last_wrap_alignment; + _resort(); +} + +FlowContainer::LastWrapAlignmentMode FlowContainer::get_last_wrap_alignment() const { + return last_wrap_alignment; +} + void FlowContainer::set_vertical(bool p_vertical) { ERR_FAIL_COND_MSG(is_fixed, "Can't change orientation of " + get_class() + "."); vertical = p_vertical; @@ -346,6 +391,8 @@ void FlowContainer::_bind_methods() { ClassDB::bind_method(D_METHOD("set_alignment", "alignment"), &FlowContainer::set_alignment); ClassDB::bind_method(D_METHOD("get_alignment"), &FlowContainer::get_alignment); + ClassDB::bind_method(D_METHOD("set_last_wrap_alignment", "last_wrap_alignment"), &FlowContainer::set_last_wrap_alignment); + ClassDB::bind_method(D_METHOD("get_last_wrap_alignment"), &FlowContainer::get_last_wrap_alignment); ClassDB::bind_method(D_METHOD("set_vertical", "vertical"), &FlowContainer::set_vertical); ClassDB::bind_method(D_METHOD("is_vertical"), &FlowContainer::is_vertical); ClassDB::bind_method(D_METHOD("set_reverse_fill", "reverse_fill"), &FlowContainer::set_reverse_fill); @@ -354,8 +401,13 @@ void FlowContainer::_bind_methods() { BIND_ENUM_CONSTANT(ALIGNMENT_BEGIN); BIND_ENUM_CONSTANT(ALIGNMENT_CENTER); BIND_ENUM_CONSTANT(ALIGNMENT_END); + BIND_ENUM_CONSTANT(LAST_WRAP_ALIGNMENT_INHERIT); + BIND_ENUM_CONSTANT(LAST_WRAP_ALIGNMENT_BEGIN); + BIND_ENUM_CONSTANT(LAST_WRAP_ALIGNMENT_CENTER); + BIND_ENUM_CONSTANT(LAST_WRAP_ALIGNMENT_END); ADD_PROPERTY(PropertyInfo(Variant::INT, "alignment", PROPERTY_HINT_ENUM, "Begin,Center,End"), "set_alignment", "get_alignment"); + ADD_PROPERTY(PropertyInfo(Variant::INT, "last_wrap_alignment", PROPERTY_HINT_ENUM, "Inherit,Begin,Center,End"), "set_last_wrap_alignment", "get_last_wrap_alignment"); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "vertical"), "set_vertical", "is_vertical"); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "reverse_fill"), "set_reverse_fill", "is_reverse_fill"); diff --git a/scene/gui/flow_container.h b/scene/gui/flow_container.h index 90da73aaab..65ebc89c78 100644 --- a/scene/gui/flow_container.h +++ b/scene/gui/flow_container.h @@ -42,6 +42,12 @@ public: ALIGNMENT_CENTER, ALIGNMENT_END }; + enum LastWrapAlignmentMode { + LAST_WRAP_ALIGNMENT_INHERIT, + LAST_WRAP_ALIGNMENT_BEGIN, + LAST_WRAP_ALIGNMENT_CENTER, + LAST_WRAP_ALIGNMENT_END + }; private: int cached_size = 0; @@ -50,6 +56,7 @@ private: bool vertical = false; bool reverse_fill = false; AlignmentMode alignment = ALIGNMENT_BEGIN; + LastWrapAlignmentMode last_wrap_alignment = LAST_WRAP_ALIGNMENT_INHERIT; struct ThemeCache { int h_separation = 0; @@ -71,6 +78,9 @@ public: void set_alignment(AlignmentMode p_alignment); AlignmentMode get_alignment() const; + void set_last_wrap_alignment(LastWrapAlignmentMode p_last_wrap_alignment); + LastWrapAlignmentMode get_last_wrap_alignment() const; + void set_vertical(bool p_vertical); bool is_vertical() const; @@ -102,5 +112,6 @@ public: }; VARIANT_ENUM_CAST(FlowContainer::AlignmentMode); +VARIANT_ENUM_CAST(FlowContainer::LastWrapAlignmentMode); #endif // FLOW_CONTAINER_H diff --git a/scene/main/viewport.cpp b/scene/main/viewport.cpp index 06cdc02213..b8c691c6e7 100644 --- a/scene/main/viewport.cpp +++ b/scene/main/viewport.cpp @@ -2374,7 +2374,7 @@ void Viewport::_gui_hide_control(Control *p_control) { if (gui.key_focus == p_control) { gui_release_focus(); } - if (gui.mouse_over == p_control || gui.mouse_over_hierarchy.find(p_control) >= 0) { + if (gui.mouse_over == p_control || gui.mouse_over_hierarchy.has(p_control)) { _drop_mouse_over(p_control->get_parent_control()); } if (gui.drag_mouse_over == p_control) { @@ -2394,7 +2394,7 @@ void Viewport::_gui_remove_control(Control *p_control) { if (gui.key_focus == p_control) { gui.key_focus = nullptr; } - if (gui.mouse_over == p_control || gui.mouse_over_hierarchy.find(p_control) >= 0) { + if (gui.mouse_over == p_control || gui.mouse_over_hierarchy.has(p_control)) { _drop_mouse_over(p_control->get_parent_control()); } if (gui.drag_mouse_over == p_control) { diff --git a/servers/physics_3d/godot_shape_3d.cpp b/servers/physics_3d/godot_shape_3d.cpp index 6eb983d5e0..70b6bcf19e 100644 --- a/servers/physics_3d/godot_shape_3d.cpp +++ b/servers/physics_3d/godot_shape_3d.cpp @@ -1133,7 +1133,7 @@ void GodotConvexPolygonShape3D::_setup(const Vector<Vector3> &p_vertices) { max_support = s; } } - if (extreme_vertices.find(best_vertex) == -1) + if (!extreme_vertices.has(best_vertex)) extreme_vertices.push_back(best_vertex); } } diff --git a/servers/physics_3d/godot_soft_body_3d.cpp b/servers/physics_3d/godot_soft_body_3d.cpp index e621977326..fd9141f46e 100644 --- a/servers/physics_3d/godot_soft_body_3d.cpp +++ b/servers/physics_3d/godot_soft_body_3d.cpp @@ -626,11 +626,11 @@ void GodotSoftBody3D::generate_bending_constraints(int p_distance) { for (Link &link : links) { const int ia = (int)(link.n[0] - &nodes[0]); const int ib = (int)(link.n[1] - &nodes[0]); - if (node_links[ia].find(ib) == -1) { + if (!node_links[ia].has(ib)) { node_links[ia].push_back(ib); } - if (node_links[ib].find(ia) == -1) { + if (!node_links[ib].has(ia)) { node_links[ib].push_back(ia); } } diff --git a/servers/rendering/renderer_rd/forward_mobile/scene_shader_forward_mobile.cpp b/servers/rendering/renderer_rd/forward_mobile/scene_shader_forward_mobile.cpp index 733cbd1ff2..dd722cc2dd 100644 --- a/servers/rendering/renderer_rd/forward_mobile/scene_shader_forward_mobile.cpp +++ b/servers/rendering/renderer_rd/forward_mobile/scene_shader_forward_mobile.cpp @@ -587,7 +587,7 @@ void SceneShaderForwardMobile::init(const String p_defines) { actions.usage_defines["COLOR"] = "#define COLOR_USED\n"; actions.usage_defines["INSTANCE_CUSTOM"] = "#define ENABLE_INSTANCE_CUSTOM\n"; actions.usage_defines["POSITION"] = "#define OVERRIDE_POSITION\n"; - actions.usage_defines["LIGHT_VERTEX"] = "#define LIGHT_VERTEX\n"; + actions.usage_defines["LIGHT_VERTEX"] = "#define LIGHT_VERTEX_USED\n"; actions.usage_defines["ALPHA_SCISSOR_THRESHOLD"] = "#define ALPHA_SCISSOR_USED\n"; actions.usage_defines["ALPHA_HASH_SCALE"] = "#define ALPHA_HASH_USED\n"; diff --git a/tests/core/templates/test_local_vector.h b/tests/core/templates/test_local_vector.h index 2873a9a028..c9544c625b 100644 --- a/tests/core/templates/test_local_vector.h +++ b/tests/core/templates/test_local_vector.h @@ -63,7 +63,7 @@ TEST_CASE("[LocalVector] Push Back.") { CHECK(vector[4] == 4); } -TEST_CASE("[LocalVector] Find.") { +TEST_CASE("[LocalVector] Find, has.") { LocalVector<int> vector; vector.push_back(3); vector.push_back(1); @@ -85,6 +85,15 @@ TEST_CASE("[LocalVector] Find.") { CHECK(vector.find(-1) == -1); CHECK(vector.find(5) == -1); + + CHECK(vector.has(0)); + CHECK(vector.has(1)); + CHECK(vector.has(2)); + CHECK(vector.has(3)); + CHECK(vector.has(4)); + + CHECK(!vector.has(-1)); + CHECK(!vector.has(5)); } TEST_CASE("[LocalVector] Remove.") { diff --git a/tests/scene/test_timer.h b/tests/scene/test_timer.h new file mode 100644 index 0000000000..913ed92de5 --- /dev/null +++ b/tests/scene/test_timer.h @@ -0,0 +1,217 @@ +/**************************************************************************/ +/* test_timer.h */ +/**************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/**************************************************************************/ +/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */ +/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/**************************************************************************/ + +#ifndef TEST_TIMER_H +#define TEST_TIMER_H + +#include "scene/main/timer.h" + +#include "tests/test_macros.h" + +namespace TestTimer { + +TEST_CASE("[SceneTree][Timer] Check Timer Setters and Getters") { + Timer *test_timer = memnew(Timer); + + SUBCASE("[Timer] Timer set and get wait time") { + // check default + CHECK(Math::is_equal_approx(test_timer->get_wait_time(), 1.0)); + + test_timer->set_wait_time(50.0); + CHECK(Math::is_equal_approx(test_timer->get_wait_time(), 50.0)); + + test_timer->set_wait_time(42.0); + CHECK(Math::is_equal_approx(test_timer->get_wait_time(), 42.0)); + + // wait time remains unchanged if we attempt to set it negative or zero + ERR_PRINT_OFF; + test_timer->set_wait_time(-22.0); + ERR_PRINT_ON; + CHECK(Math::is_equal_approx(test_timer->get_wait_time(), 42.0)); + + ERR_PRINT_OFF; + test_timer->set_wait_time(0.0); + ERR_PRINT_ON; + CHECK(Math::is_equal_approx(test_timer->get_wait_time(), 42.0)); + } + + SUBCASE("[Timer] Timer set and get one shot") { + // check default + CHECK(test_timer->is_one_shot() == false); + + test_timer->set_one_shot(true); + CHECK(test_timer->is_one_shot() == true); + + test_timer->set_one_shot(false); + CHECK(test_timer->is_one_shot() == false); + } + + SUBCASE("[Timer] Timer set and get autostart") { + // check default + CHECK(test_timer->has_autostart() == false); + + test_timer->set_autostart(true); + CHECK(test_timer->has_autostart() == true); + + test_timer->set_autostart(false); + CHECK(test_timer->has_autostart() == false); + } + + SUBCASE("[Timer] Timer start and stop") { + test_timer->set_autostart(false); + } + + SUBCASE("[Timer] Timer set and get paused") { + // check default + CHECK(test_timer->is_paused() == false); + + test_timer->set_paused(true); + CHECK(test_timer->is_paused() == true); + + test_timer->set_paused(false); + CHECK(test_timer->is_paused() == false); + } + + memdelete(test_timer); +} + +TEST_CASE("[SceneTree][Timer] Check Timer Start and Stop") { + Timer *test_timer = memnew(Timer); + + SUBCASE("[Timer] Timer start and stop") { + SceneTree::get_singleton()->get_root()->add_child(test_timer); + + test_timer->start(5.0); + + CHECK(Math::is_equal_approx(test_timer->get_wait_time(), 5.0)); + CHECK(Math::is_equal_approx(test_timer->get_time_left(), 5.0)); + + test_timer->start(-2.0); + + // the wait time and time left remains unchanged when started with a negative start time + CHECK(Math::is_equal_approx(test_timer->get_wait_time(), 5.0)); + CHECK(Math::is_equal_approx(test_timer->get_time_left(), 5.0)); + + test_timer->stop(); + CHECK(test_timer->is_processing() == false); + CHECK(test_timer->has_autostart() == false); + } + + memdelete(test_timer); +} + +TEST_CASE("[SceneTree][Timer] Check Timer process callback") { + Timer *test_timer = memnew(Timer); + + SUBCASE("[Timer] Timer process callback") { + // check default + CHECK(test_timer->get_timer_process_callback() == Timer::TimerProcessCallback::TIMER_PROCESS_IDLE); + + test_timer->set_timer_process_callback(Timer::TimerProcessCallback::TIMER_PROCESS_PHYSICS); + CHECK(test_timer->get_timer_process_callback() == Timer::TimerProcessCallback::TIMER_PROCESS_PHYSICS); + + test_timer->set_timer_process_callback(Timer::TimerProcessCallback::TIMER_PROCESS_IDLE); + CHECK(test_timer->get_timer_process_callback() == Timer::TimerProcessCallback::TIMER_PROCESS_IDLE); + } + + memdelete(test_timer); +} + +TEST_CASE("[SceneTree][Timer] Check Timer timeout signal") { + Timer *test_timer = memnew(Timer); + SceneTree::get_singleton()->get_root()->add_child(test_timer); + + test_timer->set_process(true); + test_timer->set_physics_process(true); + + SUBCASE("[Timer] Timer process timeout signal must be emitted") { + SIGNAL_WATCH(test_timer, SNAME("timeout")); + test_timer->start(0.1); + + SceneTree::get_singleton()->process(0.2); + + Array signal_args; + signal_args.push_back(Array()); + + SIGNAL_CHECK(SNAME("timeout"), signal_args); + + SIGNAL_UNWATCH(test_timer, SNAME("timeout")); + } + + SUBCASE("[Timer] Timer process timeout signal must not be emitted") { + SIGNAL_WATCH(test_timer, SNAME("timeout")); + test_timer->start(0.1); + + SceneTree::get_singleton()->process(0.05); + + Array signal_args; + signal_args.push_back(Array()); + + SIGNAL_CHECK_FALSE(SNAME("timeout")); + + SIGNAL_UNWATCH(test_timer, SNAME("timeout")); + } + + test_timer->set_timer_process_callback(Timer::TimerProcessCallback::TIMER_PROCESS_PHYSICS); + + SUBCASE("[Timer] Timer physics process timeout signal must be emitted") { + SIGNAL_WATCH(test_timer, SNAME("timeout")); + test_timer->start(0.1); + + SceneTree::get_singleton()->physics_process(0.2); + + Array signal_args; + signal_args.push_back(Array()); + + SIGNAL_CHECK(SNAME("timeout"), signal_args); + + SIGNAL_UNWATCH(test_timer, SNAME("timeout")); + } + + SUBCASE("[Timer] Timer physics process timeout signal must not be emitted") { + SIGNAL_WATCH(test_timer, SNAME("timeout")); + test_timer->start(0.1); + + SceneTree::get_singleton()->physics_process(0.05); + + Array signal_args; + signal_args.push_back(Array()); + + SIGNAL_CHECK_FALSE(SNAME("timeout")); + + SIGNAL_UNWATCH(test_timer, SNAME("timeout")); + } + + memdelete(test_timer); +} + +} // namespace TestTimer + +#endif // TEST_TIMER_H diff --git a/tests/test_main.cpp b/tests/test_main.cpp index 038b045c68..69d8113e64 100644 --- a/tests/test_main.cpp +++ b/tests/test_main.cpp @@ -118,6 +118,7 @@ #include "tests/scene/test_sprite_frames.h" #include "tests/scene/test_text_edit.h" #include "tests/scene/test_theme.h" +#include "tests/scene/test_timer.h" #include "tests/scene/test_viewport.h" #include "tests/scene/test_visual_shader.h" #include "tests/scene/test_window.h" |