summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--.clang-tidy1
-rw-r--r--core/config/project_settings.cpp2
-rw-r--r--core/input/input.cpp12
-rw-r--r--core/input/input.h2
-rw-r--r--core/templates/local_vector.h4
-rw-r--r--doc/classes/EditorFileDialog.xml6
-rw-r--r--doc/classes/FlowContainer.xml15
-rw-r--r--doc/classes/PhysicsServer2D.xml6
-rw-r--r--doc/classes/PhysicsServer3D.xml5
-rw-r--r--doc/classes/ResourceLoader.xml2
-rw-r--r--drivers/vulkan/rendering_device_driver_vulkan.cpp2
-rw-r--r--editor/connections_dialog.cpp2
-rw-r--r--editor/editor_property_name_processor.cpp2
-rw-r--r--editor/filesystem_dock.cpp4
-rw-r--r--editor/gui/editor_file_dialog.cpp1
-rw-r--r--editor/multi_node_edit.h2
-rw-r--r--editor/plugins/mesh_instance_3d_editor_plugin.cpp45
-rw-r--r--editor/plugins/node_3d_editor_plugin.cpp4
-rw-r--r--editor/plugins/node_3d_editor_plugin.h2
-rw-r--r--editor/themes/editor_theme_manager.cpp2
-rw-r--r--modules/csg/csg.cpp2
-rw-r--r--modules/navigation/3d/godot_navigation_server_3d.cpp2
-rw-r--r--modules/navigation/nav_map.cpp4
-rw-r--r--platform/android/detect.py2
-rw-r--r--platform/windows/detect.py105
-rw-r--r--platform/windows/platform_windows_builders.py29
-rw-r--r--scene/3d/cpu_particles_3d.cpp2
-rw-r--r--scene/gui/control.cpp25
-rw-r--r--scene/gui/control.h4
-rw-r--r--scene/gui/flow_container.cpp72
-rw-r--r--scene/gui/flow_container.h11
-rw-r--r--scene/main/viewport.cpp4
-rw-r--r--servers/physics_3d/godot_shape_3d.cpp2
-rw-r--r--servers/physics_3d/godot_soft_body_3d.cpp4
-rw-r--r--servers/rendering/renderer_rd/forward_mobile/scene_shader_forward_mobile.cpp2
-rw-r--r--tests/core/templates/test_local_vector.h11
-rw-r--r--tests/scene/test_timer.h217
-rw-r--r--tests/test_main.cpp1
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"