Path: blob/master/editor/inspector/editor_resource_picker.cpp
9903 views
/**************************************************************************/1/* editor_resource_picker.cpp */2/**************************************************************************/3/* This file is part of: */4/* GODOT ENGINE */5/* https://godotengine.org */6/**************************************************************************/7/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */8/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */9/* */10/* Permission is hereby granted, free of charge, to any person obtaining */11/* a copy of this software and associated documentation files (the */12/* "Software"), to deal in the Software without restriction, including */13/* without limitation the rights to use, copy, modify, merge, publish, */14/* distribute, sublicense, and/or sell copies of the Software, and to */15/* permit persons to whom the Software is furnished to do so, subject to */16/* the following conditions: */17/* */18/* The above copyright notice and this permission notice shall be */19/* included in all copies or substantial portions of the Software. */20/* */21/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */22/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */23/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */24/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */25/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */26/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */27/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */28/**************************************************************************/2930#include "editor_resource_picker.h"3132#include "editor/audio/audio_stream_preview.h"33#include "editor/doc/editor_help.h"34#include "editor/docks/filesystem_dock.h"35#include "editor/docks/scene_tree_dock.h"36#include "editor/editor_node.h"37#include "editor/editor_string_names.h"38#include "editor/gui/editor_file_dialog.h"39#include "editor/gui/editor_quick_open_dialog.h"40#include "editor/inspector/editor_inspector.h"41#include "editor/inspector/editor_resource_preview.h"42#include "editor/plugins/editor_resource_conversion_plugin.h"43#include "editor/script/script_editor_plugin.h"44#include "editor/settings/editor_settings.h"45#include "editor/themes/editor_scale.h"46#include "scene/gui/button.h"47#include "scene/gui/texture_rect.h"48#include "scene/resources/gradient_texture.h"49#include "scene/resources/image_texture.h"5051void EditorResourcePicker::_update_resource() {52String resource_path;53if (edited_resource.is_valid() && edited_resource->get_path().is_resource_file()) {54resource_path = edited_resource->get_path() + "\n";55}56String class_name = _get_resource_type(edited_resource);5758if (preview_rect) {59preview_rect->set_texture(Ref<Texture2D>());6061assign_button->set_custom_minimum_size(assign_button_min_size);6263if (edited_resource == Ref<Resource>()) {64assign_button->set_button_icon(Ref<Texture2D>());65assign_button->set_text(TTR("<empty>"));66assign_button->set_tooltip_text("");67} else {68assign_button->set_button_icon(EditorNode::get_singleton()->get_object_icon(edited_resource.operator->(), SNAME("Object")));6970if (!edited_resource->get_name().is_empty()) {71assign_button->set_text(edited_resource->get_name());72} else if (edited_resource->get_path().is_resource_file()) {73assign_button->set_text(edited_resource->get_path().get_file());74} else {75assign_button->set_text(class_name);76}7778if (edited_resource->get_path().is_resource_file()) {79resource_path = edited_resource->get_path() + "\n";80}81assign_button->set_tooltip_text(resource_path + TTR("Type:") + " " + class_name);8283// Preview will override the above, so called at the end.84EditorResourcePreview::get_singleton()->queue_edited_resource_preview(edited_resource, this, "_update_resource_preview", edited_resource->get_instance_id());85}86} else if (edited_resource.is_valid()) {87assign_button->set_tooltip_text(resource_path + TTR("Type:") + " " + edited_resource->get_class());88}8990assign_button->set_disabled(!editable && edited_resource.is_null());91quick_load_button->set_visible(editable && edited_resource.is_null());92}9394void EditorResourcePicker::_update_resource_preview(const String &p_path, const Ref<Texture2D> &p_preview, const Ref<Texture2D> &p_small_preview, ObjectID p_obj) {95if (edited_resource.is_null() || edited_resource->get_instance_id() != p_obj) {96return;97}9899if (preview_rect) {100Ref<Script> scr = edited_resource;101if (scr.is_valid()) {102assign_button->set_text(scr->get_path().get_file());103return;104}105106if (p_preview.is_valid()) {107preview_rect->set_offset(SIDE_LEFT, assign_button->get_button_icon()->get_width() + assign_button->get_theme_stylebox(CoreStringName(normal))->get_content_margin(SIDE_LEFT) + get_theme_constant(SNAME("h_separation"), SNAME("Button")));108109// Resource-specific stretching.110if (Ref<GradientTexture1D>(edited_resource).is_valid() || Ref<Gradient>(edited_resource).is_valid()) {111preview_rect->set_stretch_mode(TextureRect::STRETCH_SCALE);112assign_button->set_custom_minimum_size(assign_button_min_size);113} else {114preview_rect->set_stretch_mode(TextureRect::STRETCH_KEEP_ASPECT_CENTERED);115int thumbnail_size = EDITOR_GET("filesystem/file_dialog/thumbnail_size");116thumbnail_size *= EDSCALE;117assign_button->set_custom_minimum_size(assign_button_min_size.max(Size2(1, thumbnail_size)));118}119120preview_rect->set_texture(p_preview);121assign_button->set_text("");122}123}124}125126void EditorResourcePicker::_resource_selected() {127if (edited_resource.is_null()) {128edit_button->set_pressed(true);129_update_menu();130return;131}132133emit_signal(SNAME("resource_selected"), edited_resource, false);134}135136void EditorResourcePicker::_resource_changed() {137emit_signal(SNAME("resource_changed"), edited_resource);138_update_resource();139}140141void EditorResourcePicker::_file_selected(const String &p_path) {142Ref<Resource> loaded_resource = ResourceLoader::load(p_path);143ERR_FAIL_COND_MSG(loaded_resource.is_null(), "Cannot load resource from path '" + p_path + "'.");144145if (!base_type.is_empty()) {146bool any_type_matches = false;147148String res_type = loaded_resource->get_class();149Ref<Script> res_script = loaded_resource->get_script();150bool is_global_class = false;151if (res_script.is_valid()) {152String script_type = EditorNode::get_editor_data().script_class_get_name(res_script->get_path());153if (!script_type.is_empty()) {154is_global_class = true;155res_type = script_type;156}157}158159for (int i = 0; i < base_type.get_slice_count(","); i++) {160String base = base_type.get_slicec(',', i);161162any_type_matches = is_global_class ? EditorNode::get_editor_data().script_class_is_parent(res_type, base) : loaded_resource->is_class(base);163164if (any_type_matches) {165break;166}167}168169if (!any_type_matches) {170EditorNode::get_singleton()->show_warning(vformat(TTR("The selected resource (%s) does not match any type expected for this property (%s)."), res_type, base_type));171return;172}173}174175edited_resource = loaded_resource;176_resource_changed();177}178179void EditorResourcePicker::_resource_saved(Object *p_resource) {180if (edited_resource.is_valid() && p_resource == edited_resource.ptr()) {181emit_signal(SNAME("resource_changed"), edited_resource);182_update_resource();183}184}185186void EditorResourcePicker::_update_menu() {187if (edit_menu && edit_menu->is_visible()) {188edit_button->set_pressed(false);189edit_menu->hide();190return;191}192193_update_menu_items();194195Rect2 gt = edit_button->get_screen_rect();196edit_menu->reset_size();197int ms = edit_menu->get_contents_minimum_size().width;198Vector2 popup_pos = gt.get_end() - Vector2(ms, 0);199edit_menu->set_position(popup_pos);200edit_menu->popup();201}202203void EditorResourcePicker::_update_menu_items() {204_ensure_resource_menu();205edit_menu->clear();206207// Add options for creating specific subtypes of the base resource type.208if (is_editable()) {209set_create_options(edit_menu);210211// Add an option to load a resource from a file using the QuickOpen dialog.212edit_menu->add_icon_item(get_editor_theme_icon(SNAME("Load")), TTR("Quick Load..."), OBJ_MENU_QUICKLOAD);213edit_menu->set_item_tooltip(-1, TTR("Opens a quick menu to select from a list of allowed Resource files."));214215// Add an option to load a resource from a file using the regular file dialog.216edit_menu->add_icon_item(get_editor_theme_icon(SNAME("Load")), TTR("Load..."), OBJ_MENU_LOAD);217}218219// Add options for changing existing value of the resource.220if (edited_resource.is_valid()) {221// Determine if the edited resource is part of another scene (foreign) which was imported222bool is_edited_resource_foreign_import = EditorNode::get_singleton()->is_resource_read_only(edited_resource, true);223224// If the resource is determined to be foreign and imported, change the menu entry's description to 'inspect' rather than 'edit'225// since will only be able to view its properties in read-only mode.226if (is_edited_resource_foreign_import) {227// The 'Search' icon is a magnifying glass, which seems appropriate, but maybe a bespoke icon is preferred here.228edit_menu->add_icon_item(get_editor_theme_icon(SNAME("Search")), TTR("Inspect"), OBJ_MENU_INSPECT);229} else {230edit_menu->add_icon_item(get_editor_theme_icon(SNAME("Edit")), TTR("Edit"), OBJ_MENU_INSPECT);231}232233if (is_editable()) {234if (!_is_custom_type_script()) {235edit_menu->add_icon_item(get_editor_theme_icon(SNAME("Clear")), TTR("Clear"), OBJ_MENU_CLEAR);236}237edit_menu->add_icon_item(get_editor_theme_icon(SNAME("Duplicate")), TTR("Make Unique"), OBJ_MENU_MAKE_UNIQUE);238239// Check whether the resource has subresources.240List<PropertyInfo> property_list;241edited_resource->get_property_list(&property_list);242bool has_subresources = false;243for (PropertyInfo &p : property_list) {244if ((p.type == Variant::OBJECT) && (p.hint == PROPERTY_HINT_RESOURCE_TYPE) && (p.name != "script") && ((Object *)edited_resource->get(p.name) != nullptr)) {245has_subresources = true;246break;247}248}249if (has_subresources) {250edit_menu->add_icon_item(get_editor_theme_icon(SNAME("Duplicate")), TTR("Make Unique (Recursive)"), OBJ_MENU_MAKE_UNIQUE_RECURSIVE);251}252253edit_menu->add_icon_item(get_editor_theme_icon(SNAME("Save")), TTR("Save"), OBJ_MENU_SAVE);254edit_menu->add_icon_item(get_editor_theme_icon(SNAME("Save")), TTR("Save As..."), OBJ_MENU_SAVE_AS);255}256257if (edited_resource->get_path().is_resource_file()) {258edit_menu->add_separator();259edit_menu->add_icon_item(get_editor_theme_icon(SNAME("ShowInFileSystem")), TTR("Show in FileSystem"), OBJ_MENU_SHOW_IN_FILE_SYSTEM);260}261}262263// Add options to copy/paste resource.264Ref<Resource> cb = EditorSettings::get_singleton()->get_resource_clipboard();265bool paste_valid = false;266if (is_editable() && cb.is_valid()) {267if (base_type.is_empty()) {268paste_valid = true;269} else {270String res_type = _get_resource_type(cb);271272for (int i = 0; i < base_type.get_slice_count(","); i++) {273String base = base_type.get_slicec(',', i);274275paste_valid = ClassDB::is_parent_class(res_type, base) || EditorNode::get_editor_data().script_class_is_parent(res_type, base);276277if (paste_valid) {278break;279}280}281}282}283284if (edited_resource.is_valid() || paste_valid) {285edit_menu->add_separator();286287if (edited_resource.is_valid()) {288edit_menu->add_item(TTR("Copy"), OBJ_MENU_COPY);289}290291if (paste_valid) {292edit_menu->add_item(TTR("Paste"), OBJ_MENU_PASTE);293edit_menu->add_item(TTRC("Paste as Unique"), OBJ_MENU_PASTE_AS_UNIQUE);294}295}296297// Add options to convert existing resource to another type of resource.298if (is_editable() && edited_resource.is_valid()) {299Vector<Ref<EditorResourceConversionPlugin>> conversions = EditorNode::get_singleton()->find_resource_conversion_plugin_for_resource(edited_resource);300if (!conversions.is_empty()) {301edit_menu->add_separator();302}303int relative_id = 0;304for (const Ref<EditorResourceConversionPlugin> &conversion : conversions) {305String what = conversion->converts_to();306Ref<Texture2D> icon;307if (has_theme_icon(what, EditorStringName(EditorIcons))) {308icon = get_editor_theme_icon(what);309} else {310icon = get_editor_theme_icon(SNAME("Object"));311}312313edit_menu->add_icon_item(icon, vformat(TTR("Convert to %s"), what), CONVERT_BASE_ID + relative_id);314relative_id++;315}316}317}318319void EditorResourcePicker::_edit_menu_cbk(int p_which) {320switch (p_which) {321case OBJ_MENU_LOAD: {322List<String> extensions;323for (int i = 0; i < base_type.get_slice_count(","); i++) {324String base = base_type.get_slicec(',', i);325if (base == "Resource") {326base = "";327}328ResourceLoader::get_recognized_extensions_for_type(base, &extensions);329if (ScriptServer::is_global_class(base)) {330ResourceLoader::get_recognized_extensions_for_type(ScriptServer::get_global_class_native_base(base), &extensions);331}332}333334HashSet<String> valid_extensions;335for (const String &E : extensions) {336valid_extensions.insert(E);337}338339if (!file_dialog) {340file_dialog = memnew(EditorFileDialog);341file_dialog->set_file_mode(EditorFileDialog::FILE_MODE_OPEN_FILE);342add_child(file_dialog);343file_dialog->connect("file_selected", callable_mp(this, &EditorResourcePicker::_file_selected));344}345346file_dialog->clear_filters();347for (const String &E : valid_extensions) {348file_dialog->add_filter("*." + E, E.to_upper());349}350351file_dialog->popup_file_dialog();352} break;353354case OBJ_MENU_QUICKLOAD: {355const Vector<String> &base_types_string = base_type.split(",");356357Vector<StringName> base_types;358for (const String &type : base_types_string) {359base_types.push_back(type);360}361362EditorNode::get_singleton()->get_quick_open_dialog()->popup_dialog(base_types, callable_mp(this, &EditorResourcePicker::_file_selected));363} break;364365case OBJ_MENU_INSPECT: {366if (edited_resource.is_valid()) {367emit_signal(SNAME("resource_selected"), edited_resource, true);368}369} break;370371case OBJ_MENU_CLEAR: {372edited_resource = Ref<Resource>();373_resource_changed();374} break;375376case OBJ_MENU_MAKE_UNIQUE: {377if (edited_resource.is_null()) {378return;379}380381Ref<Resource> unique_resource = edited_resource->duplicate();382ERR_FAIL_COND(unique_resource.is_null()); // duplicate() may fail.383384edited_resource = unique_resource;385_resource_changed();386} break;387388case OBJ_MENU_MAKE_UNIQUE_RECURSIVE: {389if (edited_resource.is_null()) {390return;391}392393if (!duplicate_resources_dialog) {394duplicate_resources_dialog = memnew(ConfirmationDialog);395add_child(duplicate_resources_dialog);396duplicate_resources_dialog->set_title(TTR("Make Unique (Recursive)"));397duplicate_resources_dialog->connect(SceneStringName(confirmed), callable_mp(this, &EditorResourcePicker::_duplicate_selected_resources));398399VBoxContainer *vb = memnew(VBoxContainer);400duplicate_resources_dialog->add_child(vb);401402Label *label = memnew(Label(TTR("Select resources to make unique:")));403vb->add_child(label);404405duplicate_resources_tree = memnew(Tree);406duplicate_resources_tree->set_accessibility_name(TTRC("Duplicate resources"));407duplicate_resources_tree->set_auto_translate_mode(AUTO_TRANSLATE_MODE_DISABLED);408vb->add_child(duplicate_resources_tree);409duplicate_resources_tree->set_columns(2);410duplicate_resources_tree->set_v_size_flags(SIZE_EXPAND_FILL);411}412413duplicate_resources_tree->clear();414TreeItem *root = duplicate_resources_tree->create_item();415_gather_resources_to_duplicate(edited_resource, root);416417duplicate_resources_dialog->reset_size();418duplicate_resources_dialog->popup_centered(Vector2(500, 400) * EDSCALE);419} break;420421case OBJ_MENU_SAVE: {422if (edited_resource.is_null()) {423return;424}425EditorNode::get_singleton()->save_resource(edited_resource);426} break;427428case OBJ_MENU_SAVE_AS: {429if (edited_resource.is_null()) {430return;431}432Callable resource_saved = callable_mp(this, &EditorResourcePicker::_resource_saved);433if (!EditorNode::get_singleton()->is_connected("resource_saved", resource_saved)) {434EditorNode::get_singleton()->connect("resource_saved", resource_saved);435}436EditorNode::get_singleton()->save_resource_as(edited_resource);437} break;438439case OBJ_MENU_COPY: {440EditorSettings::get_singleton()->set_resource_clipboard(edited_resource);441} break;442443case OBJ_MENU_PASTE: {444edited_resource = EditorSettings::get_singleton()->get_resource_clipboard();445bool make_unique = true;446447// Automatically make resource unique if it belongs to another scene or resource.448if (!EditorNode::get_singleton()->get_edited_scene() || !edited_resource->is_built_in() || edited_resource->get_path().get_slice("::", 0) == EditorNode::get_singleton()->get_edited_scene()->get_scene_file_path()) {449make_unique = false;450} else if (resource_owner) {451Resource *res = Object::cast_to<Resource>(resource_owner);452if (res && edited_resource->get_path().get_slice("::", 0) == res->get_path().get_slice("::", 0)) {453make_unique = false;454}455}456457if (make_unique) {458_edit_menu_cbk(OBJ_MENU_MAKE_UNIQUE);459} else {460_resource_changed();461}462} break;463464case OBJ_MENU_PASTE_AS_UNIQUE: {465edited_resource = EditorSettings::get_singleton()->get_resource_clipboard();466// Use the recursive version when using Paste as Unique.467// This will show up a dialog to select which resources to make unique.468_edit_menu_cbk(OBJ_MENU_MAKE_UNIQUE_RECURSIVE);469} break;470471case OBJ_MENU_SHOW_IN_FILE_SYSTEM: {472FileSystemDock::get_singleton()->navigate_to_path(edited_resource->get_path());473} break;474475default: {476// Allow subclasses to handle their own options first, only then fallback on the default branch logic.477if (handle_menu_selected(p_which)) {478break;479}480481if (p_which >= CONVERT_BASE_ID) {482int to_type = p_which - CONVERT_BASE_ID;483Vector<Ref<EditorResourceConversionPlugin>> conversions = EditorNode::get_singleton()->find_resource_conversion_plugin_for_resource(edited_resource);484ERR_FAIL_INDEX(to_type, conversions.size());485486edited_resource = conversions[to_type]->convert(edited_resource);487_resource_changed();488break;489}490491ERR_FAIL_COND(inheritors_array.is_empty());492493String intype = inheritors_array[p_which - TYPE_BASE_ID];494Variant obj;495496if (ScriptServer::is_global_class(intype)) {497obj = EditorNode::get_editor_data().script_class_instance(intype);498} else {499obj = ClassDB::instantiate(intype);500}501502if (!obj) {503obj = EditorNode::get_editor_data().instantiate_custom_type(intype, "Resource");504}505506Resource *resp = Object::cast_to<Resource>(obj);507ERR_BREAK(!resp);508resp->set_path(_get_owner_path() + "::" + resp->generate_scene_unique_id()); // Assign a base path for built-in Resources.509510EditorNode::get_editor_data().instantiate_object_properties(obj);511512// Prevent freeing of the object until the end of the update of the resource (GH-88286).513Ref<Resource> old_edited_resource = edited_resource;514edited_resource = Ref<Resource>(resp);515_resource_changed();516} break;517}518}519520void EditorResourcePicker::set_create_options(Object *p_menu_node) {521_ensure_resource_menu();522// If a subclass implements this method, use it to replace all create items.523if (GDVIRTUAL_CALL(_set_create_options, p_menu_node)) {524return;525}526527// By default provide generic "New ..." options.528if (!base_type.is_empty()) {529int idx = 0;530531_ensure_allowed_types();532HashSet<StringName> allowed_types = allowed_types_without_convert;533if (!allowed_types.is_empty()) {534edit_menu->add_separator(TTRC("New"));535}536537for (const StringName &E : allowed_types) {538const String &t = E;539540if (!ClassDB::can_instantiate(t)) {541continue;542}543544inheritors_array.push_back(t);545546Ref<Texture2D> icon = EditorNode::get_singleton()->get_class_icon(t, "Object");547int id = TYPE_BASE_ID + idx;548edit_menu->add_icon_item(icon, t, id);549edit_menu->set_item_auto_translate_mode(-1, AUTO_TRANSLATE_MODE_DISABLED);550551HashMap<String, DocData::ClassDoc>::Iterator class_doc = EditorHelp::get_doc_data()->class_list.find(t);552if (class_doc) {553edit_menu->set_item_tooltip(-1, DTR(class_doc->value.brief_description));554}555556idx++;557}558559if (edit_menu->get_item_count()) {560edit_menu->add_separator();561}562}563}564565bool EditorResourcePicker::handle_menu_selected(int p_which) {566bool success = false;567GDVIRTUAL_CALL(_handle_menu_selected, p_which, success);568return success;569}570571void EditorResourcePicker::_button_draw() {572if (dropping) {573Color color = get_theme_color(SNAME("accent_color"), EditorStringName(Editor));574assign_button->draw_rect(Rect2(Point2(), assign_button->get_size()), color, false);575}576}577578void EditorResourcePicker::_button_input(const Ref<InputEvent> &p_event) {579Ref<InputEventMouseButton> mb = p_event;580581if (mb.is_valid() && mb->is_pressed() && mb->get_button_index() == MouseButton::RIGHT) {582// Only attempt to update and show the menu if we have583// a valid resource or the Picker is editable, as584// there will otherwise be nothing to display.585if (edited_resource.is_valid() || is_editable()) {586if (edit_menu && edit_menu->is_visible()) {587edit_button->set_pressed(false);588edit_menu->hide();589return;590}591592_update_menu_items();593594Vector2 pos = get_screen_position() + mb->get_position();595edit_menu->reset_size();596edit_menu->set_position(pos);597edit_menu->popup();598}599}600}601602String EditorResourcePicker::_get_owner_path() const {603EditorProperty *property = Object::cast_to<EditorProperty>(get_parent());604if (!property) {605return String();606}607Object *obj = property->get_edited_object();608609Node *node = Object::cast_to<Node>(obj);610if (node) {611if (node->get_scene_file_path().is_empty()) {612node = node->get_owner();613}614if (node) {615return node->get_scene_file_path();616}617return String();618}619620Resource *res = Object::cast_to<Resource>(obj);621if (res && !res->is_built_in()) {622return res->get_path();623}624// TODO: It would be nice to handle deeper Resource nesting.625return String();626}627628String EditorResourcePicker::_get_resource_type(const Ref<Resource> &p_resource) const {629if (p_resource.is_null()) {630return String();631}632String res_type = p_resource->get_class();633634Ref<Script> res_script = p_resource->get_script();635if (res_script.is_null()) {636return res_type;637}638639// TODO: Replace with EditorFileSystem when PR #60606 is merged to use cached resource type.640String script_type = EditorNode::get_editor_data().script_class_get_name(res_script->get_path());641if (!script_type.is_empty()) {642res_type = script_type;643}644return res_type;645}646647static void _add_allowed_type(const StringName &p_type, List<StringName> *p_vector) {648if (p_vector->find(p_type)) {649// Already added.650return;651}652653if (ClassDB::class_exists(p_type)) {654// Engine class,655656if (!ClassDB::is_virtual(p_type)) {657p_vector->push_back(p_type);658}659660LocalVector<StringName> inheriters;661ClassDB::get_inheriters_from_class(p_type, inheriters);662for (const StringName &S : inheriters) {663_add_allowed_type(S, p_vector);664}665} else {666// Script class.667p_vector->push_back(p_type);668}669670List<StringName> inheriters;671ScriptServer::get_inheriters_list(p_type, &inheriters);672for (const StringName &S : inheriters) {673_add_allowed_type(S, p_vector);674}675}676677void EditorResourcePicker::_ensure_allowed_types() const {678if (!allowed_types_without_convert.is_empty()) {679return;680}681682List<StringName> final_allowed;683684Vector<String> allowed_types = base_type.split(",");685int size = allowed_types.size();686687for (const String &S : allowed_types) {688const String base = S.strip_edges();689if (base.begins_with("-")) {690final_allowed.erase(base.right(-1));691continue;692}693_add_allowed_type(base, &final_allowed);694}695696for (const StringName &SN : final_allowed) {697allowed_types_without_convert.insert(SN);698}699700allowed_types_with_convert = HashSet<StringName>(allowed_types_without_convert);701702for (int i = 0; i < size; i++) {703const String base = allowed_types[i].strip_edges();704if (base == "BaseMaterial3D") {705allowed_types_with_convert.insert("Texture2D");706} else if (ClassDB::is_parent_class("ShaderMaterial", base)) {707allowed_types_with_convert.insert("Shader");708} else if (ClassDB::is_parent_class("ImageTexture", base)) {709allowed_types_with_convert.insert("Image");710}711}712}713714bool EditorResourcePicker::_is_drop_valid(const Dictionary &p_drag_data) const {715{716const ObjectID source_picker = p_drag_data.get("source_picker", ObjectID());717if (source_picker == get_instance_id()) {718return false;719}720}721722if (base_type.is_empty()) {723return true;724}725726Dictionary drag_data = p_drag_data;727728Ref<Resource> res;729if (drag_data.has("type") && String(drag_data["type"]) == "script_list_element") {730ScriptEditorBase *se = Object::cast_to<ScriptEditorBase>(drag_data["script_list_element"]);731if (se) {732res = se->get_edited_resource();733}734} else if (drag_data.has("type") && String(drag_data["type"]) == "resource") {735res = drag_data["resource"];736} else if (drag_data.has("type") && String(drag_data["type"]) == "files") {737Vector<String> files = drag_data["files"];738739if (files.size() == 1) {740if (ResourceLoader::exists(files[0])) {741// TODO: Extract the typename of the dropped filepath's resource in a more performant way, without fully loading it.742res = ResourceLoader::load(files[0]);743}744}745}746747_ensure_allowed_types();748HashSet<StringName> allowed_types = allowed_types_with_convert;749750if (res.is_valid()) {751String res_type = _get_resource_type(res);752753if (_is_type_valid(res_type, allowed_types)) {754return true;755}756757if (res->get_script()) {758StringName custom_class = EditorNode::get_singleton()->get_object_custom_type_name(res->get_script());759if (_is_type_valid(custom_class, allowed_types)) {760return true;761}762}763}764765return false;766}767768bool EditorResourcePicker::_is_type_valid(const String &p_type_name, const HashSet<StringName> &p_allowed_types) const {769for (const StringName &E : p_allowed_types) {770String at = E;771if (p_type_name == at || ClassDB::is_parent_class(p_type_name, at) || EditorNode::get_editor_data().script_class_is_parent(p_type_name, at)) {772return true;773}774}775776return false;777}778779bool EditorResourcePicker::_is_custom_type_script() const {780Ref<Script> resource_as_script = edited_resource;781782if (resource_as_script.is_valid() && resource_owner && resource_owner->has_meta(SceneStringName(_custom_type_script))) {783return true;784}785786return false;787}788789Variant EditorResourcePicker::get_drag_data_fw(const Point2 &p_point, Control *p_from) {790if (edited_resource.is_valid()) {791Dictionary drag_data = EditorNode::get_singleton()->drag_resource(edited_resource, p_from);792drag_data["source_picker"] = get_instance_id();793return drag_data;794}795796return Variant();797}798799bool EditorResourcePicker::can_drop_data_fw(const Point2 &p_point, const Variant &p_data, Control *p_from) const {800return editable && _is_drop_valid(p_data);801}802803void EditorResourcePicker::drop_data_fw(const Point2 &p_point, const Variant &p_data, Control *p_from) {804ERR_FAIL_COND(!_is_drop_valid(p_data));805806Dictionary drag_data = p_data;807808Ref<Resource> dropped_resource;809if (drag_data.has("type") && String(drag_data["type"]) == "script_list_element") {810ScriptEditorBase *se = Object::cast_to<ScriptEditorBase>(drag_data["script_list_element"]);811if (se) {812dropped_resource = se->get_edited_resource();813}814} else if (drag_data.has("type") && String(drag_data["type"]) == "resource") {815dropped_resource = drag_data["resource"];816}817818if (dropped_resource.is_null() && drag_data.has("type") && String(drag_data["type"]) == "files") {819Vector<String> files = drag_data["files"];820821if (files.size() == 1) {822dropped_resource = ResourceLoader::load(files[0]);823}824}825826if (dropped_resource.is_valid()) {827_ensure_allowed_types();828HashSet<StringName> allowed_types = allowed_types_without_convert;829830String res_type = _get_resource_type(dropped_resource);831832// If the accepted dropped resource is from the extended list, it requires conversion.833if (!_is_type_valid(res_type, allowed_types)) {834for (const StringName &E : allowed_types) {835String at = E;836837if (at == "BaseMaterial3D" && Ref<Texture2D>(dropped_resource).is_valid()) {838// Use existing resource if possible and only replace its data.839Ref<StandardMaterial3D> mat = edited_resource;840if (mat.is_null()) {841mat.instantiate();842}843mat->set_texture(StandardMaterial3D::TextureParam::TEXTURE_ALBEDO, dropped_resource);844dropped_resource = mat;845break;846}847848if (at == "ShaderMaterial" && Ref<Shader>(dropped_resource).is_valid()) {849Ref<ShaderMaterial> mat = edited_resource;850if (mat.is_null()) {851mat.instantiate();852}853mat->set_shader(dropped_resource);854dropped_resource = mat;855break;856}857858if (at == "ImageTexture" && Ref<Image>(dropped_resource).is_valid()) {859Ref<ImageTexture> texture = edited_resource;860if (texture.is_null()) {861texture.instantiate();862}863texture->set_image(dropped_resource);864dropped_resource = texture;865break;866}867}868}869870edited_resource = dropped_resource;871_resource_changed();872}873}874875void EditorResourcePicker::_bind_methods() {876ClassDB::bind_method(D_METHOD("_update_resource_preview"), &EditorResourcePicker::_update_resource_preview);877878ClassDB::bind_method(D_METHOD("set_base_type", "base_type"), &EditorResourcePicker::set_base_type);879ClassDB::bind_method(D_METHOD("get_base_type"), &EditorResourcePicker::get_base_type);880ClassDB::bind_method(D_METHOD("get_allowed_types"), &EditorResourcePicker::get_allowed_types);881ClassDB::bind_method(D_METHOD("set_edited_resource", "resource"), &EditorResourcePicker::set_edited_resource);882ClassDB::bind_method(D_METHOD("get_edited_resource"), &EditorResourcePicker::get_edited_resource);883ClassDB::bind_method(D_METHOD("set_toggle_mode", "enable"), &EditorResourcePicker::set_toggle_mode);884ClassDB::bind_method(D_METHOD("is_toggle_mode"), &EditorResourcePicker::is_toggle_mode);885ClassDB::bind_method(D_METHOD("set_toggle_pressed", "pressed"), &EditorResourcePicker::set_toggle_pressed);886ClassDB::bind_method(D_METHOD("set_editable", "enable"), &EditorResourcePicker::set_editable);887ClassDB::bind_method(D_METHOD("is_editable"), &EditorResourcePicker::is_editable);888889GDVIRTUAL_BIND(_set_create_options, "menu_node");890GDVIRTUAL_BIND(_handle_menu_selected, "id");891892ADD_PROPERTY(PropertyInfo(Variant::STRING, "base_type"), "set_base_type", "get_base_type");893ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "edited_resource", PROPERTY_HINT_RESOURCE_TYPE, "Resource", PROPERTY_USAGE_NONE), "set_edited_resource", "get_edited_resource");894ADD_PROPERTY(PropertyInfo(Variant::BOOL, "editable"), "set_editable", "is_editable");895ADD_PROPERTY(PropertyInfo(Variant::BOOL, "toggle_mode"), "set_toggle_mode", "is_toggle_mode");896897ADD_SIGNAL(MethodInfo("resource_selected", PropertyInfo(Variant::OBJECT, "resource", PROPERTY_HINT_RESOURCE_TYPE, "Resource"), PropertyInfo(Variant::BOOL, "inspect")));898ADD_SIGNAL(MethodInfo("resource_changed", PropertyInfo(Variant::OBJECT, "resource", PROPERTY_HINT_RESOURCE_TYPE, "Resource")));899}900901void EditorResourcePicker::_notification(int p_what) {902switch (p_what) {903case NOTIFICATION_ENTER_TREE: {904_update_resource();905[[fallthrough]];906}907case NOTIFICATION_THEME_CHANGED: {908const int icon_width = get_theme_constant(SNAME("class_icon_size"), EditorStringName(Editor));909assign_button->add_theme_constant_override("icon_max_width", icon_width);910if (edit_menu) {911edit_menu->add_theme_constant_override("icon_max_width", icon_width);912}913914quick_load_button->set_button_icon(get_editor_theme_icon(SNAME("Load")));915edit_button->set_button_icon(get_theme_icon(SNAME("select_arrow"), SNAME("Tree")));916} break;917918case NOTIFICATION_DRAW: {919draw_style_box(get_theme_stylebox(SceneStringName(panel), SNAME("Tree")), Rect2(Point2(), get_size()));920} break;921922case NOTIFICATION_DRAG_BEGIN: {923if (editable && _is_drop_valid(get_viewport()->gui_get_drag_data())) {924dropping = true;925assign_button->queue_redraw();926}927} break;928929case NOTIFICATION_DRAG_END: {930if (dropping) {931dropping = false;932assign_button->queue_redraw();933}934} break;935936case NOTIFICATION_EXIT_TREE: {937Callable resource_saved = callable_mp(this, &EditorResourcePicker::_resource_saved);938if (EditorNode::get_singleton()->is_connected("resource_saved", resource_saved)) {939EditorNode::get_singleton()->disconnect("resource_saved", resource_saved);940}941} break;942}943}944945void EditorResourcePicker::set_assign_button_min_size(const Size2i &p_size) {946assign_button_min_size = p_size;947assign_button->set_custom_minimum_size(assign_button_min_size);948}949950void EditorResourcePicker::set_base_type(const String &p_base_type) {951base_type = p_base_type;952953// There is a possibility that the new base type is conflicting with the existing value.954// Keep the value, but warn the user that there is a potential mistake.955if (!base_type.is_empty() && edited_resource.is_valid()) {956_ensure_allowed_types();957HashSet<StringName> allowed_types = allowed_types_with_convert;958959StringName custom_class;960bool is_custom = false;961if (edited_resource->get_script()) {962custom_class = EditorNode::get_singleton()->get_object_custom_type_name(edited_resource->get_script());963is_custom = _is_type_valid(custom_class, allowed_types);964}965966if (!is_custom && !_is_type_valid(edited_resource->get_class(), allowed_types)) {967String class_str = (custom_class == StringName() ? edited_resource->get_class() : vformat("%s (%s)", custom_class, edited_resource->get_class()));968WARN_PRINT(vformat("Value mismatch between the new base type of this EditorResourcePicker, '%s', and the type of the value it already has, '%s'.", base_type, class_str));969}970}971}972973String EditorResourcePicker::get_base_type() const {974return base_type;975}976977Vector<String> EditorResourcePicker::get_allowed_types() const {978_ensure_allowed_types();979HashSet<StringName> allowed_types = allowed_types_without_convert;980981Vector<String> types;982types.resize(allowed_types.size());983984int i = 0;985String *w = types.ptrw();986for (const StringName &E : allowed_types) {987w[i] = E;988i++;989}990991return types;992}993994void EditorResourcePicker::set_edited_resource(Ref<Resource> p_resource) {995if (p_resource.is_null()) {996edited_resource = Ref<Resource>();997_update_resource();998return;999}10001001if (!base_type.is_empty()) {1002_ensure_allowed_types();1003HashSet<StringName> allowed_types = allowed_types_with_convert;10041005StringName custom_class;1006bool is_custom = false;1007if (p_resource->get_script()) {1008custom_class = EditorNode::get_singleton()->get_object_custom_type_name(p_resource->get_script());1009is_custom = _is_type_valid(custom_class, allowed_types);1010}10111012if (!is_custom && !_is_type_valid(p_resource->get_class(), allowed_types)) {1013String class_str = (custom_class == StringName() ? p_resource->get_class() : vformat("%s (%s)", custom_class, p_resource->get_class()));1014ERR_FAIL_MSG(vformat("Failed to set a resource of the type '%s' because this EditorResourcePicker only accepts '%s' and its derivatives.", class_str, base_type));1015}1016}1017set_edited_resource_no_check(p_resource);1018}10191020void EditorResourcePicker::set_edited_resource_no_check(Ref<Resource> p_resource) {1021edited_resource = p_resource;1022_update_resource();1023}10241025Ref<Resource> EditorResourcePicker::get_edited_resource() {1026return edited_resource;1027}10281029void EditorResourcePicker::set_toggle_mode(bool p_enable) {1030assign_button->set_toggle_mode(p_enable);1031}10321033bool EditorResourcePicker::is_toggle_mode() const {1034return assign_button->is_toggle_mode();1035}10361037void EditorResourcePicker::set_toggle_pressed(bool p_pressed) {1038if (!is_toggle_mode()) {1039return;1040}10411042assign_button->set_pressed(p_pressed);1043}10441045bool EditorResourcePicker::is_toggle_pressed() const {1046return assign_button->is_pressed();1047}10481049void EditorResourcePicker::set_resource_owner(Object *p_object) {1050resource_owner = p_object;1051}10521053void EditorResourcePicker::set_editable(bool p_editable) {1054editable = p_editable;1055assign_button->set_disabled(!editable && edited_resource.is_null());1056quick_load_button->set_visible(editable && edited_resource.is_null());1057edit_button->set_visible(editable);1058}10591060bool EditorResourcePicker::is_editable() const {1061return editable;1062}10631064void EditorResourcePicker::_ensure_resource_menu() {1065if (edit_menu) {1066return;1067}1068edit_menu = memnew(PopupMenu);1069edit_menu->add_theme_constant_override("icon_max_width", get_theme_constant(SNAME("class_icon_size"), EditorStringName(Editor)));1070add_child(edit_menu);1071edit_menu->connect(SceneStringName(id_pressed), callable_mp(this, &EditorResourcePicker::_edit_menu_cbk));1072edit_menu->connect("popup_hide", callable_mp((BaseButton *)edit_button, &BaseButton::set_pressed).bind(false));1073}10741075void EditorResourcePicker::_gather_resources_to_duplicate(const Ref<Resource> p_resource, TreeItem *p_item, const String &p_property_name) const {1076p_item->set_cell_mode(0, TreeItem::CELL_MODE_CHECK);10771078String res_name = p_resource->get_name();1079if (res_name.is_empty() && !p_resource->is_built_in()) {1080res_name = p_resource->get_path().get_file();1081}10821083if (res_name.is_empty()) {1084p_item->set_text(0, p_resource->get_class());1085} else {1086p_item->set_text(0, vformat("%s (%s)", p_resource->get_class(), res_name));1087}10881089p_item->set_icon(0, EditorNode::get_singleton()->get_object_icon(p_resource.ptr()));1090p_item->set_editable(0, true);10911092Array meta = { p_resource };1093p_item->set_metadata(0, meta);10941095if (!p_property_name.is_empty()) {1096p_item->set_text(1, p_property_name);1097}10981099static Vector<String> unique_exceptions = { "Image", "Shader", "Mesh", "FontFile" };1100if (!unique_exceptions.has(p_resource->get_class())) {1101// Automatically select resource, unless it's something that shouldn't be duplicated.1102p_item->set_checked(0, true);1103}11041105List<PropertyInfo> plist;1106p_resource->get_property_list(&plist);11071108for (const PropertyInfo &E : plist) {1109if (!(E.usage & PROPERTY_USAGE_STORAGE) || E.type != Variant::OBJECT || E.hint != PROPERTY_HINT_RESOURCE_TYPE) {1110continue;1111}11121113Ref<Resource> res = p_resource->get(E.name);1114if (res.is_null()) {1115continue;1116}11171118TreeItem *child = p_item->create_child();1119_gather_resources_to_duplicate(res, child, E.name);11201121meta = child->get_metadata(0);1122// Remember property name.1123meta.append(E.name);11241125if ((E.usage & PROPERTY_USAGE_NEVER_DUPLICATE)) {1126// The resource can't be duplicated, but make it appear on the list anyway.1127child->set_checked(0, false);1128child->set_editable(0, false);1129}1130}1131}11321133void EditorResourcePicker::_duplicate_selected_resources() {1134for (TreeItem *item = duplicate_resources_tree->get_root(); item; item = item->get_next_in_tree()) {1135if (!item->is_checked(0)) {1136continue;1137}11381139Array meta = item->get_metadata(0);1140Ref<Resource> res = meta[0];1141Ref<Resource> unique_resource = res->duplicate();1142ERR_FAIL_COND(unique_resource.is_null()); // duplicate() may fail.1143meta[0] = unique_resource;11441145if (meta.size() == 1) { // Root.1146edited_resource = unique_resource;1147_resource_changed();1148} else {1149Array parent_meta = item->get_parent()->get_metadata(0);1150Ref<Resource> parent = parent_meta[0];1151parent->set(meta[1], unique_resource);1152}1153}1154}11551156EditorResourcePicker::EditorResourcePicker(bool p_hide_assign_button_controls) {1157assign_button = memnew(Button);1158assign_button->set_flat(true);1159assign_button->set_h_size_flags(SIZE_EXPAND_FILL);1160assign_button->set_accessibility_name(TTRC("Assign Resource"));1161assign_button->set_expand_icon(true);1162assign_button->set_clip_text(true);1163assign_button->set_auto_translate_mode(AUTO_TRANSLATE_MODE_DISABLED);1164SET_DRAG_FORWARDING_GCD(assign_button, EditorResourcePicker);1165add_child(assign_button);1166assign_button->connect(SceneStringName(pressed), callable_mp(this, &EditorResourcePicker::_resource_selected));1167assign_button->connect(SceneStringName(draw), callable_mp(this, &EditorResourcePicker::_button_draw));1168assign_button->connect(SceneStringName(gui_input), callable_mp(this, &EditorResourcePicker::_button_input));11691170if (!p_hide_assign_button_controls) {1171preview_rect = memnew(TextureRect);1172preview_rect->set_expand_mode(TextureRect::EXPAND_IGNORE_SIZE);1173preview_rect->set_anchors_and_offsets_preset(PRESET_FULL_RECT);1174preview_rect->set_offset(SIDE_TOP, 1);1175preview_rect->set_offset(SIDE_BOTTOM, -1);1176preview_rect->set_offset(SIDE_RIGHT, -1);1177preview_rect->set_texture_filter(TEXTURE_FILTER_NEAREST_WITH_MIPMAPS);1178assign_button->add_child(preview_rect);1179}11801181quick_load_button = memnew(Button);1182quick_load_button->set_tooltip_text(TTRC("Quick Load"));1183add_child(quick_load_button);1184quick_load_button->connect(SceneStringName(pressed), callable_mp(this, &EditorResourcePicker::_edit_menu_cbk).bind(OBJ_MENU_QUICKLOAD));11851186edit_button = memnew(Button);1187edit_button->set_flat(false);1188edit_button->set_toggle_mode(true);1189edit_button->set_action_mode(BaseButton::ACTION_MODE_BUTTON_PRESS);1190edit_button->set_accessibility_name(TTRC("Edit"));1191add_child(edit_button);1192edit_button->connect(SceneStringName(pressed), callable_mp(this, &EditorResourcePicker::_update_menu));1193edit_button->connect(SceneStringName(gui_input), callable_mp(this, &EditorResourcePicker::_button_input));11941195add_theme_constant_override("separation", 0);1196}11971198// EditorScriptPicker11991200void EditorScriptPicker::set_create_options(Object *p_menu_node) {1201PopupMenu *menu_node = Object::cast_to<PopupMenu>(p_menu_node);1202if (!menu_node) {1203return;1204}12051206if (!(script_owner && script_owner->has_meta(SceneStringName(_custom_type_script)))) {1207menu_node->add_icon_item(get_editor_theme_icon(SNAME("ScriptCreate")), TTR("New Script..."), OBJ_MENU_NEW_SCRIPT);1208}12091210if (script_owner) {1211Ref<Script> scr = script_owner->get_script();1212if (scr.is_valid()) {1213menu_node->add_icon_item(get_editor_theme_icon(SNAME("ScriptExtend")), TTR("Extend Script..."), OBJ_MENU_EXTEND_SCRIPT);1214}1215}1216menu_node->add_separator();1217}12181219bool EditorScriptPicker::handle_menu_selected(int p_which) {1220switch (p_which) {1221case OBJ_MENU_NEW_SCRIPT: {1222if (script_owner) {1223SceneTreeDock::get_singleton()->open_script_dialog(script_owner, false);1224}1225return true;1226}12271228case OBJ_MENU_EXTEND_SCRIPT: {1229if (script_owner) {1230SceneTreeDock::get_singleton()->open_script_dialog(script_owner, true);1231}1232return true;1233}1234}12351236return false;1237}12381239void EditorScriptPicker::set_script_owner(Node *p_owner) {1240script_owner = p_owner;1241}12421243Node *EditorScriptPicker::get_script_owner() const {1244return script_owner;1245}12461247void EditorScriptPicker::_bind_methods() {1248ClassDB::bind_method(D_METHOD("set_script_owner", "owner_node"), &EditorScriptPicker::set_script_owner);1249ClassDB::bind_method(D_METHOD("get_script_owner"), &EditorScriptPicker::get_script_owner);12501251ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "script_owner", PROPERTY_HINT_RESOURCE_TYPE, "Node", PROPERTY_USAGE_NONE), "set_script_owner", "get_script_owner");1252}12531254// EditorShaderPicker12551256void EditorShaderPicker::set_create_options(Object *p_menu_node) {1257PopupMenu *menu_node = Object::cast_to<PopupMenu>(p_menu_node);1258if (!menu_node) {1259return;1260}12611262menu_node->add_icon_item(get_editor_theme_icon(SNAME("Shader")), TTR("New Shader..."), OBJ_MENU_NEW_SHADER);1263menu_node->add_separator();1264}12651266bool EditorShaderPicker::handle_menu_selected(int p_which) {1267Ref<ShaderMaterial> ed_material = Ref<ShaderMaterial>(get_edited_material());12681269switch (p_which) {1270case OBJ_MENU_NEW_SHADER: {1271if (ed_material.is_valid()) {1272SceneTreeDock::get_singleton()->open_shader_dialog(ed_material, preferred_mode);1273return true;1274}1275} break;1276default:1277break;1278}1279return false;1280}12811282void EditorShaderPicker::set_edited_material(ShaderMaterial *p_material) {1283edited_material = p_material;1284}12851286ShaderMaterial *EditorShaderPicker::get_edited_material() const {1287return edited_material;1288}12891290void EditorShaderPicker::set_preferred_mode(int p_mode) {1291preferred_mode = p_mode;1292}12931294//////////////12951296void EditorAudioStreamPicker::_notification(int p_what) {1297switch (p_what) {1298case NOTIFICATION_READY:1299case NOTIFICATION_THEME_CHANGED: {1300_update_resource();1301} break;1302case NOTIFICATION_INTERNAL_PROCESS: {1303Ref<AudioStream> audio_stream = get_edited_resource();1304if (audio_stream.is_valid()) {1305if (audio_stream->get_length() > 0) {1306Ref<AudioStreamPreview> preview = AudioStreamPreviewGenerator::get_singleton()->generate_preview(audio_stream);1307if (preview.is_valid()) {1308if (preview->get_version() != last_preview_version) {1309stream_preview_rect->queue_redraw();1310last_preview_version = preview->get_version();1311}1312}1313}13141315uint64_t tagged_frame = audio_stream->get_tagged_frame();1316uint64_t diff_frames = AudioServer::get_singleton()->get_mixed_frames() - tagged_frame;1317uint64_t diff_msec = diff_frames * 1000 / AudioServer::get_singleton()->get_mix_rate();13181319if (diff_msec < 300) {1320uint32_t count = audio_stream->get_tagged_frame_count();13211322bool differ = false;13231324if (count != tagged_frame_offset_count) {1325differ = true;1326}1327float offsets[MAX_TAGGED_FRAMES];13281329for (uint32_t i = 0; i < MIN(count, uint32_t(MAX_TAGGED_FRAMES)); i++) {1330offsets[i] = audio_stream->get_tagged_frame_offset(i);1331if (offsets[i] != tagged_frame_offsets[i]) {1332differ = true;1333}1334}13351336if (differ) {1337tagged_frame_offset_count = count;1338for (uint32_t i = 0; i < count; i++) {1339tagged_frame_offsets[i] = offsets[i];1340}1341}13421343stream_preview_rect->queue_redraw();1344} else {1345if (tagged_frame_offset_count != 0) {1346stream_preview_rect->queue_redraw();1347}1348tagged_frame_offset_count = 0;1349}1350}1351} break;1352}1353}13541355void EditorAudioStreamPicker::_update_resource() {1356EditorResourcePicker::_update_resource();13571358Ref<Font> font = get_theme_font(SceneStringName(font), SNAME("Label"));1359int font_size = get_theme_font_size(SceneStringName(font_size), SNAME("Label"));1360Ref<AudioStream> audio_stream = get_edited_resource();1361if (audio_stream.is_valid() && audio_stream->get_length() > 0.0) {1362set_assign_button_min_size(Size2(1, font->get_height(font_size) * 3));1363} else {1364set_assign_button_min_size(Size2(1, font->get_height(font_size) * 1.5));1365}13661367stream_preview_rect->queue_redraw();1368}13691370void EditorAudioStreamPicker::_preview_draw() {1371Ref<AudioStream> audio_stream = get_edited_resource();1372if (audio_stream.is_null()) {1373get_assign_button()->set_text(TTR("<empty>"));1374return;1375}13761377int font_size = get_theme_font_size(SceneStringName(font_size), SNAME("Label"));13781379get_assign_button()->set_text("");13801381Size2i size = stream_preview_rect->get_size();1382Ref<Font> font = get_theme_font(SceneStringName(font), SNAME("Label"));13831384Rect2 rect(Point2(), size);13851386if (audio_stream->get_length() > 0 && size.width > 0) {1387rect.size.height *= 0.5;13881389Ref<AudioStreamPreview> preview = AudioStreamPreviewGenerator::get_singleton()->generate_preview(audio_stream);1390float preview_len = preview->get_length();13911392Vector<Vector2> points;1393points.resize(size.width * 2);13941395for (int i = 0; i < size.width; i++) {1396float ofs = i * preview_len / size.width;1397float ofs_n = (i + 1) * preview_len / size.width;1398float max = preview->get_max(ofs, ofs_n) * 0.5 + 0.5;1399float min = preview->get_min(ofs, ofs_n) * 0.5 + 0.5;14001401int idx = i;1402points.write[idx * 2 + 0] = Vector2(i + 1, rect.position.y + min * rect.size.y);1403points.write[idx * 2 + 1] = Vector2(i + 1, rect.position.y + max * rect.size.y);1404}14051406Vector<Color> colors = { get_theme_color(SNAME("contrast_color_2"), EditorStringName(Editor)) };14071408RS::get_singleton()->canvas_item_add_multiline(stream_preview_rect->get_canvas_item(), points, colors);14091410if (tagged_frame_offset_count) {1411Color accent = get_theme_color(SNAME("accent_color"), EditorStringName(Editor));14121413for (uint32_t i = 0; i < tagged_frame_offset_count; i++) {1414int x = CLAMP(tagged_frame_offsets[i] * size.width / preview_len, 0, size.width);1415if (x == 0) {1416continue; // Because some may always return 0, ignore offset 0.1417}1418stream_preview_rect->draw_rect(Rect2i(x, 0, 2, rect.size.height), accent);1419}1420}1421rect.position.y += rect.size.height;1422}14231424Ref<Texture2D> icon;1425Color icon_modulate(1, 1, 1, 1);14261427if (tagged_frame_offset_count > 0) {1428icon = get_editor_theme_icon(SNAME("Play"));1429if ((OS::get_singleton()->get_ticks_msec() % 500) > 250) {1430icon_modulate = Color(1, 0.5, 0.5, 1); // get_theme_color(SNAME("accent_color"), EditorStringName(Editor));1431}1432} else {1433icon = EditorNode::get_singleton()->get_object_icon(audio_stream.operator->(), "Object");1434}1435String text;1436if (!audio_stream->get_name().is_empty()) {1437text = audio_stream->get_name();1438} else if (audio_stream->get_path().is_resource_file()) {1439text = audio_stream->get_path().get_file();1440} else {1441text = audio_stream->get_class().replace_first("AudioStream", "");1442}14431444stream_preview_rect->draw_texture(icon, Point2i(EDSCALE * 4, rect.position.y + (rect.size.height - icon->get_height()) / 2), icon_modulate);1445stream_preview_rect->draw_string(font, Point2i(EDSCALE * 4 + icon->get_width(), rect.position.y + font->get_ascent(font_size) + (rect.size.height - font->get_height(font_size)) / 2), text, HORIZONTAL_ALIGNMENT_CENTER, size.width - 4 * EDSCALE - icon->get_width(), font_size, get_theme_color(SceneStringName(font_color), EditorStringName(Editor)));1446}14471448EditorAudioStreamPicker::EditorAudioStreamPicker() :1449EditorResourcePicker(true) {1450stream_preview_rect = memnew(Control);14511452stream_preview_rect->set_anchors_and_offsets_preset(PRESET_FULL_RECT);1453stream_preview_rect->set_offset(SIDE_TOP, 1);1454stream_preview_rect->set_offset(SIDE_BOTTOM, -1);1455stream_preview_rect->set_offset(SIDE_RIGHT, -1);1456stream_preview_rect->set_mouse_filter(MOUSE_FILTER_IGNORE);1457stream_preview_rect->connect(SceneStringName(draw), callable_mp(this, &EditorAudioStreamPicker::_preview_draw));14581459get_assign_button()->add_child(stream_preview_rect);1460get_assign_button()->move_child(stream_preview_rect, 0);1461set_process_internal(true);1462}146314641465