1
0
mirror of https://github.com/godotengine/godot.git synced 2025-12-05 17:15:09 +00:00

Merge pull request #112729 from KoBeWi/multi_group_yoink

Edit groups on multiple nodes
This commit is contained in:
Thaddeus Crews
2025-11-20 16:47:07 -06:00
8 changed files with 154 additions and 89 deletions

View File

@@ -93,6 +93,41 @@ void GroupsEditor::_set_group_checked(const String &p_name, bool p_checked) {
ti->set_checked(0, p_checked);
}
void GroupsEditor::_add_to_group(const StringName &p_name, bool p_persist, const Array &p_nodes) {
for (const Variant &v : p_nodes) {
Node *node = Object::cast_to<Node>(v.get_validated_object());
if (node) {
node->add_to_group(p_name, p_persist);
}
}
}
void GroupsEditor::_remove_from_group(const StringName &p_name, const Array &p_nodes) {
for (const Variant &v : p_nodes) {
Node *node = Object::cast_to<Node>(v.get_validated_object());
if (node) {
node->remove_from_group(p_name);
}
}
}
void GroupsEditor::_get_group_mask(const StringName &p_name, Array &r_nodes, bool p_invert) {
for (Node *p_node : selection) {
if (p_invert != p_node->is_in_group(p_name)) {
r_nodes.push_back(p_node);
}
}
}
bool GroupsEditor::_can_edit(const StringName &p_group) {
for (Node *p_node : selection) {
if (!can_edit(p_node, p_group)) {
return false;
}
}
return true;
}
bool GroupsEditor::_has_group(const String &p_name) {
return global_groups.has(p_name) || scene_groups.has(p_name);
}
@@ -102,7 +137,7 @@ void GroupsEditor::_modify_group(Object *p_item, int p_column, int p_id, MouseBu
return;
}
if (!node) {
if (selection.is_empty()) {
return;
}
@@ -177,7 +212,7 @@ void GroupsEditor::_update_tree() {
return;
}
if (!node) {
if (selection.is_empty()) {
return;
}
@@ -190,7 +225,9 @@ void GroupsEditor::_update_tree() {
tree->clear();
List<Node::GroupInfo> groups;
node->get_groups(&groups);
for (Node *p_node : selection) {
p_node->get_groups(&groups);
}
groups.sort_custom<_GroupInfoComparator>();
List<StringName> current_groups;
@@ -220,7 +257,7 @@ void GroupsEditor::_update_tree() {
TreeItem *item = tree->create_item(local_root);
item->set_cell_mode(0, TreeItem::CELL_MODE_CHECK);
item->set_editable(0, can_edit(node, E));
item->set_editable(0, _can_edit(E));
item->set_checked(0, current_groups.find(E) != nullptr);
item->set_text(0, E);
item->set_meta("__local", true);
@@ -252,7 +289,7 @@ void GroupsEditor::_update_tree() {
TreeItem *item = tree->create_item(global_root);
item->set_cell_mode(0, TreeItem::CELL_MODE_CHECK);
item->set_editable(0, can_edit(node, E));
item->set_editable(0, _can_edit(E));
item->set_checked(0, current_groups.find(E) != nullptr);
item->set_text(0, E);
item->set_meta("__local", false);
@@ -307,15 +344,18 @@ void GroupsEditor::_cache_scene_groups(const ObjectID &p_id) {
}
}
void GroupsEditor::set_current(Node *p_node) {
if (node == p_node) {
void GroupsEditor::set_selection(const Vector<Node *> &p_nodes) {
if (p_nodes.is_empty()) {
holder->hide();
select_a_node->show();
selection.clear();
return;
}
node = p_node;
if (!node) {
return;
}
selection = p_nodes;
holder->show();
select_a_node->hide();
if (scene_tree->get_edited_scene_root() != scene_root_node) {
scene_root_node = scene_tree->get_edited_scene_root();
@@ -338,8 +378,10 @@ void GroupsEditor::_item_edited() {
if (ti->is_checked(0)) {
undo_redo->create_action(TTR("Add to Group"));
undo_redo->add_do_method(node, "add_to_group", name, true);
undo_redo->add_undo_method(node, "remove_from_group", name);
Array nodes;
_get_group_mask(name, nodes, true);
undo_redo->add_do_method(this, "_add_to_group", name, true, nodes);
undo_redo->add_undo_method(this, "_remove_from_group", name, nodes);
undo_redo->add_do_method(this, "_set_group_checked", name, true);
undo_redo->add_undo_method(this, "_set_group_checked", name, false);
@@ -353,8 +395,10 @@ void GroupsEditor::_item_edited() {
} else {
undo_redo->create_action(TTR("Remove from Group"));
undo_redo->add_do_method(node, "remove_from_group", name);
undo_redo->add_undo_method(node, "add_to_group", name, true);
Array nodes;
_get_group_mask(name, nodes, false);
undo_redo->add_do_method(this, "_remove_from_group", name, nodes);
undo_redo->add_undo_method(this, "_add_to_group", name, true, nodes);
undo_redo->add_do_method(this, "_set_group_checked", name, false);
undo_redo->add_undo_method(this, "_set_group_checked", name, true);
@@ -489,8 +533,10 @@ void GroupsEditor::_confirm_add() {
EditorUndoRedoManager *undo_redo = EditorUndoRedoManager::get_singleton();
undo_redo->create_action(TTR("Add to Group"));
undo_redo->add_do_method(node, "add_to_group", name, true);
undo_redo->add_undo_method(node, "remove_from_group", name);
Array nodes;
_get_group_mask(name, nodes, true);
undo_redo->add_do_method(this, "_add_to_group", name, true, nodes);
undo_redo->add_undo_method(this, "_remove_from_group", name, nodes);
bool is_local = !global_group_button->is_pressed();
if (is_local) {
@@ -819,6 +865,9 @@ void GroupsEditor::_bind_methods() {
ClassDB::bind_method("_rename_scene_group", &GroupsEditor::_rename_scene_group);
ClassDB::bind_method("_remove_scene_group", &GroupsEditor::_remove_scene_group);
ClassDB::bind_method("_set_group_checked", &GroupsEditor::_set_group_checked);
ClassDB::bind_method("_add_to_group", &GroupsEditor::_add_to_group);
ClassDB::bind_method("_remove_from_group", &GroupsEditor::_remove_from_group);
}
void GroupsEditor::_node_removed(Node *p_node) {
@@ -834,15 +883,19 @@ void GroupsEditor::_node_removed(Node *p_node) {
}
GroupsEditor::GroupsEditor() {
node = nullptr;
scene_tree = SceneTree::get_singleton();
ED_SHORTCUT("groups_editor/delete", TTRC("Delete"), Key::KEY_DELETE);
ED_SHORTCUT("groups_editor/rename", TTRC("Rename"), Key::F2);
ED_SHORTCUT_OVERRIDE("groups_editor/rename", "macos", Key::ENTER);
holder = memnew(VBoxContainer);
holder->set_v_size_flags(SIZE_EXPAND_FILL);
holder->hide();
add_child(holder);
HBoxContainer *hbc = memnew(HBoxContainer);
add_child(hbc);
holder->add_child(hbc);
add = memnew(Button);
add->set_theme_type_variation("FlatMenuButton");
@@ -867,11 +920,21 @@ GroupsEditor::GroupsEditor() {
tree->connect("button_clicked", callable_mp(this, &GroupsEditor::_modify_group));
tree->connect("item_mouse_selected", callable_mp(this, &GroupsEditor::_item_mouse_selected));
tree->connect(SceneStringName(gui_input), callable_mp(this, &GroupsEditor::_groups_gui_input));
add_child(tree);
holder->add_child(tree);
menu = memnew(PopupMenu);
menu->connect(SceneStringName(id_pressed), callable_mp(this, &GroupsEditor::_menu_id_pressed));
tree->add_child(menu);
select_a_node = memnew(Label);
select_a_node->set_focus_mode(FOCUS_ACCESSIBILITY);
select_a_node->set_text(TTRC("Select one or more nodes to edit their groups."));
select_a_node->set_custom_minimum_size(Size2(100 * EDSCALE, 0));
select_a_node->set_v_size_flags(SIZE_EXPAND_FILL);
select_a_node->set_vertical_alignment(VERTICAL_ALIGNMENT_CENTER);
select_a_node->set_horizontal_alignment(HORIZONTAL_ALIGNMENT_CENTER);
select_a_node->set_autowrap_mode(TextServer::AUTOWRAP_WORD_SMART);
add_child(select_a_node);
ProjectSettingsEditor::get_singleton()->get_group_settings()->connect("group_changed", callable_mp(this, &GroupsEditor::_update_groups_and_tree));
}

View File

@@ -52,7 +52,7 @@ class GroupsEditor : public VBoxContainer {
bool groups_dirty = false;
bool update_groups_and_tree_queued = false;
Node *node = nullptr;
LocalVector<Node *> selection;
Node *scene_root_node = nullptr;
SceneTree *scene_tree = nullptr;
@@ -73,9 +73,11 @@ class GroupsEditor : public VBoxContainer {
PopupMenu *menu = nullptr;
VBoxContainer *holder = nullptr;
LineEdit *filter = nullptr;
Button *add = nullptr;
Tree *tree = nullptr;
Label *select_a_node = nullptr;
HashMap<ObjectID, HashMap<StringName, bool>> scene_groups_cache;
HashMap<StringName, bool> scene_groups_for_caching;
@@ -122,6 +124,11 @@ class GroupsEditor : public VBoxContainer {
void _node_removed(Node *p_node);
void _add_to_group(const StringName &p_name, bool p_persist, const Array &p_nodes);
void _remove_from_group(const StringName &p_name, const Array &p_nodes);
void _get_group_mask(const StringName &p_name, Array &r_nodes, bool p_invert);
bool _can_edit(const StringName &p_group);
protected:
void _notification(int p_what);
static void _bind_methods();
@@ -134,7 +141,7 @@ public:
CONVERT_GROUP,
};
void set_current(Node *p_node);
void set_selection(const Vector<Node *> &p_nodes);
GroupsEditor();
};

View File

@@ -55,15 +55,7 @@ void NodeDock::save_layout_to_config(Ref<ConfigFile> &p_layout, const String &p_
void NodeDock::load_layout_from_config(const Ref<ConfigFile> &p_layout, const String &p_section) {
const int current_tab = p_layout->get_value(p_section, "current_tab", 0);
if (select_a_node->is_visible()) {
if (current_tab == 0) {
groups_button->set_pressed_no_signal(false);
connections_button->set_pressed_no_signal(true);
} else if (current_tab == 1) {
groups_button->set_pressed_no_signal(true);
connections_button->set_pressed_no_signal(false);
}
} else if (current_tab == 0) {
if (current_tab == 0) {
show_connections();
} else if (current_tab == 1) {
show_groups();
@@ -83,32 +75,17 @@ void NodeDock::update_lists() {
connections->update_tree();
}
void NodeDock::set_object(Object *p_object) {
connections->set_object(p_object);
groups->set_current(Object::cast_to<Node>(p_object));
void NodeDock::set_selection(const Vector<Object *> &p_objects) {
connections->set_selection(p_objects);
if (p_object) {
if (connections_button->is_pressed()) {
connections->show();
} else {
groups->show();
Vector<Node *> nodes;
for (Object *obj : p_objects) {
Node *n = Object::cast_to<Node>(obj);
if (n) {
nodes.append(n);
}
if (Object::cast_to<Resource>(p_object)) {
show_connections();
groups_button->set_disabled(true);
} else {
groups_button->set_disabled(false);
}
mode_hb->show();
select_a_node->hide();
} else {
connections->hide();
groups->hide();
mode_hb->hide();
select_a_node->show();
}
groups->set_selection(nodes);
}
NodeDock::NodeDock() {
@@ -123,13 +100,11 @@ NodeDock::NodeDock() {
mode_hb = memnew(HBoxContainer);
main_vb->add_child(mode_hb);
mode_hb->hide();
connections_button = memnew(Button);
connections_button->set_theme_type_variation(SceneStringName(FlatButton));
connections_button->set_text(TTRC("Signals"));
connections_button->set_toggle_mode(true);
connections_button->set_pressed(true);
connections_button->set_h_size_flags(SIZE_EXPAND_FILL);
connections_button->set_clip_text(true);
mode_hb->add_child(connections_button);
@@ -139,7 +114,6 @@ NodeDock::NodeDock() {
groups_button->set_theme_type_variation(SceneStringName(FlatButton));
groups_button->set_text(TTRC("Groups"));
groups_button->set_toggle_mode(true);
groups_button->set_pressed(false);
groups_button->set_h_size_flags(SIZE_EXPAND_FILL);
groups_button->set_clip_text(true);
mode_hb->add_child(groups_button);
@@ -155,15 +129,7 @@ NodeDock::NodeDock() {
groups->set_v_size_flags(SIZE_EXPAND_FILL);
groups->hide();
select_a_node = memnew(Label);
select_a_node->set_focus_mode(FOCUS_ACCESSIBILITY);
select_a_node->set_text(TTRC("Select a single node to edit its signals and groups, or select an independent resource to view its signals."));
select_a_node->set_custom_minimum_size(Size2(100 * EDSCALE, 0));
select_a_node->set_v_size_flags(SIZE_EXPAND_FILL);
select_a_node->set_vertical_alignment(VERTICAL_ALIGNMENT_CENTER);
select_a_node->set_horizontal_alignment(HORIZONTAL_ALIGNMENT_CENTER);
select_a_node->set_autowrap_mode(TextServer::AUTOWRAP_WORD_SMART);
main_vb->add_child(select_a_node);
show_connections();
}
NodeDock::~NodeDock() {

View File

@@ -47,8 +47,6 @@ class NodeDock : public EditorDock {
HBoxContainer *mode_hb = nullptr;
Label *select_a_node = nullptr;
private:
inline static NodeDock *singleton = nullptr;
@@ -62,7 +60,7 @@ protected:
virtual void load_layout_from_config(const Ref<ConfigFile> &p_layout, const String &p_section) override;
public:
void set_object(Object *p_object);
void set_selection(const Vector<Object *> &p_objects);
void show_groups();
void show_connections();

View File

@@ -1351,7 +1351,7 @@ void SceneTreeDock::_tool_selected(int p_tool, bool p_confirm_override) {
undo_redo->add_undo_method(node, "set_scene_file_path", node->get_scene_file_path());
_node_replace_owner(node, node, root);
_node_strip_signal_inheritance(node);
NodeDock::get_singleton()->set_object(node); // Refresh.
NodeDock::get_singleton()->set_selection(Vector<Object *>{ node }); // Refresh.
undo_redo->add_do_method(scene_tree, "update_tree");
undo_redo->add_undo_method(scene_tree, "update_tree");
undo_redo->commit_action();
@@ -2855,7 +2855,7 @@ void SceneTreeDock::_delete_confirm(bool p_cut) {
editor_history->cleanup_history();
InspectorDock::get_singleton()->call("_prepare_history");
InspectorDock::get_singleton()->update(nullptr);
NodeDock::get_singleton()->set_object(nullptr);
NodeDock::get_singleton()->set_selection(Vector<Object *>{});
}
void SceneTreeDock::_update_script_button() {