diff --git a/doc/classes/EditorSettings.xml b/doc/classes/EditorSettings.xml
index e1c85d3595f..bd9f7a665ac 100644
--- a/doc/classes/EditorSettings.xml
+++ b/doc/classes/EditorSettings.xml
@@ -241,6 +241,9 @@
If [code]true[/code], new node created when reparenting node(s) will be positioned at the average position of the selected node(s).
+
+ If [code]true[/code], the scene tree dock will only show nodes that match the filter, without showing parents that don't. This settings can also be changed in the Scene dock's top menu.
+
If [code]true[/code], the Create dialog (Create New Node/Create New Resource) will start with all its sections expanded. Otherwise, sections will be collapsed until the user starts searching (which will automatically expand sections as needed).
diff --git a/editor/debugger/editor_debugger_tree.cpp b/editor/debugger/editor_debugger_tree.cpp
index a9e4adf674c..24c505aec4b 100644
--- a/editor/debugger/editor_debugger_tree.cpp
+++ b/editor/debugger/editor_debugger_tree.cpp
@@ -32,6 +32,7 @@
#include "editor/debugger/editor_debugger_node.h"
#include "editor/editor_node.h"
+#include "editor/editor_settings.h"
#include "editor/editor_string_names.h"
#include "editor/gui/editor_file_dialog.h"
#include "editor/scene_tree_dock.h"
@@ -146,24 +147,50 @@ void EditorDebuggerTree::_scene_tree_rmb_selected(const Vector2 &p_position, Mou
/// |-E
///
void EditorDebuggerTree::update_scene_tree(const SceneDebuggerTree *p_tree, int p_debugger) {
+ set_hide_root(false);
+
updating_scene_tree = true;
const String last_path = get_selected_path();
const String filter = SceneTreeDock::get_singleton()->get_filter();
+ TreeItem *select_item = nullptr;
+ bool hide_filtered_out_parents = EDITOR_GET("docks/scene_tree/hide_filtered_out_parents");
+
bool should_scroll = scrolling_to_item || filter != last_filter;
scrolling_to_item = false;
TreeItem *scroll_item = nullptr;
// Nodes are in a flatten list, depth first. Use a stack of parents, avoid recursion.
- List> parents;
+ List parents;
for (const SceneDebuggerTree::RemoteNode &node : p_tree->nodes) {
TreeItem *parent = nullptr;
+ Pair move_from_to;
if (parents.size()) { // Find last parent.
- Pair &p = parents.front()->get();
- parent = p.first;
- if (!(--p.second)) { // If no child left, remove it.
+ ParentItem &p = parents.front()->get();
+ parent = p.tree_item;
+ if (!(--p.child_count)) { // If no child left, remove it.
parents.pop_front();
+
+ if (hide_filtered_out_parents && !filter.is_subsequence_ofn(parent->get_text(0))) {
+ if (parent == get_root()) {
+ set_hide_root(true);
+ } else {
+ move_from_to.first = parent;
+ // Find the closest ancestor that matches the filter.
+ for (const ParentItem p2 : parents) {
+ move_from_to.second = p2.tree_item;
+ if (p2.matches_filter || move_from_to.second == get_root()) {
+ break;
+ }
+ }
+
+ if (!move_from_to.second) {
+ move_from_to.second = get_root();
+ }
+ }
+ }
}
}
+
// Add this node.
TreeItem *item = create_item(parent);
item->set_text(0, node.name);
@@ -178,12 +205,17 @@ void EditorDebuggerTree::update_scene_tree(const SceneDebuggerTree *p_tree, int
}
item->set_metadata(0, node.id);
- // Set current item as collapsed if necessary (root is never collapsed).
+ String current_path;
if (parent) {
+ current_path += (String)parent->get_meta("node_path");
+
+ // Set current item as collapsed if necessary (root is never collapsed).
if (!unfold_cache.has(node.id)) {
item->set_collapsed(true);
}
}
+ item->set_meta("node_path", current_path + "/" + item->get_text(0));
+
// Select previously selected node.
if (debugger_id == p_debugger) { // Can use remote id.
if (node.id == inspected_object_id) {
@@ -196,21 +228,18 @@ void EditorDebuggerTree::update_scene_tree(const SceneDebuggerTree *p_tree, int
updating_scene_tree = true;
}
- item->select(0);
-
+ select_item = item;
if (should_scroll) {
scroll_item = item;
}
}
- } else { // Must use path
- if (last_path == _get_path(item)) {
- updating_scene_tree = false; // Force emission of new selection.
- item->select(0);
- if (should_scroll) {
- scroll_item = item;
- }
- updating_scene_tree = true;
+ } else if (last_path == (String)item->get_meta("node_path")) { // Must use path.
+ updating_scene_tree = false; // Force emission of new selection.
+ select_item = item;
+ if (should_scroll) {
+ scroll_item = item;
}
+ updating_scene_tree = true;
}
// Add buttons.
@@ -242,7 +271,7 @@ void EditorDebuggerTree::update_scene_tree(const SceneDebuggerTree *p_tree, int
// Add in front of the parents stack if children are expected.
if (node.child_count) {
- parents.push_front(Pair(item, node.child_count));
+ parents.push_front(ParentItem(item, node.child_count, filter.is_subsequence_ofn(item->get_text(0))));
} else {
// Apply filters.
while (parent) {
@@ -250,31 +279,60 @@ void EditorDebuggerTree::update_scene_tree(const SceneDebuggerTree *p_tree, int
if (filter.is_subsequence_ofn(item->get_text(0))) {
break; // Filter matches, must survive.
}
+
parent->remove_child(item);
memdelete(item);
- if (scroll_item == item) {
+ if (select_item == item || scroll_item == item) {
+ select_item = nullptr;
scroll_item = nullptr;
}
+
if (had_siblings) {
break; // Parent must survive.
}
+
item = parent;
parent = item->get_parent();
// Check if parent expects more children.
- for (const Pair &pair : parents) {
- if (pair.first == item) {
+ for (ParentItem &pair : parents) {
+ if (pair.tree_item == item) {
parent = nullptr;
break; // Might have more children.
}
}
}
}
+
+ // Move all children to the ancestor that matches the filter, if picked.
+ if (move_from_to.first) {
+ TreeItem *from = move_from_to.first;
+ TypedArray children = from->get_children();
+ if (!children.is_empty()) {
+ for (Variant &c : children) {
+ TreeItem *ti = Object::cast_to(c);
+ from->remove_child(ti);
+ move_from_to.second->add_child(ti);
+ }
+
+ from->get_parent()->remove_child(from);
+ memdelete(from);
+ if (select_item == from || scroll_item == from) {
+ select_item = nullptr;
+ scroll_item = nullptr;
+ }
+ }
+ }
}
debugger_id = p_debugger; // Needed by hook, could be avoided if every debugger had its own tree.
+
+ if (select_item) {
+ select_item->select(0);
+ }
if (scroll_item) {
scroll_to_item(scroll_item, false);
}
+
last_filter = filter;
updating_scene_tree = false;
}
@@ -338,22 +396,7 @@ String EditorDebuggerTree::get_selected_path() {
if (!get_selected()) {
return "";
}
- return _get_path(get_selected());
-}
-
-String EditorDebuggerTree::_get_path(TreeItem *p_item) {
- ERR_FAIL_NULL_V(p_item, "");
-
- if (p_item->get_parent() == nullptr) {
- return "/root";
- }
- String text = p_item->get_text(0);
- TreeItem *cur = p_item->get_parent();
- while (cur) {
- text = cur->get_text(0) + "/" + text;
- cur = cur->get_parent();
- }
- return "/" + text;
+ return get_selected()->get_meta("node_path");
}
void EditorDebuggerTree::_item_menu_id_pressed(int p_option) {
diff --git a/editor/debugger/editor_debugger_tree.h b/editor/debugger/editor_debugger_tree.h
index d048688cad2..46893d6dc3b 100644
--- a/editor/debugger/editor_debugger_tree.h
+++ b/editor/debugger/editor_debugger_tree.h
@@ -40,6 +40,18 @@ class EditorDebuggerTree : public Tree {
GDCLASS(EditorDebuggerTree, Tree);
private:
+ struct ParentItem {
+ TreeItem *tree_item;
+ int child_count;
+ bool matches_filter;
+
+ ParentItem(TreeItem *p_tree_item = nullptr, int p_child_count = 0, bool p_matches_filter = false) {
+ tree_item = p_tree_item;
+ child_count = p_child_count;
+ matches_filter = p_matches_filter;
+ }
+ };
+
enum ItemMenu {
ITEM_MENU_SAVE_REMOTE_NODE,
ITEM_MENU_COPY_NODE_PATH,
@@ -56,7 +68,6 @@ private:
EditorFileDialog *file_dialog = nullptr;
String last_filter;
- String _get_path(TreeItem *p_item);
void _scene_tree_folded(Object *p_obj);
void _scene_tree_selected();
void _scene_tree_rmb_selected(const Vector2 &p_position, MouseButton p_button);
diff --git a/editor/editor_settings.cpp b/editor/editor_settings.cpp
index 78dd3919147..95531599bd0 100644
--- a/editor/editor_settings.cpp
+++ b/editor/editor_settings.cpp
@@ -633,6 +633,7 @@ void EditorSettings::_load_defaults(Ref p_extra_config) {
_initial_set("docks/scene_tree/start_create_dialog_fully_expanded", false);
_initial_set("docks/scene_tree/auto_expand_to_selected", true);
_initial_set("docks/scene_tree/center_node_on_reparent", false);
+ _initial_set("docks/scene_tree/hide_filtered_out_parents", true);
// FileSystem
EDITOR_SETTING(Variant::INT, PROPERTY_HINT_RANGE, "docks/filesystem/thumbnail_size", 64, "32,128,16")
diff --git a/editor/gui/scene_tree_editor.cpp b/editor/gui/scene_tree_editor.cpp
index fbe679cd15c..9f823c9b155 100644
--- a/editor/gui/scene_tree_editor.cpp
+++ b/editor/gui/scene_tree_editor.cpp
@@ -957,49 +957,62 @@ bool SceneTreeEditor::_update_filter(TreeItem *p_parent, bool p_scroll_to_select
return false;
}
+ // Now find other reasons to keep this Node, too.
+ PackedStringArray terms = filter.to_lower().split_spaces();
+ bool keep = _item_matches_all_terms(p_parent, terms);
+
+ bool selectable = keep;
+ bool is_root = p_parent == tree->get_root();
+
+ if (keep) {
+ Node *n = get_node(p_parent->get_metadata(0));
+ if (!p_parent->is_visible() || (is_root && tree->is_root_hidden())) {
+ // Place back moved out children from when this item has hidden.
+ HashMap::Iterator I = node_cache.get(n, false);
+ if (I && I->value.has_moved_children) {
+ _update_node_subtree(I->value.node, nullptr, true);
+ }
+ }
+
+ if (!valid_types.is_empty()) {
+ selectable = false;
+ for (const StringName &E : valid_types) {
+ if (n->is_class(E) ||
+ EditorNode::get_singleton()->is_object_of_custom_type(n, E)) {
+ selectable = true;
+ break;
+ } else {
+ Ref