/*************************************************************************/ /* editor_inspector.cpp */ /*************************************************************************/ /* This file is part of: */ /* GODOT ENGINE */ /* https://godotengine.org */ /*************************************************************************/ /* Copyright (c) 2007-2021 Juan Linietsky, Ariel Manzur. */ /* Copyright (c) 2014-2021 Godot Engine contributors (cf. AUTHORS.md). */ /* */ /* 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 "editor_inspector.h" #include "array_property_edit.h" #include "core/os/keyboard.h" #include "dictionary_property_edit.h" #include "editor/doc_tools.h" #include "editor_feature_profile.h" #include "editor_node.h" #include "editor_scale.h" #include "editor_settings.h" #include "multi_node_edit.h" #include "scene/resources/packed_scene.h" Size2 EditorProperty::get_minimum_size() const { Size2 ms; Ref font = get_theme_font(SNAME("font"), SNAME("Tree")); int font_size = get_theme_font_size(SNAME("font_size"), SNAME("Tree")); ms.height = font->get_height(font_size); for (int i = 0; i < get_child_count(); i++) { Control *c = Object::cast_to(get_child(i)); if (!c) { continue; } if (c->is_set_as_top_level()) { continue; } if (!c->is_visible()) { continue; } if (c == bottom_editor) { continue; } Size2 minsize = c->get_combined_minimum_size(); ms.width = MAX(ms.width, minsize.width); ms.height = MAX(ms.height, minsize.height); } if (keying) { Ref key = get_theme_icon(SNAME("Key"), SNAME("EditorIcons")); ms.width += key->get_width() + get_theme_constant(SNAME("hseparator"), SNAME("Tree")); } if (deletable) { Ref key = get_theme_icon(SNAME("Close"), SNAME("EditorIcons")); ms.width += key->get_width() + get_theme_constant(SNAME("hseparator"), SNAME("Tree")); } if (checkable) { Ref check = get_theme_icon(SNAME("checked"), SNAME("CheckBox")); ms.width += check->get_width() + get_theme_constant(SNAME("hseparation"), SNAME("CheckBox")) + get_theme_constant(SNAME("hseparator"), SNAME("Tree")); } if (bottom_editor != nullptr && bottom_editor->is_visible()) { ms.height += get_theme_constant(SNAME("vseparation")); Size2 bems = bottom_editor->get_combined_minimum_size(); //bems.width += get_constant("item_margin", "Tree"); ms.height += bems.height; ms.width = MAX(ms.width, bems.width); } return ms; } void EditorProperty::emit_changed(const StringName &p_property, const Variant &p_value, const StringName &p_field, bool p_changing) { Variant args[4] = { p_property, p_value, p_field, p_changing }; const Variant *argptrs[4] = { &args[0], &args[1], &args[2], &args[3] }; cache[p_property] = p_value; emit_signal(SNAME("property_changed"), (const Variant **)argptrs, 4); } void EditorProperty::_notification(int p_what) { if (p_what == NOTIFICATION_SORT_CHILDREN) { Size2 size = get_size(); Rect2 rect; Rect2 bottom_rect; right_child_rect = Rect2(); bottom_child_rect = Rect2(); { int child_room = size.width * (1.0 - split_ratio); Ref font = get_theme_font(SNAME("font"), SNAME("Tree")); int font_size = get_theme_font_size(SNAME("font_size"), SNAME("Tree")); int height = font->get_height(font_size); bool no_children = true; //compute room needed for (int i = 0; i < get_child_count(); i++) { Control *c = Object::cast_to(get_child(i)); if (!c) { continue; } if (c->is_set_as_top_level()) { continue; } if (c == bottom_editor) { continue; } Size2 minsize = c->get_combined_minimum_size(); child_room = MAX(child_room, minsize.width); height = MAX(height, minsize.height); no_children = false; } if (no_children) { text_size = size.width; rect = Rect2(size.width - 1, 0, 1, height); } else { text_size = MAX(0, size.width - (child_room + 4 * EDSCALE)); if (is_layout_rtl()) { rect = Rect2(1, 0, child_room, height); } else { rect = Rect2(size.width - child_room, 0, child_room, height); } } if (bottom_editor) { int m = 0; //get_constant("item_margin", "Tree"); bottom_rect = Rect2(m, rect.size.height + get_theme_constant(SNAME("vseparation")), size.width - m, bottom_editor->get_combined_minimum_size().height); } if (keying) { Ref key; if (use_keying_next()) { key = get_theme_icon(SNAME("KeyNext"), SNAME("EditorIcons")); } else { key = get_theme_icon(SNAME("Key"), SNAME("EditorIcons")); } rect.size.x -= key->get_width() + get_theme_constant(SNAME("hseparator"), SNAME("Tree")); if (is_layout_rtl()) { rect.position.x += key->get_width() + get_theme_constant(SNAME("hseparator"), SNAME("Tree")); } if (no_children) { text_size -= key->get_width() + 4 * EDSCALE; } } if (deletable) { Ref close; close = get_theme_icon(SNAME("Close"), SNAME("EditorIcons")); rect.size.x -= close->get_width() + get_theme_constant(SNAME("hseparator"), SNAME("Tree")); if (is_layout_rtl()) { rect.position.x += close->get_width() + get_theme_constant(SNAME("hseparator"), SNAME("Tree")); } if (no_children) { text_size -= close->get_width() + 4 * EDSCALE; } } } //set children for (int i = 0; i < get_child_count(); i++) { Control *c = Object::cast_to(get_child(i)); if (!c) { continue; } if (c->is_set_as_top_level()) { continue; } if (c == bottom_editor) { continue; } fit_child_in_rect(c, rect); right_child_rect = rect; } if (bottom_editor) { fit_child_in_rect(bottom_editor, bottom_rect); bottom_child_rect = bottom_rect; } update(); //need to redraw text } if (p_what == NOTIFICATION_DRAW) { Ref font = get_theme_font(SNAME("font"), SNAME("Tree")); int font_size = get_theme_font_size(SNAME("font_size"), SNAME("Tree")); Color dark_color = get_theme_color(SNAME("dark_color_2"), SNAME("Editor")); bool rtl = is_layout_rtl(); Size2 size = get_size(); if (bottom_editor) { size.height = bottom_editor->get_offset(SIDE_TOP); } else if (label_reference) { size.height = label_reference->get_size().height; } Ref sb; if (selected) { sb = get_theme_stylebox(SNAME("bg_selected")); } else { sb = get_theme_stylebox(SNAME("bg")); } draw_style_box(sb, Rect2(Vector2(), size)); if (draw_top_bg && right_child_rect != Rect2()) { draw_rect(right_child_rect, dark_color); } if (bottom_child_rect != Rect2()) { draw_rect(bottom_child_rect, dark_color); } Color color; if (draw_red) { color = get_theme_color(SNAME("error_color")); } else { color = get_theme_color(SNAME("property_color")); } if (label.find(".") != -1) { color.a = 0.5; //this should be un-hacked honestly, as it's used for editor overrides } int ofs = get_theme_constant(SNAME("font_offset")); int text_limit = text_size; if (checkable) { Ref checkbox; if (checked) { checkbox = get_theme_icon(SNAME("GuiChecked"), SNAME("EditorIcons")); } else { checkbox = get_theme_icon(SNAME("GuiUnchecked"), SNAME("EditorIcons")); } Color color2(1, 1, 1); if (check_hover) { color2.r *= 1.2; color2.g *= 1.2; color2.b *= 1.2; } check_rect = Rect2(ofs, ((size.height - checkbox->get_height()) / 2), checkbox->get_width(), checkbox->get_height()); if (rtl) { draw_texture(checkbox, Vector2(size.width - check_rect.position.x - checkbox->get_width(), check_rect.position.y), color2); } else { draw_texture(checkbox, check_rect.position, color2); } ofs += get_theme_constant(SNAME("hseparator"), SNAME("Tree")) + checkbox->get_width() + get_theme_constant(SNAME("hseparation"), SNAME("CheckBox")); text_limit -= ofs; } else { check_rect = Rect2(); } if (can_revert) { Ref reload_icon = get_theme_icon(SNAME("ReloadSmall"), SNAME("EditorIcons")); text_limit -= reload_icon->get_width() + get_theme_constant(SNAME("hseparator"), SNAME("Tree")) * 2; revert_rect = Rect2(text_limit + get_theme_constant(SNAME("hseparator"), SNAME("Tree")), (size.height - reload_icon->get_height()) / 2, reload_icon->get_width(), reload_icon->get_height()); Color color2(1, 1, 1); if (revert_hover) { color2.r *= 1.2; color2.g *= 1.2; color2.b *= 1.2; } if (rtl) { draw_texture(reload_icon, Vector2(size.width - revert_rect.position.x - reload_icon->get_width(), revert_rect.position.y), color2); } else { draw_texture(reload_icon, revert_rect.position, color2); } } else { revert_rect = Rect2(); } int v_ofs = (size.height - font->get_height(font_size)) / 2; if (rtl) { draw_string(font, Point2(size.width - ofs - text_limit, v_ofs + font->get_ascent(font_size)), label, HALIGN_RIGHT, text_limit, font_size, color); } else { draw_string(font, Point2(ofs, v_ofs + font->get_ascent(font_size)), label, HALIGN_LEFT, text_limit, font_size, color); } if (keying) { Ref key; if (use_keying_next()) { key = get_theme_icon(SNAME("KeyNext"), SNAME("EditorIcons")); } else { key = get_theme_icon(SNAME("Key"), SNAME("EditorIcons")); } ofs = size.width - key->get_width() - get_theme_constant(SNAME("hseparator"), SNAME("Tree")); Color color2(1, 1, 1); if (keying_hover) { color2.r *= 1.2; color2.g *= 1.2; color2.b *= 1.2; } keying_rect = Rect2(ofs, ((size.height - key->get_height()) / 2), key->get_width(), key->get_height()); if (rtl) { draw_texture(key, Vector2(size.width - keying_rect.position.x - key->get_width(), keying_rect.position.y), color2); } else { draw_texture(key, keying_rect.position, color2); } } else { keying_rect = Rect2(); } if (deletable) { Ref close; close = get_theme_icon(SNAME("Close"), SNAME("EditorIcons")); ofs = size.width - close->get_width() - get_theme_constant(SNAME("hseparator"), SNAME("Tree")); Color color2(1, 1, 1); if (delete_hover) { color2.r *= 1.2; color2.g *= 1.2; color2.b *= 1.2; } delete_rect = Rect2(ofs, ((size.height - close->get_height()) / 2), close->get_width(), close->get_height()); if (rtl) { draw_texture(close, Vector2(size.width - delete_rect.position.x - close->get_width(), delete_rect.position.y), color2); } else { draw_texture(close, delete_rect.position, color2); } } else { delete_rect = Rect2(); } } } void EditorProperty::set_label(const String &p_label) { label = p_label; update(); } String EditorProperty::get_label() const { return label; } Object *EditorProperty::get_edited_object() { return object; } StringName EditorProperty::get_edited_property() { return property; } void EditorProperty::update_property() { GDVIRTUAL_CALL(_update_property); } void EditorProperty::set_read_only(bool p_read_only) { read_only = p_read_only; } bool EditorProperty::is_read_only() const { return read_only; } bool EditorPropertyRevert::may_node_be_in_instance(Node *p_node) { Node *edited_scene = EditorNode::get_singleton()->get_edited_scene(); bool might_be = false; Node *node = p_node; while (node) { if (node == edited_scene) { if (node->get_scene_inherited_state().is_valid()) { might_be = true; break; } might_be = false; break; } if (node->get_scene_instance_state().is_valid()) { might_be = true; break; } node = node->get_owner(); } return might_be; // or might not be } bool EditorPropertyRevert::get_instantiated_node_original_property(Node *p_node, const StringName &p_prop, Variant &value, bool p_check_class_default) { Node *node = p_node; Node *orig = node; Node *edited_scene = EditorNode::get_singleton()->get_edited_scene(); bool found = false; while (node) { Ref ss; if (node == edited_scene) { ss = node->get_scene_inherited_state(); } else { ss = node->get_scene_instance_state(); } if (ss.is_valid()) { NodePath np = node->get_path_to(orig); int node_idx = ss->find_node_by_path(np); if (node_idx >= 0) { bool lfound = false; Variant lvar; lvar = ss->get_property_value(node_idx, p_prop, lfound); if (lfound) { found = true; value = lvar; } } } if (node == edited_scene) { //just in case break; } node = node->get_owner(); } if (p_check_class_default && !found && p_node) { //if not found, try default class value Variant attempt = ClassDB::class_get_default_property_value(p_node->get_class_name(), p_prop); if (attempt.get_type() != Variant::NIL) { found = true; value = attempt; } } return found; } bool EditorPropertyRevert::is_node_property_different(Node *p_node, const Variant &p_current, const Variant &p_orig) { // this is a pretty difficult function, because a property may not be saved but may have // the flag to not save if one or if zero //make sure there is an actual state { Node *node = p_node; if (!node) { return false; } Node *edited_scene = EditorNode::get_singleton()->get_edited_scene(); bool found_state = false; while (node) { Ref ss; if (node == edited_scene) { ss = node->get_scene_inherited_state(); } else { ss = node->get_scene_instance_state(); } if (ss.is_valid()) { found_state = true; break; } if (node == edited_scene) { //just in case break; } node = node->get_owner(); } if (!found_state) { return false; //pointless to check if we are not comparing against anything. } } return is_property_value_different(p_current, p_orig); } bool EditorPropertyRevert::is_property_value_different(const Variant &p_a, const Variant &p_b) { if (p_a.get_type() == Variant::FLOAT && p_b.get_type() == Variant::FLOAT) { //this must be done because, as some scenes save as text, there might be a tiny difference in floats due to numerical error return !Math::is_equal_approx((float)p_a, (float)p_b); } else { return p_a != p_b; } } Variant EditorPropertyRevert::get_property_revert_value(Object *p_object, const StringName &p_property) { // If the object implements property_can_revert, rely on that completely // (i.e. don't then try to revert to default value - the property_get_revert implementation // can do that if so desired) if (p_object->has_method("property_can_revert") && p_object->call("property_can_revert", p_property)) { return p_object->call("property_get_revert", p_property); } Ref