Merge pull request #60965 from DarkMessiah/global-groups-implementation

Implement project-wide node groups
This commit is contained in:
Yuri Sizov 2023-12-20 15:07:20 +01:00
commit 6296333bad
14 changed files with 1654 additions and 734 deletions

View File

@ -281,6 +281,11 @@ bool ProjectSettings::_set(const StringName &p_name, const Variant &p_value) {
if (autoloads.has(node_name)) {
remove_autoload(node_name);
}
} else if (p_name.operator String().begins_with("global_group/")) {
String group_name = p_name.operator String().get_slice("/", 1);
if (global_groups.has(group_name)) {
remove_global_group(group_name);
}
}
} else {
if (p_name == CoreStringNames::get_singleton()->_custom_features) {
@ -327,6 +332,9 @@ bool ProjectSettings::_set(const StringName &p_name, const Variant &p_value) {
autoload.path = path;
}
add_autoload(autoload);
} else if (p_name.operator String().begins_with("global_group/")) {
String group_name = p_name.operator String().get_slice("/", 1);
add_global_group(group_name, p_value);
}
}
@ -674,6 +682,8 @@ Error ProjectSettings::setup(const String &p_path, const String &p_main_pack, bo
Compression::gzip_level = GLOBAL_GET("compression/formats/gzip/compression_level");
load_scene_groups_cache();
project_loaded = err == OK;
return err;
}
@ -1241,6 +1251,73 @@ ProjectSettings::AutoloadInfo ProjectSettings::get_autoload(const StringName &p_
return autoloads[p_name];
}
const HashMap<StringName, String> &ProjectSettings::get_global_groups_list() const {
return global_groups;
}
void ProjectSettings::add_global_group(const StringName &p_name, const String &p_description) {
ERR_FAIL_COND_MSG(p_name == StringName(), "Trying to add global group with no name.");
global_groups[p_name] = p_description;
}
void ProjectSettings::remove_global_group(const StringName &p_name) {
ERR_FAIL_COND_MSG(!global_groups.has(p_name), "Trying to remove non-existent global group.");
global_groups.erase(p_name);
}
bool ProjectSettings::has_global_group(const StringName &p_name) const {
return global_groups.has(p_name);
}
void ProjectSettings::remove_scene_groups_cache(const StringName &p_path) {
scene_groups_cache.erase(p_path);
}
void ProjectSettings::add_scene_groups_cache(const StringName &p_path, const HashSet<StringName> &p_cache) {
scene_groups_cache[p_path] = p_cache;
}
void ProjectSettings::save_scene_groups_cache() {
Ref<ConfigFile> cf;
cf.instantiate();
for (const KeyValue<StringName, HashSet<StringName>> &E : scene_groups_cache) {
if (E.value.is_empty()) {
continue;
}
Array list;
for (const StringName &group : E.value) {
list.push_back(group);
}
cf->set_value(E.key, "groups", list);
}
cf->save(get_scene_groups_cache_path());
}
String ProjectSettings::get_scene_groups_cache_path() const {
return get_project_data_path().path_join("scene_groups_cache.cfg");
}
void ProjectSettings::load_scene_groups_cache() {
Ref<ConfigFile> cf;
cf.instantiate();
if (cf->load(get_scene_groups_cache_path()) == OK) {
List<String> scene_paths;
cf->get_sections(&scene_paths);
for (const String &E : scene_paths) {
Array scene_groups = cf->get_value(E, "groups", Array());
HashSet<StringName> cache;
for (int i = 0; i < scene_groups.size(); ++i) {
cache.insert(scene_groups[i]);
}
add_scene_groups_cache(E, cache);
}
}
}
const HashMap<StringName, HashSet<StringName>> &ProjectSettings::get_scene_groups_cache() const {
return scene_groups_cache;
}
void ProjectSettings::_bind_methods() {
ClassDB::bind_method(D_METHOD("has_setting", "name"), &ProjectSettings::has_setting);
ClassDB::bind_method(D_METHOD("set_setting", "name", "value"), &ProjectSettings::set_setting);

View File

@ -106,6 +106,8 @@ protected:
LocalVector<String> hidden_prefixes;
HashMap<StringName, AutoloadInfo> autoloads;
HashMap<StringName, String> global_groups;
HashMap<StringName, HashSet<StringName>> scene_groups_cache;
Array global_class_list;
bool is_global_class_list_loaded = false;
@ -208,6 +210,18 @@ public:
bool has_autoload(const StringName &p_autoload) const;
AutoloadInfo get_autoload(const StringName &p_name) const;
const HashMap<StringName, String> &get_global_groups_list() const;
void add_global_group(const StringName &p_name, const String &p_description);
void remove_global_group(const StringName &p_name);
bool has_global_group(const StringName &p_name) const;
const HashMap<StringName, HashSet<StringName>> &get_scene_groups_cache() const;
void add_scene_groups_cache(const StringName &p_path, const HashSet<StringName> &p_cache);
void remove_scene_groups_cache(const StringName &p_path);
void save_scene_groups_cache();
String get_scene_groups_cache_path() const;
void load_scene_groups_cache();
ProjectSettings();
~ProjectSettings();
};

View File

