/**************************************************************************/ /* editor_properties.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 "editor_properties.h" #include "core/config/project_settings.h" #include "core/core_string_names.h" #include "editor/create_dialog.h" #include "editor/editor_file_dialog.h" #include "editor/editor_node.h" #include "editor/editor_properties_array_dict.h" #include "editor/editor_resource_picker.h" #include "editor/editor_scale.h" #include "editor/editor_settings.h" #include "editor/inspector_dock.h" #include "editor/plugins/script_editor_plugin.h" #include "editor/project_settings_editor.h" #include "editor/property_selector.h" #include "editor/scene_tree_editor.h" #include "scene/2d/gpu_particles_2d.h" #include "scene/3d/fog_volume.h" #include "scene/3d/gpu_particles_3d.h" #include "scene/gui/color_picker.h" #include "scene/main/window.h" #include "scene/resources/font.h" #include "scene/resources/mesh.h" #include "scene/resources/packed_scene.h" ///////////////////// Nil ///////////////////////// void EditorPropertyNil::update_property() { } EditorPropertyNil::EditorPropertyNil() { Label *prop_label = memnew(Label); prop_label->set_text(""); add_child(prop_label); } ///////////////////// TEXT ///////////////////////// void EditorPropertyText::_set_read_only(bool p_read_only) { text->set_editable(!p_read_only); }; void EditorPropertyText::_text_submitted(const String &p_string) { if (updating) { return; } if (text->has_focus()) { text->release_focus(); _text_changed(p_string); } } void EditorPropertyText::_text_changed(const String &p_string) { if (updating) { return; } if (string_name) { emit_changed(get_edited_property(), StringName(p_string)); } else { emit_changed(get_edited_property(), p_string); } } void EditorPropertyText::update_property() { String s = get_edited_object()->get(get_edited_property()); updating = true; if (text->get_text() != s) { int caret = text->get_caret_column(); text->set_text(s); text->set_caret_column(caret); } text->set_editable(!is_read_only()); updating = false; } void EditorPropertyText::set_string_name(bool p_enabled) { string_name = p_enabled; } void EditorPropertyText::set_secret(bool p_enabled) { text->set_secret(p_enabled); } void EditorPropertyText::set_placeholder(const String &p_string) { text->set_placeholder(p_string); } void EditorPropertyText::_bind_methods() { } EditorPropertyText::EditorPropertyText() { text = memnew(LineEdit); add_child(text); add_focusable(text); text->connect("text_changed", callable_mp(this, &EditorPropertyText::_text_changed)); text->connect("text_submitted", callable_mp(this, &EditorPropertyText::_text_submitted)); } ///////////////////// MULTILINE TEXT ///////////////////////// void EditorPropertyMultilineText::_set_read_only(bool p_read_only) { text->set_editable(!p_read_only); open_big_text->set_disabled(p_read_only); }; void EditorPropertyMultilineText::_big_text_changed() { text->set_text(big_text->get_text()); emit_changed(get_edited_property(), big_text->get_text(), "", true); } void EditorPropertyMultilineText::_text_changed() { emit_changed(get_edited_property(), text->get_text(), "", true); } void EditorPropertyMultilineText::_open_big_text() { if (!big_text_dialog) { big_text = memnew(TextEdit); if (expression) { big_text->set_syntax_highlighter(text->get_syntax_highlighter()); big_text->add_theme_font_override("font", get_theme_font(SNAME("expression"), SNAME("EditorFonts"))); big_text->add_theme_font_size_override("font_size", get_theme_font_size(SNAME("expression_size"), SNAME("EditorFonts"))); } big_text->connect("text_changed", callable_mp(this, &EditorPropertyMultilineText::_big_text_changed)); big_text->set_line_wrapping_mode(TextEdit::LineWrappingMode::LINE_WRAPPING_BOUNDARY); big_text_dialog = memnew(AcceptDialog); big_text_dialog->add_child(big_text); big_text_dialog->set_title(TTR("Edit Text:")); add_child(big_text_dialog); } big_text_dialog->popup_centered_clamped(Size2(1000, 900) * EDSCALE, 0.8); big_text->set_text(text->get_text()); big_text->grab_focus(); } void EditorPropertyMultilineText::update_property() { String t = get_edited_object()->get(get_edited_property()); if (text->get_text() != t) { text->set_text(t); if (big_text && big_text->is_visible_in_tree()) { big_text->set_text(t); } } } void EditorPropertyMultilineText::_notification(int p_what) { switch (p_what) { case NOTIFICATION_THEME_CHANGED: case NOTIFICATION_ENTER_TREE: { Ref df = get_theme_icon(SNAME("DistractionFree"), SNAME("EditorIcons")); open_big_text->set_icon(df); Ref font; int font_size; if (expression) { font = get_theme_font(SNAME("expression"), SNAME("EditorFonts")); font_size = get_theme_font_size(SNAME("expression_size"), SNAME("EditorFonts")); text->add_theme_font_override("font", font); text->add_theme_font_size_override("font_size", font_size); if (big_text) { big_text->add_theme_font_override("font", font); big_text->add_theme_font_size_override("font_size", font_size); } } else { font = get_theme_font(SNAME("font"), SNAME("TextEdit")); font_size = get_theme_font_size(SNAME("font_size"), SNAME("TextEdit")); } text->set_custom_minimum_size(Vector2(0, font->get_height(font_size) * 6)); } break; } } void EditorPropertyMultilineText::_bind_methods() { } EditorPropertyMultilineText::EditorPropertyMultilineText(bool p_expression) { HBoxContainer *hb = memnew(HBoxContainer); hb->add_theme_constant_override("separation", 0); add_child(hb); set_bottom_editor(hb); text = memnew(TextEdit); text->connect("text_changed", callable_mp(this, &EditorPropertyMultilineText::_text_changed)); text->set_line_wrapping_mode(TextEdit::LineWrappingMode::LINE_WRAPPING_BOUNDARY); add_focusable(text); hb->add_child(text); text->set_h_size_flags(SIZE_EXPAND_FILL); open_big_text = memnew(Button); open_big_text->set_flat(true); open_big_text->connect("pressed", callable_mp(this, &EditorPropertyMultilineText::_open_big_text)); hb->add_child(open_big_text); big_text_dialog = nullptr; big_text = nullptr; if (p_expression) { expression = true; Ref highlighter; highlighter.instantiate(); text->set_syntax_highlighter(highlighter); } } ///////////////////// TEXT ENUM ///////////////////////// void EditorPropertyTextEnum::_set_read_only(bool p_read_only) { option_button->set_disabled(p_read_only); edit_button->set_disabled(p_read_only); }; void EditorPropertyTextEnum::_emit_changed_value(String p_string) { if (string_name) { emit_changed(get_edited_property(), StringName(p_string)); } else { emit_changed(get_edited_property(), p_string); } } void EditorPropertyTextEnum::_option_selected(int p_which) { _emit_changed_value(option_button->get_item_text(p_which)); } void EditorPropertyTextEnum::_edit_custom_value() { default_layout->hide(); edit_custom_layout->show(); custom_value_edit->grab_focus(); } void EditorPropertyTextEnum::_custom_value_submitted(String p_value) { edit_custom_layout->hide(); default_layout->show(); _emit_changed_value(p_value.strip_edges()); } void EditorPropertyTextEnum::_custom_value_accepted() { String new_value = custom_value_edit->get_text().strip_edges(); _custom_value_submitted(new_value); } void EditorPropertyTextEnum::_custom_value_cancelled() { custom_value_edit->set_text(get_edited_object()->get(get_edited_property())); edit_custom_layout->hide(); default_layout->show(); } void EditorPropertyTextEnum::update_property() { String current_value = get_edited_object()->get(get_edited_property()); int default_option = options.find(current_value); // The list can change in the loose mode. if (loose_mode) { custom_value_edit->set_text(current_value); option_button->clear(); // Manually entered value. if (default_option < 0 && !current_value.is_empty()) { option_button->add_item(current_value, options.size() + 1001); option_button->select(0); option_button->add_separator(); } // Add an explicit empty value for clearing the property. option_button->add_item("", options.size() + 1000); for (int i = 0; i < options.size(); i++) { option_button->add_item(options[i], i); if (options[i] == current_value) { option_button->select(option_button->get_item_count() - 1); } } } else { option_button->select(default_option); } } void EditorPropertyTextEnum::setup(const Vector &p_options, bool p_string_name, bool p_loose_mode) { string_name = p_string_name; loose_mode = p_loose_mode; options.clear(); if (loose_mode) { // Add an explicit empty value for clearing the property in the loose mode. option_button->add_item("", options.size() + 1000); } for (int i = 0; i < p_options.size(); i++) { options.append(p_options[i]); option_button->add_item(p_options[i], i); } if (loose_mode) { edit_button->show(); } } void EditorPropertyTextEnum::_bind_methods() { } void EditorPropertyTextEnum::_notification(int p_what) { switch (p_what) { case NOTIFICATION_ENTER_TREE: case NOTIFICATION_THEME_CHANGED: { edit_button->set_icon(get_theme_icon(SNAME("Edit"), SNAME("EditorIcons"))); accept_button->set_icon(get_theme_icon(SNAME("ImportCheck"), SNAME("EditorIcons"))); cancel_button->set_icon(get_theme_icon(SNAME("ImportFail"), SNAME("EditorIcons"))); } break; } } EditorPropertyTextEnum::EditorPropertyTextEnum() { default_layout = memnew(HBoxContainer); add_child(default_layout); edit_custom_layout = memnew(HBoxContainer); edit_custom_layout->hide(); add_child(edit_custom_layout); option_button = memnew(OptionButton); option_button->set_h_size_flags(SIZE_EXPAND_FILL); option_button->set_clip_text(true); option_button->set_flat(true); default_layout->add_child(option_button); option_button->connect("item_selected", callable_mp(this, &EditorPropertyTextEnum::_option_selected)); edit_button = memnew(Button); edit_button->set_flat(true); edit_button->hide(); default_layout->add_child(edit_button); edit_button->connect("pressed", callable_mp(this, &EditorPropertyTextEnum::_edit_custom_value)); custom_value_edit = memnew(LineEdit); custom_value_edit->set_h_size_flags(SIZE_EXPAND_FILL); edit_custom_layout->add_child(custom_value_edit); custom_value_edit->connect("text_submitted", callable_mp(this, &EditorPropertyTextEnum::_custom_value_submitted)); accept_button = memnew(Button); accept_button->set_flat(true); edit_custom_layout->add_child(accept_button); accept_button->connect("pressed", callable_mp(this, &EditorPropertyTextEnum::_custom_value_accepted)); cancel_button = memnew(Button); cancel_button->set_flat(true); edit_custom_layout->add_child(cancel_button); cancel_button->connect("pressed", callable_mp(this, &EditorPropertyTextEnum::_custom_value_cancelled)); add_focusable(option_button); add_focusable(edit_button); add_focusable(custom_value_edit); add_focusable(accept_button); add_focusable(cancel_button); } //////////////////// LOCALE //////////////////////// void EditorPropertyLocale::_locale_selected(const String &p_locale) { emit_changed(get_edited_property(), p_locale); update_property(); } void EditorPropertyLocale::_locale_pressed() { if (!dialog) { dialog = memnew(EditorLocaleDialog); dialog->connect("locale_selected", callable_mp(this, &EditorPropertyLocale::_locale_selected)); add_child(dialog); } String locale_code = get_edited_object()->get(get_edited_property()); dialog->set_locale(locale_code); dialog->popup_locale_dialog(); } void EditorPropertyLocale::update_property() { String locale_code = get_edited_object()->get(get_edited_property()); locale->set_text(locale_code); locale->set_tooltip_text(locale_code); } void EditorPropertyLocale::setup(const String &p_hint_text) { } void EditorPropertyLocale::_notification(int p_what) { switch (p_what) { case NOTIFICATION_ENTER_TREE: case NOTIFICATION_THEME_CHANGED: { locale_edit->set_icon(get_theme_icon(SNAME("Translation"), SNAME("EditorIcons"))); } break; } } void EditorPropertyLocale::_locale_focus_exited() { _locale_selected(locale->get_text()); } void EditorPropertyLocale::_bind_methods() { } EditorPropertyLocale::EditorPropertyLocale() { HBoxContainer *locale_hb = memnew(HBoxContainer); add_child(locale_hb); locale = memnew(LineEdit); locale_hb->add_child(locale); locale->connect("text_submitted", callable_mp(this, &EditorPropertyLocale::_locale_selected)); locale->connect("focus_exited", callable_mp(this, &EditorPropertyLocale::_locale_focus_exited)); locale->set_h_size_flags(SIZE_EXPAND_FILL); locale_edit = memnew(Button); locale_edit->set_clip_text(true); locale_hb->add_child(locale_edit); add_focusable(locale); dialog = nullptr; locale_edit->connect("pressed", callable_mp(this, &EditorPropertyLocale::_locale_pressed)); } ///////////////////// PATH ///////////////////////// void EditorPropertyPath::_set_read_only(bool p_read_only) { path->set_editable(!p_read_only); path_edit->set_disabled(p_read_only); }; void EditorPropertyPath::_path_selected(const String &p_path) { emit_changed(get_edited_property(), p_path); update_property(); } void EditorPropertyPath::_path_pressed() { if (!dialog) { dialog = memnew(EditorFileDialog); dialog->connect("file_selected", callable_mp(this, &EditorPropertyPath::_path_selected)); dialog->connect("dir_selected", callable_mp(this, &EditorPropertyPath::_path_selected)); add_child(dialog); } String full_path = get_edited_object()->get(get_edited_property()); dialog->clear_filters(); if (global) { dialog->set_access(EditorFileDialog::ACCESS_FILESYSTEM); } else { dialog->set_access(EditorFileDialog::ACCESS_RESOURCES); } if (folder) { dialog->set_file_mode(EditorFileDialog::FILE_MODE_OPEN_DIR); dialog->set_current_dir(full_path); } else { dialog->set_file_mode(save_mode ? EditorFileDialog::FILE_MODE_SAVE_FILE : EditorFileDialog::FILE_MODE_OPEN_FILE); for (int i = 0; i < extensions.size(); i++) { String e = extensions[i].strip_edges(); if (!e.is_empty()) { dialog->add_filter(extensions[i].strip_edges()); } } dialog->set_current_path(full_path); } dialog->popup_file_dialog(); } void EditorPropertyPath::update_property() { String full_path = get_edited_object()->get(get_edited_property()); path->set_text(full_path); path->set_tooltip_text(full_path); } void EditorPropertyPath::setup(const Vector &p_extensions, bool p_folder, bool p_global) { extensions = p_extensions; folder = p_folder; global = p_global; } void EditorPropertyPath::set_save_mode() { save_mode = true; } void EditorPropertyPath::_notification(int p_what) { switch (p_what) { case NOTIFICATION_ENTER_TREE: case NOTIFICATION_THEME_CHANGED: { path_edit->set_icon(get_theme_icon(SNAME("Folder"), SNAME("EditorIcons"))); } break; } } void EditorPropertyPath::_path_focus_exited() { _path_selected(path->get_text()); } void EditorPropertyPath::_drop_data_fw(const Point2 &p_point, const Variant &p_data, Control *p_from) { const Dictionary drag_data = p_data; if (!drag_data.has("type")) { return; } if (String(drag_data["type"]) != "files") { return; } const Vector filesPaths = drag_data["files"]; if (filesPaths.size() == 0) { return; } emit_changed(get_edited_property(), filesPaths[0]); update_property(); } bool EditorPropertyPath::_can_drop_data_fw(const Point2 &p_point, const Variant &p_data, Control *p_from) const { const Dictionary drag_data = p_data; if (!drag_data.has("type")) { return false; } if (String(drag_data["type"]) != "files") { return false; } const Vector filesPaths = drag_data["files"]; if (filesPaths.size() == 0) { return false; } for (const String &extension : extensions) { if (filesPaths[0].ends_with(extension.substr(1, extension.size() - 1))) { return true; } } return false; } void EditorPropertyPath::_bind_methods() { ClassDB::bind_method(D_METHOD("_can_drop_data_fw", "position", "data", "from"), &EditorPropertyPath::_can_drop_data_fw); ClassDB::bind_method(D_METHOD("_drop_data_fw", "position", "data", "from"), &EditorPropertyPath::_drop_data_fw); } EditorPropertyPath::EditorPropertyPath() { HBoxContainer *path_hb = memnew(HBoxContainer); add_child(path_hb); path = memnew(LineEdit); path->set_drag_forwarding(this); path->set_structured_text_bidi_override(TextServer::STRUCTURED_TEXT_FILE); path_hb->add_child(path); path->connect("text_submitted", callable_mp(this, &EditorPropertyPath::_path_selected)); path->connect("focus_exited", callable_mp(this, &EditorPropertyPath::_path_focus_exited)); path->set_h_size_flags(SIZE_EXPAND_FILL); path_edit = memnew(Button); path_edit->set_clip_text(true); path_hb->add_child(path_edit); add_focusable(path); dialog = nullptr; path_edit->connect("pressed", callable_mp(this, &EditorPropertyPath::_path_pressed)); } ///////////////////// CLASS NAME ///////////////////////// void EditorPropertyClassName::_set_read_only(bool p_read_only) { property->set_disabled(p_read_only); }; void EditorPropertyClassName::setup(const String &p_base_type, const String &p_selected_type) { base_type = p_base_type; dialog->set_base_type(base_type); selected_type = p_selected_type; property->set_text(selected_type); } void EditorPropertyClassName::update_property() { String s = get_edited_object()->get(get_edited_property()); property->set_text(s); selected_type = s; } void EditorPropertyClassName::_property_selected() { dialog->popup_create(true); } void EditorPropertyClassName::_dialog_created() { selected_type = dialog->get_selected_type(); emit_changed(get_edited_property(), selected_type); update_property(); } void EditorPropertyClassName::_bind_methods() { } EditorPropertyClassName::EditorPropertyClassName() { property = memnew(Button); property->set_clip_text(true); add_child(property); add_focusable(property); property->set_text(selected_type); property->connect("pressed", callable_mp(this, &EditorPropertyClassName::_property_selected)); dialog = memnew(CreateDialog); dialog->set_base_type(base_type); dialog->connect("create", callable_mp(this, &EditorPropertyClassName::_dialog_created)); add_child(dialog); } ///////////////////// MEMBER ///////////////////////// void EditorPropertyMember::_set_read_only(bool p_read_only) { property->set_disabled(p_read_only); }; void EditorPropertyMember::_property_selected(const String &p_selected) { emit_changed(get_edited_property(), p_selected); update_property(); } void EditorPropertyMember::_property_select() { if (!selector) { selector = memnew(PropertySelector); selector->connect("selected", callable_mp(this, &EditorPropertyMember::_property_selected)); add_child(selector); } String current = get_edited_object()->get(get_edited_property()); if (hint == MEMBER_METHOD_OF_VARIANT_TYPE) { Variant::Type type = Variant::NIL; for (int i = 0; i < Variant::VARIANT_MAX; i++) { if (hint_text == Variant::get_type_name(Variant::Type(i))) { type = Variant::Type(i); } } if (type != Variant::NIL) { selector->select_method_from_basic_type(type, current); } } else if (hint == MEMBER_METHOD_OF_BASE_TYPE) { selector->select_method_from_base_type(hint_text, current); } else if (hint == MEMBER_METHOD_OF_INSTANCE) { Object *instance = ObjectDB::get_instance(ObjectID(hint_text.to_int())); if (instance) { selector->select_method_from_instance(instance, current); } } else if (hint == MEMBER_METHOD_OF_SCRIPT) { Object *obj = ObjectDB::get_instance(ObjectID(hint_text.to_int())); if (Object::cast_to