Browse Source

Merge pull request #98926 from YeldhamDev/out_all_of_you

Add toggle to hide filtered out parents in the "SceneTree" dock
Rémi Verschelde 7 months ago
parent
commit
ae205b0fc6

+ 3 - 0
doc/classes/EditorSettings.xml

@@ -241,6 +241,9 @@
 		<member name="docks/scene_tree/center_node_on_reparent" type="bool" setter="" getter="">
 			If [code]true[/code], new node created when reparenting node(s) will be positioned at the average position of the selected node(s).
 		</member>
+		<member name="docks/scene_tree/hide_filtered_out_parents" type="bool" setter="" getter="">
+			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.
+		</member>
 		<member name="docks/scene_tree/start_create_dialog_fully_expanded" type="bool" setter="" getter="">
 			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).
 		</member>

+ 78 - 35
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<Pair<TreeItem *, int>> parents;
+	List<ParentItem> parents;
 	for (const SceneDebuggerTree::RemoteNode &node : p_tree->nodes) {
 		TreeItem *parent = nullptr;
+		Pair<TreeItem *, TreeItem *> move_from_to;
 		if (parents.size()) { // Find last parent.
-			Pair<TreeItem *, int> &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<TreeItem *, int>(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<TreeItem *, int> &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<TreeItem> children = from->get_children();
+			if (!children.is_empty()) {
+				for (Variant &c : children) {
+					TreeItem *ti = Object::cast_to<TreeItem>(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) {

+ 12 - 1
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);

+ 1 - 0
editor/editor_settings.cpp

@@ -633,6 +633,7 @@ void EditorSettings::_load_defaults(Ref<ConfigFile> 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")

+ 113 - 39
editor/gui/scene_tree_editor.cpp

@@ -957,47 +957,60 @@ bool SceneTreeEditor::_update_filter(TreeItem *p_parent, bool p_scroll_to_select
 		return false;
 	}
 
-	bool keep_for_children = false;
-	for (TreeItem *child = p_parent->get_first_child(); child; child = child->get_next()) {
-		// Always keep if at least one of the children are kept.
-		keep_for_children = _update_filter(child, p_scroll_to_selected) || keep_for_children;
-	}
-
 	// 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;
-	if (keep && !valid_types.is_empty()) {
-		selectable = false;
+	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<Node *, CachedNode>::Iterator I = node_cache.get(n, false);
+			if (I && I->value.has_moved_children) {
+				_update_node_subtree(I->value.node, nullptr, true);
+			}
+		}
 
-		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<Script> node_script = n->get_script();
-				while (node_script.is_valid()) {
-					if (node_script->get_path() == E) {
-						selectable = 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<Script> node_script = n->get_script();
+					while (node_script.is_valid()) {
+						if (node_script->get_path() == E) {
+							selectable = true;
+							break;
+						}
+						node_script = node_script->get_base_script();
+					}
+					if (selectable) {
 						break;
 					}
-					node_script = node_script->get_base_script();
-				}
-				if (selectable) {
-					break;
 				}
 			}
 		}
 	}
 
-	if (show_all_nodes) {
-		p_parent->set_visible(keep_for_children || keep);
-	} else {
-		// Show only selectable nodes, or parents of selectable.
-		p_parent->set_visible(keep_for_children || selectable);
+	bool keep_for_children = false;
+	for (TreeItem *child = p_parent->get_first_child(); child; child = child->get_next()) {
+		// Always keep if at least one of the children are kept.
+		keep_for_children = _update_filter(child, p_scroll_to_selected) || keep_for_children;
+	}
+
+	if (!is_root) {
+		if (show_all_nodes) {
+			p_parent->set_visible(keep_for_children || keep);
+		} else {
+			// Show only selectable nodes, or parents of selectable.
+			p_parent->set_visible(keep_for_children || selectable);
+		}
 	}
 
 	if (selectable) {
@@ -1007,24 +1020,67 @@ bool SceneTreeEditor::_update_filter(TreeItem *p_parent, bool p_scroll_to_select
 		} else {
 			p_parent->set_custom_color(0, custom_color);
 		}
+
 		p_parent->set_selectable(0, true);
 	} else if (keep_for_children) {
-		p_parent->set_custom_color(0, get_theme_color(SNAME("font_disabled_color"), EditorStringName(Editor)));
-		p_parent->set_selectable(0, false);
-		p_parent->deselect(0);
+		p_parent->set_visible(!hide_filtered_out_parents || is_root);
+
+		if (!p_parent->is_visible()) {
+			TreeItem *filtered_parent = p_parent->get_parent();
+			while (filtered_parent) {
+				if (filtered_parent == tree->get_root() || (filtered_parent->is_selectable(0) && filtered_parent->is_visible())) {
+					break;
+				}
+				filtered_parent = filtered_parent->get_parent();
+			}
+
+			if (filtered_parent) {
+				for (Variant &item : p_parent->get_children()) {
+					TreeItem *ti = Object::cast_to<TreeItem>(item);
+					bool is_selected = ti->is_selected(0);
+
+					p_parent->remove_child(ti);
+					filtered_parent->add_child(ti);
+					TreeItem *prev = p_parent->get_prev();
+					if (prev) {
+						ti->move_after(prev);
+					}
+
+					if (is_selected) {
+						ti->select(0);
+					}
+
+					HashMap<Node *, CachedNode>::Iterator I = node_cache.get(get_node(p_parent->get_metadata(0)), false);
+					if (I) {
+						I->value.has_moved_children = true;
+					}
+				}
+
+				return false;
+			}
+		} else {
+			p_parent->set_custom_color(0, get_theme_color(SNAME("font_disabled_color"), EditorStringName(Editor)));
+			p_parent->set_selectable(0, false);
+			p_parent->deselect(0);
+		}
+	}
+	if (is_root) {
+		tree->set_hide_root(hide_filtered_out_parents && !selectable);
+		if (tree->is_root_hidden()) {
+			p_parent->set_collapsed(false);
+		}
 	}
 
 	if (editor_selection) {
 		Node *n = get_node(p_parent->get_metadata(0));
 		if (selectable) {
 			if (p_scroll_to_selected && n && editor_selection->is_selected(n)) {
-				tree->scroll_to_item(p_parent);
-			}
-		} else {
-			if (n && p_parent->is_selected(0)) {
-				editor_selection->remove_node(n);
-				p_parent->deselect(0);
+				// Needs to be deferred to account for possible root visibility change.
+				callable_mp(tree, &Tree::scroll_to_item).call_deferred(p_parent, false);
 			}
+		} else if (n && p_parent->is_selected(0)) {
+			editor_selection->remove_node(n);
+			p_parent->deselect(0);
 		}
 	}
 
@@ -1894,19 +1950,37 @@ void SceneTreeEditor::set_auto_expand_selected(bool p_auto, bool p_update_settin
 	auto_expand_selected = p_auto;
 }
 
+void SceneTreeEditor::set_hide_filtered_out_parents(bool p_hide, bool p_update_settings) {
+	if (p_hide == hide_filtered_out_parents) {
+		return;
+	}
+
+	if (p_update_settings) {
+		EditorSettings::get_singleton()->set("docks/scene_tree/hide_filtered_out_parents", p_hide);
+	}
+	hide_filtered_out_parents = p_hide;
+
+	if (hide_filtered_out_parents) {
+		_update_filter();
+	} else {
+		node_cache.force_update = true;
+		_update_tree();
+	}
+}
+
 void SceneTreeEditor::set_connect_to_script_mode(bool p_enable) {
 	connect_to_script_mode = p_enable;
-	update_tree();
+	_update_tree();
 }
 
 void SceneTreeEditor::set_connecting_signal(bool p_enable) {
 	connecting_signal = p_enable;
-	update_tree();
+	_update_tree();
 }
 
 void SceneTreeEditor::set_update_when_invisible(bool p_enable) {
 	update_when_invisible = p_enable;
-	update_tree();
+	_update_tree();
 }
 
 void SceneTreeEditor::_bind_methods() {

+ 3 - 1
editor/gui/scene_tree_editor.h

@@ -126,6 +126,7 @@ class SceneTreeEditor : public Control {
 	Node *revoke_node = nullptr;
 
 	bool auto_expand_selected = true;
+	bool hide_filtered_out_parents = false;
 	bool connect_to_script_mode = false;
 	bool connecting_signal = false;
 	bool update_when_invisible = true;
@@ -243,9 +244,10 @@ public:
 	void set_show_enabled_subscene(bool p_show) { show_enabled_subscene = p_show; }
 	void set_valid_types(const Vector<StringName> &p_valid);
 
-	void update_tree() { _update_tree(); }
+	inline void update_tree() { _update_tree(); }
 
 	void set_auto_expand_selected(bool p_auto, bool p_update_settings);
+	void set_hide_filtered_out_parents(bool p_hide, bool p_update_settings);
 	void set_connect_to_script_mode(bool p_enable);
 	void set_connecting_signal(bool p_enable);
 	void set_update_when_invisible(bool p_enable);

+ 12 - 3
editor/scene_tree_dock.cpp

@@ -1242,6 +1242,9 @@ void SceneTreeDock::_tool_selected(int p_tool, bool p_confirm_override) {
 		case TOOL_CENTER_PARENT: {
 			EditorSettings::get_singleton()->set("docks/scene_tree/center_node_on_reparent", !EDITOR_GET("docks/scene_tree/center_node_on_reparent"));
 		} break;
+		case TOOL_HIDE_FILTERED_OUT_PARENTS: {
+			scene_tree->set_hide_filtered_out_parents(!EDITOR_GET("docks/scene_tree/hide_filtered_out_parents"), true);
+		} break;
 		case TOOL_SCENE_EDITABLE_CHILDREN: {
 			if (!profile_allow_editing) {
 				break;
@@ -1667,6 +1670,7 @@ void SceneTreeDock::_notification(int p_what) {
 		case NOTIFICATION_ENTER_TREE: {
 			clear_inherit_confirm->connect(SceneStringName(confirmed), callable_mp(this, &SceneTreeDock::_tool_selected).bind(TOOL_SCENE_CLEAR_INHERITANCE_CONFIRM, false));
 			scene_tree->set_auto_expand_selected(EDITOR_GET("docks/scene_tree/auto_expand_to_selected"), false);
+			scene_tree->set_hide_filtered_out_parents(EDITOR_GET("docks/scene_tree/hide_filtered_out_parents"), false);
 		} break;
 
 		case NOTIFICATION_EXIT_TREE: {
@@ -1676,6 +1680,7 @@ void SceneTreeDock::_notification(int p_what) {
 		case EditorSettings::NOTIFICATION_EDITOR_SETTINGS_CHANGED: {
 			if (EditorSettings::get_singleton()->check_changed_settings_in_group("docks/scene_tree")) {
 				scene_tree->set_auto_expand_selected(EDITOR_GET("docks/scene_tree/auto_expand_to_selected"), false);
+				scene_tree->set_hide_filtered_out_parents(EDITOR_GET("docks/scene_tree/hide_filtered_out_parents"), false);
 			}
 		} break;
 
@@ -3961,12 +3966,16 @@ void SceneTreeDock::_update_tree_menu() {
 
 	tree_menu->add_separator();
 	tree_menu->add_check_item(TTR("Auto Expand to Selected"), TOOL_AUTO_EXPAND);
-	tree_menu->set_item_checked(tree_menu->get_item_index(TOOL_AUTO_EXPAND), EDITOR_GET("docks/scene_tree/auto_expand_to_selected"));
+	tree_menu->set_item_checked(-1, EDITOR_GET("docks/scene_tree/auto_expand_to_selected"));
 
 	tree_menu->add_check_item(TTR("Center Node on Reparent"), TOOL_CENTER_PARENT);
-	tree_menu->set_item_checked(tree_menu->get_item_index(TOOL_CENTER_PARENT), EDITOR_GET("docks/scene_tree/center_node_on_reparent"));
-	tree_menu->set_item_tooltip(tree_menu->get_item_index(TOOL_CENTER_PARENT), TTR("If enabled, Reparent to New Node will create the new node in the center of the selected nodes, if possible."));
+	tree_menu->set_item_checked(-1, EDITOR_GET("docks/scene_tree/center_node_on_reparent"));
+	tree_menu->set_item_tooltip(-1, TTR("If enabled, Reparent to New Node will create the new node in the center of the selected nodes, if possible."));
+
+	tree_menu->add_check_item(TTR("Hide Filtered Out Parents"), TOOL_HIDE_FILTERED_OUT_PARENTS);
+	tree_menu->set_item_checked(-1, EDITOR_GET("docks/scene_tree/hide_filtered_out_parents"));
 
+	tree_menu->add_separator();
 	PopupMenu *resource_list = memnew(PopupMenu);
 	resource_list->set_auto_translate_mode(AUTO_TRANSLATE_MODE_DISABLED);
 	resource_list->connect("about_to_popup", callable_mp(this, &SceneTreeDock::_list_all_subresources).bind(resource_list));

+ 1 - 1
editor/scene_tree_dock.h

@@ -94,7 +94,7 @@ class SceneTreeDock : public VBoxContainer {
 		TOOL_CREATE_USER_INTERFACE,
 		TOOL_CREATE_FAVORITE,
 		TOOL_CENTER_PARENT,
-
+		TOOL_HIDE_FILTERED_OUT_PARENTS,
 	};
 
 	enum {