diff --git a/doc/classes/Node.xml b/doc/classes/Node.xml
index cb829ae1836..42418b3e8dd 100644
--- a/doc/classes/Node.xml
+++ b/doc/classes/Node.xml
@@ -830,6 +830,11 @@
When this signal is received, the child [param node] is still in the tree and valid. This signal is emitted [i]after[/i] the child node's own [signal tree_exiting] and [constant NOTIFICATION_EXIT_TREE].
+
+
+ Emitted when the list of children is changed. This happens when child nodes are added, moved or removed.
+
+
Emitted when the node is ready. Comes after [method _ready] callback and follows the same rules.
@@ -867,8 +872,8 @@
Notification received when the node is about to exit a [SceneTree].
This notification is emitted [i]after[/i] the related [signal tree_exiting].
-
- Notification received when the node is moved in the parent.
+
+ This notification is deprecated and is no longer emitted. Use [constant NOTIFICATION_CHILD_ORDER_CHANGED] instead.
Notification received when the node is ready. See [method _ready].
@@ -907,6 +912,9 @@
Notification received when the node's name or one of its parents' name is changed. This notification is [i]not[/i] received when the node is removed from the scene tree to be added to another parent later on.
+
+ Notification received when the list of children is changed. This happens when child nodes are added, moved or removed.
+
Notification received every frame when the internal process flag is set (see [method set_process_internal]).
diff --git a/scene/2d/node_2d.cpp b/scene/2d/node_2d.cpp
index 1c0efe773fd..43a2f62163b 100644
--- a/scene/2d/node_2d.cpp
+++ b/scene/2d/node_2d.cpp
@@ -385,9 +385,14 @@ Point2 Node2D::to_global(Point2 p_local) const {
void Node2D::_notification(int p_notification) {
switch (p_notification) {
- case NOTIFICATION_MOVED_IN_PARENT: {
+ case NOTIFICATION_ENTER_TREE: {
if (get_viewport()) {
- get_viewport()->gui_set_root_order_dirty();
+ get_parent()->connect(SNAME("child_order_changed"), callable_mp(get_viewport(), &Viewport::gui_set_root_order_dirty), CONNECT_REFERENCE_COUNTED);
+ }
+ } break;
+ case NOTIFICATION_EXIT_TREE: {
+ if (get_viewport()) {
+ get_parent()->disconnect(SNAME("child_order_changed"), callable_mp(get_viewport(), &Viewport::gui_set_root_order_dirty));
}
} break;
}
diff --git a/scene/2d/skeleton_2d.cpp b/scene/2d/skeleton_2d.cpp
index 4fdc7b35840..f2270d2b18a 100644
--- a/scene/2d/skeleton_2d.cpp
+++ b/scene/2d/skeleton_2d.cpp
@@ -115,6 +115,7 @@ void Bone2D::_notification(int p_what) {
bone.bone = this;
skeleton->bones.push_back(bone);
skeleton->_make_bone_setup_dirty();
+ get_parent()->connect(SNAME("child_order_changed"), callable_mp(skeleton, &Skeleton2D::_make_bone_setup_dirty), CONNECT_REFERENCE_COUNTED);
}
cache_transform = get_transform();
@@ -154,15 +155,6 @@ void Bone2D::_notification(int p_what) {
#endif // TOOLS_ENABLED
} break;
- case NOTIFICATION_MOVED_IN_PARENT: {
- if (skeleton) {
- skeleton->_make_bone_setup_dirty();
- }
- if (copy_transform_to_cache) {
- cache_transform = get_transform();
- }
- } break;
-
case NOTIFICATION_EXIT_TREE: {
if (skeleton) {
for (int i = 0; i < skeleton->bones.size(); i++) {
@@ -172,7 +164,7 @@ void Bone2D::_notification(int p_what) {
}
}
skeleton->_make_bone_setup_dirty();
- skeleton = nullptr;
+ get_parent()->disconnect(SNAME("child_order_changed"), callable_mp(skeleton, &Skeleton2D::_make_bone_setup_dirty));
}
parent_bone = nullptr;
set_transform(cache_transform);
diff --git a/scene/gui/control.cpp b/scene/gui/control.cpp
index 569b89f6885..8957a9492e3 100644
--- a/scene/gui/control.cpp
+++ b/scene/gui/control.cpp
@@ -3021,6 +3021,8 @@ void Control::_notification(int p_notification) {
Viewport *viewport = get_viewport();
ERR_FAIL_COND(!viewport);
data.RI = viewport->_gui_add_root_control(this);
+
+ get_parent()->connect(SNAME("child_order_changed"), callable_mp(get_viewport(), &Viewport::gui_set_root_order_dirty), CONNECT_REFERENCE_COUNTED);
}
data.parent_canvas_item = get_parent_item();
@@ -3049,24 +3051,17 @@ void Control::_notification(int p_notification) {
if (data.RI) {
get_viewport()->_gui_remove_root_control(data.RI);
data.RI = nullptr;
+ get_parent()->disconnect(SNAME("child_order_changed"), callable_mp(get_viewport(), &Viewport::gui_set_root_order_dirty));
}
data.parent_canvas_item = nullptr;
data.is_rtl_dirty = true;
} break;
- case NOTIFICATION_MOVED_IN_PARENT: {
+ case NOTIFICATION_CHILD_ORDER_CHANGED: {
// Some parents need to know the order of the children to draw (like TabContainer),
// so we update them just in case.
- Control *parent_control = get_parent_control();
- if (parent_control) {
- parent_control->queue_redraw();
- }
queue_redraw();
-
- if (data.RI) {
- get_viewport()->gui_set_root_order_dirty();
- }
} break;
case NOTIFICATION_RESIZED: {
diff --git a/scene/main/canvas_item.cpp b/scene/main/canvas_item.cpp
index 72fb8387320..4eabc4916cb 100644
--- a/scene/main/canvas_item.cpp
+++ b/scene/main/canvas_item.cpp
@@ -188,10 +188,13 @@ void CanvasItem::_enter_canvas() {
// Resolves to nullptr if the node is top_level.
CanvasItem *parent_item = get_parent_item();
+ if (get_parent()) {
+ get_viewport()->canvas_parent_mark_dirty(get_parent());
+ }
+
if (parent_item) {
canvas_layer = parent_item->canvas_layer;
RenderingServer::get_singleton()->canvas_item_set_parent(canvas_item, parent_item->get_canvas_item());
- RenderingServer::get_singleton()->canvas_item_set_draw_index(canvas_item, get_index());
RenderingServer::get_singleton()->canvas_item_set_visibility_layer(canvas_item, visibility_layer);
} else {
Node *n = this;
@@ -227,8 +230,6 @@ void CanvasItem::_enter_canvas() {
} else {
get_viewport()->gui_reset_canvas_sort_index();
}
-
- get_tree()->call_group_flags(SceneTree::GROUP_CALL_UNIQUE | SceneTree::GROUP_CALL_DEFERRED, canvas_group, SNAME("_top_level_raise_self"));
}
pending_update = false;
@@ -302,21 +303,12 @@ void CanvasItem::_notification(int p_what) {
if (!block_transform_notify && !xform_change.in_list()) {
get_tree()->xform_change_list.add(&xform_change);
}
- } break;
- case NOTIFICATION_MOVED_IN_PARENT: {
- if (!is_inside_tree()) {
- break;
+ if (get_viewport()) {
+ get_parent()->connect(SNAME("child_order_changed"), callable_mp(get_viewport(), &Viewport::canvas_parent_mark_dirty).bind(get_parent()), CONNECT_REFERENCE_COUNTED);
}
- if (canvas_group != StringName()) {
- get_tree()->call_group_flags(SceneTree::GROUP_CALL_UNIQUE | SceneTree::GROUP_CALL_DEFERRED, canvas_group, "_top_level_raise_self");
- } else {
- ERR_FAIL_COND_MSG(!get_parent_item(), "Moved child is in incorrect state (no canvas group, no canvas item parent).");
- RenderingServer::get_singleton()->canvas_item_set_draw_index(canvas_item, get_index());
- }
} break;
-
case NOTIFICATION_EXIT_TREE: {
if (xform_change.in_list()) {
get_tree()->xform_change_list.remove(&xform_change);
@@ -332,6 +324,10 @@ void CanvasItem::_notification(int p_what) {
}
global_invalid = true;
parent_visible_in_tree = false;
+
+ if (get_viewport()) {
+ get_parent()->disconnect(SNAME("child_order_changed"), callable_mp(get_viewport(), &Viewport::canvas_parent_mark_dirty).bind(get_parent()));
+ }
} break;
case NOTIFICATION_VISIBILITY_CHANGED: {
@@ -340,6 +336,19 @@ void CanvasItem::_notification(int p_what) {
}
}
+void CanvasItem::update_draw_order() {
+ if (!is_inside_tree()) {
+ return;
+ }
+
+ if (canvas_group != StringName()) {
+ get_tree()->call_group_flags(SceneTree::GROUP_CALL_UNIQUE | SceneTree::GROUP_CALL_DEFERRED, canvas_group, "_top_level_raise_self");
+ } else {
+ ERR_FAIL_COND_MSG(!get_parent_item(), "Moved child is in incorrect state (no canvas group, no canvas item parent).");
+ RenderingServer::get_singleton()->canvas_item_set_draw_index(canvas_item, get_index());
+ }
+}
+
void CanvasItem::_window_visibility_changed() {
if (visible) {
_propagate_visibility_changed(window->is_visible());
diff --git a/scene/main/canvas_item.h b/scene/main/canvas_item.h
index 5fbf0431598..ea1e666b08d 100644
--- a/scene/main/canvas_item.h
+++ b/scene/main/canvas_item.h
@@ -212,6 +212,8 @@ public:
virtual Transform2D _edit_get_transform() const;
#endif
+ void update_draw_order();
+
/* VISIBILITY */
void set_visible(bool p_visible);
diff --git a/scene/main/canvas_layer.cpp b/scene/main/canvas_layer.cpp
index 76cc17922ad..274bd1ad681 100644
--- a/scene/main/canvas_layer.cpp
+++ b/scene/main/canvas_layer.cpp
@@ -180,25 +180,30 @@ void CanvasLayer::_notification(int p_what) {
viewport = vp->get_viewport_rid();
RenderingServer::get_singleton()->viewport_attach_canvas(viewport, canvas);
- RenderingServer::get_singleton()->viewport_set_canvas_stacking(viewport, canvas, layer, get_index());
RenderingServer::get_singleton()->viewport_set_canvas_transform(viewport, canvas, transform);
_update_follow_viewport();
+
+ if (vp) {
+ get_parent()->connect(SNAME("child_order_changed"), callable_mp(vp, &Viewport::canvas_parent_mark_dirty).bind(get_parent()), CONNECT_REFERENCE_COUNTED);
+ vp->canvas_parent_mark_dirty(get_parent());
+ }
} break;
case NOTIFICATION_EXIT_TREE: {
ERR_FAIL_NULL_MSG(vp, "Viewport is not initialized.");
+ get_parent()->disconnect(SNAME("child_order_changed"), callable_mp(vp, &Viewport::canvas_parent_mark_dirty).bind(get_parent()));
vp->_canvas_layer_remove(this);
RenderingServer::get_singleton()->viewport_remove_canvas(viewport, canvas);
viewport = RID();
_update_follow_viewport(false);
} break;
+ }
+}
- case NOTIFICATION_MOVED_IN_PARENT: {
- if (is_inside_tree()) {
- RenderingServer::get_singleton()->viewport_set_canvas_stacking(viewport, canvas, layer, get_index());
- }
- } break;
+void CanvasLayer::update_draw_order() {
+ if (is_inside_tree()) {
+ RenderingServer::get_singleton()->viewport_set_canvas_stacking(viewport, canvas, layer, get_index());
}
}
diff --git a/scene/main/canvas_layer.h b/scene/main/canvas_layer.h
index 47077dc7fdf..be1a377a142 100644
--- a/scene/main/canvas_layer.h
+++ b/scene/main/canvas_layer.h
@@ -67,6 +67,8 @@ protected:
void _validate_property(PropertyInfo &p_property) const;
public:
+ void update_draw_order();
+
void set_layer(int p_xform);
int get_layer() const;
diff --git a/scene/main/node.cpp b/scene/main/node.cpp
index 22bcfc947bd..d8375dbc9f1 100644
--- a/scene/main/node.cpp
+++ b/scene/main/node.cpp
@@ -393,9 +393,9 @@ void Node::_move_child(Node *p_child, int p_index, bool p_ignore_end) {
}
// notification second
move_child_notify(p_child);
- for (int i = motion_from; i <= motion_to; i++) {
- data.children[i]->notification(NOTIFICATION_MOVED_IN_PARENT);
- }
+ notification(NOTIFICATION_CHILD_ORDER_CHANGED);
+ emit_signal(SNAME("child_order_changed"));
+
p_child->_propagate_groups_dirty();
data.blocked--;
@@ -1124,6 +1124,8 @@ void Node::_add_child_nocheck(Node *p_child, const StringName &p_name) {
//recognize children created in this node constructor
p_child->data.parent_owned = data.in_constructor;
add_child_notify(p_child);
+ notification(NOTIFICATION_CHILD_ORDER_CHANGED);
+ emit_signal(SNAME("child_order_changed"));
}
void Node::add_child(Node *p_child, bool p_force_readable_name, InternalMode p_internal) {
@@ -1213,10 +1215,8 @@ void Node::remove_child(Node *p_child) {
child_count = data.children.size();
children = data.children.ptrw();
- for (int i = idx; i < child_count; i++) {
- children[i]->data.index = i;
- children[i]->notification(NOTIFICATION_MOVED_IN_PARENT);
- }
+ notification(NOTIFICATION_CHILD_ORDER_CHANGED);
+ emit_signal(SNAME("child_order_changed"));
p_child->data.parent = nullptr;
p_child->data.index = -1;
@@ -2974,6 +2974,7 @@ void Node::_bind_methods() {
BIND_CONSTANT(NOTIFICATION_DRAG_BEGIN);
BIND_CONSTANT(NOTIFICATION_DRAG_END);
BIND_CONSTANT(NOTIFICATION_PATH_RENAMED);
+ BIND_CONSTANT(NOTIFICATION_CHILD_ORDER_CHANGED);
BIND_CONSTANT(NOTIFICATION_INTERNAL_PROCESS);
BIND_CONSTANT(NOTIFICATION_INTERNAL_PHYSICS_PROCESS);
BIND_CONSTANT(NOTIFICATION_POST_ENTER_TREE);
@@ -3027,6 +3028,7 @@ void Node::_bind_methods() {
ADD_SIGNAL(MethodInfo("tree_exited"));
ADD_SIGNAL(MethodInfo("child_entered_tree", PropertyInfo(Variant::OBJECT, "node", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_DEFAULT, "Node")));
ADD_SIGNAL(MethodInfo("child_exiting_tree", PropertyInfo(Variant::OBJECT, "node", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_DEFAULT, "Node")));
+ ADD_SIGNAL(MethodInfo("child_order_changed"));
ADD_PROPERTY(PropertyInfo(Variant::STRING_NAME, "name", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NONE), "set_name", "get_name");
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "unique_name_in_owner", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NO_EDITOR), "set_unique_name_in_owner", "is_unique_name_in_owner");
diff --git a/scene/main/node.h b/scene/main/node.h
index 8ce42d04bdb..939632770e0 100644
--- a/scene/main/node.h
+++ b/scene/main/node.h
@@ -264,7 +264,7 @@ public:
NOTIFICATION_DRAG_BEGIN = 21,
NOTIFICATION_DRAG_END = 22,
NOTIFICATION_PATH_RENAMED = 23,
- //NOTIFICATION_TRANSLATION_CHANGED = 24, moved below
+ NOTIFICATION_CHILD_ORDER_CHANGED = 24,
NOTIFICATION_INTERNAL_PROCESS = 25,
NOTIFICATION_INTERNAL_PHYSICS_PROCESS = 26,
NOTIFICATION_POST_ENTER_TREE = 27,
diff --git a/scene/main/viewport.cpp b/scene/main/viewport.cpp
index feddb1e779c..9f9a2eeb18d 100644
--- a/scene/main/viewport.cpp
+++ b/scene/main/viewport.cpp
@@ -165,6 +165,31 @@ ViewportTexture::~ViewportTexture() {
}
}
+void Viewport::_process_dirty_canvas_parent_orders() {
+ for (const ObjectID &id : gui.canvas_parents_with_dirty_order) {
+ Object *obj = ObjectDB::get_instance(id);
+ if (!obj) {
+ continue; // May have been deleted.
+ }
+
+ Node *n = static_cast(obj);
+ for (int i = 0; i < n->get_child_count(); i++) {
+ Node *c = n->get_child(i);
+ CanvasItem *ci = Object::cast_to(c);
+ if (ci) {
+ ci->update_draw_order();
+ continue;
+ }
+ CanvasLayer *cl = Object::cast_to(c);
+ if (cl) {
+ cl->update_draw_order();
+ }
+ }
+ }
+
+ gui.canvas_parents_with_dirty_order.clear();
+}
+
void Viewport::_sub_window_update_order() {
if (gui.sub_windows.size() < 2) {
return;
@@ -929,6 +954,14 @@ Rect2 Viewport::get_visible_rect() const {
return r;
}
+void Viewport::canvas_parent_mark_dirty(Node *p_node) {
+ bool request_update = gui.canvas_parents_with_dirty_order.is_empty();
+ gui.canvas_parents_with_dirty_order.insert(p_node->get_instance_id());
+ if (request_update) {
+ MessageQueue::get_singleton()->push_callable(callable_mp(this, &Viewport::_process_dirty_canvas_parent_orders));
+ }
+}
+
void Viewport::_update_audio_listener_2d() {
if (AudioServer::get_singleton()) {
AudioServer::get_singleton()->notify_listener_changed();
diff --git a/scene/main/viewport.h b/scene/main/viewport.h
index 5213c0db018..f12e7921a3d 100644
--- a/scene/main/viewport.h
+++ b/scene/main/viewport.h
@@ -376,6 +376,7 @@ private:
double tooltip_delay = 0.0;
bool roots_order_dirty = false;
List roots;
+ HashSet canvas_parents_with_dirty_order;
int canvas_sort_index = 0; //for sorting items with canvas as root
bool dragging = false;
bool drag_successful = false;
@@ -468,6 +469,8 @@ private:
virtual bool _can_consume_input_events() const { return true; }
uint64_t event_count = 0;
+ void _process_dirty_canvas_parent_orders();
+
protected:
void _set_size(const Size2i &p_size, const Size2i &p_size_2d_override, bool p_allocated);
@@ -480,6 +483,8 @@ protected:
static void _bind_methods();
public:
+ void canvas_parent_mark_dirty(Node *p_node);
+
uint64_t get_processed_events_count() const { return event_count; }
AudioListener2D *get_audio_listener_2d() const;