summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--core/object/object.cpp5
-rw-r--r--core/object/object.h30
-rw-r--r--core/string/string_name.cpp8
-rw-r--r--core/string/string_name.h2
-rw-r--r--core/string/ustring.cpp79
-rw-r--r--core/string/ustring.h3
-rw-r--r--doc/classes/CompressedCubemap.xml3
-rw-r--r--doc/classes/CompressedCubemapArray.xml3
-rw-r--r--doc/classes/CompressedTexture2D.xml3
-rw-r--r--doc/classes/CompressedTexture2DArray.xml3
-rw-r--r--doc/classes/CompressedTextureLayered.xml8
-rw-r--r--doc/classes/TextEdit.xml3
-rw-r--r--doc/classes/TextureLayered.xml2
-rw-r--r--doc/classes/Tree.xml18
-rw-r--r--editor/editor_themes.cpp12
-rw-r--r--editor/plugins/canvas_item_editor_plugin.cpp1
-rw-r--r--editor/plugins/node_3d_editor_plugin.cpp6
-rw-r--r--editor/plugins/sprite_frames_editor_plugin.cpp411
-rw-r--r--editor/plugins/sprite_frames_editor_plugin.h32
-rw-r--r--editor/plugins/visual_shader_editor_plugin.cpp2
-rw-r--r--editor/scene_tree_editor.cpp2
-rw-r--r--platform/web/dom_keys.inc2
-rw-r--r--scene/2d/audio_stream_player_2d.cpp6
-rw-r--r--scene/2d/audio_stream_player_2d.h3
-rw-r--r--scene/2d/navigation_obstacle_2d.cpp7
-rw-r--r--scene/2d/navigation_obstacle_2d.h2
-rw-r--r--scene/3d/navigation_obstacle_3d.cpp7
-rw-r--r--scene/3d/navigation_obstacle_3d.h2
-rw-r--r--scene/animation/animation_blend_space_1d.cpp20
-rw-r--r--scene/gui/code_edit.cpp2
-rw-r--r--scene/gui/text_edit.cpp18
-rw-r--r--scene/gui/text_edit.h4
-rw-r--r--scene/gui/tree.cpp105
-rw-r--r--scene/gui/tree.h10
-rw-r--r--scene/main/node.cpp423
-rw-r--r--scene/main/node.h64
-rw-r--r--scene/resources/convex_polygon_shape_2d.cpp30
-rw-r--r--scene/resources/default_theme/default_theme.cpp6
-rw-r--r--servers/physics_3d/godot_collision_solver_3d_sat.cpp2
-rw-r--r--servers/physics_server_2d.cpp2
-rw-r--r--servers/physics_server_3d.cpp6
-rw-r--r--servers/rendering/renderer_rd/renderer_canvas_render_rd.cpp5
-rw-r--r--servers/rendering/renderer_rd/shaders/effects/screen_space_reflection.glsl5
-rw-r--r--tests/core/string/test_string.h4
44 files changed, 941 insertions, 430 deletions
diff --git a/core/object/object.cpp b/core/object/object.cpp
index c324eab9bb..39cae7c5bd 100644
--- a/core/object/object.cpp
+++ b/core/object/object.cpp
@@ -195,14 +195,15 @@ bool Object::_predelete() {
_predelete_ok = 1;
notification(NOTIFICATION_PREDELETE, true);
if (_predelete_ok) {
- _class_ptr = nullptr; //must restore so destructors can access class ptr correctly
+ _class_name_ptr = nullptr; // Must restore, so constructors/destructors have proper class name access at each stage.
}
return _predelete_ok;
}
void Object::_postinitialize() {
- _class_ptr = _get_class_namev();
+ _class_name_ptr = _get_class_namev(); // Set the direct pointer, which is much faster to obtain, but can only happen after postinitialize.
_initialize_classv();
+ _class_name_ptr = nullptr; // May have been called from a constructor.
notification(NOTIFICATION_POSTINITIALIZE);
}
diff --git a/core/object/object.h b/core/object/object.h
index 5ec69a371b..4226b5e67b 100644
--- a/core/object/object.h
+++ b/core/object/object.h
@@ -376,7 +376,6 @@ private:
#define GDCLASS(m_class, m_inherits) \
private: \
void operator=(const m_class &p_rval) {} \
- mutable StringName _class_name; \
friend class ::ClassDB; \
\
public: \
@@ -388,13 +387,11 @@ public:
return String(#m_class); \
} \
virtual const StringName *_get_class_namev() const override { \
- if (_get_extension()) { \
- return &_get_extension()->class_name; \
- } \
- if (!_class_name) { \
- _class_name = get_class_static(); \
+ static StringName _class_name_static; \
+ if (unlikely(!_class_name_static)) { \
+ StringName::assign_static_unique_class_name(&_class_name_static, #m_class); \
} \
- return &_class_name; \
+ return &_class_name_static; \
} \
static _FORCE_INLINE_ void *get_class_ptr_static() { \
static int ptr; \
@@ -614,8 +611,7 @@ private:
Variant script; // Reference does not exist yet, store it in a Variant.
HashMap<StringName, Variant> metadata;
HashMap<StringName, Variant *> metadata_properties;
- mutable StringName _class_name;
- mutable const StringName *_class_ptr = nullptr;
+ mutable const StringName *_class_name_ptr = nullptr;
void _add_user_signal(const String &p_name, const Array &p_args = Array());
bool _has_user_signal(const StringName &p_name) const;
@@ -714,10 +710,11 @@ protected:
Variant _call_deferred_bind(const Variant **p_args, int p_argcount, Callable::CallError &r_error);
virtual const StringName *_get_class_namev() const {
- if (!_class_name) {
- _class_name = get_class_static();
+ static StringName _class_name_static;
+ if (unlikely(!_class_name_static)) {
+ StringName::assign_static_unique_class_name(&_class_name_static, "Object");
}
- return &_class_name;
+ return &_class_name_static;
}
Vector<StringName> _get_meta_list_bind() const;
@@ -788,13 +785,16 @@ public:
_FORCE_INLINE_ const StringName &get_class_name() const {
if (_extension) {
+ // Can't put inside the unlikely as constructor can run it
return _extension->class_name;
}
- if (!_class_ptr) {
+
+ if (unlikely(!_class_name_ptr)) {
+ // While class is initializing / deinitializing, constructors and destructurs
+ // need access to the proper class at the proper stage.
return *_get_class_namev();
- } else {
- return *_class_ptr;
}
+ return *_class_name_ptr;
}
/* IAPI */
diff --git a/core/string/string_name.cpp b/core/string/string_name.cpp
index df9b6b3f1a..6099fea13f 100644
--- a/core/string/string_name.cpp
+++ b/core/string/string_name.cpp
@@ -201,6 +201,14 @@ StringName::StringName(const StringName &p_name) {
}
}
+void StringName::assign_static_unique_class_name(StringName *ptr, const char *p_name) {
+ mutex.lock();
+ if (*ptr == StringName()) {
+ *ptr = StringName(p_name, true);
+ }
+ mutex.unlock();
+}
+
StringName::StringName(const char *p_name, bool p_static) {
_data = nullptr;
diff --git a/core/string/string_name.h b/core/string/string_name.h
index 177e82896d..07abc781a2 100644
--- a/core/string/string_name.h
+++ b/core/string/string_name.h
@@ -177,6 +177,8 @@ public:
StringName(const String &p_name, bool p_static = false);
StringName(const StaticCString &p_static_string, bool p_static = false);
StringName() {}
+
+ static void assign_static_unique_class_name(StringName *ptr, const char *p_name);
_FORCE_INLINE_ ~StringName() {
if (likely(configured) && _data) { //only free if configured
unref();
diff --git a/core/string/ustring.cpp b/core/string/ustring.cpp
index 773445edb6..73b5bc2d56 100644
--- a/core/string/ustring.cpp
+++ b/core/string/ustring.cpp
@@ -2593,6 +2593,23 @@ double String::to_float(const wchar_t *p_str, const wchar_t **r_end) {
return built_in_strtod<wchar_t>(p_str, (wchar_t **)r_end);
}
+uint32_t String::num_characters(int64_t p_int) {
+ int r = 1;
+ if (p_int < 0) {
+ r += 1;
+ if (p_int == INT64_MIN) {
+ p_int = INT64_MAX;
+ } else {
+ p_int = -p_int;
+ }
+ }
+ while (p_int >= 10) {
+ p_int /= 10;
+ r++;
+ }
+ return r;
+}
+
int64_t String::to_int(const char32_t *p_str, int p_len, bool p_clamp) {
if (p_len == 0 || !p_str[0]) {
return 0;
@@ -4561,15 +4578,65 @@ String String::property_name_encode() const {
}
// Changes made to the set of invalid characters must also be reflected in the String documentation.
-const String String::invalid_node_name_characters = ". : @ / \" " UNIQUE_NODE_PREFIX;
+
+static const char32_t invalid_node_name_characters[] = { '.', ':', '@', '/', '\"', UNIQUE_NODE_PREFIX[0], 0 };
+
+String String::get_invalid_node_name_characters() {
+ // Do not use this function for critical validation.
+ String r;
+ const char32_t *c = invalid_node_name_characters;
+ while (*c) {
+ if (c != invalid_node_name_characters) {
+ r += " ";
+ }
+ r += String::chr(*c);
+ c++;
+ }
+ return r;
+}
String String::validate_node_name() const {
- Vector<String> chars = String::invalid_node_name_characters.split(" ");
- String name = this->replace(chars[0], "");
- for (int i = 1; i < chars.size(); i++) {
- name = name.replace(chars[i], "");
+ // This is a critical validation in node addition, so it must be optimized.
+ const char32_t *cn = ptr();
+ if (cn == nullptr) {
+ return String();
}
- return name;
+ bool valid = true;
+ uint32_t idx = 0;
+ while (cn[idx]) {
+ const char32_t *c = invalid_node_name_characters;
+ while (*c) {
+ if (cn[idx] == *c) {
+ valid = false;
+ break;
+ }
+ c++;
+ }
+ if (!valid) {
+ break;
+ }
+ idx++;
+ }
+
+ if (valid) {
+ return *this;
+ }
+
+ String validated = *this;
+ char32_t *nn = validated.ptrw();
+ while (nn[idx]) {
+ const char32_t *c = invalid_node_name_characters;
+ while (*c) {
+ if (nn[idx] == *c) {
+ nn[idx] = '_';
+ break;
+ }
+ c++;
+ }
+ idx++;
+ }
+
+ return validated;
}
String String::get_basename() const {
diff --git a/core/string/ustring.h b/core/string/ustring.h
index 90034b1b07..e1512cfb26 100644
--- a/core/string/ustring.h
+++ b/core/string/ustring.h
@@ -337,6 +337,7 @@ public:
static double to_float(const char *p_str);
static double to_float(const wchar_t *p_str, const wchar_t **r_end = nullptr);
static double to_float(const char32_t *p_str, const char32_t **r_end = nullptr);
+ static uint32_t num_characters(int64_t p_int);
String capitalize() const;
String to_camel_case() const;
@@ -432,7 +433,7 @@ public:
String property_name_encode() const;
// node functions
- static const String invalid_node_name_characters;
+ static String get_invalid_node_name_characters();
String validate_node_name() const;
String validate_identifier() const;
String validate_filename() const;
diff --git a/doc/classes/CompressedCubemap.xml b/doc/classes/CompressedCubemap.xml
index 745e9c87e3..f98dfc61a3 100644
--- a/doc/classes/CompressedCubemap.xml
+++ b/doc/classes/CompressedCubemap.xml
@@ -5,10 +5,11 @@
</brief_description>
<description>
A cubemap that is loaded from a [code].ccube[/code] file. This file format is internal to Godot; it is created by importing other image formats with the import system. [CompressedCubemap] can use one of 4 compresson methods:
- - Uncompressed (uncompressed on the GPU)
- Lossless (WebP or PNG, uncompressed on the GPU)
- Lossy (WebP, uncompressed on the GPU)
- VRAM Compressed (compressed on the GPU)
+ - VRAM Uncompressed (uncompressed on the GPU)
+ - Basis Universal (compressed on the GPU. Lower file sizes than VRAM Compressed, but slower to compress and lower quality than VRAM Compressed)
Only [b]VRAM Compressed[/b] actually reduces the memory usage on the GPU. The [b]Lossless[/b] and [b]Lossy[/b] compression methods will reduce the required storage on disk, but they will not reduce memory usage on the GPU as the texture is sent to the GPU uncompressed.
Using [b]VRAM Compressed[/b] also improves loading times, as VRAM-compressed textures are faster to load compared to textures using lossless or lossy compression. VRAM compression can exhibit noticeable artifacts and is intended to be used for 3D rendering, not 2D.
See [Cubemap] for a general description of cubemaps.
diff --git a/doc/classes/CompressedCubemapArray.xml b/doc/classes/CompressedCubemapArray.xml
index 8606027242..75e9b3d513 100644
--- a/doc/classes/CompressedCubemapArray.xml
+++ b/doc/classes/CompressedCubemapArray.xml
@@ -5,10 +5,11 @@
</brief_description>
<description>
A cubemap array that is loaded from a [code].ccubearray[/code] file. This file format is internal to Godot; it is created by importing other image formats with the import system. [CompressedCubemapArray] can use one of 4 compresson methods:
- - Uncompressed (uncompressed on the GPU)
- Lossless (WebP or PNG, uncompressed on the GPU)
- Lossy (WebP, uncompressed on the GPU)
- VRAM Compressed (compressed on the GPU)
+ - VRAM Uncompressed (uncompressed on the GPU)
+ - Basis Universal (compressed on the GPU. Lower file sizes than VRAM Compressed, but slower to compress and lower quality than VRAM Compressed)
Only [b]VRAM Compressed[/b] actually reduces the memory usage on the GPU. The [b]Lossless[/b] and [b]Lossy[/b] compression methods will reduce the required storage on disk, but they will not reduce memory usage on the GPU as the texture is sent to the GPU uncompressed.
Using [b]VRAM Compressed[/b] also improves loading times, as VRAM-compressed textures are faster to load compared to textures using lossless or lossy compression. VRAM compression can exhibit noticeable artifacts and is intended to be used for 3D rendering, not 2D.
See [CubemapArray] for a general description of cubemap arrays.
diff --git a/doc/classes/CompressedTexture2D.xml b/doc/classes/CompressedTexture2D.xml
index f4e7398d6e..9fde9ebeea 100644
--- a/doc/classes/CompressedTexture2D.xml
+++ b/doc/classes/CompressedTexture2D.xml
@@ -5,10 +5,11 @@
</brief_description>
<description>
A texture that is loaded from a [code].ctex[/code] file. This file format is internal to Godot; it is created by importing other image formats with the import system. [CompressedTexture2D] can use one of 4 compression methods (including a lack of any compression):
- - Uncompressed (uncompressed on the GPU)
- Lossless (WebP or PNG, uncompressed on the GPU)
- Lossy (WebP, uncompressed on the GPU)
- VRAM Compressed (compressed on the GPU)
+ - VRAM Uncompressed (uncompressed on the GPU)
+ - Basis Universal (compressed on the GPU. Lower file sizes than VRAM Compressed, but slower to compress and lower quality than VRAM Compressed)
Only [b]VRAM Compressed[/b] actually reduces the memory usage on the GPU. The [b]Lossless[/b] and [b]Lossy[/b] compression methods will reduce the required storage on disk, but they will not reduce memory usage on the GPU as the texture is sent to the GPU uncompressed.
Using [b]VRAM Compressed[/b] also improves loading times, as VRAM-compressed textures are faster to load compared to textures using lossless or lossy compression. VRAM compression can exhibit noticeable artifacts and is intended to be used for 3D rendering, not 2D.
</description>
diff --git a/doc/classes/CompressedTexture2DArray.xml b/doc/classes/CompressedTexture2DArray.xml
index bfb3cabe05..71888bf824 100644
--- a/doc/classes/CompressedTexture2DArray.xml
+++ b/doc/classes/CompressedTexture2DArray.xml
@@ -5,10 +5,11 @@
</brief_description>
<description>
A texture array that is loaded from a [code].ctexarray[/code] file. This file format is internal to Godot; it is created by importing other image formats with the import system. [CompressedTexture2DArray] can use one of 4 compresson methods:
- - Uncompressed (uncompressed on the GPU)
- Lossless (WebP or PNG, uncompressed on the GPU)
- Lossy (WebP, uncompressed on the GPU)
- VRAM Compressed (compressed on the GPU)
+ - VRAM Uncompressed (uncompressed on the GPU)
+ - Basis Universal (compressed on the GPU. Lower file sizes than VRAM Compressed, but slower to compress and lower quality than VRAM Compressed)
Only [b]VRAM Compressed[/b] actually reduces the memory usage on the GPU. The [b]Lossless[/b] and [b]Lossy[/b] compression methods will reduce the required storage on disk, but they will not reduce memory usage on the GPU as the texture is sent to the GPU uncompressed.
Using [b]VRAM Compressed[/b] also improves loading times, as VRAM-compressed textures are faster to load compared to textures using lossless or lossy compression. VRAM compression can exhibit noticeable artifacts and is intended to be used for 3D rendering, not 2D.
See [Texture2DArray] for a general description of texture arrays.
diff --git a/doc/classes/CompressedTextureLayered.xml b/doc/classes/CompressedTextureLayered.xml
index 43ce8f5df7..da34180c93 100644
--- a/doc/classes/CompressedTextureLayered.xml
+++ b/doc/classes/CompressedTextureLayered.xml
@@ -4,13 +4,7 @@
Base class for texture arrays that can optionally be compressed.
</brief_description>
<description>
- A texture array that is loaded from a [code].ctexarray[/code] file. This file format is internal to Godot; it is created by importing other image formats with the import system. [CompressedTexture2D] can use one of 4 compresson methods:
- - Uncompressed (uncompressed on the GPU)
- - Lossless (WebP or PNG, uncompressed on the GPU)
- - Lossy (WebP, uncompressed on the GPU)
- - VRAM Compressed (compressed on the GPU)
- Only [b]VRAM Compressed[/b] actually reduces the memory usage on the GPU. The [b]Lossless[/b] and [b]Lossy[/b] compression methods will reduce the required storage on disk, but they will not reduce memory usage on the GPU as the texture is sent to the GPU uncompressed.
- Using [b]VRAM Compressed[/b] also improves loading times, as VRAM-compressed textures are faster to load compared to textures using lossless or lossy compression. VRAM compression can exhibit noticeable artifacts and is intended to be used for 3D rendering, not 2D.
+ Base class for [CompressedTexture2DArray] and [CompressedTexture3D]. Cannot be used directly, but contains all the functions necessary for accessing the derived resource types. See also [TextureLayered].
</description>
<tutorials>
</tutorials>
diff --git a/doc/classes/TextEdit.xml b/doc/classes/TextEdit.xml
index a292c594cf..531906e69d 100644
--- a/doc/classes/TextEdit.xml
+++ b/doc/classes/TextEdit.xml
@@ -1093,6 +1093,9 @@
<member name="caret_blink_interval" type="float" setter="set_caret_blink_interval" getter="get_caret_blink_interval" default="0.65">
Duration (in seconds) of a caret's blinking cycle.
</member>
+ <member name="caret_draw_when_editable_disabled" type="bool" setter="set_draw_caret_when_editable_disabled" getter="is_drawing_caret_when_editable_disabled" default="false">
+ If [code]true[/code], caret will be visible when [member editable] is disabled.
+ </member>
<member name="caret_mid_grapheme" type="bool" setter="set_caret_mid_grapheme_enabled" getter="is_caret_mid_grapheme_enabled" default="true">
Allow moving caret, selecting and removing the individual composite character components.
[b]Note:[/b] [kbd]Backspace[/kbd] is always removing individual composite character components.
diff --git a/doc/classes/TextureLayered.xml b/doc/classes/TextureLayered.xml
index adf391841f..52f3b9aca2 100644
--- a/doc/classes/TextureLayered.xml
+++ b/doc/classes/TextureLayered.xml
@@ -4,7 +4,7 @@
Base class for texture types which contain the data of multiple [Image]s. Each image is of the same size and format.
</brief_description>
<description>
- Base class for [ImageTextureLayered]. Cannot be used directly, but contains all the functions necessary for accessing the derived resource types. See also [Texture3D].
+ Base class for [ImageTextureLayered] and [CompressedTextureLayered]. Cannot be used directly, but contains all the functions necessary for accessing the derived resource types. See also [Texture3D].
Data is set on a per-layer basis. For [Texture2DArray]s, the layer specifies the array layer.
All images need to have the same width, height and number of mipmap levels.
A [TextureLayered] can be loaded with [method ResourceLoader.load].
diff --git a/doc/classes/Tree.xml b/doc/classes/Tree.xml
index 9c60ba1b37..1e4282cd0b 100644
--- a/doc/classes/Tree.xml
+++ b/doc/classes/Tree.xml
@@ -558,6 +558,24 @@
<theme_item name="scroll_speed" data_type="constant" type="int" default="12">
The speed of border scrolling.
</theme_item>
+ <theme_item name="scrollbar_h_separation" data_type="constant" type="int" default="4">
+ The horizontal separation of tree content and scrollbar.
+ </theme_item>
+ <theme_item name="scrollbar_margin_bottom" data_type="constant" type="int" default="-1">
+ The bottom margin of the scrollbars. When negative, uses [theme_item panel] bottom margin.
+ </theme_item>
+ <theme_item name="scrollbar_margin_left" data_type="constant" type="int" default="-1">
+ The left margin of the horizontal scrollbar. When negative, uses [theme_item panel] left margin.
+ </theme_item>
+ <theme_item name="scrollbar_margin_right" data_type="constant" type="int" default="-1">
+ The right margin of the scrollbars. When negative, uses [theme_item panel] right margin.
+ </theme_item>
+ <theme_item name="scrollbar_margin_top" data_type="constant" type="int" default="-1">
+ The right margin of the vertical scrollbar. When negative, uses [theme_item panel] top margin.
+ </theme_item>
+ <theme_item name="scrollbar_v_separation" data_type="constant" type="int" default="4">
+ The vertical separation of tree content and scrollbar.
+ </theme_item>
<theme_item name="v_separation" data_type="constant" type="int" default="4">
The vertical padding inside each item, i.e. the distance between the item's content and top/bottom border.
</theme_item>
diff --git a/editor/editor_themes.cpp b/editor/editor_themes.cpp
index 8ae1185923..c07e9c6707 100644
--- a/editor/editor_themes.cpp
+++ b/editor/editor_themes.cpp
@@ -1209,6 +1209,12 @@ Ref<Theme> create_editor_theme(const Ref<Theme> p_theme) {
theme->set_constant("scroll_border", "Tree", 40 * EDSCALE);
theme->set_constant("scroll_speed", "Tree", 12);
theme->set_constant("outline_size", "Tree", 0 * EDSCALE);
+ theme->set_constant("scrollbar_margin_left", "Tree", 0);
+ theme->set_constant("scrollbar_margin_top", "Tree", 0);
+ theme->set_constant("scrollbar_margin_right", "Tree", 0);
+ theme->set_constant("scrollbar_margin_bottom", "Tree", 0);
+ theme->set_constant("scrollbar_h_separation", "Tree", 1 * EDSCALE);
+ theme->set_constant("scrollbar_v_separation", "Tree", 1 * EDSCALE);
const Color guide_color = mono_color * Color(1, 1, 1, 0.05);
Color relationship_line_color = mono_color * Color(1, 1, 1, relationship_line_opacity);
@@ -1409,9 +1415,9 @@ Ref<Theme> create_editor_theme(const Ref<Theme> p_theme) {
// so this compensates for that.
style_line_edit->set_content_margin(SIDE_TOP, style_line_edit->get_content_margin(SIDE_TOP) - 1 * EDSCALE);
- // Don't round the bottom corner to make the line look sharper.
- style_tab_selected->set_corner_radius(CORNER_BOTTOM_LEFT, 0);
- style_tab_selected->set_corner_radius(CORNER_BOTTOM_RIGHT, 0);
+ // Don't round the bottom corners to make the line look sharper.
+ style_line_edit->set_corner_radius(CORNER_BOTTOM_LEFT, 0);
+ style_line_edit->set_corner_radius(CORNER_BOTTOM_RIGHT, 0);
if (draw_extra_borders) {
style_line_edit->set_border_width_all(Math::round(EDSCALE));
diff --git a/editor/plugins/canvas_item_editor_plugin.cpp b/editor/plugins/canvas_item_editor_plugin.cpp
index e969f9ab30..4814b9ae3b 100644
--- a/editor/plugins/canvas_item_editor_plugin.cpp
+++ b/editor/plugins/canvas_item_editor_plugin.cpp
@@ -5407,6 +5407,7 @@ CanvasItemEditor::CanvasItemEditor() {
singleton = this;
set_process_shortcut_input(true);
+ clear(); // Make sure values are initialized.
// Update the menus' checkboxes.
callable_mp(this, &CanvasItemEditor::set_state).bind(get_state()).call_deferred();
diff --git a/editor/plugins/node_3d_editor_plugin.cpp b/editor/plugins/node_3d_editor_plugin.cpp
index 27c97ff9f4..e443548550 100644
--- a/editor/plugins/node_3d_editor_plugin.cpp
+++ b/editor/plugins/node_3d_editor_plugin.cpp
@@ -7866,7 +7866,10 @@ void Node3DEditor::clear() {
viewports[i]->reset();
}
- RenderingServer::get_singleton()->instance_set_visible(origin_instance, true);
+ if (origin_instance.is_valid()) {
+ RenderingServer::get_singleton()->instance_set_visible(origin_instance, true);
+ }
+
view_menu->get_popup()->set_item_checked(view_menu->get_popup()->get_item_index(MENU_VIEW_ORIGIN), true);
for (int i = 0; i < 3; ++i) {
if (grid_enable[i]) {
@@ -8654,6 +8657,7 @@ void fragment() {
_load_default_preview_settings();
_preview_settings_changed();
}
+ clear(); // Make sure values are initialized.
}
Node3DEditor::~Node3DEditor() {
memdelete(preview_node);
diff --git a/editor/plugins/sprite_frames_editor_plugin.cpp b/editor/plugins/sprite_frames_editor_plugin.cpp
index 20b3cf3618..8d1aadd095 100644
--- a/editor/plugins/sprite_frames_editor_plugin.cpp
+++ b/editor/plugins/sprite_frames_editor_plugin.cpp
@@ -42,6 +42,7 @@
#include "editor/scene_tree_dock.h"
#include "scene/gui/center_container.h"
#include "scene/gui/margin_container.h"
+#include "scene/gui/option_button.h"
#include "scene/gui/panel_container.h"
#include "scene/gui/separator.h"
@@ -131,8 +132,14 @@ void SpriteFramesEditor::_sheet_preview_draw() {
Color accent = get_theme_color("accent_color", "Editor");
- for (const int &E : frames_selected) {
- const int idx = E;
+ _sheet_sort_frames();
+
+ Ref<Font> font = get_theme_font(SNAME("bold"), SNAME("EditorFonts"));
+ int font_size = get_theme_font_size(SNAME("bold_size"), SNAME("EditorFonts"));
+
+ for (int i = 0; i < frames_ordered.size(); ++i) {
+ const int idx = frames_ordered[i].second;
+
const int x = idx % frame_count.x;
const int y = idx / frame_count.x;
const Point2 pos = draw_offset + Point2(x, y) * (draw_frame_size + draw_sep);
@@ -143,6 +150,15 @@ void SpriteFramesEditor::_sheet_preview_draw() {
split_sheet_preview->draw_rect(Rect2(pos + Size2(3, 3), draw_frame_size - Size2(6, 6)), accent, false);
split_sheet_preview->draw_rect(Rect2(pos + Size2(4, 4), draw_frame_size - Size2(8, 8)), Color(0, 0, 0, 1), false);
split_sheet_preview->draw_rect(Rect2(pos + Size2(5, 5), draw_frame_size - Size2(10, 10)), Color(0, 0, 0, 1), false);
+
+ const String text = itos(i);
+ const Vector2 string_size = font->get_string_size(text, HORIZONTAL_ALIGNMENT_LEFT, -1, font_size);
+
+ // Stop rendering text if too large.
+ if (string_size.x + 6 < draw_frame_size.x && string_size.y / 2 + 10 < draw_frame_size.y) {
+ split_sheet_preview->draw_string_outline(font, pos + Size2(5, 7) + Size2(0, string_size.y / 2), text, HORIZONTAL_ALIGNMENT_LEFT, string_size.x, font_size, 1, Color(0, 0, 0, 1));
+ split_sheet_preview->draw_string(font, pos + Size2(5, 7) + Size2(0, string_size.y / 2), text, HORIZONTAL_ALIGNMENT_LEFT, string_size.x, font_size, Color(1, 1, 1));
+ }
}
split_sheet_dialog->get_ok_button()->set_disabled(false);
@@ -156,21 +172,24 @@ void SpriteFramesEditor::_sheet_preview_input(const Ref<InputEvent> &p_event) {
if (idx != -1) {
if (mb->is_shift_pressed() && last_frame_selected >= 0) {
- //select multiple
- int from = idx;
- int to = last_frame_selected;
- if (from > to) {
- SWAP(from, to);
- }
+ // Select multiple frames.
+ const int from = last_frame_selected;
+ const int to = idx;
+
+ const int diff = ABS(to - from);
+ const int dir = SIGN(to - from);
+
+ for (int i = 0; i <= diff; i++) {
+ const int this_idx = from + i * dir;
- for (int i = from; i <= to; i++) {
// Prevent double-toggling the same frame when moving the mouse when the mouse button is still held.
- frames_toggled_by_mouse_hover.insert(idx);
+ frames_toggled_by_mouse_hover.insert(this_idx);
if (mb->is_ctrl_pressed()) {
- frames_selected.erase(i);
- } else {
- frames_selected.insert(i);
+ frames_selected.erase(this_idx);
+ } else if (!frames_selected.has(this_idx)) {
+ frames_selected.insert(this_idx, selected_count);
+ selected_count++;
}
}
} else {
@@ -180,13 +199,15 @@ void SpriteFramesEditor::_sheet_preview_input(const Ref<InputEvent> &p_event) {
if (frames_selected.has(idx)) {
frames_selected.erase(idx);
} else {
- frames_selected.insert(idx);
+ frames_selected.insert(idx, selected_count);
+ selected_count++;
}
}
}
if (last_frame_selected != idx || idx != -1) {
last_frame_selected = idx;
+ frames_need_sort = true;
split_sheet_preview->queue_redraw();
}
}
@@ -209,13 +230,19 @@ void SpriteFramesEditor::_sheet_preview_input(const Ref<InputEvent> &p_event) {
if (frames_selected.has(idx)) {
frames_selected.erase(idx);
} else {
- frames_selected.insert(idx);
+ frames_selected.insert(idx, selected_count);
+ selected_count++;
}
last_frame_selected = idx;
+ frames_need_sort = true;
split_sheet_preview->queue_redraw();
}
}
+
+ if (frames_selected.is_empty()) {
+ selected_count = 0;
+ }
}
void SpriteFramesEditor::_sheet_scroll_input(const Ref<InputEvent> &p_event) {
@@ -254,8 +281,11 @@ void SpriteFramesEditor::_sheet_add_frames() {
undo_redo->create_action(TTR("Add Frame"), UndoRedo::MERGE_DISABLE, EditorNode::get_singleton()->get_edited_scene());
int fc = frames->get_frame_count(edited_anim);
- for (const int &E : frames_selected) {
- int idx = E;
+ _sheet_sort_frames();
+
+ for (const Pair<int, int> &pair : frames_ordered) {
+ const int idx = pair.second;
+
const Point2 frame_coords(idx % frame_count.x, idx / frame_count.x);
Ref<AtlasTexture> at;
@@ -300,21 +330,103 @@ void SpriteFramesEditor::_sheet_zoom_reset() {
split_sheet_preview->set_custom_minimum_size(texture_size * sheet_zoom);
}
-void SpriteFramesEditor::_sheet_select_clear_all_frames() {
- bool should_clear = true;
+void SpriteFramesEditor::_sheet_order_selected(int p_option) {
+ frames_need_sort = true;
+ split_sheet_preview->queue_redraw();
+}
+
+void SpriteFramesEditor::_sheet_select_all_frames() {
for (int i = 0; i < split_sheet_h->get_value() * split_sheet_v->get_value(); i++) {
if (!frames_selected.has(i)) {
- frames_selected.insert(i);
- should_clear = false;
+ frames_selected.insert(i, selected_count);
+ selected_count++;
+ frames_need_sort = true;
}
}
- if (should_clear) {
- frames_selected.clear();
- }
split_sheet_preview->queue_redraw();
}
+void SpriteFramesEditor::_sheet_clear_all_frames() {
+ frames_selected.clear();
+ selected_count = 0;
+
+ split_sheet_preview->queue_redraw();
+}
+
+void SpriteFramesEditor::_sheet_sort_frames() {
+ if (!frames_need_sort) {
+ return;
+ }
+ frames_need_sort = false;
+ frames_ordered.resize(frames_selected.size());
+ if (frames_selected.is_empty()) {
+ return;
+ }
+
+ const Size2i frame_count = _get_frame_count();
+ const int frame_order = split_sheet_order->get_selected_id();
+ int index = 0;
+
+ // Fill based on order.
+ for (const KeyValue<int, int> &from_pair : frames_selected) {
+ const int idx = from_pair.key;
+
+ const int selection_order = from_pair.value;
+
+ // Default to using selection order.
+ int order_by = selection_order;
+
+ // Extract coordinates for sorting.
+ const int pos_frame_x = idx % frame_count.x;
+ const int pos_frame_y = idx / frame_count.x;
+
+ const int neg_frame_x = frame_count.x - (pos_frame_x + 1);
+ const int neg_frame_y = frame_count.y - (pos_frame_y + 1);
+
+ switch (frame_order) {
+ case FRAME_ORDER_LEFT_RIGHT_TOP_BOTTOM: {
+ order_by = frame_count.x * pos_frame_y + pos_frame_x;
+ } break;
+
+ case FRAME_ORDER_LEFT_RIGHT_BOTTOM_TOP: {
+ order_by = frame_count.x * neg_frame_y + pos_frame_x;
+ } break;
+
+ case FRAME_ORDER_RIGHT_LEFT_TOP_BOTTOM: {
+ order_by = frame_count.x * pos_frame_y + neg_frame_x;
+ } break;
+
+ case FRAME_ORDER_RIGHT_LEFT_BOTTOM_TOP: {
+ order_by = frame_count.x * neg_frame_y + neg_frame_x;
+ } break;
+
+ case FRAME_ORDER_TOP_BOTTOM_LEFT_RIGHT: {
+ order_by = pos_frame_y + frame_count.y * pos_frame_x;
+ } break;
+
+ case FRAME_ORDER_TOP_BOTTOM_RIGHT_LEFT: {
+ order_by = pos_frame_y + frame_count.y * neg_frame_x;
+ } break;
+
+ case FRAME_ORDER_BOTTOM_TOP_LEFT_RIGHT: {
+ order_by = neg_frame_y + frame_count.y * pos_frame_x;
+ } break;
+
+ case FRAME_ORDER_BOTTOM_TOP_RIGHT_LEFT: {
+ order_by = neg_frame_y + frame_count.y * neg_frame_x;
+ } break;
+ }
+
+ // Assign in vector.
+ frames_ordered.set(index, Pair<int, int>(order_by, idx));
+ index++;
+ }
+
+ // Sort frames.
+ frames_ordered.sort_custom<PairSort<int, int>>();
+}
+
void SpriteFramesEditor::_sheet_spin_changed(double p_value, int p_dominant_param) {
if (updating_split_settings) {
return;
@@ -367,10 +479,25 @@ void SpriteFramesEditor::_sheet_spin_changed(double p_value, int p_dominant_para
updating_split_settings = false;
frames_selected.clear();
+ selected_count = 0;
last_frame_selected = -1;
split_sheet_preview->queue_redraw();
}
+void SpriteFramesEditor::_toggle_show_settings() {
+ split_sheet_settings_vb->set_visible(!split_sheet_settings_vb->is_visible());
+
+ _update_show_settings();
+}
+
+void SpriteFramesEditor::_update_show_settings() {
+ if (is_layout_rtl()) {
+ toggle_settings_button->set_icon(get_theme_icon(split_sheet_settings_vb->is_visible() ? SNAME("Back") : SNAME("Forward"), SNAME("EditorIcons")));
+ } else {
+ toggle_settings_button->set_icon(get_theme_icon(split_sheet_settings_vb->is_visible() ? SNAME("Forward") : SNAME("Back"), SNAME("EditorIcons")));
+ }
+}
+
void SpriteFramesEditor::_prepare_sprite_sheet(const String &p_file) {
Ref<Texture2D> texture = ResourceLoader::load(p_file);
if (texture.is_null()) {
@@ -378,6 +505,7 @@ void SpriteFramesEditor::_prepare_sprite_sheet(const String &p_file) {
ERR_FAIL_COND(texture.is_null());
}
frames_selected.clear();
+ selected_count = 0;
last_frame_selected = -1;
bool new_texture = texture != split_sheet_preview->get_texture();
@@ -408,6 +536,7 @@ void SpriteFramesEditor::_prepare_sprite_sheet(const String &p_file) {
// Reset zoom.
_sheet_zoom_reset();
}
+
split_sheet_dialog->popup_centered_ratio(0.65);
}
@@ -450,6 +579,8 @@ void SpriteFramesEditor::_notification(int p_what) {
split_sheet_zoom_reset->set_icon(get_theme_icon(SNAME("ZoomReset"), SNAME("EditorIcons")));
split_sheet_zoom_in->set_icon(get_theme_icon(SNAME("ZoomMore"), SNAME("EditorIcons")));
split_sheet_scroll->add_theme_style_override("panel", get_theme_stylebox(SNAME("panel"), SNAME("Tree")));
+
+ _update_show_settings();
} break;
case NOTIFICATION_READY: {
@@ -1769,82 +1900,58 @@ SpriteFramesEditor::SpriteFramesEditor() {
split_sheet_dialog = memnew(ConfirmationDialog);
add_child(split_sheet_dialog);
- VBoxContainer *split_sheet_vb = memnew(VBoxContainer);
- split_sheet_dialog->add_child(split_sheet_vb);
split_sheet_dialog->set_title(TTR("Select Frames"));
split_sheet_dialog->connect("confirmed", callable_mp(this, &SpriteFramesEditor::_sheet_add_frames));
HBoxContainer *split_sheet_hb = memnew(HBoxContainer);
+ split_sheet_dialog->add_child(split_sheet_hb);
+ split_sheet_hb->set_h_size_flags(SIZE_EXPAND_FILL);
+ split_sheet_hb->set_v_size_flags(SIZE_EXPAND_FILL);
- split_sheet_hb->add_child(memnew(Label(TTR("Horizontal:"))));
- split_sheet_h = memnew(SpinBox);
- split_sheet_h->set_min(1);
- split_sheet_h->set_max(128);
- split_sheet_h->set_step(1);
- split_sheet_hb->add_child(split_sheet_h);
- split_sheet_h->connect("value_changed", callable_mp(this, &SpriteFramesEditor::_sheet_spin_changed).bind(PARAM_FRAME_COUNT));
-
- split_sheet_hb->add_child(memnew(Label(TTR("Vertical:"))));
- split_sheet_v = memnew(SpinBox);
- split_sheet_v->set_min(1);
- split_sheet_v->set_max(128);
- split_sheet_v->set_step(1);
- split_sheet_hb->add_child(split_sheet_v);
- split_sheet_v->connect("value_changed", callable_mp(this, &SpriteFramesEditor::_sheet_spin_changed).bind(PARAM_FRAME_COUNT));
-
- split_sheet_hb->add_child(memnew(VSeparator));
- split_sheet_hb->add_child(memnew(Label(TTR("Size:"))));
- split_sheet_size_x = memnew(SpinBox);
- split_sheet_size_x->set_min(1);
- split_sheet_size_x->set_step(1);
- split_sheet_size_x->set_suffix("px");
- split_sheet_size_x->connect("value_changed", callable_mp(this, &SpriteFramesEditor::_sheet_spin_changed).bind(PARAM_SIZE));
- split_sheet_hb->add_child(split_sheet_size_x);
- split_sheet_size_y = memnew(SpinBox);
- split_sheet_size_y->set_min(1);
- split_sheet_size_y->set_step(1);
- split_sheet_size_y->set_suffix("px");
- split_sheet_size_y->connect("value_changed", callable_mp(this, &SpriteFramesEditor::_sheet_spin_changed).bind(PARAM_SIZE));
- split_sheet_hb->add_child(split_sheet_size_y);
-
- split_sheet_hb->add_child(memnew(VSeparator));
- split_sheet_hb->add_child(memnew(Label(TTR("Separation:"))));
- split_sheet_sep_x = memnew(SpinBox);
- split_sheet_sep_x->set_min(0);
- split_sheet_sep_x->set_step(1);
- split_sheet_sep_x->set_suffix("px");
- split_sheet_sep_x->connect("value_changed", callable_mp(this, &SpriteFramesEditor::_sheet_spin_changed).bind(PARAM_USE_CURRENT));
- split_sheet_hb->add_child(split_sheet_sep_x);
- split_sheet_sep_y = memnew(SpinBox);
- split_sheet_sep_y->set_min(0);
- split_sheet_sep_y->set_step(1);
- split_sheet_sep_y->set_suffix("px");
- split_sheet_sep_y->connect("value_changed", callable_mp(this, &SpriteFramesEditor::_sheet_spin_changed).bind(PARAM_USE_CURRENT));
- split_sheet_hb->add_child(split_sheet_sep_y);
-
- split_sheet_hb->add_child(memnew(VSeparator));
- split_sheet_hb->add_child(memnew(Label(TTR("Offset:"))));
- split_sheet_offset_x = memnew(SpinBox);
- split_sheet_offset_x->set_min(0);
- split_sheet_offset_x->set_step(1);
- split_sheet_offset_x->set_suffix("px");
- split_sheet_offset_x->connect("value_changed", callable_mp(this, &SpriteFramesEditor::_sheet_spin_changed).bind(PARAM_USE_CURRENT));
- split_sheet_hb->add_child(split_sheet_offset_x);
- split_sheet_offset_y = memnew(SpinBox);
- split_sheet_offset_y->set_min(0);
- split_sheet_offset_y->set_step(1);
- split_sheet_offset_y->set_suffix("px");
- split_sheet_offset_y->connect("value_changed", callable_mp(this, &SpriteFramesEditor::_sheet_spin_changed).bind(PARAM_USE_CURRENT));
- split_sheet_hb->add_child(split_sheet_offset_y);
-
- split_sheet_hb->add_spacer();
-
- Button *select_clear_all = memnew(Button);
- select_clear_all->set_text(TTR("Select/Clear All Frames"));
- select_clear_all->connect("pressed", callable_mp(this, &SpriteFramesEditor::_sheet_select_clear_all_frames));
- split_sheet_hb->add_child(select_clear_all);
-
- split_sheet_vb->add_child(split_sheet_hb);
+ VBoxContainer *split_sheet_vb = memnew(VBoxContainer);
+ split_sheet_hb->add_child(split_sheet_vb);
+ split_sheet_vb->set_h_size_flags(SIZE_EXPAND_FILL);
+ split_sheet_vb->set_v_size_flags(SIZE_EXPAND_FILL);
+
+ HBoxContainer *split_sheet_menu_hb = memnew(HBoxContainer);
+
+ split_sheet_menu_hb->add_child(memnew(Label(TTR("Frame Order"))));
+
+ split_sheet_order = memnew(OptionButton);
+ split_sheet_order->add_item(TTR("As Selected"), FRAME_ORDER_SELECTION);
+ split_sheet_order->add_separator(TTR("By Row"));
+ split_sheet_order->add_item(TTR("Left to Right, Top to Bottom"), FRAME_ORDER_LEFT_RIGHT_TOP_BOTTOM);
+ split_sheet_order->add_item(TTR("Left to Right, Bottom to Top"), FRAME_ORDER_LEFT_RIGHT_BOTTOM_TOP);
+ split_sheet_order->add_item(TTR("Right to Left, Top to Bottom"), FRAME_ORDER_RIGHT_LEFT_TOP_BOTTOM);
+ split_sheet_order->add_item(TTR("Right to Left, Bottom to Top"), FRAME_ORDER_RIGHT_LEFT_BOTTOM_TOP);
+ split_sheet_order->add_separator(TTR("By Column"));
+ split_sheet_order->add_item(TTR("Top to Bottom, Left to Right"), FRAME_ORDER_TOP_BOTTOM_LEFT_RIGHT);
+ split_sheet_order->add_item(TTR("Top to Bottom, Right to Left"), FRAME_ORDER_TOP_BOTTOM_RIGHT_LEFT);
+ split_sheet_order->add_item(TTR("Bottom to Top, Left to Right"), FRAME_ORDER_BOTTOM_TOP_LEFT_RIGHT);
+ split_sheet_order->add_item(TTR("Bottom to Top, Right to Left"), FRAME_ORDER_BOTTOM_TOP_RIGHT_LEFT);
+ split_sheet_order->connect("item_selected", callable_mp(this, &SpriteFramesEditor::_sheet_order_selected));
+ split_sheet_menu_hb->add_child(split_sheet_order);
+
+ Button *select_all = memnew(Button);
+ select_all->set_text(TTR("Select All"));
+ select_all->connect("pressed", callable_mp(this, &SpriteFramesEditor::_sheet_select_all_frames));
+ split_sheet_menu_hb->add_child(select_all);
+
+ Button *clear_all = memnew(Button);
+ clear_all->set_text(TTR("Select None"));
+ clear_all->connect("pressed", callable_mp(this, &SpriteFramesEditor::_sheet_clear_all_frames));
+ split_sheet_menu_hb->add_child(clear_all);
+
+ split_sheet_menu_hb->add_spacer();
+
+ toggle_settings_button = memnew(Button);
+ toggle_settings_button->set_h_size_flags(SIZE_SHRINK_END);
+ toggle_settings_button->set_flat(true);
+ toggle_settings_button->connect("pressed", callable_mp(this, &SpriteFramesEditor::_toggle_show_settings));
+ toggle_settings_button->set_tooltip_text(TTR("Toggle Settings Panel"));
+ split_sheet_menu_hb->add_child(toggle_settings_button);
+
+ split_sheet_vb->add_child(split_sheet_menu_hb);
PanelContainer *split_sheet_panel = memnew(PanelContainer);
split_sheet_panel->set_h_size_flags(SIZE_EXPAND_FILL);
@@ -1896,6 +2003,120 @@ SpriteFramesEditor::SpriteFramesEditor() {
split_sheet_zoom_in->connect("pressed", callable_mp(this, &SpriteFramesEditor::_sheet_zoom_in));
split_sheet_zoom_hb->add_child(split_sheet_zoom_in);
+ split_sheet_settings_vb = memnew(VBoxContainer);
+ split_sheet_settings_vb->set_v_size_flags(SIZE_EXPAND_FILL);
+
+ HBoxContainer *split_sheet_h_hb = memnew(HBoxContainer);
+ split_sheet_h_hb->set_h_size_flags(SIZE_EXPAND_FILL);
+
+ Label *split_sheet_h_label = memnew(Label(TTR("Horizontal")));
+ split_sheet_h_label->set_h_size_flags(SIZE_EXPAND_FILL);
+ split_sheet_h_hb->add_child(split_sheet_h_label);
+
+ split_sheet_h = memnew(SpinBox);
+ split_sheet_h->set_h_size_flags(SIZE_EXPAND_FILL);
+ split_sheet_h->set_min(1);
+ split_sheet_h->set_max(128);
+ split_sheet_h->set_step(1);
+ split_sheet_h_hb->add_child(split_sheet_h);
+ split_sheet_h->connect("value_changed", callable_mp(this, &SpriteFramesEditor::_sheet_spin_changed).bind(PARAM_FRAME_COUNT));
+ split_sheet_settings_vb->add_child(split_sheet_h_hb);
+
+ HBoxContainer *split_sheet_v_hb = memnew(HBoxContainer);
+ split_sheet_v_hb->set_h_size_flags(SIZE_EXPAND_FILL);
+
+ Label *split_sheet_v_label = memnew(Label(TTR("Vertical")));
+ split_sheet_v_label->set_h_size_flags(SIZE_EXPAND_FILL);
+ split_sheet_v_hb->add_child(split_sheet_v_label);
+
+ split_sheet_v = memnew(SpinBox);
+ split_sheet_v->set_h_size_flags(SIZE_EXPAND_FILL);
+ split_sheet_v->set_min(1);
+ split_sheet_v->set_max(128);
+ split_sheet_v->set_step(1);
+ split_sheet_v_hb->add_child(split_sheet_v);
+ split_sheet_v->connect("value_changed", callable_mp(this, &SpriteFramesEditor::_sheet_spin_changed).bind(PARAM_FRAME_COUNT));
+ split_sheet_settings_vb->add_child(split_sheet_v_hb);
+
+ HBoxContainer *split_sheet_size_hb = memnew(HBoxContainer);
+ split_sheet_size_hb->set_h_size_flags(SIZE_EXPAND_FILL);
+
+ Label *split_sheet_size_label = memnew(Label(TTR("Size")));
+ split_sheet_size_label->set_h_size_flags(SIZE_EXPAND_FILL);
+ split_sheet_size_label->set_v_size_flags(SIZE_SHRINK_BEGIN);
+ split_sheet_size_hb->add_child(split_sheet_size_label);
+
+ VBoxContainer *split_sheet_size_vb = memnew(VBoxContainer);
+ split_sheet_size_vb->set_h_size_flags(SIZE_EXPAND_FILL);
+ split_sheet_size_x = memnew(SpinBox);
+ split_sheet_size_x->set_h_size_flags(SIZE_EXPAND_FILL);
+ split_sheet_size_x->set_min(1);
+ split_sheet_size_x->set_step(1);
+ split_sheet_size_x->set_suffix("px");
+ split_sheet_size_x->connect("value_changed", callable_mp(this, &SpriteFramesEditor::_sheet_spin_changed).bind(PARAM_SIZE));
+ split_sheet_size_vb->add_child(split_sheet_size_x);
+ split_sheet_size_y = memnew(SpinBox);
+ split_sheet_size_y->set_h_size_flags(SIZE_EXPAND_FILL);
+ split_sheet_size_y->set_min(1);
+ split_sheet_size_y->set_step(1);
+ split_sheet_size_y->set_suffix("px");
+ split_sheet_size_y->connect("value_changed", callable_mp(this, &SpriteFramesEditor::_sheet_spin_changed).bind(PARAM_SIZE));
+ split_sheet_size_vb->add_child(split_sheet_size_y);
+ split_sheet_size_hb->add_child(split_sheet_size_vb);
+ split_sheet_settings_vb->add_child(split_sheet_size_hb);
+
+ HBoxContainer *split_sheet_sep_hb = memnew(HBoxContainer);
+ split_sheet_sep_hb->set_h_size_flags(SIZE_EXPAND_FILL);
+
+ Label *split_sheet_sep_label = memnew(Label(TTR("Separation")));
+ split_sheet_sep_label->set_h_size_flags(SIZE_EXPAND_FILL);
+ split_sheet_sep_label->set_v_size_flags(SIZE_SHRINK_BEGIN);
+ split_sheet_sep_hb->add_child(split_sheet_sep_label);
+
+ VBoxContainer *split_sheet_sep_vb = memnew(VBoxContainer);
+ split_sheet_sep_vb->set_h_size_flags(SIZE_EXPAND_FILL);
+ split_sheet_sep_x = memnew(SpinBox);
+ split_sheet_sep_x->set_min(0);
+ split_sheet_sep_x->set_step(1);
+ split_sheet_sep_x->set_suffix("px");
+ split_sheet_sep_x->connect("value_changed", callable_mp(this, &SpriteFramesEditor::_sheet_spin_changed).bind(PARAM_USE_CURRENT));
+ split_sheet_sep_vb->add_child(split_sheet_sep_x);
+ split_sheet_sep_y = memnew(SpinBox);
+ split_sheet_sep_y->set_min(0);
+ split_sheet_sep_y->set_step(1);
+ split_sheet_sep_y->set_suffix("px");
+ split_sheet_sep_y->connect("value_changed", callable_mp(this, &SpriteFramesEditor::_sheet_spin_changed).bind(PARAM_USE_CURRENT));
+ split_sheet_sep_vb->add_child(split_sheet_sep_y);
+ split_sheet_sep_hb->add_child(split_sheet_sep_vb);
+ split_sheet_settings_vb->add_child(split_sheet_sep_hb);
+
+ HBoxContainer *split_sheet_offset_hb = memnew(HBoxContainer);
+ split_sheet_offset_hb->set_h_size_flags(SIZE_EXPAND_FILL);
+
+ Label *split_sheet_offset_label = memnew(Label(TTR("Offset")));
+ split_sheet_offset_label->set_h_size_flags(SIZE_EXPAND_FILL);
+ split_sheet_offset_label->set_v_size_flags(SIZE_SHRINK_BEGIN);
+ split_sheet_offset_hb->add_child(split_sheet_offset_label);
+
+ VBoxContainer *split_sheet_offset_vb = memnew(VBoxContainer);
+ split_sheet_offset_vb->set_h_size_flags(SIZE_EXPAND_FILL);
+ split_sheet_offset_x = memnew(SpinBox);
+ split_sheet_offset_x->set_min(0);
+ split_sheet_offset_x->set_step(1);
+ split_sheet_offset_x->set_suffix("px");
+ split_sheet_offset_x->connect("value_changed", callable_mp(this, &SpriteFramesEditor::_sheet_spin_changed).bind(PARAM_USE_CURRENT));
+ split_sheet_offset_vb->add_child(split_sheet_offset_x);
+ split_sheet_offset_y = memnew(SpinBox);
+ split_sheet_offset_y->set_min(0);
+ split_sheet_offset_y->set_step(1);
+ split_sheet_offset_y->set_suffix("px");
+ split_sheet_offset_y->connect("value_changed", callable_mp(this, &SpriteFramesEditor::_sheet_spin_changed).bind(PARAM_USE_CURRENT));
+ split_sheet_offset_vb->add_child(split_sheet_offset_y);
+ split_sheet_offset_hb->add_child(split_sheet_offset_vb);
+ split_sheet_settings_vb->add_child(split_sheet_offset_hb);
+
+ split_sheet_hb->add_child(split_sheet_settings_vb);
+
file_split_sheet = memnew(EditorFileDialog);
file_split_sheet->set_title(TTR("Create Frames from Sprite Sheet"));
file_split_sheet->set_file_mode(EditorFileDialog::FILE_MODE_OPEN_FILE);
diff --git a/editor/plugins/sprite_frames_editor_plugin.h b/editor/plugins/sprite_frames_editor_plugin.h
index 1dfb909388..9317e94be0 100644
--- a/editor/plugins/sprite_frames_editor_plugin.h
+++ b/editor/plugins/sprite_frames_editor_plugin.h
@@ -45,6 +45,7 @@
#include "scene/gui/texture_rect.h"
#include "scene/gui/tree.h"
+class OptionButton;
class EditorFileDialog;
class EditorSpriteFramesFrame : public Resource {
@@ -68,6 +69,22 @@ class SpriteFramesEditor : public HSplitContainer {
};
int dominant_param = PARAM_FRAME_COUNT;
+ enum {
+ FRAME_ORDER_SELECTION, // Order frames were selected in.
+
+ // By Row.
+ FRAME_ORDER_LEFT_RIGHT_TOP_BOTTOM,
+ FRAME_ORDER_LEFT_RIGHT_BOTTOM_TOP,
+ FRAME_ORDER_RIGHT_LEFT_TOP_BOTTOM,
+ FRAME_ORDER_RIGHT_LEFT_BOTTOM_TOP,
+
+ // By Column.
+ FRAME_ORDER_TOP_BOTTOM_LEFT_RIGHT,
+ FRAME_ORDER_TOP_BOTTOM_RIGHT_LEFT,
+ FRAME_ORDER_BOTTOM_TOP_LEFT_RIGHT,
+ FRAME_ORDER_BOTTOM_TOP_RIGHT_LEFT,
+ };
+
bool read_only = false;
Ref<Texture2D> autoplay_icon;
@@ -121,6 +138,7 @@ class SpriteFramesEditor : public HSplitContainer {
ConfirmationDialog *split_sheet_dialog = nullptr;
ScrollContainer *split_sheet_scroll = nullptr;
TextureRect *split_sheet_preview = nullptr;
+ VBoxContainer *split_sheet_settings_vb = nullptr;
SpinBox *split_sheet_h = nullptr;
SpinBox *split_sheet_v = nullptr;
SpinBox *split_sheet_size_x = nullptr;
@@ -132,9 +150,14 @@ class SpriteFramesEditor : public HSplitContainer {
Button *split_sheet_zoom_out = nullptr;
Button *split_sheet_zoom_reset = nullptr;
Button *split_sheet_zoom_in = nullptr;
+ Button *toggle_settings_button = nullptr;
+ OptionButton *split_sheet_order = nullptr;
EditorFileDialog *file_split_sheet = nullptr;
- HashSet<int> frames_selected;
+ HashMap<int, int> frames_selected; // Key is frame index. Value is selection order.
HashSet<int> frames_toggled_by_mouse_hover;
+ Vector<Pair<int, int>> frames_ordered; // First is the index to be ordered by. Second is the actual frame index.
+ int selected_count = 0;
+ bool frames_need_sort = false;
int last_frame_selected = 0;
float scale_ratio;
@@ -206,7 +229,12 @@ class SpriteFramesEditor : public HSplitContainer {
void _sheet_zoom_in();
void _sheet_zoom_out();
void _sheet_zoom_reset();
- void _sheet_select_clear_all_frames();
+ void _sheet_order_selected(int p_option);
+ void _sheet_select_all_frames();
+ void _sheet_clear_all_frames();
+ void _sheet_sort_frames();
+ void _toggle_show_settings();
+ void _update_show_settings();
void _edit();
void _regist_scene_undo(EditorUndoRedoManager *undo_redo);
diff --git a/editor/plugins/visual_shader_editor_plugin.cpp b/editor/plugins/visual_shader_editor_plugin.cpp
index af761a2cea..a563d17c57 100644
--- a/editor/plugins/visual_shader_editor_plugin.cpp
+++ b/editor/plugins/visual_shader_editor_plugin.cpp
@@ -5931,7 +5931,7 @@ VisualShaderEditor::VisualShaderEditor() {
add_options.push_back(AddOption("ATanH", "Vector/Functions", "VisualShaderNodeVectorFunc", TTR("Returns the inverse hyperbolic tangent of the parameter."), { VisualShaderNodeVectorFunc::FUNC_ATANH, VisualShaderNodeVectorFunc::OP_TYPE_VECTOR_2D }, VisualShaderNode::PORT_TYPE_VECTOR_2D));
add_options.push_back(AddOption("ATanH", "Vector/Functions", "VisualShaderNodeVectorFunc", TTR("Returns the inverse hyperbolic tangent of the parameter."), { VisualShaderNodeVectorFunc::FUNC_ATANH, VisualShaderNodeVectorFunc::OP_TYPE_VECTOR_3D }, VisualShaderNode::PORT_TYPE_VECTOR_3D));
add_options.push_back(AddOption("ATanH", "Vector/Functions", "VisualShaderNodeVectorFunc", TTR("Returns the inverse hyperbolic tangent of the parameter."), { VisualShaderNodeVectorFunc::FUNC_ATANH, VisualShaderNodeVectorFunc::OP_TYPE_VECTOR_4D }, VisualShaderNode::PORT_TYPE_VECTOR_4D));
- add_options.push_back(AddOption("Ceil", "Vector/Functions", "VisualShaderNodeVectorFunc", TTR("Finds the nearest integer that is greater than or equal to the parameter."), { VisualShaderNodeVectorFunc::FUNC_CEIL }, VisualShaderNode::PORT_TYPE_VECTOR_2D));
+ add_options.push_back(AddOption("Ceil", "Vector/Functions", "VisualShaderNodeVectorFunc", TTR("Finds the nearest integer that is greater than or equal to the parameter."), { VisualShaderNodeVectorFunc::FUNC_CEIL, VisualShaderNodeVectorFunc::OP_TYPE_VECTOR_2D }, VisualShaderNode::PORT_TYPE_VECTOR_2D));
add_options.push_back(AddOption("Ceil", "Vector/Functions", "VisualShaderNodeVectorFunc", TTR("Finds the nearest integer that is greater than or equal to the parameter."), { VisualShaderNodeVectorFunc::FUNC_CEIL, VisualShaderNodeVectorFunc::OP_TYPE_VECTOR_3D }, VisualShaderNode::PORT_TYPE_VECTOR_3D));
add_options.push_back(AddOption("Ceil", "Vector/Functions", "VisualShaderNodeVectorFunc", TTR("Finds the nearest integer that is greater than or equal to the parameter."), { VisualShaderNodeVectorFunc::FUNC_CEIL, VisualShaderNodeVectorFunc::OP_TYPE_VECTOR_4D }, VisualShaderNode::PORT_TYPE_VECTOR_4D));
add_options.push_back(AddOption("Clamp", "Vector/Functions", "VisualShaderNodeClamp", TTR("Constrains a value to lie between two further values."), { VisualShaderNodeClamp::OP_TYPE_VECTOR_2D }, VisualShaderNode::PORT_TYPE_VECTOR_2D));
diff --git a/editor/scene_tree_editor.cpp b/editor/scene_tree_editor.cpp
index 8ba6a1e610..808c058ecd 100644
--- a/editor/scene_tree_editor.cpp
+++ b/editor/scene_tree_editor.cpp
@@ -983,7 +983,7 @@ void SceneTreeEditor::_renamed() {
String new_name = raw_new_name.validate_node_name();
if (new_name != raw_new_name) {
- error->set_text(TTR("Invalid node name, the following characters are not allowed:") + "\n" + String::invalid_node_name_characters);
+ error->set_text(TTR("Invalid node name, the following characters are not allowed:") + "\n" + String::get_invalid_node_name_characters());
error->popup_centered();
if (new_name.is_empty()) {
diff --git a/platform/web/dom_keys.inc b/platform/web/dom_keys.inc
index ae3b2fc1a5..cd94b779c0 100644
--- a/platform/web/dom_keys.inc
+++ b/platform/web/dom_keys.inc
@@ -33,7 +33,7 @@
// See https://w3c.github.io/uievents-code/#code-value-tables
Key dom_code2godot_scancode(EM_UTF8 const p_code[32], EM_UTF8 const p_key[32], bool p_physical) {
#define DOM2GODOT(p_str, p_godot_code) \
- if (memcmp((const void *)p_str, (void *)(p_physical ? p_key : p_code), strlen(p_str) + 1) == 0) { \
+ if (memcmp((const void *)p_str, (void *)(p_physical ? p_code : p_key), strlen(p_str) + 1) == 0) { \
return Key::p_godot_code; \
}
diff --git a/scene/2d/audio_stream_player_2d.cpp b/scene/2d/audio_stream_player_2d.cpp
index c175edb6cb..466ffad951 100644
--- a/scene/2d/audio_stream_player_2d.cpp
+++ b/scene/2d/audio_stream_player_2d.cpp
@@ -68,7 +68,8 @@ void AudioStreamPlayer2D::_notification(int p_what) {
case NOTIFICATION_INTERNAL_PHYSICS_PROCESS: {
// Update anything related to position first, if possible of course.
- if (setplay.get() > 0 || (active.is_set() && last_mix_count != AudioServer::get_singleton()->get_mix_count())) {
+ if (setplay.get() > 0 || (active.is_set() && last_mix_count != AudioServer::get_singleton()->get_mix_count()) || force_update_panning) {
+ force_update_panning = false;
_update_panning();
}
@@ -109,6 +110,7 @@ void AudioStreamPlayer2D::_notification(int p_what) {
}
}
+// Interacts with PhysicsServer2D, so can only be called during _physics_process
StringName AudioStreamPlayer2D::_get_actual_bus() {
Vector2 global_pos = get_global_position();
@@ -117,6 +119,7 @@ StringName AudioStreamPlayer2D::_get_actual_bus() {
ERR_FAIL_COND_V(world_2d.is_null(), SNAME("Master"));
PhysicsDirectSpaceState2D *space_state = PhysicsServer2D::get_singleton()->space_get_direct_state(world_2d->get_space());
+ ERR_FAIL_COND_V(space_state == nullptr, SNAME("Master"));
PhysicsDirectSpaceState2D::ShapeResult sr[MAX_INTERSECT_AREAS];
PhysicsDirectSpaceState2D::PointParameters point_params;
@@ -142,6 +145,7 @@ StringName AudioStreamPlayer2D::_get_actual_bus() {
return default_bus;
}
+// Interacts with PhysicsServer2D, so can only be called during _physics_process
void AudioStreamPlayer2D::_update_panning() {
if (!active.is_set() || stream.is_null()) {
return;
diff --git a/scene/2d/audio_stream_player_2d.h b/scene/2d/audio_stream_player_2d.h
index 79a026fed2..9b23fd3943 100644
--- a/scene/2d/audio_stream_player_2d.h
+++ b/scene/2d/audio_stream_player_2d.h
@@ -61,6 +61,7 @@ private:
Vector<AudioFrame> volume_vector;
uint64_t last_mix_count = -1;
+ bool force_update_panning = false;
float volume_db = 0.0;
float pitch_scale = 1.0;
@@ -75,7 +76,7 @@ private:
void _update_panning();
void _bus_layout_changed();
- static void _listener_changed_cb(void *self) { reinterpret_cast<AudioStreamPlayer2D *>(self)->_update_panning(); }
+ static void _listener_changed_cb(void *self) { reinterpret_cast<AudioStreamPlayer2D *>(self)->force_update_panning = true; }
uint32_t area_mask = 1;
diff --git a/scene/2d/navigation_obstacle_2d.cpp b/scene/2d/navigation_obstacle_2d.cpp
index d7ef77e25b..2366cb696e 100644
--- a/scene/2d/navigation_obstacle_2d.cpp
+++ b/scene/2d/navigation_obstacle_2d.cpp
@@ -196,13 +196,20 @@ void NavigationObstacle2D::set_agent_parent(Node *p_agent_parent) {
} else {
NavigationServer2D::get_singleton()->agent_set_map(get_rid(), parent_node2d->get_world_2d()->get_navigation_map());
}
+ // Need to register Callback as obstacle requires a valid Callback to be added to avoidance simulation.
+ NavigationServer2D::get_singleton()->agent_set_callback(get_rid(), callable_mp(this, &NavigationObstacle2D::_avoidance_done));
reevaluate_agent_radius();
} else {
parent_node2d = nullptr;
NavigationServer2D::get_singleton()->agent_set_map(get_rid(), RID());
+ NavigationServer2D::get_singleton()->agent_set_callback(agent, Callable());
}
}
+void NavigationObstacle2D::_avoidance_done(Vector3 p_new_velocity) {
+ // Dummy function as obstacle requires a valid Callback to be added to avoidance simulation.
+}
+
void NavigationObstacle2D::set_navigation_map(RID p_navigation_map) {
if (map_override == p_navigation_map) {
return;
diff --git a/scene/2d/navigation_obstacle_2d.h b/scene/2d/navigation_obstacle_2d.h
index 12ddcaf219..f856c481b0 100644
--- a/scene/2d/navigation_obstacle_2d.h
+++ b/scene/2d/navigation_obstacle_2d.h
@@ -75,6 +75,8 @@ public:
PackedStringArray get_configuration_warnings() const override;
+ void _avoidance_done(Vector3 p_new_velocity); // Dummy
+
private:
void initialize_agent();
void reevaluate_agent_radius();
diff --git a/scene/3d/navigation_obstacle_3d.cpp b/scene/3d/navigation_obstacle_3d.cpp
index 85b3c164cc..14d93fb0e0 100644
--- a/scene/3d/navigation_obstacle_3d.cpp
+++ b/scene/3d/navigation_obstacle_3d.cpp
@@ -203,13 +203,20 @@ void NavigationObstacle3D::set_agent_parent(Node *p_agent_parent) {
} else {
NavigationServer3D::get_singleton()->agent_set_map(get_rid(), parent_node3d->get_world_3d()->get_navigation_map());
}
+ // Need to register Callback as obstacle requires a valid Callback to be added to avoidance simulation.
+ NavigationServer3D::get_singleton()->agent_set_callback(get_rid(), callable_mp(this, &NavigationObstacle3D::_avoidance_done));
reevaluate_agent_radius();
} else {
parent_node3d = nullptr;
NavigationServer3D::get_singleton()->agent_set_map(get_rid(), RID());
+ NavigationServer3D::get_singleton()->agent_set_callback(agent, Callable());
}
}
+void NavigationObstacle3D::_avoidance_done(Vector3 p_new_velocity) {
+ // Dummy function as obstacle requires a valid Callback to be added to avoidance simulation.
+}
+
void NavigationObstacle3D::set_navigation_map(RID p_navigation_map) {
if (map_override == p_navigation_map) {
return;
diff --git a/scene/3d/navigation_obstacle_3d.h b/scene/3d/navigation_obstacle_3d.h
index 416b32c026..c5a9d737f6 100644
--- a/scene/3d/navigation_obstacle_3d.h
+++ b/scene/3d/navigation_obstacle_3d.h
@@ -74,6 +74,8 @@ public:
PackedStringArray get_configuration_warnings() const override;
+ void _avoidance_done(Vector3 p_new_velocity); // Dummy
+
private:
void initialize_agent();
void reevaluate_agent_radius();
diff --git a/scene/animation/animation_blend_space_1d.cpp b/scene/animation/animation_blend_space_1d.cpp
index 0e9e02f247..1467939426 100644
--- a/scene/animation/animation_blend_space_1d.cpp
+++ b/scene/animation/animation_blend_space_1d.cpp
@@ -288,8 +288,6 @@ double AnimationNodeBlendSpace1D::process(double p_time, bool p_seek, bool p_is_
double max_time_remaining = 0.0;
if (blend_mode == BLEND_MODE_INTERPOLATED) {
- float weights[MAX_BLEND_POINTS] = {};
-
int point_lower = -1;
float pos_lower = 0.0;
int point_higher = -1;
@@ -300,26 +298,18 @@ double AnimationNodeBlendSpace1D::process(double p_time, bool p_seek, bool p_is_
float pos = blend_points[i].position;
if (pos <= blend_pos) {
- if (point_lower == -1) {
- point_lower = i;
- pos_lower = pos;
- } else if ((blend_pos - pos) < (blend_pos - pos_lower)) {
+ if (point_lower == -1 || pos > pos_lower) {
point_lower = i;
pos_lower = pos;
}
- } else {
- if (point_higher == -1) {
- point_higher = i;
- pos_higher = pos;
- } else if ((pos - blend_pos) < (pos_higher - blend_pos)) {
- point_higher = i;
- pos_higher = pos;
- }
+ } else if (point_higher == -1 || pos < pos_higher) {
+ point_higher = i;
+ pos_higher = pos;
}
}
// fill in weights
-
+ float weights[MAX_BLEND_POINTS] = {};
if (point_lower == -1 && point_higher != -1) {
// we are on the left side, no other point to the left
// we just play the next point.
diff --git a/scene/gui/code_edit.cpp b/scene/gui/code_edit.cpp
index 60fe0c32b6..0c9b6ffeaf 100644
--- a/scene/gui/code_edit.cpp
+++ b/scene/gui/code_edit.cpp
@@ -2002,7 +2002,7 @@ void CodeEdit::confirm_code_completion(bool p_replace) {
return;
}
- char32_t caret_last_completion_char;
+ char32_t caret_last_completion_char = 0;
begin_complex_operation();
Vector<int> caret_edit_order = get_caret_index_edit_order();
for (const int &i : caret_edit_order) {
diff --git a/scene/gui/text_edit.cpp b/scene/gui/text_edit.cpp
index 02dbb086ee..0839e4066d 100644
--- a/scene/gui/text_edit.cpp
+++ b/scene/gui/text_edit.cpp
@@ -526,7 +526,7 @@ void TextEdit::_notification(int p_what) {
theme_cache.style_normal->draw(ci, Rect2(Point2(), size));
if (!editable) {
theme_cache.style_readonly->draw(ci, Rect2(Point2(), size));
- draw_caret = false;
+ draw_caret = is_drawing_caret_when_editable_disabled();
}
if (has_focus()) {
theme_cache.style_focus->draw(ci, Rect2(Point2(), size));
@@ -4489,6 +4489,18 @@ void TextEdit::set_caret_blink_interval(const float p_interval) {
caret_blink_timer->set_wait_time(p_interval);
}
+void TextEdit::set_draw_caret_when_editable_disabled(bool p_enable) {
+ if (draw_caret_when_editable_disabled == p_enable) {
+ return;
+ }
+ draw_caret_when_editable_disabled = p_enable;
+ queue_redraw();
+}
+
+bool TextEdit::is_drawing_caret_when_editable_disabled() const {
+ return draw_caret_when_editable_disabled;
+}
+
void TextEdit::set_move_caret_on_right_click_enabled(const bool p_enabled) {
move_caret_on_right_click = p_enabled;
}
@@ -6195,6 +6207,9 @@ void TextEdit::_bind_methods() {
ClassDB::bind_method(D_METHOD("set_caret_blink_interval", "interval"), &TextEdit::set_caret_blink_interval);
ClassDB::bind_method(D_METHOD("get_caret_blink_interval"), &TextEdit::get_caret_blink_interval);
+ ClassDB::bind_method(D_METHOD("set_draw_caret_when_editable_disabled", "enable"), &TextEdit::set_draw_caret_when_editable_disabled);
+ ClassDB::bind_method(D_METHOD("is_drawing_caret_when_editable_disabled"), &TextEdit::is_drawing_caret_when_editable_disabled);
+
ClassDB::bind_method(D_METHOD("set_move_caret_on_right_click_enabled", "enable"), &TextEdit::set_move_caret_on_right_click_enabled);
ClassDB::bind_method(D_METHOD("is_move_caret_on_right_click_enabled"), &TextEdit::is_move_caret_on_right_click_enabled);
@@ -6436,6 +6451,7 @@ void TextEdit::_bind_methods() {
ADD_PROPERTY(PropertyInfo(Variant::INT, "caret_type", PROPERTY_HINT_ENUM, "Line,Block"), "set_caret_type", "get_caret_type");
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "caret_blink"), "set_caret_blink_enabled", "is_caret_blink_enabled");
ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "caret_blink_interval", PROPERTY_HINT_RANGE, "0.1,10,0.01,suffix:s"), "set_caret_blink_interval", "get_caret_blink_interval");
+ ADD_PROPERTY(PropertyInfo(Variant::BOOL, "caret_draw_when_editable_disabled"), "set_draw_caret_when_editable_disabled", "is_drawing_caret_when_editable_disabled");
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "caret_move_on_right_click"), "set_move_caret_on_right_click_enabled", "is_move_caret_on_right_click_enabled");
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "caret_mid_grapheme"), "set_caret_mid_grapheme_enabled", "is_caret_mid_grapheme_enabled");
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "caret_multiple"), "set_multiple_carets_enabled", "is_multiple_carets_enabled");
diff --git a/scene/gui/text_edit.h b/scene/gui/text_edit.h
index 6b710ae26f..6eed200a28 100644
--- a/scene/gui/text_edit.h
+++ b/scene/gui/text_edit.h
@@ -413,6 +413,7 @@ private:
CaretType caret_type = CaretType::CARET_TYPE_LINE;
bool draw_caret = true;
+ bool draw_caret_when_editable_disabled = false;
bool caret_blink_enabled = false;
Timer *caret_blink_timer = nullptr;
@@ -821,6 +822,9 @@ public:
void set_caret_blink_interval(const float p_interval);
float get_caret_blink_interval() const;
+ void set_draw_caret_when_editable_disabled(bool p_enable);
+ bool is_drawing_caret_when_editable_disabled() const;
+
void set_move_caret_on_right_click_enabled(const bool p_enabled);
bool is_move_caret_on_right_click_enabled() const;
diff --git a/scene/gui/tree.cpp b/scene/gui/tree.cpp
index 738f0033d8..84f0b26237 100644
--- a/scene/gui/tree.cpp
+++ b/scene/gui/tree.cpp
@@ -1644,6 +1644,13 @@ void Tree::_update_theme_item_cache() {
theme_cache.scroll_border = get_theme_constant(SNAME("scroll_border"));
theme_cache.scroll_speed = get_theme_constant(SNAME("scroll_speed"));
+ theme_cache.scrollbar_margin_top = get_theme_constant(SNAME("scrollbar_margin_top"));
+ theme_cache.scrollbar_margin_right = get_theme_constant(SNAME("scrollbar_margin_right"));
+ theme_cache.scrollbar_margin_bottom = get_theme_constant(SNAME("scrollbar_margin_bottom"));
+ theme_cache.scrollbar_margin_left = get_theme_constant(SNAME("scrollbar_margin_left"));
+ theme_cache.scrollbar_h_separation = get_theme_constant(SNAME("scrollbar_h_separation"));
+ theme_cache.scrollbar_v_separation = get_theme_constant(SNAME("scrollbar_v_separation"));
+
theme_cache.title_button = get_theme_stylebox(SNAME("title_button_normal"));
theme_cache.title_button_pressed = get_theme_stylebox(SNAME("title_button_pressed"));
theme_cache.title_button_hover = get_theme_stylebox(SNAME("title_button_hover"));
@@ -2018,9 +2025,8 @@ int Tree::draw_item(const Point2i &p_pos, const Point2 &p_draw_ofs, const Size2
if (i == 0) {
if (p_item->cells[0].selected && select_mode == SELECT_ROW) {
- Rect2i row_rect = Rect2i(Point2i(theme_cache.panel_style->get_margin(SIDE_LEFT), item_rect.position.y), Size2i(get_size().width - theme_cache.panel_style->get_minimum_size().width, item_rect.size.y));
- //Rect2 r = Rect2i(row_rect.pos,row_rect.size);
- //r.grow(cache.selected->get_margin(SIDE_LEFT));
+ const Rect2 content_rect = _get_content_rect();
+ Rect2i row_rect = Rect2i(Point2i(content_rect.position.x, item_rect.position.y), Size2i(content_rect.size.x, item_rect.size.y));
if (rtl) {
row_rect.position.x = get_size().width - row_rect.position.x - row_rect.size.x;
}
@@ -3188,6 +3194,42 @@ bool Tree::_scroll(bool p_horizontal, float p_pages) {
return scroll->get_value() != prev_value;
}
+Rect2 Tree::_get_scrollbar_layout_rect() const {
+ const Size2 control_size = get_size();
+ const Ref<StyleBox> background = theme_cache.panel_style;
+
+ // This is the background stylebox's content rect.
+ const real_t width = control_size.x - background->get_margin(SIDE_LEFT) - background->get_margin(SIDE_RIGHT);
+ const real_t height = control_size.y - background->get_margin(SIDE_TOP) - background->get_margin(SIDE_BOTTOM);
+ const Rect2 content_rect = Rect2(background->get_offset(), Size2(width, height));
+
+ // Use the stylebox's margins by default. Can be overridden by `scrollbar_margin_*`.
+ const real_t top = theme_cache.scrollbar_margin_top < 0 ? content_rect.get_position().y : theme_cache.scrollbar_margin_top;
+ const real_t right = theme_cache.scrollbar_margin_right < 0 ? content_rect.get_end().x : (control_size.x - theme_cache.scrollbar_margin_right);
+ const real_t bottom = theme_cache.scrollbar_margin_bottom < 0 ? content_rect.get_end().y : (control_size.y - theme_cache.scrollbar_margin_bottom);
+ const real_t left = theme_cache.scrollbar_margin_left < 0 ? content_rect.get_position().x : theme_cache.scrollbar_margin_left;
+
+ return Rect2(left, top, right - left, bottom - top);
+}
+
+Rect2 Tree::_get_content_rect() const {
+ const Size2 control_size = get_size();
+ const Ref<StyleBox> background = theme_cache.panel_style;
+
+ // This is the background stylebox's content rect.
+ const real_t width = control_size.x - background->get_margin(SIDE_LEFT) - background->get_margin(SIDE_RIGHT);
+ const real_t height = control_size.y - background->get_margin(SIDE_TOP) - background->get_margin(SIDE_BOTTOM);
+ const Rect2 content_rect = Rect2(background->get_offset(), Size2(width, height));
+
+ // Scrollbars won't affect Tree's content rect if they're not visible or placed inside the stylebox margin area.
+ const real_t v_size = v_scroll->is_visible() ? (v_scroll->get_combined_minimum_size().x + theme_cache.scrollbar_h_separation) : 0;
+ const real_t h_size = h_scroll->is_visible() ? (h_scroll->get_combined_minimum_size().y + theme_cache.scrollbar_v_separation) : 0;
+ const Point2 scroll_begin = _get_scrollbar_layout_rect().get_end() - Vector2(v_size, h_size);
+ const Size2 offset = (content_rect.get_end() - scroll_begin).max(Vector2(0, 0));
+
+ return content_rect.grow_individual(0, 0, -offset.x, -offset.y);
+}
+
void Tree::gui_input(const Ref<InputEvent> &p_event) {
ERR_FAIL_COND(p_event.is_null());
@@ -3837,7 +3879,7 @@ bool Tree::is_editing() {
}
Size2 Tree::get_internal_min_size() const {
- Size2i size = theme_cache.panel_style->get_offset();
+ Size2i size;
if (root) {
size.height += get_item_height(root);
}
@@ -3849,16 +3891,17 @@ Size2 Tree::get_internal_min_size() const {
}
void Tree::update_scrollbars() {
- const Size2 size = get_size();
+ const Size2 control_size = get_size();
+ const Ref<StyleBox> background = theme_cache.panel_style;
+
+ // This is the background stylebox's content rect.
+ const real_t width = control_size.x - background->get_margin(SIDE_LEFT) - background->get_margin(SIDE_RIGHT);
+ const real_t height = control_size.y - background->get_margin(SIDE_TOP) - background->get_margin(SIDE_BOTTOM);
+ const Rect2 content_rect = Rect2(background->get_offset(), Size2(width, height));
+
const Size2 hmin = h_scroll->get_combined_minimum_size();
const Size2 vmin = v_scroll->get_combined_minimum_size();
- const Rect2 content_rect = Rect2(theme_cache.panel_style->get_offset(), size - theme_cache.panel_style->get_minimum_size());
- v_scroll->set_begin(content_rect.get_position() + Vector2(content_rect.get_size().x - vmin.width, 0));
- v_scroll->set_end(content_rect.get_end() - Vector2(0, hmin.height));
- h_scroll->set_begin(content_rect.get_position() + Vector2(0, content_rect.get_size().y - hmin.height));
- h_scroll->set_end(content_rect.get_end() - Vector2(vmin.width, 0));
-
const Size2 internal_min_size = get_internal_min_size();
const int title_button_height = _get_title_button_height();
@@ -3896,6 +3939,12 @@ void Tree::update_scrollbars() {
h_scroll->hide();
theme_cache.offset.x = 0;
}
+
+ const Rect2 scroll_rect = _get_scrollbar_layout_rect();
+ v_scroll->set_begin(scroll_rect.get_position() + Vector2(scroll_rect.get_size().x - vmin.width, 0));
+ v_scroll->set_end(scroll_rect.get_end() - Vector2(0, display_hscroll ? hmin.height : 0));
+ h_scroll->set_begin(scroll_rect.get_position() + Vector2(0, scroll_rect.get_size().y - hmin.height));
+ h_scroll->set_end(scroll_rect.get_end() - Vector2(display_vscroll ? vmin.width : 0, 0));
}
int Tree::_get_title_button_height() const {
@@ -4010,13 +4059,10 @@ void Tree::_notification(int p_what) {
RID ci = get_canvas_item();
Ref<StyleBox> bg = theme_cache.panel_style;
+ const Rect2 content_rect = _get_content_rect();
- Point2 draw_ofs;
- draw_ofs += bg->get_offset();
- Size2 draw_size = get_size() - bg->get_minimum_size();
- if (v_scroll->is_visible()) {
- draw_size.width -= v_scroll->get_minimum_size().width;
- }
+ Point2 draw_ofs = content_rect.position;
+ Size2 draw_size = content_rect.size;
bg->draw(ci, Rect2(Point2(), get_size()));
@@ -4475,18 +4521,7 @@ int Tree::get_column_width(int p_column) const {
int column_width = get_column_minimum_width(p_column);
if (columns[p_column].expand) {
- int expand_area = get_size().width;
-
- Ref<StyleBox> bg = theme_cache.panel_style;
-
- if (bg.is_valid()) {
- expand_area -= bg->get_margin(SIDE_LEFT) + bg->get_margin(SIDE_RIGHT);
- }
-
- if (v_scroll->is_visible_in_tree()) {
- expand_area -= v_scroll->get_combined_minimum_size().width;
- }
-
+ int expand_area = _get_content_rect().size.width;
int expanding_total = 0;
for (int i = 0; i < columns.size(); i++) {
@@ -4586,7 +4621,7 @@ void Tree::ensure_cursor_is_visible() {
}
// Note: Code below similar to Tree::scroll_to_item(), in case of bug fix both.
- const Size2 area_size = get_size() - theme_cache.panel_style->get_minimum_size();
+ const Size2 area_size = _get_content_rect().size;
int y_offset = get_item_offset(selected_item);
if (y_offset != -1) {
@@ -4595,9 +4630,6 @@ void Tree::ensure_cursor_is_visible() {
const int cell_h = compute_item_height(selected_item) + theme_cache.v_separation;
int screen_h = area_size.height - tbh;
- if (h_scroll->is_visible()) {
- screen_h -= h_scroll->get_combined_minimum_size().height;
- }
if (cell_h > screen_h) { // Screen size is too small, maybe it was not resized yet.
v_scroll->set_value(y_offset);
@@ -4615,7 +4647,7 @@ void Tree::ensure_cursor_is_visible() {
}
const int cell_w = get_column_width(selected_col);
- const int screen_w = area_size.width - v_scroll->get_combined_minimum_size().width;
+ const int screen_w = area_size.width;
if (cell_w > screen_w) {
h_scroll->set_value(x_offset);
@@ -4773,7 +4805,7 @@ void Tree::scroll_to_item(TreeItem *p_item, bool p_center_on_item) {
update_scrollbars();
// Note: Code below similar to Tree::ensure_cursor_is_visible(), in case of bug fix both.
- const Size2 area_size = get_size() - theme_cache.panel_style->get_minimum_size();
+ const Size2 area_size = _get_content_rect().size;
int y_offset = get_item_offset(p_item);
if (y_offset != -1) {
@@ -4782,9 +4814,6 @@ void Tree::scroll_to_item(TreeItem *p_item, bool p_center_on_item) {
const int cell_h = compute_item_height(p_item) + theme_cache.v_separation;
int screen_h = area_size.height - tbh;
- if (h_scroll->is_visible()) {
- screen_h -= h_scroll->get_combined_minimum_size().height;
- }
if (p_center_on_item) {
v_scroll->set_value(y_offset - (screen_h - cell_h) / 2.0f);
diff --git a/scene/gui/tree.h b/scene/gui/tree.h
index f125c4a781..42fc719f46 100644
--- a/scene/gui/tree.h
+++ b/scene/gui/tree.h
@@ -549,6 +549,13 @@ private:
int scroll_border = 0;
int scroll_speed = 0;
+
+ int scrollbar_margin_top = -1;
+ int scrollbar_margin_right = -1;
+ int scrollbar_margin_bottom = -1;
+ int scrollbar_margin_left = -1;
+ int scrollbar_h_separation = 0;
+ int scrollbar_v_separation = 0;
} theme_cache;
struct Cache {
@@ -635,6 +642,9 @@ private:
bool _scroll(bool p_horizontal, float p_pages);
+ Rect2 _get_scrollbar_layout_rect() const;
+ Rect2 _get_content_rect() const; // Considering the background stylebox and scrollbars.
+
protected:
virtual void _update_theme_item_cache() override;
diff --git a/scene/main/node.cpp b/scene/main/node.cpp
index 352c8eb77f..b27123da06 100644
--- a/scene/main/node.cpp
+++ b/scene/main/node.cpp
@@ -166,7 +166,7 @@ void Node::_notification(int p_notification) {
// kill children as cleanly as possible
while (data.children.size()) {
- Node *child = data.children[data.children.size() - 1]; //begin from the end because its faster and more consistent with creation
+ Node *child = data.children.last()->value; // begin from the end because its faster and more consistent with creation
memdelete(child);
}
} break;
@@ -176,9 +176,10 @@ void Node::_notification(int p_notification) {
void Node::_propagate_ready() {
data.ready_notified = true;
data.blocked++;
- for (int i = 0; i < data.children.size(); i++) {
- data.children[i]->_propagate_ready();
+ for (KeyValue<StringName, Node *> &K : data.children) {
+ K.value->_propagate_ready();
}
+
data.blocked--;
notification(NOTIFICATION_POST_ENTER_TREE);
@@ -228,9 +229,9 @@ void Node::_propagate_enter_tree() {
data.blocked++;
//block while adding children
- for (int i = 0; i < data.children.size(); i++) {
- if (!data.children[i]->is_inside_tree()) { // could have been added in enter_tree
- data.children[i]->_propagate_enter_tree();
+ for (KeyValue<StringName, Node *> &K : data.children) {
+ if (!K.value->is_inside_tree()) { // could have been added in enter_tree
+ K.value->_propagate_enter_tree();
}
}
@@ -267,9 +268,11 @@ void Node::_propagate_after_exit_tree() {
}
data.blocked++;
- for (int i = data.children.size() - 1; i >= 0; i--) {
- data.children[i]->_propagate_after_exit_tree();
+
+ for (HashMap<StringName, Node *>::Iterator I = data.children.last(); I; --I) {
+ I->value->_propagate_after_exit_tree();
}
+
data.blocked--;
emit_signal(SceneStringNames::get_singleton()->tree_exited);
@@ -286,8 +289,8 @@ void Node::_propagate_exit_tree() {
#endif
data.blocked++;
- for (int i = data.children.size() - 1; i >= 0; i--) {
- data.children[i]->_propagate_exit_tree();
+ for (HashMap<StringName, Node *>::Iterator I = data.children.last(); I; --I) {
+ I->value->_propagate_exit_tree();
}
data.blocked--;
@@ -329,25 +332,26 @@ void Node::move_child(Node *p_child, int p_index) {
ERR_FAIL_NULL(p_child);
ERR_FAIL_COND_MSG(p_child->data.parent != this, "Child is not a child of this node.");
+ _update_children_cache();
// We need to check whether node is internal and move it only in the relevant node range.
- if (p_child->_is_internal_front()) {
+ if (p_child->data.internal_mode == INTERNAL_MODE_FRONT) {
if (p_index < 0) {
- p_index += data.internal_children_front;
+ p_index += data.internal_children_front_count_cache;
}
- ERR_FAIL_INDEX_MSG(p_index, data.internal_children_front, vformat("Invalid new child index: %d. Child is internal.", p_index));
+ ERR_FAIL_INDEX_MSG(p_index, data.internal_children_front_count_cache, vformat("Invalid new child index: %d. Child is internal.", p_index));
_move_child(p_child, p_index);
- } else if (p_child->_is_internal_back()) {
+ } else if (p_child->data.internal_mode == INTERNAL_MODE_BACK) {
if (p_index < 0) {
- p_index += data.internal_children_back;
+ p_index += data.internal_children_back_count_cache;
}
- ERR_FAIL_INDEX_MSG(p_index, data.internal_children_back, vformat("Invalid new child index: %d. Child is internal.", p_index));
- _move_child(p_child, data.children.size() - data.internal_children_back + p_index);
+ ERR_FAIL_INDEX_MSG(p_index, data.internal_children_back_count_cache, vformat("Invalid new child index: %d. Child is internal.", p_index));
+ _move_child(p_child, (int)data.children_cache.size() - data.internal_children_back_count_cache + p_index);
} else {
if (p_index < 0) {
p_index += get_child_count(false);
}
- ERR_FAIL_INDEX_MSG(p_index, data.children.size() + 1 - data.internal_children_front - data.internal_children_back, vformat("Invalid new child index: %d.", p_index));
- _move_child(p_child, p_index + data.internal_children_front);
+ ERR_FAIL_INDEX_MSG(p_index, (int)data.children_cache.size() + 1 - data.internal_children_front_count_cache - data.internal_children_back_count_cache, vformat("Invalid new child index: %d.", p_index));
+ _move_child(p_child, p_index + data.internal_children_front_count_cache);
}
}
@@ -357,30 +361,32 @@ void Node::_move_child(Node *p_child, int p_index, bool p_ignore_end) {
// Specifying one place beyond the end
// means the same as moving to the last index
if (!p_ignore_end) { // p_ignore_end is a little hack to make back internal children work properly.
- if (p_child->_is_internal_front()) {
- if (p_index == data.internal_children_front) {
+ if (p_child->data.internal_mode == INTERNAL_MODE_FRONT) {
+ if (p_index == data.internal_children_front_count_cache) {
p_index--;
}
- } else if (p_child->_is_internal_back()) {
- if (p_index == data.children.size()) {
+ } else if (p_child->data.internal_mode == INTERNAL_MODE_BACK) {
+ if (p_index == (int)data.children_cache.size()) {
p_index--;
}
} else {
- if (p_index == data.children.size() - data.internal_children_back) {
+ if (p_index == (int)data.children_cache.size() - data.internal_children_back_count_cache) {
p_index--;
}
}
}
- if (p_child->data.index == p_index) {
+ int child_index = p_child->get_index();
+
+ if (child_index == p_index) {
return; //do nothing
}
- int motion_from = MIN(p_index, p_child->data.index);
- int motion_to = MAX(p_index, p_child->data.index);
+ int motion_from = MIN(p_index, child_index);
+ int motion_to = MAX(p_index, child_index);
- data.children.remove_at(p_child->data.index);
- data.children.insert(p_index, p_child);
+ data.children_cache.remove_at(child_index);
+ data.children_cache.insert(p_index, p_child);
if (data.tree) {
data.tree->tree_changed();
@@ -389,13 +395,18 @@ void Node::_move_child(Node *p_child, int p_index, bool p_ignore_end) {
data.blocked++;
//new pos first
for (int i = motion_from; i <= motion_to; i++) {
- data.children[i]->data.index = i;
+ if (data.children_cache[i]->data.internal_mode == INTERNAL_MODE_DISABLED) {
+ data.children_cache[i]->data.index = i - data.internal_children_front_count_cache;
+ } else if (data.children_cache[i]->data.internal_mode == INTERNAL_MODE_BACK) {
+ data.children_cache[i]->data.index = i - data.internal_children_front_count_cache - data.external_children_count_cache;
+ } else {
+ data.children_cache[i]->data.index = i;
+ }
}
// notification second
move_child_notify(p_child);
notification(NOTIFICATION_CHILD_ORDER_CHANGED);
emit_signal(SNAME("child_order_changed"));
-
p_child->_propagate_groups_dirty();
data.blocked--;
@@ -408,8 +419,8 @@ void Node::_propagate_groups_dirty() {
}
}
- for (int i = 0; i < data.children.size(); i++) {
- data.children[i]->_propagate_groups_dirty();
+ for (KeyValue<StringName, Node *> &K : data.children) {
+ K.value->_propagate_groups_dirty();
}
}
@@ -529,8 +540,8 @@ void Node::_propagate_pause_notification(bool p_enable) {
notification(NOTIFICATION_UNPAUSED);
}
- for (int i = 0; i < data.children.size(); i++) {
- data.children[i]->_propagate_pause_notification(p_enable);
+ for (KeyValue<StringName, Node *> &K : data.children) {
+ K.value->_propagate_pause_notification(p_enable);
}
}
@@ -549,8 +560,8 @@ void Node::_propagate_process_owner(Node *p_owner, int p_pause_notification, int
notification(p_enabled_notification);
}
- for (int i = 0; i < data.children.size(); i++) {
- Node *c = data.children[i];
+ for (KeyValue<StringName, Node *> &K : data.children) {
+ Node *c = K.value;
if (c->data.process_mode == PROCESS_MODE_INHERIT) {
c->_propagate_process_owner(p_owner, p_pause_notification, p_enabled_notification);
}
@@ -561,8 +572,8 @@ void Node::set_multiplayer_authority(int p_peer_id, bool p_recursive) {
data.multiplayer_authority = p_peer_id;
if (p_recursive) {
- for (int i = 0; i < data.children.size(); i++) {
- data.children[i]->set_multiplayer_authority(p_peer_id, true);
+ for (KeyValue<StringName, Node *> &K : data.children) {
+ K.value->set_multiplayer_authority(p_peer_id, true);
}
}
}
@@ -912,10 +923,13 @@ void Node::set_name(const String &p_name) {
if (data.unique_name_in_owner && data.owner) {
_release_unique_name_in_owner();
}
+ String old_name = data.name;
data.name = name;
if (data.parent) {
+ data.parent->data.children.erase(old_name);
data.parent->_validate_child_name(this, true);
+ data.parent->data.children.insert(data.name, this);
}
if (data.unique_name_in_owner && data.owner) {
@@ -977,25 +991,35 @@ void Node::_validate_child_name(Node *p_child, bool p_force_human_readable) {
//new unique name must be assigned
unique = false;
} else {
- //check if exists
- Node **children = data.children.ptrw();
- int cc = data.children.size();
-
- for (int i = 0; i < cc; i++) {
- if (children[i] == p_child) {
- continue;
- }
- if (children[i]->data.name == p_child->data.name) {
- unique = false;
- break;
- }
- }
+ const Node *const *existing = data.children.getptr(p_child->data.name);
+ unique = !existing || *existing == p_child;
}
if (!unique) {
ERR_FAIL_COND(!node_hrcr_count.ref());
- String name = "@" + String(p_child->get_name()) + "@" + itos(node_hrcr_count.get());
- p_child->data.name = name;
+ // Optimized version of the code below:
+ // String name = "@" + String(p_child->get_name()) + "@" + itos(node_hrcr_count.get());
+ uint32_t c = node_hrcr_count.get();
+ String cn = p_child->get_class_name().operator String();
+ const char32_t *cn_ptr = cn.ptr();
+ uint32_t cn_length = cn.length();
+ uint32_t c_chars = String::num_characters(c);
+ uint32_t len = 2 + cn_length + c_chars;
+ char32_t *str = (char32_t *)alloca(sizeof(char32_t) * (len + 1));
+ uint32_t idx = 0;
+ str[idx++] = '@';
+ for (uint32_t i = 0; i < cn_length; i++) {
+ str[idx++] = cn_ptr[i];
+ }
+ str[idx++] = '@';
+ idx += c_chars;
+ ERR_FAIL_COND(idx != len);
+ str[idx] = 0;
+ while (c) {
+ str[--idx] = '0' + (c % 10);
+ c /= 10;
+ }
+ p_child->data.name = String(str);
}
}
}
@@ -1032,25 +1056,9 @@ void Node::_generate_serial_child_name(const Node *p_child, StringName &name) co
name = p_child->get_class();
}
- //quickly test if proposed name exists
- int cc = data.children.size(); //children count
- const Node *const *children_ptr = data.children.ptr();
-
- {
- bool exists = false;
-
- for (int i = 0; i < cc; i++) {
- if (children_ptr[i] == p_child) { //exclude self in renaming if it's already a child
- continue;
- }
- if (children_ptr[i]->data.name == name) {
- exists = true;
- }
- }
-
- if (!exists) {
- return; //if it does not exist, it does not need validation
- }
+ const Node *const *existing = data.children.getptr(name);
+ if (!existing || *existing == p_child) { // Unused, or is current node.
+ return;
}
// Extract trailing number
@@ -1077,16 +1085,9 @@ void Node::_generate_serial_child_name(const Node *p_child, StringName &name) co
for (;;) {
StringName attempt = name_string + nums;
- bool exists = false;
- for (int i = 0; i < cc; i++) {
- if (children_ptr[i] == p_child) {
- continue;
- }
- if (children_ptr[i]->data.name == attempt) {
- exists = true;
- }
- }
+ existing = data.children.getptr(attempt);
+ bool exists = existing != nullptr && *existing != p_child;
if (!exists) {
name = attempt;
@@ -1103,17 +1104,34 @@ void Node::_generate_serial_child_name(const Node *p_child, StringName &name) co
}
}
-void Node::_add_child_nocheck(Node *p_child, const StringName &p_name) {
+void Node::_add_child_nocheck(Node *p_child, const StringName &p_name, InternalMode p_internal_mode) {
//add a child node quickly, without name validation
p_child->data.name = p_name;
- p_child->data.index = data.children.size();
- data.children.push_back(p_child);
+ data.children.insert(p_name, p_child);
+
+ p_child->data.internal_mode = p_internal_mode;
+ switch (p_internal_mode) {
+ case INTERNAL_MODE_FRONT: {
+ p_child->data.index = data.internal_children_front_count_cache++;
+ } break;
+ case INTERNAL_MODE_BACK: {
+ p_child->data.index = data.internal_children_back_count_cache++;
+ } break;
+ case INTERNAL_MODE_DISABLED: {
+ p_child->data.index = data.external_children_count_cache++;
+ } break;
+ }
+
p_child->data.parent = this;
- if (data.internal_children_back > 0) {
- _move_child(p_child, data.children.size() - data.internal_children_back - 1);
+ if (!data.children_cache_dirty && p_internal_mode == INTERNAL_MODE_DISABLED && data.internal_children_back_count_cache == 0) {
+ // Special case, also add to the cached children array since its cheap.
+ data.children_cache.push_back(p_child);
+ } else {
+ data.children_cache_dirty = true;
}
+
p_child->notification(NOTIFICATION_PARENTED);
if (data.tree) {
@@ -1138,17 +1156,7 @@ void Node::add_child(Node *p_child, bool p_force_readable_name, InternalMode p_i
ERR_FAIL_COND_MSG(data.blocked > 0, "Parent node is busy setting up children, `add_child()` failed. Consider using `add_child.call_deferred(child)` instead.");
_validate_child_name(p_child, p_force_readable_name);
- _add_child_nocheck(p_child, p_child->data.name);
-
- if (p_internal == INTERNAL_MODE_FRONT) {
- _move_child(p_child, data.internal_children_front);
- data.internal_children_front++;
- } else if (p_internal == INTERNAL_MODE_BACK) {
- if (data.internal_children_back > 0) {
- _move_child(p_child, data.children.size() - 1, true);
- }
- data.internal_children_back++;
- }
+ _add_child_nocheck(p_child, p_child->data.name, p_internal);
}
void Node::add_sibling(Node *p_sibling, bool p_force_readable_name) {
@@ -1157,49 +1165,25 @@ void Node::add_sibling(Node *p_sibling, bool p_force_readable_name) {
ERR_FAIL_COND_MSG(p_sibling == this, vformat("Can't add sibling '%s' to itself.", p_sibling->get_name())); // adding to itself!
ERR_FAIL_COND_MSG(data.blocked > 0, "Parent node is busy setting up children, `add_sibling()` failed. Consider using `add_sibling.call_deferred(sibling)` instead.");
- InternalMode internal = INTERNAL_MODE_DISABLED;
- if (_is_internal_front()) { // The sibling will have the same internal status.
- internal = INTERNAL_MODE_FRONT;
- } else if (_is_internal_back()) {
- internal = INTERNAL_MODE_BACK;
- }
-
- data.parent->add_child(p_sibling, p_force_readable_name, internal);
+ data.parent->add_child(p_sibling, p_force_readable_name, data.internal_mode);
+ data.parent->_update_children_cache();
data.parent->_move_child(p_sibling, get_index() + 1);
}
void Node::remove_child(Node *p_child) {
ERR_FAIL_NULL(p_child);
ERR_FAIL_COND_MSG(data.blocked > 0, "Parent node is busy adding/removing children, `remove_child()` can't be called at this time. Consider using `remove_child.call_deferred(child)` instead.");
-
- int child_count = data.children.size();
- Node **children = data.children.ptrw();
- int idx = -1;
-
- if (p_child->data.index >= 0 && p_child->data.index < child_count) {
- if (children[p_child->data.index] == p_child) {
- idx = p_child->data.index;
- }
- }
-
- if (idx == -1) { //maybe removed while unparenting or something and index was not updated, so just in case the above fails, try this.
- for (int i = 0; i < child_count; i++) {
- if (children[i] == p_child) {
- idx = i;
- break;
- }
- }
- }
-
- ERR_FAIL_COND_MSG(idx == -1, vformat("Cannot remove child node '%s' as it is not a child of this node.", p_child->get_name()));
- //ERR_FAIL_COND( p_child->data.blocked > 0 );
-
- // If internal child, update the counter.
- if (p_child->_is_internal_front()) {
- data.internal_children_front--;
- } else if (p_child->_is_internal_back()) {
- data.internal_children_back--;
- }
+ ERR_FAIL_COND(p_child->data.parent != this);
+
+ /**
+ * Do not change the data.internal_children*cache counters here.
+ * Because if nodes are re-added, the indices can remain
+ * greater-than-everything indices and children added remain
+ * properly ordered.
+ *
+ * All children indices and counters will be updated next time the
+ * cache is re-generated.
+ */
data.blocked++;
p_child->_set_tree(nullptr);
@@ -1207,17 +1191,12 @@ void Node::remove_child(Node *p_child) {
remove_child_notify(p_child);
p_child->notification(NOTIFICATION_UNPARENTED);
- data.blocked--;
- data.children.remove_at(idx);
-
- //update pointer and size
- child_count = data.children.size();
- children = data.children.ptrw();
+ data.blocked--;
- for (int i = idx; i < child_count; i++) {
- children[i]->data.index = i;
- }
+ data.children_cache_dirty = true;
+ bool success = data.children.erase(p_child->data.name);
+ ERR_FAIL_COND_MSG(!success, "Children name does not match parent name in hashtable, this is a bug.");
notification(NOTIFICATION_CHILD_ORDER_CHANGED);
emit_signal(SNAME("child_order_changed"));
@@ -1230,42 +1209,73 @@ void Node::remove_child(Node *p_child) {
}
}
+void Node::_update_children_cache_impl() const {
+ // Assign children
+ data.children_cache.resize(data.children.size());
+ int idx = 0;
+ for (const KeyValue<StringName, Node *> &K : data.children) {
+ data.children_cache[idx] = K.value;
+ idx++;
+ }
+ // Sort them
+ data.children_cache.sort_custom<ComparatorByIndex>();
+ // Update indices
+ data.external_children_count_cache = 0;
+ data.internal_children_back_count_cache = 0;
+ data.internal_children_front_count_cache = 0;
+
+ for (uint32_t i = 0; i < data.children_cache.size(); i++) {
+ switch (data.children_cache[i]->data.internal_mode) {
+ case INTERNAL_MODE_DISABLED: {
+ data.children_cache[i]->data.index = data.external_children_count_cache++;
+ } break;
+ case INTERNAL_MODE_FRONT: {
+ data.children_cache[i]->data.index = data.internal_children_front_count_cache++;
+ } break;
+ case INTERNAL_MODE_BACK: {
+ data.children_cache[i]->data.index = data.internal_children_back_count_cache++;
+ } break;
+ }
+ }
+ data.children_cache_dirty = false;
+}
+
int Node::get_child_count(bool p_include_internal) const {
+ _update_children_cache();
+
if (p_include_internal) {
- return data.children.size();
+ return data.children_cache.size();
} else {
- return data.children.size() - data.internal_children_front - data.internal_children_back;
+ return data.children_cache.size() - data.internal_children_front_count_cache - data.internal_children_back_count_cache;
}
}
Node *Node::get_child(int p_index, bool p_include_internal) const {
+ _update_children_cache();
+
if (p_include_internal) {
if (p_index < 0) {
- p_index += data.children.size();
+ p_index += data.children_cache.size();
}
- ERR_FAIL_INDEX_V(p_index, data.children.size(), nullptr);
- return data.children[p_index];
+ ERR_FAIL_INDEX_V(p_index, (int)data.children_cache.size(), nullptr);
+ return data.children_cache[p_index];
} else {
if (p_index < 0) {
- p_index += data.children.size() - data.internal_children_front - data.internal_children_back;
+ p_index += (int)data.children_cache.size() - data.internal_children_front_count_cache - data.internal_children_back_count_cache;
}
- ERR_FAIL_INDEX_V(p_index, data.children.size() - data.internal_children_front - data.internal_children_back, nullptr);
- p_index += data.internal_children_front;
- return data.children[p_index];
+ ERR_FAIL_INDEX_V(p_index, (int)data.children_cache.size() - data.internal_children_front_count_cache - data.internal_children_back_count_cache, nullptr);
+ p_index += data.internal_children_front_count_cache;
+ return data.children_cache[p_index];
}
}
Node *Node::_get_child_by_name(const StringName &p_name) const {
- int cc = data.children.size();
- Node *const *cd = data.children.ptr();
-
- for (int i = 0; i < cc; i++) {
- if (cd[i]->data.name == p_name) {
- return cd[i];
- }
+ const Node *const *node = data.children.getptr(p_name);
+ if (node) {
+ return const_cast<Node *>(*node);
+ } else {
+ return nullptr;
}
-
- return nullptr;
}
Node *Node::get_node_or_null(const NodePath &p_path) const {
@@ -1327,18 +1337,12 @@ Node *Node::get_node_or_null(const NodePath &p_path) const {
} else {
next = nullptr;
-
- for (int j = 0; j < current->data.children.size(); j++) {
- Node *child = current->data.children[j];
-
- if (child->data.name == name) {
- next = child;
- break;
- }
- }
- if (next == nullptr) {
+ const Node *const *node = current->data.children.getptr(name);
+ if (node) {
+ next = const_cast<Node *>(*node);
+ } else {
return nullptr;
- };
+ }
}
current = next;
}
@@ -1381,9 +1385,9 @@ bool Node::has_node(const NodePath &p_path) const {
// Can be recursive or not, and limited to owned nodes.
Node *Node::find_child(const String &p_pattern, bool p_recursive, bool p_owned) const {
ERR_FAIL_COND_V(p_pattern.is_empty(), nullptr);
-
- Node *const *cptr = data.children.ptr();
- int ccount = data.children.size();
+ _update_children_cache();
+ Node *const *cptr = data.children_cache.ptr();
+ int ccount = data.children_cache.size();
for (int i = 0; i < ccount; i++) {
if (p_owned && !cptr[i]->data.owner) {
continue;
@@ -1410,9 +1414,9 @@ Node *Node::find_child(const String &p_pattern, bool p_recursive, bool p_owned)
TypedArray<Node> Node::find_children(const String &p_pattern, const String &p_type, bool p_recursive, bool p_owned) const {
TypedArray<Node> ret;
ERR_FAIL_COND_V(p_pattern.is_empty() && p_type.is_empty(), ret);
-
- Node *const *cptr = data.children.ptr();
- int ccount = data.children.size();
+ _update_children_cache();
+ Node *const *cptr = data.children_cache.ptr();
+ int ccount = data.children_cache.size();
for (int i = 0; i < ccount; i++) {
if (p_owned && !cptr[i]->data.owner) {
continue;
@@ -1505,6 +1509,8 @@ bool Node::is_greater_than(const Node *p_node) const {
ERR_FAIL_COND_V(data.depth < 0, false);
ERR_FAIL_COND_V(p_node->data.depth < 0, false);
+ _update_children_cache();
+
int *this_stack = (int *)alloca(sizeof(int) * data.depth);
int *that_stack = (int *)alloca(sizeof(int) * p_node->data.depth);
@@ -1513,15 +1519,16 @@ bool Node::is_greater_than(const Node *p_node) const {
int idx = data.depth - 1;
while (n) {
ERR_FAIL_INDEX_V(idx, data.depth, false);
- this_stack[idx--] = n->data.index;
+ this_stack[idx--] = n->get_index();
n = n->data.parent;
}
+
ERR_FAIL_COND_V(idx != -1, false);
n = p_node;
idx = p_node->data.depth - 1;
while (n) {
ERR_FAIL_INDEX_V(idx, p_node->data.depth, false);
- that_stack[idx--] = n->data.index;
+ that_stack[idx--] = n->get_index();
n = n->data.parent;
}
@@ -1555,8 +1562,8 @@ void Node::get_owned_by(Node *p_by, List<Node *> *p_owned) {
p_owned->push_back(this);
}
- for (int i = 0; i < get_child_count(); i++) {
- get_child(i)->get_owned_by(p_by, p_owned);
+ for (KeyValue<StringName, Node *> &K : data.children) {
+ K.value->get_owned_by(p_by, p_owned);
}
}
@@ -1874,9 +1881,10 @@ int Node::get_persistent_group_count() const {
void Node::_print_tree_pretty(const String &prefix, const bool last) {
String new_prefix = last ? String::utf8(" ┖╴") : String::utf8(" ┠╴");
print_line(prefix + new_prefix + String(get_name()));
- for (int i = 0; i < data.children.size(); i++) {
+ _update_children_cache();
+ for (uint32_t i = 0; i < data.children_cache.size(); i++) {
new_prefix = last ? String::utf8(" ") : String::utf8(" ┃ ");
- data.children[i]->_print_tree_pretty(prefix + new_prefix, i == data.children.size() - 1);
+ data.children_cache[i]->_print_tree_pretty(prefix + new_prefix, i == data.children_cache.size() - 1);
}
}
@@ -1890,15 +1898,17 @@ void Node::print_tree() {
void Node::_print_tree(const Node *p_node) {
print_line(String(p_node->get_path_to(this)));
- for (int i = 0; i < data.children.size(); i++) {
- data.children[i]->_print_tree(p_node);
+ _update_children_cache();
+ for (uint32_t i = 0; i < data.children_cache.size(); i++) {
+ data.children_cache[i]->_print_tree(p_node);
}
}
void Node::_propagate_reverse_notification(int p_notification) {
data.blocked++;
- for (int i = data.children.size() - 1; i >= 0; i--) {
- data.children[i]->_propagate_reverse_notification(p_notification);
+
+ for (HashMap<StringName, Node *>::Iterator I = data.children.last(); I; --I) {
+ I->value->_propagate_reverse_notification(p_notification);
}
notification(p_notification, true);
@@ -1914,8 +1924,8 @@ void Node::_propagate_deferred_notification(int p_notification, bool p_reverse)
MessageQueue::get_singleton()->push_notification(this, p_notification);
}
- for (int i = 0; i < data.children.size(); i++) {
- data.children[i]->_propagate_deferred_notification(p_notification, p_reverse);
+ for (KeyValue<StringName, Node *> &K : data.children) {
+ K.value->_propagate_deferred_notification(p_notification, p_reverse);
}
if (p_reverse) {
@@ -1929,8 +1939,8 @@ void Node::propagate_notification(int p_notification) {
data.blocked++;
notification(p_notification);
- for (int i = 0; i < data.children.size(); i++) {
- data.children[i]->propagate_notification(p_notification);
+ for (KeyValue<StringName, Node *> &K : data.children) {
+ K.value->propagate_notification(p_notification);
}
data.blocked--;
}
@@ -1942,8 +1952,8 @@ void Node::propagate_call(const StringName &p_method, const Array &p_args, const
callv(p_method, p_args);
}
- for (int i = 0; i < data.children.size(); i++) {
- data.children[i]->propagate_call(p_method, p_args, p_parent_first);
+ for (KeyValue<StringName, Node *> &K : data.children) {
+ K.value->propagate_call(p_method, p_args, p_parent_first);
}
if (!p_parent_first && has_method(p_method)) {
@@ -1959,22 +1969,12 @@ void Node::_propagate_replace_owner(Node *p_owner, Node *p_by_owner) {
}
data.blocked++;
- for (int i = 0; i < data.children.size(); i++) {
- data.children[i]->_propagate_replace_owner(p_owner, p_by_owner);
+ for (KeyValue<StringName, Node *> &K : data.children) {
+ K.value->_propagate_replace_owner(p_owner, p_by_owner);
}
data.blocked--;
}
-int Node::get_index(bool p_include_internal) const {
- // p_include_internal = false doesn't make sense if the node is internal.
- ERR_FAIL_COND_V_MSG(!p_include_internal && (_is_internal_front() || _is_internal_back()), -1, "Node is internal. Can't get index with 'include_internal' being false.");
-
- if (data.parent && !p_include_internal) {
- return data.index - data.parent->data.internal_children_front;
- }
- return data.index;
-}
-
Ref<Tween> Node::create_tween() {
ERR_FAIL_COND_V_MSG(!data.tree, nullptr, "Can't create Tween when not inside scene tree.");
Ref<Tween> tween = get_tree()->create_tween();
@@ -2481,7 +2481,7 @@ void Node::replace_by(Node *p_node, bool p_keep_groups) {
}
Node *parent = data.parent;
- int index_in_parent = data.index;
+ int index_in_parent = get_index();
if (data.parent) {
parent->remove_child(this);
@@ -2733,8 +2733,8 @@ void Node::get_argument_options(const StringName &p_function, int p_idx, List<St
void Node::clear_internal_tree_resource_paths() {
clear_internal_resource_paths();
- for (int i = 0; i < data.children.size(); i++) {
- data.children[i]->clear_internal_tree_resource_paths();
+ for (KeyValue<StringName, Node *> &K : data.children) {
+ K.value->clear_internal_tree_resource_paths();
}
}
@@ -3081,9 +3081,10 @@ Node::~Node() {
data.grouped.clear();
data.owned.clear();
data.children.clear();
+ data.children_cache.clear();
ERR_FAIL_COND(data.parent);
- ERR_FAIL_COND(data.children.size());
+ ERR_FAIL_COND(data.children_cache.size());
orphan_node_count--;
}
diff --git a/scene/main/node.h b/scene/main/node.h
index 939632770e..8b3c07b39b 100644
--- a/scene/main/node.h
+++ b/scene/main/node.h
@@ -92,6 +92,18 @@ private:
SceneTree::Group *group = nullptr;
};
+ struct ComparatorByIndex {
+ bool operator()(const Node *p_left, const Node *p_right) const {
+ static const uint32_t order[3] = { 1, 0, 2 };
+ uint32_t order_left = order[p_left->data.internal_mode];
+ uint32_t order_right = order[p_right->data.internal_mode];
+ if (order_left == order_right) {
+ return p_left->data.index < p_right->data.index;
+ }
+ return order_left < order_right;
+ }
+ };
+
// This Data struct is to avoid namespace pollution in derived classes.
struct Data {
String scene_file_path;
@@ -100,13 +112,16 @@ private:
Node *parent = nullptr;
Node *owner = nullptr;
- Vector<Node *> children;
+ HashMap<StringName, Node *> children;
+ mutable bool children_cache_dirty = true;
+ mutable LocalVector<Node *> children_cache;
HashMap<StringName, Node *> owned_unique_nodes;
bool unique_name_in_owner = false;
-
- int internal_children_front = 0;
- int internal_children_back = 0;
- int index = -1;
+ InternalMode internal_mode = INTERNAL_MODE_DISABLED;
+ mutable int internal_children_front_count_cache = 0;
+ mutable int internal_children_back_count_cache = 0;
+ mutable int external_children_count_cache = 0;
+ mutable int index = -1; // relative to front, normal or back.
int depth = -1;
int blocked = 0; // Safeguard that throws an error when attempting to modify the tree in a harmful way while being traversed.
StringName name;
@@ -187,9 +202,6 @@ private:
Error _rpc_bind(const Variant **p_args, int p_argcount, Callable::CallError &r_error);
Error _rpc_id_bind(const Variant **p_args, int p_argcount, Callable::CallError &r_error);
- _FORCE_INLINE_ bool _is_internal_front() const { return data.parent && data.index < data.parent->data.internal_children_front; }
- _FORCE_INLINE_ bool _is_internal_back() const { return data.parent && data.index >= data.parent->data.children.size() - data.parent->data.internal_children_back; }
-
friend class SceneTree;
void _set_tree(SceneTree *p_tree);
@@ -201,6 +213,14 @@ private:
void _release_unique_name_in_owner();
void _acquire_unique_name_in_owner();
+ _FORCE_INLINE_ void _update_children_cache() const {
+ if (unlikely(data.children_cache_dirty)) {
+ _update_children_cache_impl();
+ }
+ }
+
+ void _update_children_cache_impl() const;
+
protected:
void _block() { data.blocked++; }
void _unblock() { data.blocked--; }
@@ -219,7 +239,7 @@ protected:
friend class SceneState;
- void _add_child_nocheck(Node *p_child, const StringName &p_name);
+ void _add_child_nocheck(Node *p_child, const StringName &p_name, InternalMode p_internal_mode = INTERNAL_MODE_DISABLED);
void _set_owner_nocheck(Node *p_owner);
void _set_name_nocheck(const StringName &p_name);
@@ -361,7 +381,31 @@ public:
void set_unique_name_in_owner(bool p_enabled);
bool is_unique_name_in_owner() const;
- int get_index(bool p_include_internal = true) const;
+ _FORCE_INLINE_ int get_index(bool p_include_internal = true) const {
+ // p_include_internal = false doesn't make sense if the node is internal.
+ ERR_FAIL_COND_V_MSG(!p_include_internal && data.internal_mode != INTERNAL_MODE_DISABLED, -1, "Node is internal. Can't get index with 'include_internal' being false.");
+ if (!data.parent) {
+ return data.index;
+ }
+ data.parent->_update_children_cache();
+
+ if (!p_include_internal) {
+ return data.index;
+ } else {
+ switch (data.internal_mode) {
+ case INTERNAL_MODE_DISABLED: {
+ return data.parent->data.internal_children_front_count_cache + data.index;
+ } break;
+ case INTERNAL_MODE_FRONT: {
+ return data.index;
+ } break;
+ case INTERNAL_MODE_BACK: {
+ return data.parent->data.internal_children_front_count_cache + data.parent->data.external_children_count_cache + data.index;
+ } break;
+ }
+ return -1;
+ }
+ }
Ref<Tween> create_tween();
diff --git a/scene/resources/convex_polygon_shape_2d.cpp b/scene/resources/convex_polygon_shape_2d.cpp
index 7f19dd63e6..0d9e570149 100644
--- a/scene/resources/convex_polygon_shape_2d.cpp
+++ b/scene/resources/convex_polygon_shape_2d.cpp
@@ -38,11 +38,41 @@ bool ConvexPolygonShape2D::_edit_is_selected_on_click(const Point2 &p_point, dou
return Geometry2D::is_point_in_polygon(p_point, points);
}
+#ifdef DEBUG_ENABLED
+// Check if point p3 is to the left of [p1, p2] segment or on it.
+bool left_test(const Vector2 &p1, const Vector2 &p2, const Vector2 &p3) {
+ Vector2 p12 = p2 - p1;
+ Vector2 p13 = p3 - p1;
+ // If the value of the cross product is positive or zero; p3 is to the left or on the segment, respectively.
+ return p12.cross(p13) >= 0;
+}
+
+bool is_convex(const Vector<Vector2> &p_points) {
+ // Pre-condition: Polygon is in counter-clockwise order.
+ int polygon_size = p_points.size();
+ for (int i = 0; i < polygon_size && polygon_size > 3; i++) {
+ int j = (i + 1) % polygon_size;
+ int k = (j + 1) % polygon_size;
+ // If any consecutive three points fail left-test, then there is a concavity.
+ if (!left_test(p_points[i], p_points[j], p_points[k])) {
+ return false;
+ }
+ }
+
+ return true;
+}
+#endif
+
void ConvexPolygonShape2D::_update_shape() {
Vector<Vector2> final_points = points;
if (Geometry2D::is_polygon_clockwise(final_points)) { //needs to be counter clockwise
final_points.reverse();
}
+#ifdef DEBUG_ENABLED
+ if (!is_convex(final_points)) {
+ WARN_PRINT("Concave polygon is assigned to ConvexPolygonShape2D.");
+ }
+#endif
PhysicsServer2D::get_singleton()->shape_set_data(get_rid(), final_points);
emit_changed();
}
diff --git a/scene/resources/default_theme/default_theme.cpp b/scene/resources/default_theme/default_theme.cpp
index 4c7525c745..16b1ed0703 100644
--- a/scene/resources/default_theme/default_theme.cpp
+++ b/scene/resources/default_theme/default_theme.cpp
@@ -783,6 +783,12 @@ void fill_default_theme(Ref<Theme> &theme, const Ref<Font> &default_font, const
theme->set_constant("scroll_speed", "Tree", 12);
theme->set_constant("outline_size", "Tree", 0);
theme->set_constant("icon_max_width", "Tree", 0);
+ theme->set_constant("scrollbar_margin_left", "Tree", -1);
+ theme->set_constant("scrollbar_margin_top", "Tree", -1);
+ theme->set_constant("scrollbar_margin_right", "Tree", -1);
+ theme->set_constant("scrollbar_margin_bottom", "Tree", -1);
+ theme->set_constant("scrollbar_h_separation", "Tree", 4 * scale);
+ theme->set_constant("scrollbar_v_separation", "Tree", 4 * scale);
// ItemList
diff --git a/servers/physics_3d/godot_collision_solver_3d_sat.cpp b/servers/physics_3d/godot_collision_solver_3d_sat.cpp
index 2cb29b3dd0..92a4d47cd3 100644
--- a/servers/physics_3d/godot_collision_solver_3d_sat.cpp
+++ b/servers/physics_3d/godot_collision_solver_3d_sat.cpp
@@ -2065,7 +2065,7 @@ static void _collision_convex_polygon_convex_polygon(const GodotShape3D *p_a, co
int vertex_count_B = mesh_B.vertices.size();
// Precalculating this makes the transforms faster.
- Basis a_xform_normal = p_transform_b.basis.inverse().transposed();
+ Basis a_xform_normal = p_transform_a.basis.inverse().transposed();
// faces of A
for (int i = 0; i < face_count_A; i++) {
diff --git a/servers/physics_server_2d.cpp b/servers/physics_server_2d.cpp
index 4ad83ef327..fb5092e498 100644
--- a/servers/physics_server_2d.cpp
+++ b/servers/physics_server_2d.cpp
@@ -893,7 +893,7 @@ PhysicsServer2D::PhysicsServer2D() {
GLOBAL_DEF(PropertyInfo(Variant::INT, "physics/2d/solver/solver_iterations", PROPERTY_HINT_RANGE, "1,32,1,or_greater"), 16);
GLOBAL_DEF(PropertyInfo(Variant::FLOAT, "physics/2d/solver/contact_recycle_radius", PROPERTY_HINT_RANGE, "0,10,0.01,or_greater"), 1.0);
GLOBAL_DEF(PropertyInfo(Variant::FLOAT, "physics/2d/solver/contact_max_separation", PROPERTY_HINT_RANGE, "0,10,0.01,or_greater"), 1.5);
- GLOBAL_DEF(PropertyInfo(Variant::FLOAT, "physics/2d/solver/contact_max_allowed_penetration", PROPERTY_HINT_RANGE, "0,10,0.01,or_greater"), 0.3);
+ GLOBAL_DEF(PropertyInfo(Variant::FLOAT, "physics/2d/solver/contact_max_allowed_penetration", PROPERTY_HINT_RANGE, "0.01,10,0.01,or_greater"), 0.3);
GLOBAL_DEF(PropertyInfo(Variant::FLOAT, "physics/2d/solver/default_contact_bias", PROPERTY_HINT_RANGE, "0,1,0.01"), 0.8);
GLOBAL_DEF(PropertyInfo(Variant::FLOAT, "physics/2d/solver/default_constraint_bias", PROPERTY_HINT_RANGE, "0,1,0.01"), 0.2);
}
diff --git a/servers/physics_server_3d.cpp b/servers/physics_server_3d.cpp
index 9495ce2262..ee749de1c6 100644
--- a/servers/physics_server_3d.cpp
+++ b/servers/physics_server_3d.cpp
@@ -1059,9 +1059,9 @@ PhysicsServer3D::PhysicsServer3D() {
GLOBAL_DEF("physics/3d/sleep_threshold_angular", Math::deg_to_rad(8.0));
GLOBAL_DEF(PropertyInfo(Variant::FLOAT, "physics/3d/time_before_sleep", PROPERTY_HINT_RANGE, "0,5,0.01,or_greater"), 0.5);
GLOBAL_DEF(PropertyInfo(Variant::INT, "physics/3d/solver/solver_iterations", PROPERTY_HINT_RANGE, "1,32,1,or_greater"), 16);
- GLOBAL_DEF(PropertyInfo(Variant::FLOAT, "physics/3d/solver/contact_recycle_radius", PROPERTY_HINT_RANGE, "0,0.1,0.01,or_greater"), 0.01);
- GLOBAL_DEF(PropertyInfo(Variant::FLOAT, "physics/3d/solver/contact_max_separation", PROPERTY_HINT_RANGE, "0,0.1,0.01,or_greater"), 0.05);
- GLOBAL_DEF(PropertyInfo(Variant::FLOAT, "physics/3d/solver/contact_max_allowed_penetration", PROPERTY_HINT_RANGE, "0,0.1,0.01,or_greater"), 0.01);
+ GLOBAL_DEF(PropertyInfo(Variant::FLOAT, "physics/3d/solver/contact_recycle_radius", PROPERTY_HINT_RANGE, "0,0.1,0.001,or_greater"), 0.01);
+ GLOBAL_DEF(PropertyInfo(Variant::FLOAT, "physics/3d/solver/contact_max_separation", PROPERTY_HINT_RANGE, "0,0.1,0.001,or_greater"), 0.05);
+ GLOBAL_DEF(PropertyInfo(Variant::FLOAT, "physics/3d/solver/contact_max_allowed_penetration", PROPERTY_HINT_RANGE, "0.001,0.1,0.001,or_greater"), 0.01);
GLOBAL_DEF(PropertyInfo(Variant::FLOAT, "physics/3d/solver/default_contact_bias", PROPERTY_HINT_RANGE, "0,1,0.01"), 0.8);
}
diff --git a/servers/rendering/renderer_rd/renderer_canvas_render_rd.cpp b/servers/rendering/renderer_rd/renderer_canvas_render_rd.cpp
index f49d8be37a..5c6f630355 100644
--- a/servers/rendering/renderer_rd/renderer_canvas_render_rd.cpp
+++ b/servers/rendering/renderer_rd/renderer_canvas_render_rd.cpp
@@ -1866,7 +1866,7 @@ void RendererCanvasRenderRD::occluder_polygon_set_shape(RID p_occluder, const Ve
}
}
- if (oc->line_point_count != lines.size() && oc->vertex_array.is_valid()) {
+ if ((oc->line_point_count != lines.size() || lines.size() == 0) && oc->vertex_array.is_valid()) {
RD::get_singleton()->free(oc->vertex_array);
RD::get_singleton()->free(oc->vertex_buffer);
RD::get_singleton()->free(oc->index_array);
@@ -1881,6 +1881,7 @@ void RendererCanvasRenderRD::occluder_polygon_set_shape(RID p_occluder, const Ve
}
if (lines.size()) {
+ oc->line_point_count = lines.size();
Vector<uint8_t> geometry;
Vector<uint8_t> indices;
int lc = lines.size();
@@ -1971,7 +1972,7 @@ void RendererCanvasRenderRD::occluder_polygon_set_shape(RID p_occluder, const Ve
}
}
- if (oc->sdf_index_count != sdf_indices.size() && oc->sdf_point_count != p_points.size() && oc->sdf_vertex_array.is_valid()) {
+ if (((oc->sdf_index_count != sdf_indices.size() && oc->sdf_point_count != p_points.size()) || p_points.size() == 0) && oc->sdf_vertex_array.is_valid()) {
RD::get_singleton()->free(oc->sdf_vertex_array);
RD::get_singleton()->free(oc->sdf_vertex_buffer);
RD::get_singleton()->free(oc->sdf_index_array);
diff --git a/servers/rendering/renderer_rd/shaders/effects/screen_space_reflection.glsl b/servers/rendering/renderer_rd/shaders/effects/screen_space_reflection.glsl
index 631d1968b0..1626244b0a 100644
--- a/servers/rendering/renderer_rd/shaders/effects/screen_space_reflection.glsl
+++ b/servers/rendering/renderer_rd/shaders/effects/screen_space_reflection.glsl
@@ -263,9 +263,8 @@ void main() {
// Schlick term.
float metallic = texelFetch(source_metallic, ssC << 1, 0).w;
- float f0 = mix(0.04, 1.0, metallic); // Assume a "specular" amount of 0.5
- normal.y = -normal.y;
- float m = clamp(1.0 - dot(normalize(normal), -view_dir), 0.0, 1.0);
+ float f0 = mix(0.04, 0.37, metallic); // The default value of R0 is 0.04 and the maximum value is considered to be Germanium with R0 value of 0.37
+ float m = clamp(1.0 - dot(normal, -view_dir), 0.0, 1.0);
float m2 = m * m;
m = m2 * m2 * m; // pow(m,5)
final_color.a *= f0 + (1.0 - f0) * m; // Fresnel Schlick term.
diff --git a/tests/core/string/test_string.h b/tests/core/string/test_string.h
index 5d19b5a164..abe9f78ccc 100644
--- a/tests/core/string/test_string.h
+++ b/tests/core/string/test_string.h
@@ -1678,8 +1678,8 @@ TEST_CASE("[String] validate_node_name") {
String name_with_kana = U"Name with kana ゴドツ";
CHECK(name_with_kana.validate_node_name() == U"Name with kana ゴドツ");
- String name_with_invalid_chars = "Name with invalid characters :.@removed!";
- CHECK(name_with_invalid_chars.validate_node_name() == "Name with invalid characters removed!");
+ String name_with_invalid_chars = "Name with invalid characters :.@%removed!";
+ CHECK(name_with_invalid_chars.validate_node_name() == "Name with invalid characters ____removed!");
}
TEST_CASE("[String] validate_identifier") {