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