Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
godotengine
GitHub Repository: godotengine/godot
Path: blob/master/editor/inspector/editor_resource_picker.cpp
20852 views
1
/**************************************************************************/
2
/* editor_resource_picker.cpp */
3
/**************************************************************************/
4
/* This file is part of: */
5
/* GODOT ENGINE */
6
/* https://godotengine.org */
7
/**************************************************************************/
8
/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
9
/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
10
/* */
11
/* Permission is hereby granted, free of charge, to any person obtaining */
12
/* a copy of this software and associated documentation files (the */
13
/* "Software"), to deal in the Software without restriction, including */
14
/* without limitation the rights to use, copy, modify, merge, publish, */
15
/* distribute, sublicense, and/or sell copies of the Software, and to */
16
/* permit persons to whom the Software is furnished to do so, subject to */
17
/* the following conditions: */
18
/* */
19
/* The above copyright notice and this permission notice shall be */
20
/* included in all copies or substantial portions of the Software. */
21
/* */
22
/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
23
/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
24
/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */
25
/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
26
/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
27
/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
28
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
29
/**************************************************************************/
30
31
#include "editor_resource_picker.h"
32
33
#include "core/input/input.h"
34
#include "editor/audio/audio_stream_preview.h"
35
#include "editor/doc/editor_help.h"
36
#include "editor/docks/filesystem_dock.h"
37
#include "editor/docks/scene_tree_dock.h"
38
#include "editor/editor_node.h"
39
#include "editor/editor_string_names.h"
40
#include "editor/gui/editor_file_dialog.h"
41
#include "editor/gui/editor_quick_open_dialog.h"
42
#include "editor/inspector/editor_inspector.h"
43
#include "editor/inspector/editor_resource_preview.h"
44
#include "editor/plugins/editor_resource_conversion_plugin.h"
45
#include "editor/script/script_editor_plugin.h"
46
#include "editor/settings/editor_settings.h"
47
#include "editor/themes/editor_scale.h"
48
#include "scene/gui/button.h"
49
#include "scene/gui/texture_rect.h"
50
#include "scene/property_utils.h"
51
#include "scene/resources/gradient_texture.h"
52
#include "scene/resources/image_texture.h"
53
54
static bool _has_sub_resources(const Ref<Resource> &p_res) {
55
List<PropertyInfo> property_list;
56
p_res->get_property_list(&property_list);
57
for (const PropertyInfo &p : property_list) {
58
Variant value = p_res->get(p.name);
59
if (p.type == Variant::OBJECT && p.hint == PROPERTY_HINT_RESOURCE_TYPE && !(p.usage & PROPERTY_USAGE_NEVER_DUPLICATE) && p_res->get(p.name).get_validated_object()) {
60
return true;
61
} else if (p.type == Variant::ARRAY) {
62
Array arr = value;
63
for (Variant &var : arr) {
64
Ref<Resource> res = var;
65
if (res.is_valid()) {
66
return true;
67
}
68
}
69
} else if (p.type == Variant::DICTIONARY) {
70
Dictionary dict = value;
71
for (const KeyValue<Variant, Variant> &kv : dict) {
72
Ref<Resource> resk = kv.key;
73
Ref<Resource> resv = kv.value;
74
if (resk.is_valid() || resv.is_valid()) {
75
return true;
76
}
77
}
78
}
79
}
80
return false;
81
}
82
83
void EditorResourcePicker::_update_resource() {
84
String resource_path;
85
if (edited_resource.is_valid() && edited_resource->get_path().is_resource_file()) {
86
resource_path = edited_resource->get_path() + "\n";
87
}
88
String class_name = _get_resource_type(edited_resource);
89
90
if (preview_rect) {
91
preview_rect->set_texture(Ref<Texture2D>());
92
93
assign_button->set_custom_minimum_size(assign_button_min_size);
94
if (edited_resource == Ref<Resource>()) {
95
assign_button->set_button_icon(Ref<Texture2D>());
96
assign_button->set_text(TTR("<empty>"));
97
assign_button->set_tooltip_text("");
98
} else {
99
assign_button->set_button_icon(EditorNode::get_singleton()->get_object_icon(edited_resource.operator->()));
100
101
if (!edited_resource->get_name().is_empty()) {
102
assign_button->set_text(edited_resource->get_name());
103
} else if (edited_resource->get_path().is_resource_file()) {
104
assign_button->set_text(edited_resource->get_path().get_file());
105
} else {
106
assign_button->set_text(class_name);
107
}
108
109
if (edited_resource->get_path().is_resource_file()) {
110
resource_path = edited_resource->get_path() + "\n";
111
}
112
assign_button->set_tooltip_text(resource_path + TTR("Type:") + " " + class_name);
113
114
// Preview will override the above, so called at the end.
115
EditorResourcePreview::get_singleton()->queue_edited_resource_preview(edited_resource, callable_mp(this, &EditorResourcePicker::_update_resource_preview).bind(edited_resource->get_instance_id()));
116
}
117
} else if (edited_resource.is_valid()) {
118
assign_button->set_tooltip_text(resource_path + TTR("Type:") + " " + edited_resource->get_class());
119
}
120
121
if (edited_resource.is_null()) {
122
make_unique_button->set_visible(false);
123
} else {
124
Ref<Resource> parent_res = _has_parent_resource();
125
bool unique_enable = _is_uniqueness_enabled();
126
bool unique_recursive_enabled = _is_uniqueness_enabled(true);
127
bool is_internal = EditorNode::get_singleton()->is_resource_internal_to_scene(edited_resource);
128
int num_of_copies = EditorNode::get_singleton()->get_resource_count(edited_resource);
129
make_unique_button->set_button_icon(get_editor_theme_icon(SNAME("Instance")));
130
make_unique_button->set_visible(num_of_copies > 1 || (!is_internal && !Object::cast_to<Script>(edited_resource.ptr())));
131
make_unique_button->set_disabled((!unique_enable && !unique_recursive_enabled) || !editable);
132
133
String tooltip;
134
135
String resource_name = "resource";
136
if (edited_resource.is_valid()) {
137
resource_name = edited_resource->get_class();
138
139
if (edited_resource->has_meta(SceneStringName(_custom_type_script))) {
140
const Ref<Script> custom_script = PropertyUtils::get_custom_type_script(edited_resource.ptr());
141
if (custom_script.is_valid()) {
142
const String global_name = custom_script->get_global_name();
143
if (!global_name.is_empty()) {
144
resource_name = global_name;
145
}
146
}
147
}
148
}
149
150
if (num_of_copies > 1) {
151
tooltip = vformat(TTRN("This %s is used in %d place.", "This %s is used in %d places.", num_of_copies), resource_name, num_of_copies);
152
} else if (!is_internal) {
153
tooltip = vformat(TTR("This %s is external to scene."), resource_name);
154
}
155
156
if (!editable) {
157
tooltip += "\n" + vformat(TTR("The %s cannot be edited in the inspector and can't be made unique directly."), resource_name) + "\n";
158
} else {
159
if (unique_enable) {
160
tooltip += "\n" + TTR("Left-click to make it unique.") + "\n";
161
}
162
163
if (unique_recursive_enabled) {
164
tooltip += TTR("It is possible to make its subresources unique.") + "\n" + TTR("Right-click to make them unique.");
165
}
166
167
if (!unique_enable && EditorNode::get_singleton()->get_editor_selection()->get_full_selected_node_list().size() == 1) {
168
tooltip += TTR("In order to duplicate it, make its parent Resource unique.") + "\n";
169
}
170
}
171
172
make_unique_button->set_tooltip_text(tooltip);
173
}
174
175
assign_button->set_disabled(!editable && edited_resource.is_null());
176
quick_load_button->set_visible(editable && edited_resource.is_null());
177
}
178
179
void EditorResourcePicker::_update_resource_preview(const String &p_path, const Ref<Texture2D> &p_preview, const Ref<Texture2D> &p_small_preview, ObjectID p_obj) {
180
if (edited_resource.is_null() || edited_resource->get_instance_id() != p_obj) {
181
return;
182
}
183
184
if (preview_rect) {
185
Ref<Script> scr = edited_resource;
186
if (scr.is_valid()) {
187
assign_button->set_text(scr->get_path().get_file());
188
return;
189
}
190
191
if (p_preview.is_valid()) {
192
int thumbnail_size = (int)EDITOR_GET("filesystem/file_dialog/thumbnail_size") * EDSCALE;
193
if (assign_button->get_button_icon().is_valid()) {
194
preview_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")));
195
}
196
197
// Resource-specific stretching.
198
if (Ref<GradientTexture1D>(edited_resource).is_valid() || Ref<Gradient>(edited_resource).is_valid()) {
199
preview_rect->set_stretch_mode(TextureRect::STRETCH_SCALE);
200
assign_button->set_custom_minimum_size(assign_button_min_size);
201
} else {
202
preview_rect->set_stretch_mode(TextureRect::STRETCH_KEEP_ASPECT_CENTERED);
203
assign_button->set_custom_minimum_size(assign_button_min_size.max(Size2(1, thumbnail_size)));
204
}
205
preview_rect->set_texture(p_preview);
206
assign_button->set_text("");
207
208
if (preview_rect->get_size().x <= thumbnail_size) {
209
assign_button->set_button_icon(Ref<Texture2D>());
210
preview_rect->set_offset(SIDE_LEFT, 0);
211
}
212
}
213
}
214
}
215
216
void EditorResourcePicker::_resource_selected() {
217
if (edited_resource.is_null()) {
218
edit_button->set_pressed(true);
219
_update_menu();
220
return;
221
}
222
223
emit_signal(SNAME("resource_selected"), edited_resource, false);
224
}
225
226
void EditorResourcePicker::_resource_changed() {
227
emit_signal(SNAME("resource_changed"), edited_resource);
228
_update_resource();
229
}
230
231
void EditorResourcePicker::_file_selected(const String &p_path) {
232
Ref<Resource> loaded_resource = ResourceLoader::load(p_path);
233
ERR_FAIL_COND_MSG(loaded_resource.is_null(), "Cannot load resource from path '" + p_path + "'.");
234
235
if (!base_type.is_empty()) {
236
bool any_type_matches = false;
237
238
String res_type = loaded_resource->get_class();
239
Ref<Script> res_script = loaded_resource->get_script();
240
bool is_global_class = false;
241
if (res_script.is_valid()) {
242
String script_type = EditorNode::get_editor_data().script_class_get_name(res_script->get_path());
243
if (!script_type.is_empty()) {
244
is_global_class = true;
245
res_type = script_type;
246
}
247
}
248
249
for (int i = 0; i < base_type.get_slice_count(","); i++) {
250
String base = base_type.get_slicec(',', i);
251
252
any_type_matches = is_global_class ? EditorNode::get_editor_data().script_class_is_parent(res_type, base) : loaded_resource->is_class(base);
253
254
if (any_type_matches) {
255
break;
256
}
257
}
258
259
if (!any_type_matches) {
260
EditorNode::get_singleton()->show_warning(vformat(TTR("The selected resource (%s) does not match any type expected for this property (%s)."), res_type, base_type));
261
return;
262
}
263
}
264
265
edited_resource = loaded_resource;
266
_resource_changed();
267
}
268
269
void EditorResourcePicker::_resource_saved(Object *p_resource) {
270
if (edited_resource.is_valid() && p_resource == edited_resource.ptr()) {
271
emit_signal(SNAME("resource_changed"), edited_resource);
272
_update_resource();
273
}
274
}
275
276
void EditorResourcePicker::_update_menu() {
277
if (edit_menu && edit_menu->is_visible()) {
278
edit_button->set_pressed(false);
279
edit_menu->hide();
280
return;
281
}
282
283
_update_menu_items();
284
285
Rect2 gt = edit_button->get_screen_rect();
286
edit_menu->reset_size();
287
int ms = edit_menu->get_contents_minimum_size().width;
288
Vector2 popup_pos = gt.get_end() - Vector2(ms, 0);
289
edit_menu->set_position(popup_pos);
290
edit_menu->popup();
291
}
292
293
void EditorResourcePicker::_update_menu_items() {
294
_ensure_resource_menu();
295
edit_menu->clear();
296
297
// Add options for creating specific subtypes of the base resource type.
298
if (is_editable()) {
299
set_create_options(edit_menu);
300
301
// Add an option to load a resource from a file using the QuickOpen dialog.
302
edit_menu->add_icon_item(get_editor_theme_icon(SNAME("LoadQuick")), TTR("Quick Load..."), OBJ_MENU_QUICKLOAD);
303
edit_menu->set_item_tooltip(-1, TTR("Opens a quick menu to select from a list of allowed Resource files."));
304
305
// Add an option to load a resource from a file using the regular file dialog.
306
edit_menu->add_icon_item(get_editor_theme_icon(SNAME("Load")), TTR("Load..."), OBJ_MENU_LOAD);
307
}
308
309
// Add options for changing existing value of the resource.
310
if (edited_resource.is_valid()) {
311
// Determine if the edited resource is part of another scene (foreign) which was imported
312
bool is_edited_resource_foreign_import = EditorNode::get_singleton()->is_resource_read_only(edited_resource, true);
313
314
// If the resource is determined to be foreign and imported, change the menu entry's description to 'inspect' rather than 'edit'
315
// since will only be able to view its properties in read-only mode.
316
if (is_edited_resource_foreign_import) {
317
// The 'Search' icon is a magnifying glass, which seems appropriate, but maybe a bespoke icon is preferred here.
318
edit_menu->add_icon_item(get_editor_theme_icon(SNAME("Search")), TTR("Inspect"), OBJ_MENU_INSPECT);
319
} else {
320
edit_menu->add_icon_item(get_editor_theme_icon(SNAME("Edit")), TTR("Edit"), OBJ_MENU_INSPECT);
321
}
322
323
if (is_editable()) {
324
if (!_is_custom_type_script()) {
325
edit_menu->add_icon_item(get_editor_theme_icon(SNAME("Clear")), TTR("Clear"), OBJ_MENU_CLEAR);
326
}
327
bool unique_enabled = _is_uniqueness_enabled();
328
edit_menu->add_icon_item(get_editor_theme_icon(SNAME("Duplicate")), TTR("Make Unique"), OBJ_MENU_MAKE_UNIQUE);
329
edit_menu->set_item_disabled(-1, !unique_enabled);
330
331
String modifier = "Ctrl";
332
if (OS::get_singleton()->has_feature("macos") || OS::get_singleton()->has_feature("web_macos") || OS::get_singleton()->has_feature("web_ios")) {
333
modifier = "Cmd";
334
}
335
const String drag_and_drop_text = vformat(TTR("Hold %s while drag-and-dropping from the FileSystem dock or another resource picker to automatically make a dropped resource unique."), modifier);
336
337
if (!unique_enabled) {
338
if (EditorNode::get_singleton()->is_resource_internal_to_scene(edited_resource) && EditorNode::get_singleton()->get_resource_count(edited_resource) == 1) {
339
edit_menu->set_item_tooltip(-1, TTR("This Resource is already unique.") + "\n" + drag_and_drop_text);
340
} else if (_has_parent_resource().is_valid()) {
341
edit_menu->set_item_tooltip(-1, TTR("In order to duplicate it, make its parent Resource unique.") + "\n" + drag_and_drop_text);
342
}
343
} else {
344
edit_menu->set_item_tooltip(-1, drag_and_drop_text);
345
}
346
347
if (_has_sub_resources(edited_resource)) {
348
unique_enabled = _is_uniqueness_enabled(true);
349
edit_menu->add_icon_item(get_editor_theme_icon(SNAME("Duplicate")), TTR("Make Unique (Recursive)"), OBJ_MENU_MAKE_UNIQUE_RECURSIVE);
350
edit_menu->set_item_disabled(-1, !unique_enabled);
351
if (!unique_enabled) {
352
Ref<Resource> parent_res = _has_parent_resource();
353
if (EditorNode::get_singleton()->get_editor_selection()->get_full_selected_node_list().size() == 1) {
354
edit_menu->set_item_tooltip(-1, (parent_res.is_valid() && EditorNode::get_singleton()->get_resource_count(parent_res) > 1) ? TTRC("In order to duplicate recursively, make its parent Resource unique.") : TTRC("Subresources have already been made unique."));
355
}
356
}
357
}
358
359
edit_menu->add_icon_item(get_editor_theme_icon(SNAME("Save")), TTR("Save"), OBJ_MENU_SAVE);
360
edit_menu->add_icon_item(get_editor_theme_icon(SNAME("Save")), TTR("Save As..."), OBJ_MENU_SAVE_AS);
361
}
362
363
if (edited_resource->get_path().is_resource_file()) {
364
edit_menu->add_separator();
365
edit_menu->add_icon_item(get_editor_theme_icon(SNAME("ShowInFileSystem")), TTR("Show in FileSystem"), OBJ_MENU_SHOW_IN_FILE_SYSTEM);
366
}
367
}
368
369
// Add options to copy/paste resource.
370
Ref<Resource> cb = EditorSettings::get_singleton()->get_resource_clipboard();
371
bool paste_valid = false;
372
if (is_editable() && cb.is_valid()) {
373
if (base_type.is_empty()) {
374
paste_valid = true;
375
} else {
376
String res_type = _get_resource_type(cb);
377
378
for (int i = 0; i < base_type.get_slice_count(","); i++) {
379
String base = base_type.get_slicec(',', i);
380
381
paste_valid = ClassDB::is_parent_class(res_type, base) || EditorNode::get_editor_data().script_class_is_parent(res_type, base);
382
383
if (paste_valid) {
384
break;
385
}
386
}
387
}
388
}
389
390
if (edited_resource.is_valid() || paste_valid) {
391
edit_menu->add_separator();
392
393
if (edited_resource.is_valid()) {
394
edit_menu->add_item(TTRC("Copy"), OBJ_MENU_COPY);
395
}
396
397
if (paste_valid) {
398
edit_menu->add_item(TTRC("Paste"), OBJ_MENU_PASTE);
399
edit_menu->add_item(TTRC("Paste as Unique"), OBJ_MENU_PASTE_AS_UNIQUE);
400
}
401
}
402
403
// Add options to convert existing resource to another type of resource.
404
if (is_editable() && edited_resource.is_valid()) {
405
Vector<Ref<EditorResourceConversionPlugin>> conversions = EditorNode::get_singleton()->find_resource_conversion_plugin_for_resource(edited_resource);
406
if (!conversions.is_empty()) {
407
edit_menu->add_separator();
408
}
409
int relative_id = 0;
410
for (const Ref<EditorResourceConversionPlugin> &conversion : conversions) {
411
String what = conversion->converts_to();
412
Ref<Texture2D> icon;
413
if (has_theme_icon(what, EditorStringName(EditorIcons))) {
414
icon = get_editor_theme_icon(what);
415
} else {
416
icon = get_editor_theme_icon(SNAME("Object"));
417
}
418
419
edit_menu->add_icon_item(icon, vformat(TTR("Convert to %s"), what), CONVERT_BASE_ID + relative_id);
420
relative_id++;
421
}
422
}
423
}
424
425
void EditorResourcePicker::_edit_menu_cbk(int p_which) {
426
switch (p_which) {
427
case OBJ_MENU_LOAD: {
428
List<String> extensions;
429
for (int i = 0; i < base_type.get_slice_count(","); i++) {
430
String base = base_type.get_slicec(',', i);
431
if (base == "Resource") {
432
base = "";
433
}
434
ResourceLoader::get_recognized_extensions_for_type(base, &extensions);
435
if (ScriptServer::is_global_class(base)) {
436
ResourceLoader::get_recognized_extensions_for_type(ScriptServer::get_global_class_native_base(base), &extensions);
437
}
438
}
439
440
HashSet<String> valid_extensions;
441
for (const String &E : extensions) {
442
valid_extensions.insert(E);
443
}
444
445
if (!file_dialog) {
446
file_dialog = memnew(EditorFileDialog);
447
file_dialog->set_file_mode(EditorFileDialog::FILE_MODE_OPEN_FILE);
448
add_child(file_dialog);
449
file_dialog->connect("file_selected", callable_mp(this, &EditorResourcePicker::_file_selected));
450
}
451
452
file_dialog->clear_filters();
453
for (const String &E : valid_extensions) {
454
file_dialog->add_filter("*." + E, E.to_upper());
455
}
456
457
file_dialog->popup_file_dialog();
458
} break;
459
460
case OBJ_MENU_QUICKLOAD: {
461
const Vector<String> &base_types_string = base_type.split(",");
462
463
Vector<StringName> base_types;
464
for (const String &type : base_types_string) {
465
base_types.push_back(type);
466
}
467
468
EditorQuickOpenDialog *quick_open = EditorNode::get_singleton()->get_quick_open_dialog();
469
if (resource_owner) {
470
quick_open->popup_dialog_for_property(base_types, resource_owner, property_path, callable_mp(this, &EditorResourcePicker::_file_selected));
471
} else {
472
quick_open->popup_dialog(base_types, callable_mp(this, &EditorResourcePicker::_file_selected));
473
}
474
475
} break;
476
477
case OBJ_MENU_INSPECT: {
478
if (edited_resource.is_valid()) {
479
emit_signal(SNAME("resource_selected"), edited_resource, true);
480
}
481
} break;
482
483
case OBJ_MENU_CLEAR: {
484
edited_resource = Ref<Resource>();
485
_resource_changed();
486
} break;
487
488
case OBJ_MENU_MAKE_UNIQUE: {
489
if (edited_resource.is_null()) {
490
return;
491
}
492
Ref<Resource> unique_resource = edited_resource->duplicate();
493
ERR_FAIL_COND(unique_resource.is_null()); // duplicate() may fail.
494
495
edited_resource = unique_resource;
496
_resource_changed();
497
} break;
498
499
case OBJ_MENU_MAKE_UNIQUE_RECURSIVE: {
500
if (edited_resource.is_null()) {
501
return;
502
}
503
504
if (!duplicate_resources_dialog) {
505
duplicate_resources_dialog = memnew(ConfirmationDialog);
506
add_child(duplicate_resources_dialog);
507
duplicate_resources_dialog->set_title(TTR("Make Unique (Recursive)"));
508
duplicate_resources_dialog->connect(SceneStringName(confirmed), callable_mp(this, &EditorResourcePicker::_duplicate_selected_resources));
509
510
VBoxContainer *vb = memnew(VBoxContainer);
511
duplicate_resources_dialog->add_child(vb);
512
513
Label *label = memnew(Label(TTR("Select resources to make unique:")));
514
vb->add_child(label);
515
516
duplicate_resources_tree = memnew(Tree);
517
duplicate_resources_tree->set_accessibility_name(TTRC("Duplicate resources"));
518
duplicate_resources_tree->set_auto_translate_mode(AUTO_TRANSLATE_MODE_DISABLED);
519
vb->add_child(duplicate_resources_tree);
520
duplicate_resources_tree->set_columns(2);
521
duplicate_resources_tree->set_v_size_flags(SIZE_EXPAND_FILL);
522
}
523
524
duplicate_resources_tree->clear();
525
TreeItem *root = duplicate_resources_tree->create_item();
526
_gather_resources_to_duplicate(edited_resource, root);
527
528
duplicate_resources_dialog->reset_size();
529
duplicate_resources_dialog->popup_centered(Vector2(500, 400) * EDSCALE);
530
} break;
531
532
case OBJ_MENU_SAVE: {
533
if (edited_resource.is_null()) {
534
return;
535
}
536
EditorNode::get_singleton()->save_resource(edited_resource);
537
} break;
538
539
case OBJ_MENU_SAVE_AS: {
540
if (edited_resource.is_null()) {
541
return;
542
}
543
Callable resource_saved = callable_mp(this, &EditorResourcePicker::_resource_saved);
544
if (!EditorNode::get_singleton()->is_connected("resource_saved", resource_saved)) {
545
EditorNode::get_singleton()->connect("resource_saved", resource_saved);
546
}
547
EditorNode::get_singleton()->save_resource_as(edited_resource);
548
} break;
549
550
case OBJ_MENU_COPY: {
551
EditorSettings::get_singleton()->set_resource_clipboard(edited_resource);
552
} break;
553
554
case OBJ_MENU_PASTE: {
555
edited_resource = EditorSettings::get_singleton()->get_resource_clipboard();
556
bool make_unique = true;
557
558
// Automatically make resource unique if it belongs to another scene or resource.
559
if (!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()) {
560
make_unique = false;
561
} else if (resource_owner) {
562
Resource *res = Object::cast_to<Resource>(resource_owner);
563
if (res && edited_resource->get_path().get_slice("::", 0) == res->get_path().get_slice("::", 0)) {
564
make_unique = false;
565
}
566
}
567
568
if (make_unique) {
569
_edit_menu_cbk(OBJ_MENU_MAKE_UNIQUE);
570
} else {
571
_resource_changed();
572
}
573
} break;
574
575
case OBJ_MENU_PASTE_AS_UNIQUE: {
576
edited_resource = EditorSettings::get_singleton()->get_resource_clipboard();
577
if (_has_sub_resources(edited_resource)) {
578
// Use the recursive version when the Resource has sub-resources.
579
// This will show up a dialog to select which resources to make unique.
580
_edit_menu_cbk(OBJ_MENU_MAKE_UNIQUE_RECURSIVE);
581
} else {
582
_edit_menu_cbk(OBJ_MENU_MAKE_UNIQUE);
583
}
584
} break;
585
586
case OBJ_MENU_SHOW_IN_FILE_SYSTEM: {
587
FileSystemDock::get_singleton()->navigate_to_path(edited_resource->get_path());
588
} break;
589
590
default: {
591
// Allow subclasses to handle their own options first, only then fallback on the default branch logic.
592
if (handle_menu_selected(p_which)) {
593
break;
594
}
595
596
if (p_which >= CONVERT_BASE_ID) {
597
int to_type = p_which - CONVERT_BASE_ID;
598
Vector<Ref<EditorResourceConversionPlugin>> conversions = EditorNode::get_singleton()->find_resource_conversion_plugin_for_resource(edited_resource);
599
ERR_FAIL_INDEX(to_type, conversions.size());
600
601
Ref<Resource> converted_resource = conversions[to_type]->convert(edited_resource);
602
if (converted_resource.is_valid()) {
603
edited_resource = converted_resource;
604
_resource_changed();
605
}
606
break;
607
}
608
609
ERR_FAIL_COND(inheritors_array.is_empty());
610
611
String intype = inheritors_array[p_which - TYPE_BASE_ID];
612
Variant obj;
613
614
if (ScriptServer::is_global_class(intype)) {
615
obj = EditorNode::get_editor_data().script_class_instance(intype);
616
} else {
617
obj = ClassDB::instantiate(intype);
618
}
619
620
if (!obj) {
621
obj = EditorNode::get_editor_data().instantiate_custom_type(intype, "Resource");
622
}
623
624
Resource *resp = Object::cast_to<Resource>(obj);
625
ERR_BREAK(!resp);
626
resp->set_path(_get_owner_path() + "::" + resp->generate_scene_unique_id()); // Assign a base path for built-in Resources.
627
628
EditorNode::get_editor_data().instantiate_object_properties(obj);
629
630
// Prevent freeing of the object until the end of the update of the resource (GH-88286).
631
Ref<Resource> old_edited_resource = edited_resource;
632
edited_resource = Ref<Resource>(resp);
633
_resource_changed();
634
} break;
635
}
636
}
637
638
void EditorResourcePicker::set_create_options(Object *p_menu_node) {
639
_ensure_resource_menu();
640
// If a subclass implements this method, use it to replace all create items.
641
if (GDVIRTUAL_CALL(_set_create_options, p_menu_node)) {
642
return;
643
}
644
645
// By default provide generic "New ..." options.
646
if (!base_type.is_empty()) {
647
int idx = 0;
648
649
_ensure_allowed_types();
650
HashSet<StringName> allowed_types = allowed_types_without_convert;
651
if (!allowed_types.is_empty()) {
652
edit_menu->add_separator(TTRC("New"));
653
}
654
655
for (const StringName &E : allowed_types) {
656
const String &t = E;
657
658
if (!ClassDB::can_instantiate(t)) {
659
continue;
660
}
661
662
inheritors_array.push_back(t);
663
664
Ref<Texture2D> icon = EditorNode::get_singleton()->get_class_icon(t);
665
int id = TYPE_BASE_ID + idx;
666
edit_menu->add_icon_item(icon, t, id);
667
edit_menu->set_item_auto_translate_mode(-1, AUTO_TRANSLATE_MODE_DISABLED);
668
669
HashMap<String, DocData::ClassDoc>::Iterator class_doc = EditorHelp::get_doc_data()->class_list.find(t);
670
if (class_doc) {
671
edit_menu->set_item_tooltip(-1, DTR(class_doc->value.brief_description));
672
}
673
674
idx++;
675
}
676
677
if (edit_menu->get_item_count()) {
678
edit_menu->add_separator();
679
}
680
}
681
}
682
683
bool EditorResourcePicker::handle_menu_selected(int p_which) {
684
bool success = false;
685
GDVIRTUAL_CALL(_handle_menu_selected, p_which, success);
686
return success;
687
}
688
689
void EditorResourcePicker::_button_draw() {
690
if (dropping) {
691
Color color = get_theme_color(SNAME("accent_color"), EditorStringName(Editor));
692
assign_button->draw_rect(Rect2(Point2(), assign_button->get_size()), color, false);
693
}
694
}
695
696
void EditorResourcePicker::_button_input(const Ref<InputEvent> &p_event) {
697
Ref<InputEventMouseButton> mb = p_event;
698
699
if (mb.is_valid() && mb->is_pressed() && mb->get_button_index() == MouseButton::RIGHT) {
700
// Only attempt to update and show the menu if we have
701
// a valid resource or the Picker is editable, as
702
// there will otherwise be nothing to display.
703
if (edited_resource.is_valid() || is_editable()) {
704
if (edit_menu && edit_menu->is_visible()) {
705
edit_button->set_pressed(false);
706
edit_menu->hide();
707
return;
708
}
709
710
_update_menu_items();
711
712
Vector2 pos = get_screen_position() + mb->get_position();
713
edit_menu->reset_size();
714
edit_menu->set_position(pos);
715
edit_menu->popup();
716
}
717
}
718
}
719
720
void EditorResourcePicker::_on_unique_button_pressed() {
721
if (Input::get_singleton()->is_mouse_button_pressed(MouseButton::LEFT)) {
722
_edit_menu_cbk(OBJ_MENU_MAKE_UNIQUE);
723
} else if (Input::get_singleton()->is_mouse_button_pressed(MouseButton::RIGHT) && _is_uniqueness_enabled(true)) {
724
_edit_menu_cbk(OBJ_MENU_MAKE_UNIQUE_RECURSIVE);
725
}
726
}
727
728
String EditorResourcePicker::_get_owner_path() const {
729
EditorProperty *property = Object::cast_to<EditorProperty>(get_parent());
730
if (!property) {
731
return String();
732
}
733
Object *obj = property->get_edited_object();
734
735
Node *node = Object::cast_to<Node>(obj);
736
if (node) {
737
Node *p_edited_scene_root = EditorNode::get_singleton()->get_editor_data().get_edited_scene_root();
738
if (node->get_scene_file_path().is_empty()) {
739
node = node->get_owner();
740
} else if (p_edited_scene_root != nullptr && p_edited_scene_root->get_scene_file_path() != node->get_scene_file_path()) {
741
// PackedScene should use root scene path.
742
return p_edited_scene_root->get_scene_file_path();
743
}
744
if (node) {
745
return node->get_scene_file_path();
746
}
747
return String();
748
}
749
750
Resource *res = Object::cast_to<Resource>(obj);
751
if (res && !res->is_built_in()) {
752
return res->get_path();
753
}
754
// TODO: It would be nice to handle deeper Resource nesting.
755
return String();
756
}
757
758
String EditorResourcePicker::_get_resource_type(const Ref<Resource> &p_resource) const {
759
if (p_resource.is_null()) {
760
return String();
761
}
762
String res_type = p_resource->get_class();
763
764
Ref<Script> res_script = p_resource->get_script();
765
if (res_script.is_null()) {
766
return res_type;
767
}
768
769
// TODO: Replace with EditorFileSystem when PR #60606 is merged to use cached resource type.
770
String script_type = EditorNode::get_editor_data().script_class_get_name(res_script->get_path());
771
if (!script_type.is_empty()) {
772
res_type = script_type;
773
}
774
return res_type;
775
}
776
777
static bool _should_hide_type(const StringName &p_type) {
778
if (ClassDB::is_virtual(p_type)) {
779
return true;
780
}
781
782
if (p_type == SNAME("MissingResource")) {
783
return true;
784
}
785
786
return false;
787
}
788
789
static void _add_allowed_type(const StringName &p_type, List<StringName> *p_vector) {
790
if (p_vector->find(p_type)) {
791
// Already added.
792
return;
793
}
794
795
if (ClassDB::class_exists(p_type)) {
796
// Engine class.
797
798
if (!_should_hide_type(p_type)) {
799
p_vector->push_back(p_type);
800
}
801
802
LocalVector<StringName> inheriters;
803
ClassDB::get_inheriters_from_class(p_type, inheriters);
804
for (const StringName &S : inheriters) {
805
_add_allowed_type(S, p_vector);
806
}
807
} else {
808
// Script class.
809
p_vector->push_back(p_type);
810
}
811
812
List<StringName> inheriters;
813
ScriptServer::get_inheriters_list(p_type, &inheriters);
814
for (const StringName &S : inheriters) {
815
_add_allowed_type(S, p_vector);
816
}
817
}
818
819
void EditorResourcePicker::_ensure_allowed_types() const {
820
if (!allowed_types_without_convert.is_empty()) {
821
return;
822
}
823
824
List<StringName> final_allowed;
825
826
Vector<String> allowed_types = base_type.split(",");
827
int size = allowed_types.size();
828
829
for (const String &S : allowed_types) {
830
const String base = S.strip_edges();
831
if (base.begins_with("-")) {
832
final_allowed.erase(base.right(-1));
833
continue;
834
}
835
_add_allowed_type(base, &final_allowed);
836
}
837
838
for (const StringName &SN : final_allowed) {
839
allowed_types_without_convert.insert(SN);
840
}
841
842
allowed_types_with_convert = HashSet<StringName>(allowed_types_without_convert);
843
844
for (int i = 0; i < size; i++) {
845
const String base = allowed_types[i].strip_edges();
846
if (base == "BaseMaterial3D") {
847
allowed_types_with_convert.insert("Texture2D");
848
} else if (ClassDB::is_parent_class("ShaderMaterial", base)) {
849
allowed_types_with_convert.insert("Shader");
850
} else if (ClassDB::is_parent_class("ImageTexture", base)) {
851
allowed_types_with_convert.insert("Image");
852
}
853
}
854
}
855
856
bool EditorResourcePicker::_is_drop_valid(const Dictionary &p_drag_data) const {
857
{
858
const ObjectID source_picker = p_drag_data.get("source_picker", ObjectID());
859
if (source_picker == get_instance_id()) {
860
return false;
861
}
862
}
863
864
if (base_type.is_empty()) {
865
return true;
866
}
867
868
Ref<Resource> res = _get_dropped_resource(p_drag_data);
869
if (res.is_null()) {
870
return false;
871
}
872
873
_ensure_allowed_types();
874
HashSet<StringName> allowed_types = allowed_types_with_convert;
875
876
String res_type = _get_resource_type(res);
877
878
if (_is_type_valid(res_type, allowed_types)) {
879
return true;
880
}
881
882
if (res->get_script()) {
883
StringName custom_class = EditorNode::get_singleton()->get_object_custom_type_name(res->get_script());
884
if (_is_type_valid(custom_class, allowed_types)) {
885
return true;
886
}
887
}
888
return false;
889
}
890
891
bool EditorResourcePicker::_is_type_valid(const String &p_type_name, const HashSet<StringName> &p_allowed_types) const {
892
for (const StringName &E : p_allowed_types) {
893
String at = E;
894
if (p_type_name == at || ClassDB::is_parent_class(p_type_name, at) || EditorNode::get_editor_data().script_class_is_parent(p_type_name, at)) {
895
return true;
896
}
897
}
898
899
return false;
900
}
901
902
bool EditorResourcePicker::_is_custom_type_script() const {
903
EditorProperty *editor_property = Object::cast_to<EditorProperty>(get_parent());
904
if (!editor_property) {
905
return false;
906
}
907
908
// Check if the property being edited is 'script'.
909
if (editor_property->get_edited_property() == CoreStringName(script)) {
910
// If there's currently a valid script assigned and the owning Node/Resource also has a custom type script assigned, then
911
// the currently assigned script is either the custom type script itself or an extension of it.
912
Ref<Script> resource_as_script = edited_resource;
913
if (resource_as_script.is_valid() && resource_owner && resource_owner->has_meta(SceneStringName(_custom_type_script))) {
914
return true;
915
}
916
}
917
918
return false;
919
}
920
921
Ref<Resource> EditorResourcePicker::_get_dropped_resource(const Variant &p_data) const {
922
Dictionary drag_data = p_data;
923
const String type = drag_data.get("type", "");
924
if (type.is_empty()) {
925
return Ref<Resource>();
926
}
927
928
Ref<Resource> res;
929
if (type == "script_list_element") {
930
ScriptEditorBase *se = Object::cast_to<ScriptEditorBase>(drag_data["script_list_element"]);
931
if (se) {
932
return se->get_edited_resource();
933
}
934
} else if (type == "shader_list_element") {
935
return ResourceCache::get_ref(drag_data["file_path"]);
936
} else if (type == "resource") {
937
return drag_data["resource"];
938
} else if (type == "files") {
939
Vector<String> files = drag_data["files"];
940
941
if (files.size() == 1) {
942
if (ResourceLoader::exists(files[0])) {
943
// TODO: Extract the typename of the dropped filepath's resource in a more performant way, without fully loading it.
944
return ResourceLoader::load(files[0]);
945
}
946
}
947
}
948
return Ref<Resource>();
949
}
950
951
Variant EditorResourcePicker::get_drag_data_fw(const Point2 &p_point, Control *p_from) {
952
if (edited_resource.is_valid()) {
953
Dictionary drag_data = EditorNode::get_singleton()->drag_resource(edited_resource, p_from);
954
drag_data["source_picker"] = get_instance_id();
955
return drag_data;
956
}
957
958
return Variant();
959
}
960
961
bool EditorResourcePicker::can_drop_data_fw(const Point2 &p_point, const Variant &p_data, Control *p_from) const {
962
return editable && _is_drop_valid(p_data);
963
}
964
965
void EditorResourcePicker::drop_data_fw(const Point2 &p_point, const Variant &p_data, Control *p_from) {
966
Ref<Resource> dropped_resource = _get_dropped_resource(p_data);
967
if (dropped_resource.is_valid()) {
968
_ensure_allowed_types();
969
HashSet<StringName> allowed_types = allowed_types_without_convert;
970
971
String res_type = _get_resource_type(dropped_resource);
972
973
// If the accepted dropped resource is from the extended list, it requires conversion.
974
if (!_is_type_valid(res_type, allowed_types)) {
975
for (const StringName &E : allowed_types) {
976
String at = E;
977
978
if (at == "BaseMaterial3D" && Ref<Texture2D>(dropped_resource).is_valid()) {
979
// Use existing resource if possible and only replace its data.
980
Ref<StandardMaterial3D> mat = edited_resource;
981
if (mat.is_null()) {
982
mat.instantiate();
983
}
984
mat->set_texture(StandardMaterial3D::TextureParam::TEXTURE_ALBEDO, dropped_resource);
985
dropped_resource = mat;
986
break;
987
}
988
989
if (at == "ShaderMaterial" && Ref<Shader>(dropped_resource).is_valid()) {
990
Ref<ShaderMaterial> mat = edited_resource;
991
if (mat.is_null()) {
992
mat.instantiate();
993
}
994
mat->set_shader(dropped_resource);
995
dropped_resource = mat;
996
break;
997
}
998
999
if (at == "ImageTexture" && Ref<Image>(dropped_resource).is_valid()) {
1000
Ref<ImageTexture> texture = edited_resource;
1001
if (texture.is_null()) {
1002
texture.instantiate();
1003
}
1004
texture->set_image(dropped_resource);
1005
dropped_resource = texture;
1006
break;
1007
}
1008
}
1009
}
1010
1011
edited_resource = dropped_resource;
1012
1013
if (Input::get_singleton()->is_key_pressed(Key::CMD_OR_CTRL)) {
1014
// `_edit_menu_cbk(OBJ_MENU_MAKE_UNIQUE)` already calls `_resource_changed()`,
1015
// so we don't need to manually call it in this case.
1016
_edit_menu_cbk(OBJ_MENU_MAKE_UNIQUE);
1017
} else {
1018
_resource_changed();
1019
}
1020
}
1021
}
1022
1023
void EditorResourcePicker::_bind_methods() {
1024
ClassDB::bind_method(D_METHOD("set_base_type", "base_type"), &EditorResourcePicker::set_base_type);
1025
ClassDB::bind_method(D_METHOD("get_base_type"), &EditorResourcePicker::get_base_type);
1026
ClassDB::bind_method(D_METHOD("get_allowed_types"), &EditorResourcePicker::get_allowed_types);
1027
ClassDB::bind_method(D_METHOD("set_edited_resource", "resource"), &EditorResourcePicker::set_edited_resource);
1028
ClassDB::bind_method(D_METHOD("get_edited_resource"), &EditorResourcePicker::get_edited_resource);
1029
ClassDB::bind_method(D_METHOD("set_toggle_mode", "enable"), &EditorResourcePicker::set_toggle_mode);
1030
ClassDB::bind_method(D_METHOD("is_toggle_mode"), &EditorResourcePicker::is_toggle_mode);
1031
ClassDB::bind_method(D_METHOD("set_toggle_pressed", "pressed"), &EditorResourcePicker::set_toggle_pressed);
1032
ClassDB::bind_method(D_METHOD("set_editable", "enable"), &EditorResourcePicker::set_editable);
1033
ClassDB::bind_method(D_METHOD("is_editable"), &EditorResourcePicker::is_editable);
1034
1035
GDVIRTUAL_BIND(_set_create_options, "menu_node");
1036
GDVIRTUAL_BIND(_handle_menu_selected, "id");
1037
1038
ADD_PROPERTY(PropertyInfo(Variant::STRING, "base_type"), "set_base_type", "get_base_type");
1039
ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "edited_resource", PROPERTY_HINT_RESOURCE_TYPE, Resource::get_class_static(), PROPERTY_USAGE_NONE), "set_edited_resource", "get_edited_resource");
1040
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "editable"), "set_editable", "is_editable");
1041
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "toggle_mode"), "set_toggle_mode", "is_toggle_mode");
1042
1043
ADD_SIGNAL(MethodInfo("resource_selected", PropertyInfo(Variant::OBJECT, "resource", PROPERTY_HINT_RESOURCE_TYPE, Resource::get_class_static()), PropertyInfo(Variant::BOOL, "inspect")));
1044
ADD_SIGNAL(MethodInfo("resource_changed", PropertyInfo(Variant::OBJECT, "resource", PROPERTY_HINT_RESOURCE_TYPE, Resource::get_class_static())));
1045
}
1046
1047
void EditorResourcePicker::_notification(int p_what) {
1048
switch (p_what) {
1049
case NOTIFICATION_ENTER_TREE: {
1050
EditorNode::get_singleton()->connect("resource_counter_changed", callable_mp(this, &EditorResourcePicker::_update_resource));
1051
_update_resource();
1052
[[fallthrough]];
1053
}
1054
case NOTIFICATION_THEME_CHANGED: {
1055
const int icon_width = get_theme_constant(SNAME("class_icon_size"), EditorStringName(Editor));
1056
assign_button->add_theme_constant_override("icon_max_width", icon_width);
1057
make_unique_button->add_theme_constant_override("icon_max_width", icon_width);
1058
if (edit_menu) {
1059
edit_menu->add_theme_constant_override("icon_max_width", icon_width);
1060
}
1061
1062
quick_load_button->set_button_icon(get_editor_theme_icon(SNAME("LoadQuick")));
1063
edit_button->set_button_icon(get_theme_icon(SNAME("select_arrow"), SNAME("Tree")));
1064
} break;
1065
1066
case NOTIFICATION_DRAW: {
1067
draw_style_box(get_theme_stylebox(SceneStringName(panel), SNAME("Tree")), Rect2(Point2(), get_size()));
1068
} break;
1069
1070
case NOTIFICATION_SORT_CHILDREN: {
1071
_update_resource();
1072
} break;
1073
1074
case NOTIFICATION_DRAG_BEGIN: {
1075
if (editable && _is_drop_valid(get_viewport()->gui_get_drag_data())) {
1076
dropping = true;
1077
assign_button->queue_redraw();
1078
}
1079
} break;
1080
1081
case NOTIFICATION_DRAG_END: {
1082
if (dropping) {
1083
dropping = false;
1084
assign_button->queue_redraw();
1085
}
1086
} break;
1087
1088
case NOTIFICATION_EXIT_TREE: {
1089
Callable resource_saved = callable_mp(this, &EditorResourcePicker::_resource_saved);
1090
Callable resource_counter_changed = callable_mp(this, &EditorResourcePicker::_update_resource);
1091
if (EditorNode::get_singleton()->is_connected("resource_saved", resource_saved)) {
1092
EditorNode::get_singleton()->disconnect("resource_saved", resource_saved);
1093
}
1094
if (EditorNode::get_singleton()->is_connected("resource_counter_changed", resource_counter_changed)) {
1095
EditorNode::get_singleton()->disconnect("resource_counter_changed", resource_counter_changed);
1096
}
1097
} break;
1098
}
1099
}
1100
1101
void EditorResourcePicker::set_assign_button_min_size(const Size2i &p_size) {
1102
assign_button_min_size = p_size;
1103
assign_button->set_custom_minimum_size(assign_button_min_size);
1104
}
1105
1106
void EditorResourcePicker::set_base_type(const String &p_base_type) {
1107
base_type = p_base_type;
1108
1109
// There is a possibility that the new base type is conflicting with the existing value.
1110
// Keep the value, but warn the user that there is a potential mistake.
1111
if (!base_type.is_empty() && edited_resource.is_valid()) {
1112
_ensure_allowed_types();
1113
HashSet<StringName> allowed_types = allowed_types_with_convert;
1114
1115
StringName custom_class;
1116
bool is_custom = false;
1117
if (edited_resource->get_script()) {
1118
custom_class = EditorNode::get_singleton()->get_object_custom_type_name(edited_resource->get_script());
1119
is_custom = _is_type_valid(custom_class, allowed_types);
1120
}
1121
1122
if (!is_custom && !_is_type_valid(edited_resource->get_class(), allowed_types)) {
1123
String class_str = (custom_class == StringName() ? edited_resource->get_class() : vformat("%s (%s)", custom_class, edited_resource->get_class()));
1124
WARN_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));
1125
}
1126
}
1127
}
1128
1129
String EditorResourcePicker::get_base_type() const {
1130
return base_type;
1131
}
1132
1133
Vector<String> EditorResourcePicker::get_allowed_types() const {
1134
_ensure_allowed_types();
1135
HashSet<StringName> allowed_types = allowed_types_without_convert;
1136
1137
Vector<String> types;
1138
types.resize(allowed_types.size());
1139
1140
int i = 0;
1141
String *w = types.ptrw();
1142
for (const StringName &E : allowed_types) {
1143
w[i] = E;
1144
i++;
1145
}
1146
1147
return types;
1148
}
1149
1150
bool EditorResourcePicker::is_resource_allowed(const Ref<Resource> &p_resource) {
1151
if (p_resource.is_null()) {
1152
return true;
1153
}
1154
1155
if (!base_type.is_empty()) {
1156
_ensure_allowed_types();
1157
HashSet<StringName> allowed_types = allowed_types_with_convert;
1158
1159
StringName custom_class;
1160
bool is_custom = false;
1161
if (p_resource->get_script()) {
1162
custom_class = EditorNode::get_singleton()->get_object_custom_type_name(p_resource->get_script());
1163
is_custom = _is_type_valid(custom_class, allowed_types);
1164
}
1165
1166
if (!is_custom && !_is_type_valid(p_resource->get_class(), allowed_types)) {
1167
return false;
1168
}
1169
}
1170
return true;
1171
}
1172
1173
void EditorResourcePicker::set_edited_resource(Ref<Resource> p_resource) {
1174
if (!is_resource_allowed(p_resource)) {
1175
StringName custom_class;
1176
if (p_resource->get_script()) {
1177
custom_class = EditorNode::get_singleton()->get_object_custom_type_name(p_resource->get_script());
1178
}
1179
const String class_str = (custom_class.is_empty() ? p_resource->get_class() : vformat("%s (%s)", custom_class, p_resource->get_class()));
1180
ERR_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));
1181
}
1182
set_edited_resource_no_check(p_resource);
1183
}
1184
1185
void EditorResourcePicker::set_edited_resource_no_check(Ref<Resource> p_resource) {
1186
edited_resource = p_resource;
1187
_update_resource();
1188
}
1189
1190
Ref<Resource> EditorResourcePicker::get_edited_resource() {
1191
return edited_resource;
1192
}
1193
1194
void EditorResourcePicker::set_toggle_mode(bool p_enable) {
1195
assign_button->set_toggle_mode(p_enable);
1196
}
1197
1198
bool EditorResourcePicker::is_toggle_mode() const {
1199
return assign_button->is_toggle_mode();
1200
}
1201
1202
void EditorResourcePicker::set_toggle_pressed(bool p_pressed) {
1203
if (!is_toggle_mode()) {
1204
return;
1205
}
1206
1207
assign_button->set_pressed(p_pressed);
1208
}
1209
1210
bool EditorResourcePicker::is_toggle_pressed() const {
1211
return assign_button->is_pressed();
1212
}
1213
1214
void EditorResourcePicker::set_resource_owner(Object *p_object) {
1215
resource_owner = p_object;
1216
}
1217
1218
void EditorResourcePicker::set_editable(bool p_editable) {
1219
editable = p_editable;
1220
assign_button->set_disabled(!editable && edited_resource.is_null());
1221
quick_load_button->set_visible(editable && edited_resource.is_null());
1222
edit_button->set_visible(editable);
1223
}
1224
1225
bool EditorResourcePicker::is_editable() const {
1226
return editable;
1227
}
1228
1229
void EditorResourcePicker::_ensure_resource_menu() {
1230
if (edit_menu) {
1231
return;
1232
}
1233
edit_menu = memnew(PopupMenu);
1234
edit_menu->add_theme_constant_override("icon_max_width", get_theme_constant(SNAME("class_icon_size"), EditorStringName(Editor)));
1235
add_child(edit_menu);
1236
edit_menu->connect(SceneStringName(id_pressed), callable_mp(this, &EditorResourcePicker::_edit_menu_cbk));
1237
edit_menu->connect("popup_hide", callable_mp((BaseButton *)edit_button, &BaseButton::set_pressed).bind(false));
1238
}
1239
1240
void EditorResourcePicker::_gather_resources_to_duplicate(const Ref<Resource> p_resource, TreeItem *p_item, const String &p_property_name) const {
1241
p_item->set_cell_mode(0, TreeItem::CELL_MODE_CHECK);
1242
1243
String res_name = p_resource->get_name();
1244
if (res_name.is_empty() && !p_resource->is_built_in()) {
1245
res_name = p_resource->get_path().get_file();
1246
}
1247
1248
if (res_name.is_empty()) {
1249
p_item->set_text(0, _get_resource_type(p_resource));
1250
} else {
1251
p_item->set_text(0, vformat("%s (%s)", _get_resource_type(p_resource), res_name));
1252
}
1253
1254
p_item->set_icon(0, EditorNode::get_singleton()->get_object_icon(p_resource.ptr()));
1255
p_item->set_editable(0, true);
1256
1257
Array meta = { p_resource };
1258
p_item->set_metadata(0, meta);
1259
1260
if (!p_property_name.is_empty()) {
1261
p_item->set_text(1, p_property_name);
1262
}
1263
1264
static Vector<String> unique_exceptions = { "Image", "Shader", "Mesh", "FontFile" };
1265
if (!unique_exceptions.has(p_resource->get_class())) {
1266
// Automatically select resource, unless it's something that shouldn't be duplicated.
1267
p_item->set_checked(0, true);
1268
}
1269
1270
List<PropertyInfo> plist;
1271
p_resource->get_property_list(&plist);
1272
1273
for (const PropertyInfo &E : plist) {
1274
if (!(E.usage & PROPERTY_USAGE_STORAGE) || (E.type != Variant::OBJECT && E.type != Variant::ARRAY && E.type != Variant::DICTIONARY)) {
1275
continue;
1276
}
1277
1278
Variant value = p_resource->get(E.name);
1279
TreeItem *child = nullptr;
1280
1281
if (E.type == Variant::ARRAY) {
1282
Array arr = value;
1283
for (int i = 0; i < arr.size(); i++) {
1284
Ref<Resource> res = arr[i];
1285
if (res.is_valid()) {
1286
child = p_item->create_child();
1287
_gather_resources_to_duplicate(res, child, E.name);
1288
meta = child->get_metadata(0);
1289
meta.push_back(E.name);
1290
meta.push_back(i); // Remember index.
1291
}
1292
}
1293
continue;
1294
} else if (E.type == Variant::DICTIONARY) {
1295
Dictionary dict = value;
1296
for (const KeyValue<Variant, Variant> &kv : dict) {
1297
Ref<Resource> key_res = kv.key;
1298
Ref<Resource> value_res = kv.value;
1299
if (key_res.is_valid()) {
1300
child = p_item->create_child();
1301
_gather_resources_to_duplicate(key_res, child, E.name);
1302
meta = child->get_metadata(0);
1303
meta.push_back(E.name);
1304
meta.push_back(key_res);
1305
}
1306
if (value_res.is_valid()) {
1307
child = p_item->create_child();
1308
_gather_resources_to_duplicate(value_res, child, E.name);
1309
meta = child->get_metadata(0);
1310
meta.push_back(E.name);
1311
meta.push_back(value_res);
1312
meta.push_back(kv.key);
1313
}
1314
}
1315
continue;
1316
}
1317
1318
Ref<Resource> res = p_resource->get(E.name);
1319
if (res.is_null()) {
1320
continue;
1321
}
1322
child = p_item->create_child();
1323
_gather_resources_to_duplicate(res, child, E.name);
1324
meta = child->get_metadata(0);
1325
// Remember property name.
1326
meta.push_back(E.name);
1327
1328
if ((E.usage & PROPERTY_USAGE_NEVER_DUPLICATE)) {
1329
// The resource can't be duplicated, but make it appear on the list anyway.
1330
child->set_checked(0, false);
1331
child->set_editable(0, false);
1332
}
1333
}
1334
}
1335
1336
void EditorResourcePicker::_duplicate_selected_resources() {
1337
for (TreeItem *item = duplicate_resources_tree->get_root(); item; item = item->get_next_in_tree()) {
1338
if (!item->is_checked(0)) {
1339
continue;
1340
}
1341
1342
Array meta = item->get_metadata(0);
1343
Ref<Resource> res = meta[0];
1344
Ref<Resource> unique_resource = res->duplicate();
1345
ERR_FAIL_COND(unique_resource.is_null()); // duplicate() may fail.
1346
meta[0] = unique_resource;
1347
1348
if (meta.size() == 1) { // Root.
1349
edited_resource = unique_resource;
1350
continue;
1351
}
1352
Array parent_meta = item->get_parent()->get_metadata(0);
1353
Ref<Resource> parent = parent_meta[0];
1354
Variant::Type property_type = parent->get(meta[1]).get_type();
1355
1356
if (property_type == Variant::OBJECT) {
1357
parent->set(meta[1], unique_resource);
1358
continue;
1359
}
1360
1361
Variant property = parent->get(meta[1]);
1362
1363
if (!parent_meta.has(property)) {
1364
property = property.duplicate();
1365
parent->set(meta[1], property);
1366
parent_meta.push_back(property); // Append Duplicated Type so we can check if it's already been duplicated.
1367
}
1368
1369
if (property_type == Variant::ARRAY) {
1370
Array arr = property;
1371
arr[meta[2]] = unique_resource;
1372
continue;
1373
}
1374
1375
Dictionary dict = property;
1376
LocalVector<Variant> keys = dict.get_key_list();
1377
1378
if (meta[2].get_type() == Variant::OBJECT) {
1379
if (keys.has(meta[2])) {
1380
//It's a key.
1381
dict[unique_resource] = dict[meta[2]];
1382
dict.erase(meta[2]);
1383
parent_meta.push_back(unique_resource);
1384
} else {
1385
// If key has been erased, use last appended Resource key instead.
1386
Variant key = keys.has(meta[3]) ? meta[3] : parent_meta.back();
1387
dict[key] = unique_resource;
1388
}
1389
} else {
1390
dict[meta[2]] = unique_resource;
1391
}
1392
}
1393
_resource_changed();
1394
}
1395
1396
bool EditorResourcePicker::_is_uniqueness_enabled(bool p_check_recursive) {
1397
if (force_allow_unique) {
1398
return true;
1399
}
1400
Ref<Resource> parent_resource = _has_parent_resource();
1401
EditorNode *en = EditorNode::get_singleton();
1402
bool internal_to_scene = en->is_resource_internal_to_scene(edited_resource);
1403
List<Node *> node_list = en->get_editor_selection()->get_full_selected_node_list();
1404
1405
// Todo: Implement a more elegant solution for multiple selected Nodes. This should suffice for the time being.
1406
if (node_list.size() > 1 && !p_check_recursive) {
1407
return node_list.size() != en->get_resource_count(edited_resource) || !internal_to_scene;
1408
}
1409
1410
if (!internal_to_scene) {
1411
if (parent_resource.is_valid() && (!en->is_resource_internal_to_scene(parent_resource) || en->get_resource_count(parent_resource) > 1)) {
1412
return !parent_resource->is_built_in();
1413
} else if (!p_check_recursive) {
1414
return true;
1415
}
1416
}
1417
1418
int parent_counter = en->get_resource_count(parent_resource);
1419
bool above_threshold = parent_resource.is_valid() ? (en->get_resource_count(parent_resource) <= 1 && en->get_resource_count(edited_resource) > 1) : en->get_resource_count(edited_resource) > 1;
1420
if (!p_check_recursive) {
1421
return above_threshold;
1422
}
1423
1424
if (p_check_recursive && parent_counter <= 1) {
1425
List<Ref<Resource>> nested_resources;
1426
en->gather_resources(edited_resource, nested_resources, true, true);
1427
1428
for (Ref<Resource> R : nested_resources) {
1429
// Take into account Nested External Resources.
1430
if (en->get_resource_count(R) > 1 || !EditorNode::get_singleton()->is_resource_internal_to_scene(R)) {
1431
return true;
1432
}
1433
}
1434
return false;
1435
}
1436
return false;
1437
}
1438
1439
Ref<Resource> EditorResourcePicker::_has_parent_resource() {
1440
Node *current_node = this->get_parent();
1441
while (current_node != nullptr) {
1442
EditorProperty *ep = Object::cast_to<EditorProperty>(current_node);
1443
if (ep && Object::cast_to<Resource>(ep->get_edited_object())) {
1444
return Object::cast_to<Resource>(ep->get_edited_object());
1445
}
1446
current_node = current_node->get_parent();
1447
}
1448
return nullptr;
1449
}
1450
1451
EditorResourcePicker::EditorResourcePicker(bool p_hide_assign_button_controls) {
1452
make_unique_button = memnew(Button);
1453
make_unique_button->set_flat(true);
1454
make_unique_button->set_accessibility_name(TTRC("Number of Linked Resources."));
1455
make_unique_button->set_tooltip_auto_translate_mode(AUTO_TRANSLATE_MODE_DISABLED);
1456
make_unique_button->set_button_mask(MouseButtonMask::LEFT | MouseButtonMask::RIGHT);
1457
make_unique_button->set_action_mode(BaseButton::ACTION_MODE_BUTTON_PRESS);
1458
make_unique_button->set_theme_type_variation(SNAME("EditorInspectorButton"));
1459
add_child(make_unique_button);
1460
make_unique_button->connect(SceneStringName(pressed), callable_mp(this, &EditorResourcePicker::_on_unique_button_pressed));
1461
1462
assign_button = memnew(Button);
1463
assign_button->set_flat(true);
1464
assign_button->set_h_size_flags(SIZE_EXPAND_FILL);
1465
assign_button->set_accessibility_name(TTRC("Assign Resource"));
1466
assign_button->set_expand_icon(true);
1467
assign_button->set_clip_text(true);
1468
assign_button->set_theme_type_variation(SNAME("EditorInspectorButton"));
1469
assign_button->set_auto_translate_mode(AUTO_TRANSLATE_MODE_DISABLED);
1470
SET_DRAG_FORWARDING_GCD(assign_button, EditorResourcePicker);
1471
add_child(assign_button);
1472
assign_button->connect(SceneStringName(pressed), callable_mp(this, &EditorResourcePicker::_resource_selected));
1473
assign_button->connect(SceneStringName(draw), callable_mp(this, &EditorResourcePicker::_button_draw));
1474
assign_button->connect(SceneStringName(gui_input), callable_mp(this, &EditorResourcePicker::_button_input));
1475
1476
if (!p_hide_assign_button_controls) {
1477
preview_rect = memnew(TextureRect);
1478
preview_rect->set_expand_mode(TextureRect::EXPAND_IGNORE_SIZE);
1479
preview_rect->set_anchors_and_offsets_preset(PRESET_FULL_RECT);
1480
preview_rect->set_offset(SIDE_TOP, 1);
1481
preview_rect->set_offset(SIDE_BOTTOM, -1);
1482
preview_rect->set_offset(SIDE_RIGHT, -1);
1483
preview_rect->set_texture_filter(TEXTURE_FILTER_NEAREST_WITH_MIPMAPS);
1484
assign_button->add_child(preview_rect);
1485
}
1486
1487
quick_load_button = memnew(Button);
1488
quick_load_button->set_theme_type_variation(SNAME("EditorInspectorFlatButton"));
1489
quick_load_button->set_tooltip_text(TTRC("Quick Load"));
1490
add_child(quick_load_button);
1491
quick_load_button->connect(SceneStringName(pressed), callable_mp(this, &EditorResourcePicker::_edit_menu_cbk).bind(OBJ_MENU_QUICKLOAD));
1492
1493
edit_button = memnew(Button);
1494
edit_button->set_theme_type_variation(SNAME("EditorInspectorFlatButton"));
1495
edit_button->set_toggle_mode(true);
1496
edit_button->set_action_mode(BaseButton::ACTION_MODE_BUTTON_PRESS);
1497
edit_button->set_accessibility_name(TTRC("Edit"));
1498
add_child(edit_button);
1499
edit_button->connect(SceneStringName(pressed), callable_mp(this, &EditorResourcePicker::_update_menu));
1500
edit_button->connect(SceneStringName(gui_input), callable_mp(this, &EditorResourcePicker::_button_input));
1501
1502
add_theme_constant_override("separation", 0);
1503
}
1504
1505
// EditorScriptPicker
1506
1507
void EditorScriptPicker::set_create_options(Object *p_menu_node) {
1508
PopupMenu *menu_node = Object::cast_to<PopupMenu>(p_menu_node);
1509
if (!menu_node) {
1510
return;
1511
}
1512
1513
if (!(script_owner && script_owner->has_meta(SceneStringName(_custom_type_script)))) {
1514
menu_node->add_icon_item(get_editor_theme_icon(SNAME("ScriptCreate")), TTR("New Script..."), OBJ_MENU_NEW_SCRIPT);
1515
}
1516
1517
if (script_owner) {
1518
Ref<Script> scr = script_owner->get_script();
1519
if (scr.is_valid()) {
1520
menu_node->add_icon_item(get_editor_theme_icon(SNAME("ScriptExtend")), TTR("Extend Script..."), OBJ_MENU_EXTEND_SCRIPT);
1521
}
1522
}
1523
menu_node->add_separator();
1524
}
1525
1526
bool EditorScriptPicker::handle_menu_selected(int p_which) {
1527
switch (p_which) {
1528
case OBJ_MENU_NEW_SCRIPT: {
1529
if (script_owner) {
1530
SceneTreeDock::get_singleton()->open_script_dialog(script_owner, false);
1531
}
1532
return true;
1533
}
1534
1535
case OBJ_MENU_EXTEND_SCRIPT: {
1536
if (script_owner) {
1537
SceneTreeDock::get_singleton()->open_script_dialog(script_owner, true);
1538
}
1539
return true;
1540
}
1541
}
1542
1543
return false;
1544
}
1545
1546
void EditorScriptPicker::set_script_owner(Node *p_owner) {
1547
script_owner = p_owner;
1548
}
1549
1550
Node *EditorScriptPicker::get_script_owner() const {
1551
return script_owner;
1552
}
1553
1554
void EditorScriptPicker::_bind_methods() {
1555
ClassDB::bind_method(D_METHOD("set_script_owner", "owner_node"), &EditorScriptPicker::set_script_owner);
1556
ClassDB::bind_method(D_METHOD("get_script_owner"), &EditorScriptPicker::get_script_owner);
1557
1558
ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "script_owner", PROPERTY_HINT_RESOURCE_TYPE, Node::get_class_static(), PROPERTY_USAGE_NONE), "set_script_owner", "get_script_owner");
1559
}
1560
1561
// EditorShaderPicker
1562
1563
void EditorShaderPicker::set_create_options(Object *p_menu_node) {
1564
PopupMenu *menu_node = Object::cast_to<PopupMenu>(p_menu_node);
1565
if (!menu_node) {
1566
return;
1567
}
1568
1569
menu_node->add_icon_item(get_editor_theme_icon(SNAME("Shader")), TTR("New Shader..."), OBJ_MENU_NEW_SHADER);
1570
menu_node->add_separator();
1571
}
1572
1573
bool EditorShaderPicker::handle_menu_selected(int p_which) {
1574
Ref<ShaderMaterial> ed_material = Ref<ShaderMaterial>(get_edited_material());
1575
1576
switch (p_which) {
1577
case OBJ_MENU_NEW_SHADER: {
1578
if (ed_material.is_valid()) {
1579
SceneTreeDock::get_singleton()->open_shader_dialog(ed_material, preferred_mode);
1580
return true;
1581
}
1582
} break;
1583
default:
1584
break;
1585
}
1586
return false;
1587
}
1588
1589
void EditorShaderPicker::set_edited_material(ShaderMaterial *p_material) {
1590
edited_material = p_material;
1591
}
1592
1593
ShaderMaterial *EditorShaderPicker::get_edited_material() const {
1594
return edited_material;
1595
}
1596
1597
void EditorShaderPicker::set_preferred_mode(int p_mode) {
1598
preferred_mode = p_mode;
1599
}
1600
1601
//////////////
1602
1603
void EditorAudioStreamPicker::_notification(int p_what) {
1604
switch (p_what) {
1605
case NOTIFICATION_READY:
1606
case NOTIFICATION_THEME_CHANGED: {
1607
_update_resource();
1608
} break;
1609
case NOTIFICATION_INTERNAL_PROCESS: {
1610
Ref<AudioStream> audio_stream = get_edited_resource();
1611
if (audio_stream.is_valid()) {
1612
if (audio_stream->get_length() > 0) {
1613
Ref<AudioStreamPreview> preview = AudioStreamPreviewGenerator::get_singleton()->generate_preview(audio_stream);
1614
if (preview.is_valid()) {
1615
if (preview->get_version() != last_preview_version) {
1616
stream_preview_rect->queue_redraw();
1617
last_preview_version = preview->get_version();
1618
}
1619
}
1620
}
1621
1622
uint64_t tagged_frame = audio_stream->get_tagged_frame();
1623
uint64_t diff_frames = AudioServer::get_singleton()->get_mixed_frames() - tagged_frame;
1624
uint64_t diff_msec = diff_frames * 1000 / AudioServer::get_singleton()->get_mix_rate();
1625
1626
if (diff_msec < 300) {
1627
uint32_t count = audio_stream->get_tagged_frame_count();
1628
1629
bool differ = false;
1630
1631
if (count != tagged_frame_offset_count) {
1632
differ = true;
1633
}
1634
float offsets[MAX_TAGGED_FRAMES];
1635
1636
for (uint32_t i = 0; i < MIN(count, uint32_t(MAX_TAGGED_FRAMES)); i++) {
1637
offsets[i] = audio_stream->get_tagged_frame_offset(i);
1638
if (offsets[i] != tagged_frame_offsets[i]) {
1639
differ = true;
1640
}
1641
}
1642
1643
if (differ) {
1644
tagged_frame_offset_count = count;
1645
for (uint32_t i = 0; i < count; i++) {
1646
tagged_frame_offsets[i] = offsets[i];
1647
}
1648
}
1649
1650
stream_preview_rect->queue_redraw();
1651
} else {
1652
if (tagged_frame_offset_count != 0) {
1653
stream_preview_rect->queue_redraw();
1654
}
1655
tagged_frame_offset_count = 0;
1656
}
1657
}
1658
} break;
1659
}
1660
}
1661
1662
void EditorAudioStreamPicker::_update_resource() {
1663
EditorResourcePicker::_update_resource();
1664
1665
Ref<Font> font = get_theme_font(SceneStringName(font), SNAME("Label"));
1666
int font_size = get_theme_font_size(SceneStringName(font_size), SNAME("Label"));
1667
Ref<AudioStream> audio_stream = get_edited_resource();
1668
if (audio_stream.is_valid() && audio_stream->get_length() > 0.0) {
1669
set_assign_button_min_size(Size2(1, font->get_height(font_size) * 3));
1670
} else {
1671
set_assign_button_min_size(Size2(1, font->get_height(font_size) * 1.5));
1672
}
1673
1674
stream_preview_rect->queue_redraw();
1675
}
1676
1677
void EditorAudioStreamPicker::_preview_draw() {
1678
Ref<AudioStream> audio_stream = get_edited_resource();
1679
if (audio_stream.is_null()) {
1680
get_assign_button()->set_text(TTR("<empty>"));
1681
return;
1682
}
1683
1684
int font_size = get_theme_font_size(SceneStringName(font_size), SNAME("Label"));
1685
1686
get_assign_button()->set_text("");
1687
1688
Size2i size = stream_preview_rect->get_size();
1689
Ref<Font> font = get_theme_font(SceneStringName(font), SNAME("Label"));
1690
1691
Rect2 rect(Point2(), size);
1692
1693
if (audio_stream->get_length() > 0 && size.width > 0) {
1694
rect.size.height *= 0.5;
1695
1696
Ref<AudioStreamPreview> preview = AudioStreamPreviewGenerator::get_singleton()->generate_preview(audio_stream);
1697
float preview_len = preview->get_length();
1698
1699
Vector<Vector2> points;
1700
points.resize(size.width * 2);
1701
1702
for (int i = 0; i < size.width; i++) {
1703
float ofs = i * preview_len / size.width;
1704
float ofs_n = (i + 1) * preview_len / size.width;
1705
float max = preview->get_max(ofs, ofs_n) * 0.5 + 0.5;
1706
float min = preview->get_min(ofs, ofs_n) * 0.5 + 0.5;
1707
1708
int idx = i;
1709
points.write[idx * 2 + 0] = Vector2(i + 1, rect.position.y + min * rect.size.y);
1710
points.write[idx * 2 + 1] = Vector2(i + 1, rect.position.y + max * rect.size.y);
1711
}
1712
1713
Vector<Color> colors = { get_theme_color(SNAME("contrast_color_2"), EditorStringName(Editor)) };
1714
1715
RS::get_singleton()->canvas_item_add_multiline(stream_preview_rect->get_canvas_item(), points, colors);
1716
1717
if (tagged_frame_offset_count) {
1718
Color accent = get_theme_color(SNAME("accent_color"), EditorStringName(Editor));
1719
1720
for (uint32_t i = 0; i < tagged_frame_offset_count; i++) {
1721
int x = CLAMP(tagged_frame_offsets[i] * size.width / preview_len, 0, size.width);
1722
if (x == 0) {
1723
continue; // Because some may always return 0, ignore offset 0.
1724
}
1725
stream_preview_rect->draw_rect(Rect2i(x, 0, 2, rect.size.height), accent);
1726
}
1727
}
1728
rect.position.y += rect.size.height;
1729
}
1730
1731
Ref<Texture2D> icon;
1732
Color icon_modulate(1, 1, 1, 1);
1733
1734
if (tagged_frame_offset_count > 0) {
1735
icon = get_editor_theme_icon(SNAME("Play"));
1736
if ((OS::get_singleton()->get_ticks_msec() % 500) > 250) {
1737
icon_modulate = Color(1, 0.5, 0.5, 1); // get_theme_color(SNAME("accent_color"), EditorStringName(Editor));
1738
}
1739
} else {
1740
icon = EditorNode::get_singleton()->get_object_icon(audio_stream.operator->());
1741
}
1742
String text;
1743
if (!audio_stream->get_name().is_empty()) {
1744
text = audio_stream->get_name();
1745
} else if (audio_stream->get_path().is_resource_file()) {
1746
text = audio_stream->get_path().get_file();
1747
} else {
1748
text = audio_stream->get_class().replace_first("AudioStream", "");
1749
}
1750
1751
stream_preview_rect->draw_texture(icon, Point2i(EDSCALE * 4, rect.position.y + (rect.size.height - icon->get_height()) / 2), icon_modulate);
1752
stream_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)));
1753
}
1754
1755
EditorAudioStreamPicker::EditorAudioStreamPicker() :
1756
EditorResourcePicker(true) {
1757
stream_preview_rect = memnew(Control);
1758
1759
stream_preview_rect->set_anchors_and_offsets_preset(PRESET_FULL_RECT);
1760
stream_preview_rect->set_offset(SIDE_TOP, 1);
1761
stream_preview_rect->set_offset(SIDE_BOTTOM, -1);
1762
stream_preview_rect->set_offset(SIDE_RIGHT, -1);
1763
stream_preview_rect->set_mouse_filter(MOUSE_FILTER_IGNORE);
1764
stream_preview_rect->connect(SceneStringName(draw), callable_mp(this, &EditorAudioStreamPicker::_preview_draw));
1765
1766
get_assign_button()->add_child(stream_preview_rect);
1767
get_assign_button()->move_child(stream_preview_rect, 0);
1768
set_process_internal(true);
1769
}
1770
1771