@ -44,6 +44,7 @@
#include "editor/editor_paths.h"
#include "editor/editor_resource_preview.h"
#include "editor/editor_settings.h"
#include "scene/resources/packed_scene.h"
EditorFileSystem *EditorFileSystem::singleton = nullptr;
//the name is the version, to keep compatibility with different versions of Godot
@ -615,6 +616,9 @@ bool EditorFileSystem::_update_scan_actions() {
if (ClassDB::is_parent_class(ia.new_file->type, SNAME("Script"))) {
_queue_update_script_class(ia.dir->get_file_path(idx));
}
if (ia.new_file->type == SNAME("PackedScene")) {
_queue_update_scene_groups(ia.dir->get_file_path(idx));
}
} break;
case ItemAction::ACTION_FILE_REMOVE: {
@ -624,6 +628,9 @@ bool EditorFileSystem::_update_scan_actions() {
if (ClassDB::is_parent_class(ia.dir->files[idx]->type, SNAME("Script"))) {
_queue_update_script_class(ia.dir->get_file_path(idx));
}
if (ia.dir->files[idx]->type == SNAME("PackedScene")) {
_queue_update_scene_groups(ia.dir->get_file_path(idx));
}
_delete_internal_files(ia.dir->files[idx]->file);
memdelete(ia.dir->files[idx]);
@ -662,6 +669,9 @@ bool EditorFileSystem::_update_scan_actions() {
if (ClassDB::is_parent_class(ia.dir->files[idx]->type, SNAME("Script"))) {
_queue_update_script_class(full_path);
}
if (ia.dir->files[idx]->type == SNAME("PackedScene")) {
_queue_update_scene_groups(full_path);
}
reloads.push_back(full_path);
@ -732,6 +742,7 @@ void EditorFileSystem::scan() {
_update_scan_actions();
scanning = false;
_update_pending_script_classes();
_update_pending_scene_groups();
emit_signal(SNAME("filesystem_changed"));
emit_signal(SNAME("sources_changed"), sources_changed.size() > 0);
first_scan = false;
@ -942,6 +953,9 @@ void EditorFileSystem::_scan_new_dir(EditorFileSystemDirectory *p_dir, Ref<DirAc
if (ClassDB::is_parent_class(fi->type, SNAME("Script"))) {
_queue_update_script_class(path);
}
if (fi->type == SNAME("PackedScene")) {
_queue_update_scene_groups(path);
}
}
}
@ -1196,6 +1210,7 @@ void EditorFileSystem::scan_changes() {
_scan_fs_changes(filesystem, sp);
bool changed = _update_scan_actions();
_update_pending_script_classes();
_update_pending_scene_groups();
if (changed) {
emit_signal(SNAME("filesystem_changed"));
}
@ -1262,6 +1277,7 @@ void EditorFileSystem::_notification(int p_what) {
}
bool changed = _update_scan_actions();
_update_pending_script_classes();
_update_pending_scene_groups();
if (changed) {
emit_signal(SNAME("filesystem_changed"));
}
@ -1281,6 +1297,7 @@ void EditorFileSystem::_notification(int p_what) {
thread.wait_to_finish();
_update_scan_actions();
_update_pending_script_classes();
_update_pending_scene_groups();
emit_signal(SNAME("filesystem_changed"));
emit_signal(SNAME("sources_changed"), sources_changed.size() > 0);
first_scan = false;
@ -1635,6 +1652,65 @@ void EditorFileSystem::_queue_update_script_class(const String &p_path) {
update_script_mutex.unlock();
}
void EditorFileSystem::_update_scene_groups() {
update_scene_mutex.lock();
for (const String &path : update_scene_paths) {
ProjectSettings::get_singleton()->remove_scene_groups_cache(path);
int index = -1;
EditorFileSystemDirectory *efd = find_file(path, &index);
if (!efd || index < 0) {
// The file was removed.
continue;
}
const HashSet<StringName> scene_groups = _get_scene_groups(path);
if (!scene_groups.is_empty()) {
ProjectSettings::get_singleton()->add_scene_groups_cache(path, scene_groups);
}
}
update_scene_paths.clear();
update_scene_mutex.unlock();
ProjectSettings::get_singleton()->save_scene_groups_cache();
}
void EditorFileSystem::_update_pending_scene_groups() {
if (!FileAccess::exists(ProjectSettings::get_singleton()->get_scene_groups_cache_path())) {
_get_all_scenes(get_filesystem(), update_scene_paths);
_update_scene_groups();
} else if (!update_scene_paths.is_empty()) {
_update_scene_groups();
}
}
void EditorFileSystem::_queue_update_scene_groups(const String &p_path) {
update_scene_mutex.lock();
update_scene_paths.insert(p_path);
update_scene_mutex.unlock();
}
void EditorFileSystem::_get_all_scenes(EditorFileSystemDirectory *p_dir, HashSet<String> &r_list) {
for (int i = 0; i < p_dir->get_file_count(); i++) {
if (p_dir->get_file_type(i) == SNAME("PackedScene")) {
r_list.insert(p_dir->get_file_path(i));
}
}
for (int i = 0; i < p_dir->get_subdir_count(); i++) {
_get_all_scenes(p_dir->get_subdir(i), r_list);
}
}
HashSet<StringName> EditorFileSystem::_get_scene_groups(const String &p_path) {
Ref<PackedScene> packed_scene = ResourceLoader::load(p_path);
ERR_FAIL_COND_V(packed_scene.is_null(), HashSet<StringName>());
return packed_scene->get_state()->get_all_groups();
}
void EditorFileSystem::update_file(const String &p_file) {
ERR_FAIL_COND(p_file.is_empty());
EditorFileSystemDirectory *fs = nullptr;
@ -1658,12 +1734,16 @@ void EditorFileSystem::update_file(const String &p_file) {
if (ClassDB::is_parent_class(fs->files[cpos]->type, SNAME("Script"))) {
_queue_update_script_class(p_file);
}
if (fs->files[cpos]->type == SNAME("PackedScene")) {
_queue_update_scene_groups(p_file);
}
memdelete(fs->files[cpos]);
fs->files.remove_at(cpos);
}
_update_pending_script_classes();
_update_pending_scene_groups();
call_deferred(SNAME("emit_signal"), "filesystem_changed"); //update later
return;
}
@ -1730,8 +1810,12 @@ void EditorFileSystem::update_file(const String &p_file) {
if (ClassDB::is_parent_class(fs->files[cpos]->type, SNAME("Script"))) {
_queue_update_script_class(p_file);
}
if (fs->files[cpos]->type == SNAME("PackedScene")) {
_queue_update_scene_groups(p_file);
}
_update_pending_script_classes();
_update_pending_scene_groups();
call_deferred(SNAME("emit_signal"), "filesystem_changed"); //update later
}
@ -2341,6 +2425,7 @@ void EditorFileSystem::reimport_files(const Vector<String> &p_files) {
_save_filesystem_cache();
_update_pending_script_classes();
_update_pending_scene_groups();
importing = false;
if (!is_scanning()) {
emit_signal(SNAME("filesystem_changed"));

View File

@ -267,6 +267,14 @@ class EditorFileSystem : public Node {
void _update_script_classes();
void _update_pending_script_classes();
Mutex update_scene_mutex;
HashSet<String> update_scene_paths;
void _queue_update_scene_groups(const String &p_path);
void _update_scene_groups();
void _update_pending_scene_groups();
HashSet<StringName> _get_scene_groups(const String &p_path);
void _get_all_scenes(EditorFileSystemDirectory *p_dir, HashSet<String> &r_list);
String _get_global_script_class(const String &p_type, const String &p_path, String *r_extends, String *r_icon_path) const;
static Error _resource_import(const String &p_path);

View File

@ -0,0 +1,537 @@
/**************************************************************************/
/* group_settings_editor.cpp */
/**************************************************************************/
/* This file is part of: */
/* GODOT ENGINE */
/* https://godotengine.org */
/**************************************************************************/
/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
/* */
/* Permission is hereby granted, free of charge, to any person obtaining */
/* a copy of this software and associated documentation files (the */
/* "Software"), to deal in the Software without restriction, including */
/* without limitation the rights to use, copy, modify, merge, publish, */
/* distribute, sublicense, and/or sell copies of the Software, and to */
/* permit persons to whom the Software is furnished to do so, subject to */
/* the following conditions: */
/* */
/* The above copyright notice and this permission notice shall be */
/* included in all copies or substantial portions of the Software. */
/* */
/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */
/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
/**************************************************************************/
#include "group_settings_editor.h"
#include "core/config/project_settings.h"
#include "editor/editor_scale.h"
#include "editor/editor_undo_redo_manager.h"
#include "editor/filesystem_dock.h"
#include "editor/gui/editor_validation_panel.h"
#include "editor/scene_tree_dock.h"
#include "editor_file_system.h"
#include "editor_node.h"
#include "scene/resources/packed_scene.h"
void GroupSettingsEditor::_notification(int p_what) {
switch (p_what) {
case NOTIFICATION_ENTER_TREE: {
update_groups();
} break;
}
}
void GroupSettingsEditor::_item_edited() {
if (updating_groups) {
return;
}
TreeItem *ti = tree->get_edited();
int column = tree->get_edited_column();
if (!ti) {
return;
}
EditorUndoRedoManager *undo_redo = EditorUndoRedoManager::get_singleton();
if (column == 1) {
// Description Edited.
String name = ti->get_text(0);
String new_description = ti->get_text(1);
String old_description = ti->get_meta("__description");
if (new_description == old_description) {
return;
}
name = GLOBAL_GROUP_PREFIX + name;
undo_redo->create_action(TTR("Set Group Description"));
undo_redo->add_do_property(ProjectSettings::get_singleton(), name, new_description);
undo_redo->add_undo_property(ProjectSettings::get_singleton(), name, old_description);
undo_redo->add_do_method(this, "call_deferred", "update_groups");
undo_redo->add_undo_method(this, "call_deferred", "update_groups");
undo_redo->add_do_method(this, "emit_signal", group_changed);
undo_redo->add_undo_method(this, "emit_signal", group_changed);
undo_redo->commit_action();
}
}
void GroupSettingsEditor::_item_button_pressed(Object *p_item, int p_column, int p_id, MouseButton p_button) {
if (p_button != MouseButton::LEFT) {
return;
}
TreeItem *ti = Object::cast_to<TreeItem>(p_item);
if (!ti) {
return;
}
ti->select(0);
_show_remove_dialog();
}
String GroupSettingsEditor::_check_new_group_name(const String &p_name) {
if (p_name.is_empty()) {
return TTR("Invalid group name. It cannot be empty.");
}
if (ProjectSettings::get_singleton()->has_global_group(p_name)) {
return vformat(TTR("A group with the name '%s' already exists."), p_name);
}
return "";
}
void GroupSettingsEditor::_check_rename() {
String new_name = rename_group->get_text().strip_edges();
String old_name = rename_group_dialog->get_meta("__name");
if (new_name == old_name) {
return;
}
if (new_name.is_empty()) {
rename_validation_panel->set_message(EditorValidationPanel::MSG_ID_DEFAULT, TTR("Group can't be empty."), EditorValidationPanel::MSG_ERROR);
} else if (ProjectSettings::get_singleton()->has_global_group(new_name)) {
rename_validation_panel->set_message(EditorValidationPanel::MSG_ID_DEFAULT, TTR("Group already exists."), EditorValidationPanel::MSG_ERROR);
}
}
void GroupSettingsEditor::_bind_methods() {
ClassDB::bind_method(D_METHOD("remove_references"), &GroupSettingsEditor::remove_references);
ClassDB::bind_method(D_METHOD("rename_references"), &GroupSettingsEditor::rename_references);
ClassDB::bind_method(D_METHOD("update_groups"), &GroupSettingsEditor::update_groups);
ADD_SIGNAL(MethodInfo("group_changed"));
}
void GroupSettingsEditor::_add_group(const String &p_name, const String &p_description) {
String name = p_name.strip_edges();
String error = _check_new_group_name(name);
if (!error.is_empty()) {
show_message(error);
return;
}
EditorUndoRedoManager *undo_redo = EditorUndoRedoManager::get_singleton();
undo_redo->create_action(TTR("Add Group"));
name = GLOBAL_GROUP_PREFIX + name;
undo_redo->add_do_property(ProjectSettings::get_singleton(), name, p_description);
undo_redo->add_undo_property(ProjectSettings::get_singleton(), name, Variant());
undo_redo->add_do_method(this, "call_deferred", "update_groups");
undo_redo->add_undo_method(this, "call_deferred", "update_groups");
undo_redo->add_do_method(this, "emit_signal", group_changed);
undo_redo->add_undo_method(this, "emit_signal", group_changed);
undo_redo->commit_action();
group_name->clear();
group_description->clear();
}
void GroupSettingsEditor::_add_group() {
_add_group(group_name->get_text(), group_description->get_text());
}
void GroupSettingsEditor::_text_submitted(const String &p_text) {
if (!add_button->is_disabled()) {
_add_group();
}
}
void GroupSettingsEditor::_group_name_text_changed(const String &p_name) {
String error = _check_new_group_name(p_name.strip_edges());
add_button->set_tooltip_text(error);
add_button->set_disabled(!error.is_empty());
}
void GroupSettingsEditor::_modify_references(const StringName &p_name, const StringName &p_new_name, bool p_is_rename) {
HashSet<String> scenes;
HashMap<StringName, HashSet<StringName>> scene_groups_cache = ProjectSettings::get_singleton()->get_scene_groups_cache();
for (const KeyValue<StringName, HashSet<StringName>> &E : scene_groups_cache) {
if (E.value.has(p_name)) {
scenes.insert(E.key);
}
}
int steps = scenes.size();
Vector<EditorData::EditedScene> edited_scenes = EditorNode::get_editor_data().get_edited_scenes();
for (const EditorData::EditedScene &es : edited_scenes) {
if (!es.root) {
continue;
}
if (es.path.is_empty()) {
++steps;
} else if (!scenes.has(es.path)) {
++steps;
}
}
String progress_task = p_is_rename ? "rename_reference" : "remove_references";
String progress_label = p_is_rename ? TTR("Renaming Group References") : TTR("Removing Group References");
EditorProgress progress(progress_task, progress_label, steps);
int step = 0;
// Update opened scenes.
HashSet<String> edited_scenes_path;
for (const EditorData::EditedScene &es : edited_scenes) {
if (!es.root) {
continue;
}
progress.step(es.path, step++);
bool edited = p_is_rename ? rename_node_references(es.root, p_name, p_new_name) : remove_node_references(es.root, p_name);
if (!es.path.is_empty()) {
scenes.erase(es.path);
if (edited) {
edited_scenes_path.insert(es.path);
}
}
}
if (!edited_scenes_path.is_empty()) {
EditorNode::get_singleton()->save_scene_list(edited_scenes_path);
SceneTreeDock::get_singleton()->get_tree_editor()->update_tree();
}
for (const String &E : scenes) {
Ref<PackedScene> packed_scene = ResourceLoader::load(E);
progress.step(E, step++);
ERR_CONTINUE(packed_scene.is_null());
if (p_is_rename) {
if (packed_scene->get_state()->rename_group_references(p_name, p_new_name)) {
ResourceSaver::save(packed_scene, E);
}
} else {
if (packed_scene->get_state()->remove_group_references(p_name)) {
ResourceSaver::save(packed_scene, E);
}
}
}
}
void GroupSettingsEditor::remove_references(const StringName &p_name) {
_modify_references(p_name, StringName(), false);
}
void GroupSettingsEditor::rename_references(const StringName &p_old_name, const StringName &p_new_name) {
_modify_references(p_old_name, p_new_name, true);
}
bool GroupSettingsEditor::remove_node_references(Node *p_node, const StringName &p_name) {
bool edited = false;
if (p_node->is_in_group(p_name)) {
p_node->remove_from_group(p_name);
edited = true;
}
for (int i = 0; i < p_node->get_child_count(); i++) {
edited |= remove_node_references(p_node->get_child(i), p_name);
}
return edited;
}
bool GroupSettingsEditor::rename_node_references(Node *p_node, const StringName &p_old_name, const StringName &p_new_name) {
bool edited = false;
if (p_node->is_in_group(p_old_name)) {
p_node->remove_from_group(p_old_name);
p_node->add_to_group(p_new_name, true);
edited = true;
}
for (int i = 0; i < p_node->get_child_count(); i++) {
edited |= rename_node_references(p_node->get_child(i), p_old_name, p_new_name);
}
return edited;
}
void GroupSettingsEditor::update_groups() {
if (updating_groups) {
return;
}
updating_groups = true;
groups_cache = ProjectSettings::get_singleton()->get_global_groups_list();
tree->clear();
TreeItem *root = tree->create_item();
List<StringName> keys;
for (const KeyValue<StringName, String> &E : groups_cache) {
keys.push_back(E.key);
}
keys.sort_custom<NoCaseComparator>();
for (const StringName &E : keys) {
TreeItem *item = tree->create_item(root);
item->set_meta("__name", E);
item->set_meta("__description", groups_cache[E]);
item->set_text(0, E);
item->set_editable(0, false);
item->set_text(1, groups_cache[E]);
item->set_editable(1, true);
item->add_button(2, get_editor_theme_icon(SNAME("Remove")));
item->set_selectable(2, false);
}
updating_groups = false;
}
void GroupSettingsEditor::connect_filesystem_dock_signals(FileSystemDock *p_fs_dock) {
p_fs_dock->connect("files_moved", callable_mp(ProjectSettings::get_singleton(), &ProjectSettings::remove_scene_groups_cache).unbind(1));
p_fs_dock->connect("file_removed", callable_mp(ProjectSettings::get_singleton(), &ProjectSettings::remove_scene_groups_cache));
}
void GroupSettingsEditor::_confirm_rename() {
TreeItem *ti = tree->get_selected();
if (!ti) {
return;
}
String old_name = ti->get_meta("__name");
String new_name = rename_group->get_text().strip_edges();
if (old_name == new_name) {
return;
}
EditorUndoRedoManager *undo_redo = EditorUndoRedoManager::get_singleton();
undo_redo->create_action(TTR("Rename Group"));
String property_new_name = GLOBAL_GROUP_PREFIX + new_name;
String property_old_name = GLOBAL_GROUP_PREFIX + old_name;
String description = ti->get_meta("__description");
undo_redo->add_do_property(ProjectSettings::get_singleton(), property_new_name, description);
undo_redo->add_undo_property(ProjectSettings::get_singleton(), property_new_name, Variant());
undo_redo->add_do_property(ProjectSettings::get_singleton(), property_old_name, Variant());
undo_redo->add_undo_property(ProjectSettings::get_singleton(), property_old_name, description);
if (rename_check_box->is_pressed()) {
undo_redo->add_do_method(this, "rename_references", old_name, new_name);
undo_redo->add_undo_method(this, "rename_references", new_name, old_name);
}
undo_redo->add_do_method(this, "call_deferred", "update_groups");
undo_redo->add_undo_method(this, "call_deferred", "update_groups");
undo_redo->add_do_method(this, "emit_signal", group_changed);
undo_redo->add_undo_method(this, "emit_signal", group_changed);
undo_redo->commit_action();
}
void GroupSettingsEditor::_confirm_delete() {
TreeItem *ti = tree->get_selected();
if (!ti) {
return;
}
String name = ti->get_text(0);
String description = groups_cache[name];
String property_name = GLOBAL_GROUP_PREFIX + name;
EditorUndoRedoManager *undo_redo = EditorUndoRedoManager::get_singleton();
undo_redo->create_action(TTR("Remove Group"));
undo_redo->add_do_property(ProjectSettings::get_singleton(), property_name, Variant());
undo_redo->add_undo_property(ProjectSettings::get_singleton(), property_name, description);
if (remove_check_box->is_pressed()) {
undo_redo->add_do_method(this, "remove_references", name);
}
undo_redo->add_do_method(this, "call_deferred", "update_groups");
undo_redo->add_undo_method(this, "call_deferred", "update_groups");
undo_redo->add_do_method(this, "emit_signal", group_changed);
undo_redo->add_undo_method(this, "emit_signal", group_changed);
undo_redo->commit_action();
}
void GroupSettingsEditor::show_message(const String &p_message) {
message->set_text(p_message);
message->popup_centered();
}
void GroupSettingsEditor::_show_remove_dialog() {
if (!remove_dialog) {
remove_dialog = memnew(ConfirmationDialog);
remove_dialog->connect("confirmed", callable_mp(this, &GroupSettingsEditor::_confirm_delete));
VBoxContainer *vbox = memnew(VBoxContainer);
remove_label = memnew(Label);
vbox->add_child(remove_label);
remove_check_box = memnew(CheckBox);
remove_check_box->set_text(TTR("Delete references from all scenes"));
vbox->add_child(remove_check_box);
remove_dialog->add_child(vbox);
add_child(remove_dialog);
}
TreeItem *ti = tree->get_selected();
if (!ti) {
return;
}
remove_check_box->set_pressed(false);
remove_label->set_text(vformat(TTR("Delete group \"%s\"?"), ti->get_text(0)));
remove_dialog->reset_size();
remove_dialog->popup_centered();
}
void GroupSettingsEditor::_show_rename_dialog() {
if (!rename_group_dialog) {
rename_group_dialog = memnew(ConfirmationDialog);
rename_group_dialog->set_title(TTR("Rename Group"));
rename_group_dialog->connect("confirmed", callable_mp(this, &GroupSettingsEditor::_confirm_rename));
VBoxContainer *vbc = memnew(VBoxContainer);
rename_group_dialog->add_child(vbc);
HBoxContainer *hbc = memnew(HBoxContainer);
hbc->add_child(memnew(Label(TTR("Name:"))));
rename_group = memnew(LineEdit);
rename_group->set_custom_minimum_size(Size2(300 * EDSCALE, 1));
hbc->add_child(rename_group);
vbc->add_child(hbc);
rename_group_dialog->register_text_enter(rename_group);
rename_validation_panel = memnew(EditorValidationPanel);
rename_validation_panel->add_line(EditorValidationPanel::MSG_ID_DEFAULT, TTR("Group name is valid."));
rename_validation_panel->set_update_callback(callable_mp(this, &GroupSettingsEditor::_check_rename));
rename_validation_panel->set_accept_button(rename_group_dialog->get_ok_button());
rename_group->connect("text_changed", callable_mp(rename_validation_panel, &EditorValidationPanel::update).unbind(1));
vbc->add_child(rename_validation_panel);
rename_check_box = memnew(CheckBox);
rename_check_box->set_text(TTR("Rename references in all scenes"));
vbc->add_child(rename_check_box);
add_child(rename_group_dialog);
}
TreeItem *ti = tree->get_selected();
if (!ti) {
return;
}
rename_check_box->set_pressed(false);
String name = ti->get_meta("__name");
rename_group->set_text(name);
rename_group_dialog->set_meta("__name", name);
rename_validation_panel->update();
rename_group_dialog->reset_size();
rename_group_dialog->popup_centered();
rename_group->select_all();
rename_group->grab_focus();
}
GroupSettingsEditor::GroupSettingsEditor() {
ProjectSettings::get_singleton()->add_hidden_prefix("global_group/");
HBoxContainer *hbc = memnew(HBoxContainer);
add_child(hbc);
Label *l = memnew(Label);
l->set_text(TTR("Name:"));
hbc->add_child(l);
group_name = memnew(LineEdit);
group_name->set_h_size_flags(SIZE_EXPAND_FILL);
group_name->set_clear_button_enabled(true);
group_name->connect("text_changed", callable_mp(this, &GroupSettingsEditor::_group_name_text_changed));
group_name->connect("text_submitted", callable_mp(this, &GroupSettingsEditor::_text_submitted));
hbc->add_child(group_name);
l = memnew(Label);
l->set_text(TTR("Description:"));
hbc->add_child(l);
group_description = memnew(LineEdit);
group_description->set_clear_button_enabled(true);
group_description->set_h_size_flags(SIZE_EXPAND_FILL);
group_description->connect("text_submitted", callable_mp(this, &GroupSettingsEditor::_text_submitted));
hbc->add_child(group_description);
add_button = memnew(Button);
add_button->set_text(TTR("Add"));
add_button->set_disabled(true);
add_button->connect("pressed", callable_mp(this, &GroupSettingsEditor::_add_group));
hbc->add_child(add_button);
tree = memnew(Tree);
tree->set_hide_root(true);
tree->set_select_mode(Tree::SELECT_SINGLE);
tree->set_allow_reselect(true);
tree->set_columns(3);
tree->set_column_titles_visible(true);
tree->set_column_title(0, TTR("Name"));
tree->set_column_title(1, TTR("Description"));
tree->set_column_expand(2, false);
tree->connect("item_edited", callable_mp(this, &GroupSettingsEditor::_item_edited));
tree->connect("item_activated", callable_mp(this, &GroupSettingsEditor::_show_rename_dialog));
tree->connect("button_clicked", callable_mp(this, &GroupSettingsEditor::_item_button_pressed));
tree->set_v_size_flags(SIZE_EXPAND_FILL);
add_child(tree, true);
message = memnew(AcceptDialog);
add_child(message);
}

View File

@ -0,0 +1,107 @@
/**************************************************************************/
/* group_settings_editor.h */
/**************************************************************************/
/* This file is part of: */
/* GODOT ENGINE */
/* https://godotengine.org */
/**************************************************************************/
/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
/* */
/* Permission is hereby granted, free of charge, to any person obtaining */
/* a copy of this software and associated documentation files (the */
/* "Software"), to deal in the Software without restriction, including */
/* without limitation the rights to use, copy, modify, merge, publish, */
/* distribute, sublicense, and/or sell copies of the Software, and to */
/* permit persons to whom the Software is furnished to do so, subject to */
/* the following conditions: */
/* */
/* The above copyright notice and this permission notice shall be */
/* included in all copies or substantial portions of the Software. */
/* */
/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */
/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
/**************************************************************************/
#ifndef GROUP_SETTINGS_EDITOR_H
#define GROUP_SETTINGS_EDITOR_H
#include "scene/gui/dialogs.h"
class CheckBox;
class EditorFileSystemDirectory;
class EditorValidationPanel;
class FileSystemDock;
class Label;
class Tree;
class GroupSettingsEditor : public VBoxContainer {
GDCLASS(GroupSettingsEditor, VBoxContainer);
const String GLOBAL_GROUP_PREFIX = "global_group/";
const StringName group_changed = "group_changed";
HashMap<StringName, String> groups_cache;
bool updating_groups = false;
AcceptDialog *message = nullptr;
Tree *tree = nullptr;
LineEdit *group_name = nullptr;
LineEdit *group_description = nullptr;
Button *add_button = nullptr;
ConfirmationDialog *remove_dialog = nullptr;
CheckBox *remove_check_box = nullptr;
Label *remove_label = nullptr;
ConfirmationDialog *rename_group_dialog = nullptr;
LineEdit *rename_group = nullptr;
CheckBox *rename_check_box = nullptr;
EditorValidationPanel *rename_validation_panel = nullptr;
void _show_remove_dialog();
void _show_rename_dialog();
String _check_new_group_name(const String &p_name);
void _check_rename();
void _add_group();
void _add_group(const String &p_name, const String &p_description);
void _modify_references(const StringName &p_name, const StringName &p_new_name, bool p_is_rename);
void _confirm_rename();
void _confirm_delete();
void _text_submitted(const String &p_text);
void _group_name_text_changed(const String &p_name);
void _item_edited();
void _item_button_pressed(Object *p_item, int p_column, int p_id, MouseButton p_button);
protected:
void _notification(int p_what);
static void _bind_methods();
public:
void show_message(const String &p_message);
void remove_references(const StringName &p_name);
void rename_references(const StringName &p_old_name, const StringName &p_new_name);
bool remove_node_references(Node *p_node, const StringName &p_name);
bool rename_node_references(Node *p_node, const StringName &p_old_name, const StringName &p_new_name);
void update_groups();
void connect_filesystem_dock_signals(FileSystemDock *p_fs_dock);
GroupSettingsEditor();
};
#endif // GROUP_SETTINGS_EDITOR_H

File diff suppressed because it is too large Load Diff

View File

@ -34,57 +34,94 @@
#include "scene/gui/dialogs.h"
class Button;
class CheckBox;
class CheckButton;
class EditorValidationPanel;
class Label;
class LineEdit;
class PopupMenu;
class Tree;
class TreeItem;
class GroupDialog : public AcceptDialog {
GDCLASS(GroupDialog, AcceptDialog);
class GroupsEditor : public VBoxContainer {
GDCLASS(GroupsEditor, VBoxContainer);
AcceptDialog *error = nullptr;
const String GLOBAL_GROUP_PREFIX = "global_group/";
bool updating_tree = false;
bool updating_groups = false;
bool groups_dirty = false;
bool update_groups_and_tree_queued = false;
Node *node = nullptr;
Node *scene_root_node = nullptr;
SceneTree *scene_tree = nullptr;
TreeItem *groups_root = nullptr;
LineEdit *add_group_text = nullptr;
Button *add_group_button = nullptr;
ConfirmationDialog *add_group_dialog = nullptr;
LineEdit *add_group_name = nullptr;
LineEdit *add_group_description = nullptr;
CheckButton *global_group_button = nullptr;
EditorValidationPanel *add_validation_panel = nullptr;
Tree *groups = nullptr;
ConfirmationDialog *rename_group_dialog = nullptr;
LineEdit *rename_group = nullptr;
CheckBox *rename_check_box = nullptr;
EditorValidationPanel *rename_validation_panel = nullptr;
Tree *nodes_to_add = nullptr;
TreeItem *add_node_root = nullptr;
LineEdit *add_filter = nullptr;
ConfirmationDialog *remove_group_dialog = nullptr;
CheckBox *remove_check_box = nullptr;
Label *remove_label = nullptr;
Tree *nodes_to_remove = nullptr;
TreeItem *remove_node_root = nullptr;
LineEdit *remove_filter = nullptr;
PopupMenu *menu = nullptr;
Label *group_empty = nullptr;
LineEdit *filter = nullptr;
Button *add = nullptr;
Tree *tree = nullptr;
Button *add_button = nullptr;
Button *remove_button = nullptr;
HashMap<Node *, HashMap<StringName, bool>> scene_groups_cache;
HashMap<StringName, bool> scene_groups_for_caching;
String selected_group;
HashMap<StringName, bool> scene_groups;
HashMap<StringName, String> global_groups;
void _group_selected();
void _update_scene_groups(Node *p_node);
void _cache_scene_groups(Node *p_node);
void _remove_filter_changed(const String &p_filter);
void _add_filter_changed(const String &p_filter);
void _show_add_group_dialog();
void _show_rename_group_dialog();
void _show_remove_group_dialog();
void _add_pressed();
void _removed_pressed();
void _add_group_pressed(const String &p_name);
void _add_group_text_changed(const String &p_new_text);
void _check_add();
void _check_rename();
void _validate_name(const String &p_name, EditorValidationPanel *p_validation_panel);
void _group_renamed();
void _rename_group_item(const String &p_old_name, const String &p_new_name);
void _update_tree();
void _add_group(String p_name);
void _modify_group_pressed(Object *p_item, int p_column, int p_id, MouseButton p_button);
void _delete_group_item(const String &p_name);
void _update_groups();
void _load_scene_groups(Node *p_node);
void _load_groups(Node *p_current);
void _load_nodes(Node *p_current);
void _add_scene_group(const String &p_name);
void _rename_scene_group(const String &p_old_name, const String &p_new_name);
void _remove_scene_group(const String &p_name);
bool _has_group(const String &p_name);
void _set_group_checked(const String &p_name, bool p_checked);
void _confirm_add();
void _confirm_rename();
void _confirm_delete();
void _item_edited();
void _item_mouse_selected(const Vector2 &p_pos, MouseButton p_mouse_button);
void _modify_group(Object *p_item, int p_column, int p_id, MouseButton p_mouse_button);
void _menu_id_pressed(int p_id);
void _update_groups_and_tree();
void _queue_update_groups_and_tree();
void _groups_gui_input(Ref<InputEvent> p_event);
void _node_removed(Node *p_node);
protected:
void _notification(int p_what);
@ -94,45 +131,8 @@ public:
enum ModifyButton {
DELETE_GROUP,
COPY_GROUP,
};
void edit();
GroupDialog();
};
class GroupsEditor : public VBoxContainer {
GDCLASS(GroupsEditor, VBoxContainer);
Node *node = nullptr;
TreeItem *groups_root = nullptr;
GroupDialog *group_dialog = nullptr;
AcceptDialog *error = nullptr;
LineEdit *group_name = nullptr;
Button *add = nullptr;
Tree *tree = nullptr;
String selected_group;
void update_tree();
void _add_group(const String &p_group = "");
void _modify_group(Object *p_item, int p_column, int p_id, MouseButton p_button);
void _group_name_changed(const String &p_new_text);
void _group_selected();
void _group_renamed();
void _show_group_dialog();
protected:
static void _bind_methods();
public:
enum ModifyButton {
DELETE_GROUP,
COPY_GROUP,
RENAME_GROUP,
CONVERT_GROUP,
};
void set_current(Node *p_node);

View File

@ -45,6 +45,7 @@ ProjectSettingsEditor *ProjectSettingsEditor::singleton = nullptr;
void ProjectSettingsEditor::connect_filesystem_dock_signals(FileSystemDock *p_fs_dock) {
localization_editor->connect_filesystem_dock_signals(p_fs_dock);
group_settings->connect_filesystem_dock_signals(p_fs_dock);
}
void ProjectSettingsEditor::popup_project_settings(bool p_clear_filter) {
@ -62,6 +63,7 @@ void ProjectSettingsEditor::popup_project_settings(bool p_clear_filter) {
localization_editor->update_translations();
autoload_settings->update_autoload();
group_settings->update_groups();
plugin_settings->update_plugins();
import_defaults_editor->clear();
@ -709,6 +711,11 @@ ProjectSettingsEditor::ProjectSettingsEditor(EditorData *p_data) {
shaders_global_shader_uniforms_editor->connect("globals_changed", callable_mp(this, &ProjectSettingsEditor::queue_save));
tab_container->add_child(shaders_global_shader_uniforms_editor);
group_settings = memnew(GroupSettingsEditor);
group_settings->set_name(TTR("Global Groups"));
group_settings->connect("group_changed", callable_mp(this, &ProjectSettingsEditor::queue_save));
tab_container->add_child(group_settings);
plugin_settings = memnew(EditorPluginSettings);
plugin_settings->set_name(TTR("Plugins"));
tab_container->add_child(plugin_settings);

View File

@ -37,6 +37,7 @@
#include "editor/editor_data.h"
#include "editor/editor_plugin_settings.h"
#include "editor/editor_sectioned_inspector.h"
#include "editor/group_settings_editor.h"
#include "editor/import_defaults_editor.h"
#include "editor/localization_editor.h"
#include "editor/shader_globals_editor.h"
@ -58,6 +59,7 @@ class ProjectSettingsEditor : public AcceptDialog {
LocalizationEditor *localization_editor = nullptr;
EditorAutoloadSettings *autoload_settings = nullptr;
ShaderGlobalsEditor *shaders_global_shader_uniforms_editor = nullptr;
GroupSettingsEditor *group_settings = nullptr;
EditorPluginSettings *plugin_settings = nullptr;
LineEdit *search_box = nullptr;
@ -122,6 +124,7 @@ public:
void update_plugins();
EditorAutoloadSettings *get_autoload_settings() { return autoload_settings; }
GroupSettingsEditor *get_group_settings() { return group_settings; }
TabContainer *get_tabs() { return tab_container; }
void queue_save();

View File

@ -3069,8 +3069,13 @@ static void _add_nodes_to_options(const Node *p_base, const Node *p_node, List<S
void Node::get_argument_options(const StringName &p_function, int p_idx, List<String> *r_options) const {
String pf = p_function;
if ((pf == "has_node" || pf == "get_node") && p_idx == 0) {
if (p_idx == 0 && (pf == "has_node" || pf == "get_node")) {
_add_nodes_to_options(this, this, r_options);
} else if (p_idx == 0 && (pf == "add_to_group" || pf == "remove_from_group" || pf == "is_in_group")) {
HashMap<StringName, String> global_groups = ProjectSettings::get_singleton()->get_global_groups_list();
for (const KeyValue<StringName, String> &E : global_groups) {
r_options->push_back(E.key.operator String().quote());
}
}
Object::get_argument_options(p_function, p_idx, r_options);
}

View File

@ -1714,6 +1714,19 @@ void SceneTree::get_argument_options(const StringName &p_function, int p_idx, Li
filename = dir_access->get_next();
}
}
} else {
bool add_options = false;
if (p_idx == 0) {
add_options = p_function == "get_nodes_in_group" || p_function == "has_group" || p_function == "get_first_node_in_group" || p_function == "set_group" || p_function == "notify_group" || p_function == "call_group" || p_function == "add_to_group";
} else if (p_idx == 1) {
add_options = p_function == "set_group_flags" || p_function == "call_group_flags" || p_function == "notify_group_flags";
}
if (add_options) {
HashMap<StringName, String> global_groups = ProjectSettings::get_singleton()->get_global_groups_list();
for (const KeyValue<StringName, String> &E : global_groups) {
r_options->push_back(E.key.operator String().quote());
}
}
}
}

View File

@ -1849,6 +1849,44 @@ void SceneState::add_editable_instance(const NodePath &p_path) {
editable_instances.push_back(p_path);
}
bool SceneState::remove_group_references(const StringName &p_name) {
bool edited = false;
for (NodeData &node : nodes) {
for (const int &group : node.groups) {
if (names[group] == p_name) {
node.groups.erase(group);
edited = true;
break;
}
}
}
return edited;
}
bool SceneState::rename_group_references(const StringName &p_old_name, const StringName &p_new_name) {
bool edited = false;
for (const NodeData &node : nodes) {
for (const int &group : node.groups) {
if (names[group] == p_old_name) {
names.write[group] = p_new_name;
edited = true;
break;
}
}
}
return edited;
}
HashSet<StringName> SceneState::get_all_groups() {
HashSet<StringName> ret;
for (const NodeData &node : nodes) {
for (const int &group : node.groups) {
ret.insert(names[group]);
}
}
return ret;
}
Vector<String> SceneState::_get_node_groups(int p_idx) const {
Vector<StringName> groups = get_node_groups(p_idx);
Vector<String> ret;

View File

@ -204,6 +204,10 @@ public:
void add_connection(int p_from, int p_to, int p_signal, int p_method, int p_flags, int p_unbinds, const Vector<int> &p_binds);
void add_editable_instance(const NodePath &p_path);
bool remove_group_references(const StringName &p_name);
bool rename_group_references(const StringName &p_old_name, const StringName &p_new_name);
HashSet<StringName> get_all_groups();
virtual void set_last_modified_time(uint64_t p_time) { last_modified_time = p_time; }
uint64_t get_last_modified_time() const { return last_modified_time; }