Merge pull request #62061 from fire-forge/multinodeedit

Improvements to multi-node editing
This commit is contained in:
Rémi Verschelde 2022-09-06 12:34:10 +02:00
commit 73026fbde9
6 changed files with 182 additions and 52 deletions

View File

@ -52,13 +52,9 @@ public:
bool setting = false; bool setting = false;
bool animation_read_only = false; bool animation_read_only = false;
bool _hide_script_from_inspector() { bool _hide_script_from_inspector() { return true; }
return true; bool _hide_metadata_from_inspector() { return true; }
} bool _dont_undo_redo() { return true; }
bool _dont_undo_redo() {
return true;
}
bool _is_read_only() { bool _is_read_only() {
return animation_read_only; return animation_read_only;
@ -68,6 +64,7 @@ public:
ClassDB::bind_method(D_METHOD("_update_obj"), &AnimationTrackKeyEdit::_update_obj); ClassDB::bind_method(D_METHOD("_update_obj"), &AnimationTrackKeyEdit::_update_obj);
ClassDB::bind_method(D_METHOD("_key_ofs_changed"), &AnimationTrackKeyEdit::_key_ofs_changed); ClassDB::bind_method(D_METHOD("_key_ofs_changed"), &AnimationTrackKeyEdit::_key_ofs_changed);
ClassDB::bind_method(D_METHOD("_hide_script_from_inspector"), &AnimationTrackKeyEdit::_hide_script_from_inspector); ClassDB::bind_method(D_METHOD("_hide_script_from_inspector"), &AnimationTrackKeyEdit::_hide_script_from_inspector);
ClassDB::bind_method(D_METHOD("_hide_metadata_from_inspector"), &AnimationTrackKeyEdit::_hide_metadata_from_inspector);
ClassDB::bind_method(D_METHOD("get_root_path"), &AnimationTrackKeyEdit::get_root_path); ClassDB::bind_method(D_METHOD("get_root_path"), &AnimationTrackKeyEdit::get_root_path);
ClassDB::bind_method(D_METHOD("_dont_undo_redo"), &AnimationTrackKeyEdit::_dont_undo_redo); ClassDB::bind_method(D_METHOD("_dont_undo_redo"), &AnimationTrackKeyEdit::_dont_undo_redo);
ClassDB::bind_method(D_METHOD("_is_read_only"), &AnimationTrackKeyEdit::_is_read_only); ClassDB::bind_method(D_METHOD("_is_read_only"), &AnimationTrackKeyEdit::_is_read_only);
@ -719,13 +716,9 @@ public:
bool setting = false; bool setting = false;
bool animation_read_only = false; bool animation_read_only = false;
bool _hide_script_from_inspector() { bool _hide_script_from_inspector() { return true; }
return true; bool _hide_metadata_from_inspector() { return true; }
} bool _dont_undo_redo() { return true; }
bool _dont_undo_redo() {
return true;
}
bool _is_read_only() { bool _is_read_only() {
return animation_read_only; return animation_read_only;
@ -735,6 +728,7 @@ public:
ClassDB::bind_method(D_METHOD("_update_obj"), &AnimationMultiTrackKeyEdit::_update_obj); ClassDB::bind_method(D_METHOD("_update_obj"), &AnimationMultiTrackKeyEdit::_update_obj);
ClassDB::bind_method(D_METHOD("_key_ofs_changed"), &AnimationMultiTrackKeyEdit::_key_ofs_changed); ClassDB::bind_method(D_METHOD("_key_ofs_changed"), &AnimationMultiTrackKeyEdit::_key_ofs_changed);
ClassDB::bind_method(D_METHOD("_hide_script_from_inspector"), &AnimationMultiTrackKeyEdit::_hide_script_from_inspector); ClassDB::bind_method(D_METHOD("_hide_script_from_inspector"), &AnimationMultiTrackKeyEdit::_hide_script_from_inspector);
ClassDB::bind_method(D_METHOD("_hide_metadata_from_inspector"), &AnimationMultiTrackKeyEdit::_hide_metadata_from_inspector);
ClassDB::bind_method(D_METHOD("get_root_path"), &AnimationMultiTrackKeyEdit::get_root_path); ClassDB::bind_method(D_METHOD("get_root_path"), &AnimationMultiTrackKeyEdit::get_root_path);
ClassDB::bind_method(D_METHOD("_dont_undo_redo"), &AnimationMultiTrackKeyEdit::_dont_undo_redo); ClassDB::bind_method(D_METHOD("_dont_undo_redo"), &AnimationMultiTrackKeyEdit::_dont_undo_redo);
ClassDB::bind_method(D_METHOD("_is_read_only"), &AnimationMultiTrackKeyEdit::_is_read_only); ClassDB::bind_method(D_METHOD("_is_read_only"), &AnimationMultiTrackKeyEdit::_is_read_only);

View File

@ -2711,6 +2711,11 @@ void EditorInspector::update_tree() {
continue; continue;
} }
// Hide the "MultiNodeEdit" category for MultiNodeEdit.
if (Object::cast_to<MultiNodeEdit>(object) && p.name == "MultiNodeEdit") {
continue;
}
// Iterate over remaining properties. If no properties in category, skip the category. // Iterate over remaining properties. If no properties in category, skip the category.
List<PropertyInfo>::Element *N = E_property->next(); List<PropertyInfo>::Element *N = E_property->next();
bool valid = true; bool valid = true;
@ -2819,6 +2824,11 @@ void EditorInspector::update_tree() {
continue; continue;
} }
if (p.name.begins_with("metadata/") && bool(object->call("_hide_metadata_from_inspector"))) {
// Hide metadata from inspector if required.
continue;
}
// Get the path for property. // Get the path for property.
String path = p.name; String path = p.name;
@ -3089,6 +3099,8 @@ void EditorInspector::update_tree() {
StringName classname = doc_name == "" ? object->get_class_name() : doc_name; StringName classname = doc_name == "" ? object->get_class_name() : doc_name;
if (!object_class.is_empty()) { if (!object_class.is_empty()) {
classname = object_class; classname = object_class;
} else if (Object::cast_to<MultiNodeEdit>(object)) {
classname = Object::cast_to<MultiNodeEdit>(object)->get_edited_class_name();
} }
StringName propname = property_prefix + p.name; StringName propname = property_prefix + p.name;
@ -3242,7 +3254,7 @@ void EditorInspector::update_tree() {
} }
} }
if (!hide_metadata) { if (!hide_metadata && !object->call("_hide_metadata_from_inspector")) {
// Add 4px of spacing between the "Add Metadata" button and the content above it. // Add 4px of spacing between the "Add Metadata" button and the content above it.
Control *spacer = memnew(Control); Control *spacer = memnew(Control);
spacer->set_custom_minimum_size(Size2(0, 4) * EDSCALE); spacer->set_custom_minimum_size(Size2(0, 4) * EDSCALE);

View File

@ -33,6 +33,7 @@
#include "editor/editor_data.h" #include "editor/editor_data.h"
#include "editor/editor_node.h" #include "editor/editor_node.h"
#include "editor/editor_scale.h" #include "editor/editor_scale.h"
#include "editor/multi_node_edit.h"
void EditorPath::_add_children_to_popup(Object *p_obj, int p_depth) { void EditorPath::_add_children_to_popup(Object *p_obj, int p_depth) {
if (p_depth > 8) { if (p_depth > 8) {
@ -121,14 +122,22 @@ void EditorPath::update_path() {
continue; continue;
} }
Ref<Texture2D> icon = EditorNode::get_singleton()->get_object_icon(obj); Ref<Texture2D> icon;
if (Object::cast_to<MultiNodeEdit>(obj)) {
icon = EditorNode::get_singleton()->get_class_icon(Object::cast_to<MultiNodeEdit>(obj)->get_edited_class_name());
} else {
icon = EditorNode::get_singleton()->get_object_icon(obj);
}
if (icon.is_valid()) { if (icon.is_valid()) {
current_object_icon->set_texture(icon); current_object_icon->set_texture(icon);
} }
if (i == history->get_path_size() - 1) { if (i == history->get_path_size() - 1) {
String name; String name;
if (Object::cast_to<Resource>(obj)) { if (obj->has_method("_get_editor_name")) {
name = obj->call("_get_editor_name");
} else if (Object::cast_to<Resource>(obj)) {
Resource *r = Object::cast_to<Resource>(obj); Resource *r = Object::cast_to<Resource>(obj);
if (r->get_path().is_resource_file()) { if (r->get_path().is_resource_file()) {
name = r->get_path().get_file(); name = r->get_path().get_file();
@ -149,7 +158,7 @@ void EditorPath::update_path() {
name = obj->get_class(); name = obj->get_class();
} }
current_object_label->set_text(" " + name); // An extra space so the text is not too close of the icon. current_object_label->set_text(name);
set_tooltip_text(obj->get_class()); set_tooltip_text(obj->get_class());
} }
} }
@ -161,12 +170,12 @@ void EditorPath::clear_path() {
current_object_label->set_text(""); current_object_label->set_text("");
current_object_icon->set_texture(nullptr); current_object_icon->set_texture(nullptr);
sub_objects_icon->set_visible(false); sub_objects_icon->hide();
} }
void EditorPath::enable_path() { void EditorPath::enable_path() {
set_disabled(false); set_disabled(false);
sub_objects_icon->set_visible(true); sub_objects_icon->show();
} }
void EditorPath::_id_pressed(int p_idx) { void EditorPath::_id_pressed(int p_idx) {
@ -186,7 +195,7 @@ void EditorPath::_notification(int p_what) {
case NOTIFICATION_THEME_CHANGED: { case NOTIFICATION_THEME_CHANGED: {
update_path(); update_path();
sub_objects_icon->set_texture(get_theme_icon(SNAME("select_arrow"), SNAME("Tree"))); sub_objects_icon->set_texture(get_theme_icon(SNAME("arrow"), SNAME("OptionButton")));
current_object_label->add_theme_font_override("font", get_theme_font(SNAME("main"), SNAME("EditorFonts"))); current_object_label->add_theme_font_override("font", get_theme_font(SNAME("main"), SNAME("EditorFonts")));
} break; } break;
@ -216,13 +225,12 @@ EditorPath::EditorPath(EditorSelectionHistory *p_history) {
main_hb->add_child(current_object_icon); main_hb->add_child(current_object_icon);
current_object_label = memnew(Label); current_object_label = memnew(Label);
current_object_label->set_clip_text(true); current_object_label->set_text_overrun_behavior(TextServer::OVERRUN_TRIM_ELLIPSIS);
current_object_label->set_horizontal_alignment(HORIZONTAL_ALIGNMENT_LEFT);
current_object_label->set_h_size_flags(SIZE_EXPAND_FILL); current_object_label->set_h_size_flags(SIZE_EXPAND_FILL);
main_hb->add_child(current_object_label); main_hb->add_child(current_object_label);
sub_objects_icon = memnew(TextureRect); sub_objects_icon = memnew(TextureRect);
sub_objects_icon->set_visible(false); sub_objects_icon->hide();
sub_objects_icon->set_stretch_mode(TextureRect::STRETCH_KEEP_CENTERED); sub_objects_icon->set_stretch_mode(TextureRect::STRETCH_KEEP_CENTERED);
main_hb->add_child(sub_objects_icon); main_hb->add_child(sub_objects_icon);

View File

@ -311,7 +311,6 @@ void InspectorDock::_prepare_history() {
history_menu->get_popup()->clear(); history_menu->get_popup()->clear();
Ref<Texture2D> base_icon = get_theme_icon(SNAME("Object"), SNAME("EditorIcons"));
HashSet<ObjectID> already; HashSet<ObjectID> already;
for (int i = editor_history->get_history_len() - 1; i >= history_to; i--) { for (int i = editor_history->get_history_len() - 1; i >= history_to; i--) {
ObjectID id = editor_history->get_history_obj(i); ObjectID id = editor_history->get_history_obj(i);
@ -325,13 +324,12 @@ void InspectorDock::_prepare_history() {
already.insert(id); already.insert(id);
Ref<Texture2D> icon = EditorNode::get_singleton()->get_object_icon(obj, ""); Ref<Texture2D> icon = EditorNode::get_singleton()->get_object_icon(obj, "Object");
if (icon.is_null()) {
icon = base_icon;
}
String text; String text;
if (Object::cast_to<Resource>(obj)) { if (obj->has_method("_get_editor_name")) {
text = obj->call("_get_editor_name");
} else if (Object::cast_to<Resource>(obj)) {
Resource *r = Object::cast_to<Resource>(obj); Resource *r = Object::cast_to<Resource>(obj);
if (r->get_path().is_resource_file()) { if (r->get_path().is_resource_file()) {
text = r->get_path().get_file(); text = r->get_path().get_file();
@ -349,14 +347,14 @@ void InspectorDock::_prepare_history() {
} }
if (i == editor_history->get_history_pos() && current) { if (i == editor_history->get_history_pos() && current) {
text = "[" + text + "]"; text += " " + TTR("(Current)");
} }
history_menu->get_popup()->add_icon_item(icon, text, i); history_menu->get_popup()->add_icon_item(icon, text, i);
} }
} }
void InspectorDock::_select_history(int p_idx) { void InspectorDock::_select_history(int p_idx) {
//push it to the top, it is not correct, but it's more useful // Push it to the top, it is not correct, but it's more useful.
ObjectID id = EditorNode::get_singleton()->get_editor_selection_history()->get_history_obj(p_idx); ObjectID id = EditorNode::get_singleton()->get_editor_selection_history()->get_history_obj(p_idx);
Object *obj = ObjectDB::get_instance(id); Object *obj = ObjectDB::get_instance(id);
if (!obj) { if (!obj) {

View File

@ -46,7 +46,7 @@ bool MultiNodeEdit::_set_impl(const StringName &p_name, const Variant &p_value,
String name = p_name; String name = p_name;
if (name == "scripts") { // script set is intercepted at object level (check Variant Object::get() ) ,so use a different name if (name == "scripts") { // Script set is intercepted at object level (check Variant Object::get()), so use a different name.
name = "script"; name = "script";
} }
@ -57,13 +57,9 @@ bool MultiNodeEdit::_set_impl(const StringName &p_name, const Variant &p_value,
Ref<EditorUndoRedoManager> &ur = EditorNode::get_undo_redo(); Ref<EditorUndoRedoManager> &ur = EditorNode::get_undo_redo();
ur->create_action(TTR("MultiNode Set") + " " + String(name), UndoRedo::MERGE_ENDS); ur->create_action(vformat(TTR("Set %s on %d nodes"), name, get_node_count()), UndoRedo::MERGE_ENDS);
for (const NodePath &E : nodes) { for (const NodePath &E : nodes) {
if (!es->has_node(E)) { Node *n = es->get_node_or_null(E);
continue;
}
Node *n = es->get_node(E);
if (!n) { if (!n) {
continue; continue;
} }
@ -100,16 +96,12 @@ bool MultiNodeEdit::_get(const StringName &p_name, Variant &r_ret) const {
} }
String name = p_name; String name = p_name;
if (name == "scripts") { // script set is intercepted at object level (check Variant Object::get() ) ,so use a different name if (name == "scripts") { // Script set is intercepted at object level (check Variant Object::get()), so use a different name.
name = "script"; name = "script";
} }
for (const NodePath &E : nodes) { for (const NodePath &E : nodes) {
if (!es->has_node(E)) { const Node *n = es->get_node_or_null(E);
continue;
}
const Node *n = es->get_node(E);
if (!n) { if (!n) {
continue; continue;
} }
@ -137,11 +129,7 @@ void MultiNodeEdit::_get_property_list(List<PropertyInfo> *p_list) const {
List<PLData *> data_list; List<PLData *> data_list;
for (const NodePath &E : nodes) { for (const NodePath &E : nodes) {
if (!es->has_node(E)) { Node *n = es->get_node_or_null(E);
continue;
}
Node *n = es->get_node(E);
if (!n) { if (!n) {
continue; continue;
} }
@ -151,7 +139,7 @@ void MultiNodeEdit::_get_property_list(List<PropertyInfo> *p_list) const {
for (const PropertyInfo &F : plist) { for (const PropertyInfo &F : plist) {
if (F.name == "script") { if (F.name == "script") {
continue; //added later manually, since this is intercepted before being set (check Variant Object::get() ) continue; // Added later manually, since this is intercepted before being set (check Variant Object::get()).
} }
if (!usage.has(F.name)) { if (!usage.has(F.name)) {
PLData pld; PLData pld;
@ -161,7 +149,7 @@ void MultiNodeEdit::_get_property_list(List<PropertyInfo> *p_list) const {
data_list.push_back(usage.getptr(F.name)); data_list.push_back(usage.getptr(F.name));
} }
// Make sure only properties with the same exact PropertyInfo data will appear // Make sure only properties with the same exact PropertyInfo data will appear.
if (usage[F.name].info == F) { if (usage[F.name].info == F) {
usage[F.name].uses++; usage[F.name].uses++;
} }
@ -179,6 +167,66 @@ void MultiNodeEdit::_get_property_list(List<PropertyInfo> *p_list) const {
p_list->push_back(PropertyInfo(Variant::OBJECT, "scripts", PROPERTY_HINT_RESOURCE_TYPE, "Script")); p_list->push_back(PropertyInfo(Variant::OBJECT, "scripts", PROPERTY_HINT_RESOURCE_TYPE, "Script"));
} }
String MultiNodeEdit::_get_editor_name() const {
return vformat(TTR("%s (%d Selected)"), get_edited_class_name(), get_node_count());
}
bool MultiNodeEdit::_property_can_revert(const StringName &p_name) const {
Node *es = EditorNode::get_singleton()->get_edited_scene();
if (!es) {
return false;
}
if (ClassDB::has_property(get_edited_class_name(), p_name)) {
StringName class_name;
for (const NodePath &E : nodes) {
Node *node = es->get_node_or_null(E);
if (!node) {
continue;
}
class_name = node->get_class_name();
}
Variant default_value = ClassDB::class_get_default_property_value(class_name, p_name);
for (const NodePath &E : nodes) {
Node *node = es->get_node_or_null(E);
if (!node) {
continue;
}
if (node->get(p_name) != default_value) {
// A node that doesn't have the default value has been found, so show the revert button.
return true;
}
}
return false;
}
// Don't show the revert button if the edited class doesn't have the property.
return false;
}
bool MultiNodeEdit::_property_get_revert(const StringName &p_name, Variant &r_property) const {
Node *es = EditorNode::get_singleton()->get_edited_scene();
if (!es) {
return false;
}
for (const NodePath &E : nodes) {
Node *node = es->get_node_or_null(E);
if (!node) {
continue;
}
r_property = ClassDB::class_get_default_property_value(node->get_class_name(), p_name);
return true;
}
return false;
}
void MultiNodeEdit::add_node(const NodePath &p_node) { void MultiNodeEdit::add_node(const NodePath &p_node) {
nodes.push_back(p_node); nodes.push_back(p_node);
} }
@ -192,9 +240,69 @@ NodePath MultiNodeEdit::get_node(int p_index) const {
return nodes[p_index]; return nodes[p_index];
} }
StringName MultiNodeEdit::get_edited_class_name() const {
Node *es = EditorNode::get_singleton()->get_edited_scene();
if (!es) {
return SNAME("Node");
}
// Get the class name of the first node.
StringName class_name;
for (const NodePath &E : nodes) {
Node *node = es->get_node_or_null(E);
if (!node) {
continue;
}
class_name = node->get_class_name();
break;
}
if (class_name == StringName()) {
return SNAME("Node");
}
bool check_again = true;
while (check_again) {
check_again = false;
if (class_name == SNAME("Node") || class_name == StringName()) {
// All nodes inherit from Node, so no need to continue checking.
return SNAME("Node");
}
// Check that all nodes inherit from class_name.
for (const NodePath &E : nodes) {
Node *node = es->get_node_or_null(E);
if (!node) {
continue;
}
const StringName node_class_name = node->get_class_name();
if (class_name == node_class_name || ClassDB::is_parent_class(node_class_name, class_name)) {
// class_name is the same or a parent of the node's class.
continue;
}
// class_name is not a parent of the node's class, so check again with the parent class.
class_name = ClassDB::get_parent_class(class_name);
check_again = true;
break;
}
}
return class_name;
}
void MultiNodeEdit::set_property_field(const StringName &p_property, const Variant &p_value, const String &p_field) { void MultiNodeEdit::set_property_field(const StringName &p_property, const Variant &p_value, const String &p_field) {
_set_impl(p_property, p_value, p_field); _set_impl(p_property, p_value, p_field);
} }
void MultiNodeEdit::_bind_methods() {
ClassDB::bind_method("_hide_script_from_inspector", &MultiNodeEdit::_hide_script_from_inspector);
ClassDB::bind_method("_hide_metadata_from_inspector", &MultiNodeEdit::_hide_metadata_from_inspector);
ClassDB::bind_method("_get_editor_name", &MultiNodeEdit::_get_editor_name);
}
MultiNodeEdit::MultiNodeEdit() { MultiNodeEdit::MultiNodeEdit() {
} }

View File

@ -45,15 +45,25 @@ class MultiNodeEdit : public RefCounted {
bool _set_impl(const StringName &p_name, const Variant &p_value, const String &p_field); bool _set_impl(const StringName &p_name, const Variant &p_value, const String &p_field);
protected: protected:
static void _bind_methods();
bool _set(const StringName &p_name, const Variant &p_value); bool _set(const StringName &p_name, const Variant &p_value);
bool _get(const StringName &p_name, Variant &r_ret) const; bool _get(const StringName &p_name, Variant &r_ret) const;
void _get_property_list(List<PropertyInfo> *p_list) const; void _get_property_list(List<PropertyInfo> *p_list) const;
public: public:
bool _hide_script_from_inspector() { return true; }
bool _hide_metadata_from_inspector() { return true; }
bool _property_can_revert(const StringName &p_name) const;
bool _property_get_revert(const StringName &p_name, Variant &r_property) const;
String _get_editor_name() const;
void add_node(const NodePath &p_node); void add_node(const NodePath &p_node);
int get_node_count() const; int get_node_count() const;
NodePath get_node(int p_index) const; NodePath get_node(int p_index) const;
StringName get_edited_class_name() const;
void set_property_field(const StringName &p_property, const Variant &p_value, const String &p_field); void set_property_field(const StringName &p_property, const Variant &p_value, const String &p_field);