Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
godotengine
GitHub Repository: godotengine/godot
Path: blob/master/editor/inspector/editor_resource_picker.cpp
9903 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 "editor/audio/audio_stream_preview.h"
34
#include "editor/doc/editor_help.h"
35
#include "editor/docks/filesystem_dock.h"
36
#include "editor/docks/scene_tree_dock.h"
37
#include "editor/editor_node.h"
38
#include "editor/editor_string_names.h"
39
#include "editor/gui/editor_file_dialog.h"
40
#include "editor/gui/editor_quick_open_dialog.h"
41
#include "editor/inspector/editor_inspector.h"
42
#include "editor/inspector/editor_resource_preview.h"
43
#include "editor/plugins/editor_resource_conversion_plugin.h"
44
#include "editor/script/script_editor_plugin.h"
45
#include "editor/settings/editor_settings.h"
46
#include "editor/themes/editor_scale.h"
47
#include "scene/gui/button.h"
48
#include "scene/gui/texture_rect.h"
49
#include "scene/resources/gradient_texture.h"
50
#include "scene/resources/image_texture.h"
51
52
void EditorResourcePicker::_update_resource() {
53
String resource_path;
54
if (edited_resource.is_valid() && edited_resource->get_path().is_resource_file()) {
55
resource_path = edited_resource->get_path() + "\n";
56
}
57
String class_name = _get_resource_type(edited_resource);
58
59
if (preview_rect) {
60
preview_rect->set_texture(Ref<Texture2D>());
61
62
assign_button->set_custom_minimum_size(assign_button_min_size);
63
64
if (edited_resource == Ref<Resource>()) {
65
assign_button->set_button_icon(Ref<Texture2D>());
66
assign_button->set_text(TTR("<empty>"));
67
assign_button->set_tooltip_text("");
68
} else {
69
assign_button->set_button_icon(EditorNode::get_singleton()->get_object_icon(edited_resource.operator->(), SNAME("Object")));
70
71
if (!edited_resource->get_name().is_empty()) {
72
assign_button->set_text(edited_resource->get_name());
73
} else if (edited_resource->get_path().is_resource_file()) {
74
assign_button->set_text(edited_resource->get_path().get_file());
75
} else {
76
assign_button->set_text(class_name);
77
}
78
79
if (edited_resource->get_path().is_resource_file()) {
80
resource_path = edited_resource->get_path() + "\n";
81
}
82
assign_button->set_tooltip_text(resource_path + TTR("Type:") + " " + class_name);
83
84
// Preview will override the above, so called at the end.
85
EditorResourcePreview::get_singleton()->queue_edited_resource_preview(edited_resource, this, "_update_resource_preview", edited_resource->get_instance_id());
86
}
87
} else if (edited_resource.is_valid()) {
88
assign_button->set_tooltip_text(resource_path + TTR("Type:") + " " + edited_resource->get_class());
89
}
90
91
assign_button->set_disabled(!editable && edited_resource.is_null());
92
quick_load_button->set_visible(editable && edited_resource.is_null());
93
}
94
95
void EditorResourcePicker::_update_resource_preview(const String &p_path, const Ref<Texture2D> &p_preview, const Ref<Texture2D> &p_small_preview, ObjectID p_obj) {
96
if (edited_resource.is_null() || edited_resource->get_instance_id() != p_obj) {
97
return;
98
}
99
100
if (preview_rect) {
101
Ref<Script> scr = edited_resource;
102
if (scr.is_valid()) {
103
assign_button->set_text(scr->get_path().get_file());
104
return;
105
}
106
107
if (p_preview.is_valid()) {
108
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")));
109
110
// Resource-specific stretching.
111
if (Ref<GradientTexture1D>(edited_resource).is_valid() || Ref<Gradient>(edited_resource).is_valid()) {
112
preview_rect->set_stretch_mode(TextureRect::STRETCH_SCALE);
113
assign_button->set_custom_minimum_size(assign_button_min_size);
114
} else {
115
preview_rect->set_stretch_mode(TextureRect::STRETCH_KEEP_ASPECT_CENTERED);
116
int thumbnail_size = EDITOR_GET("filesystem/file_dialog/thumbnail_size");
117
thumbnail_size *= EDSCALE;
118
assign_button->set_custom_minimum_size(assign_button_min_size.max(Size2(1, thumbnail_size)));
119
}
120
121
preview_rect->set_texture(p_preview);
122
assign_button->set_text("");
123
}
124
}
125
}
126
127
void EditorResourcePicker::_resource_selected() {
128
if (edited_resource.is_null()) {
129
edit_button->set_pressed(true);
130
_update_menu();
131
return;
132
}
133
134
emit_signal(SNAME("resource_selected"), edited_resource, false);
135
}
136
137
void EditorResourcePicker::_resource_changed() {
138
emit_signal(SNAME("resource_changed"), edited_resource);
139
_update_resource();
140
}
141
142
void EditorResourcePicker::_file_selected(const String &p_path) {
143
Ref<Resource> loaded_resource = ResourceLoader::load(p_path);
144
ERR_FAIL_COND_MSG(loaded_resource.is_null(), "Cannot load resource from path '" + p_path + "'.");
145
146
if (!base_type.is_empty()) {
147
bool any_type_matches = false;
148
149
String res_type = loaded_resource->get_class();
150
Ref<Script> res_script = loaded_resource->get_script();
151
bool is_global_class = false;
152
if (res_script.is_valid()) {
153
String script_type = EditorNode::get_editor_data().script_class_get_name(res_script->get_path());
154
if (!script_type.is_empty()) {
155
is_global_class = true;
156
res_type = script_type;
157
}
158
}
159
160
for (int i = 0; i < base_type.get_slice_count(","); i++) {
161
String base = base_type.get_slicec(',', i);
162
163
any_type_matches = is_global_class ? EditorNode::get_editor_data().script_class_is_parent(res_type, base) : loaded_resource->is_class(base);
164
165
if (any_type_matches) {
166
break;
167
}
168
}
169
170
if (!any_type_matches) {
171
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));
172
return;
173
}
174
}
175
176
edited_resource = loaded_resource;
177
_resource_changed();
178
}
179
180
void EditorResourcePicker::_resource_saved(Object *p_resource) {
181
if (edited_resource.is_valid() && p_resource == edited_resource.ptr()) {
182
emit_signal(SNAME("resource_changed"), edited_resource);
183
_update_resource();
184
}
185
}
186
187
void EditorResourcePicker::_update_menu() {
188
if (edit_menu && edit_menu->is_visible()) {
189
edit_button->set_pressed(false);
190
edit_menu->hide();
191
return;
192
}
193
194
_update_menu_items();
195
196
Rect2 gt = edit_button->get_screen_rect();
197
edit_menu->reset_size();
198
int ms = edit_menu->get_contents_minimum_size().width;
199
Vector2 popup_pos = gt.get_end() - Vector2(ms, 0);
200
edit_menu->set_position(popup_pos);
201
edit_menu->popup();
202
}
203
204
void EditorResourcePicker::_update_menu_items() {
205
_ensure_resource_menu();
206
edit_menu->clear();
207
208
// Add options for creating specific subtypes of the base resource type.
209
if (is_editable()) {
210
set_create_options(edit_menu);
211
212
// Add an option to load a resource from a file using the QuickOpen dialog.
213
edit_menu->add_icon_item(get_editor_theme_icon(SNAME("Load")), TTR("Quick Load..."), OBJ_MENU_QUICKLOAD);
214
edit_menu->set_item_tooltip(-1, TTR("Opens a quick menu to select from a list of allowed Resource files."));
215
216
// Add an option to load a resource from a file using the regular file dialog.
217
edit_menu->add_icon_item(get_editor_theme_icon(SNAME("Load")), TTR("Load..."), OBJ_MENU_LOAD);
218
}
219
220
// Add options for changing existing value of the resource.
221
if (edited_resource.is_valid()) {
222
// Determine if the edited resource is part of another scene (foreign) which was imported
223
bool is_edited_resource_foreign_import = EditorNode::get_singleton()->is_resource_read_only(edited_resource, true);
224
225
// If the resource is determined to be foreign and imported, change the menu entry's description to 'inspect' rather than 'edit'
226
// since will only be able to view its properties in read-only mode.
227
if (is_edited_resource_foreign_import) {
228
// The 'Search' icon is a magnifying glass, which seems appropriate, but maybe a bespoke icon is preferred here.
229
edit_menu->add_icon_item(get_editor_theme_icon(SNAME("Search")), TTR("Inspect"), OBJ_MENU_INSPECT);
230
} else {
231
edit_menu->add_icon_item(get_editor_theme_icon(SNAME("Edit")), TTR("Edit"), OBJ_MENU_INSPECT);
232
}
233
234
if (is_editable()) {
235
if (!_is_custom_type_script()) {
236
edit_menu->add_icon_item(get_editor_theme_icon(SNAME("Clear")), TTR("Clear"), OBJ_MENU_CLEAR);
237
}
238
edit_menu->add_icon_item(get_editor_theme_icon(SNAME("Duplicate")), TTR("Make Unique"), OBJ_MENU_MAKE_UNIQUE);
239
240
// Check whether the resource has subresources.
241
List<PropertyInfo> property_list;
242
edited_resource->get_property_list(&property_list);
243
bool has_subresources = false;
244
for (PropertyInfo &p : property_list) {
245
if ((p.type == Variant::OBJECT) && (p.hint == PROPERTY_HINT_RESOURCE_TYPE) && (p.name != "script") && ((Object *)edited_resource->get(p.name) != nullptr)) {
246
has_subresources = true;
247
break;
248
}
249
}
250
if (has_subresources) {
251
edit_menu->add_icon_item(get_editor_theme_icon(SNAME("Duplicate")), TTR("Make Unique (Recursive)"), OBJ_MENU_MAKE_UNIQUE_RECURSIVE);
252
}
253
254
edit_menu->add_icon_item(get_editor_theme_icon(SNAME("Save")), TTR("Save"), OBJ_MENU_SAVE);
255
edit_menu->add_icon_item(get_editor_theme_icon(SNAME("Save")), TTR("Save As..."), OBJ_MENU_SAVE_AS);
256
}
257
258
if (edited_resource->get_path().is_resource_file()) {
259
edit_menu->add_separator();
260
edit_menu->add_icon_item(get_editor_theme_icon(SNAME("ShowInFileSystem")), TTR("Show in FileSystem"), OBJ_MENU_SHOW_IN_FILE_SYSTEM);
261
}
262
}
263
264
// Add options to copy/paste resource.
265
Ref<Resource> cb = EditorSettings::get_singleton()->get_resource_clipboard();
266
bool paste_valid = false;
267
if (is_editable() && cb.is_valid()) {
268
if (base_type.is_empty()) {
269
paste_valid = true;
270
} else {
271
String res_type = _get_resource_type(cb);
272
273
for (int i = 0; i < base_type.get_slice_count(","); i++) {
274
String base = base_type.get_slicec(',', i);
275
276
paste_valid = ClassDB::is_parent_class(res_type, base) || EditorNode::get_editor_data().script_class_is_parent(res_type, base);
277
278
if (paste_valid) {
279
break;
280
}
281
}
282
}
283
}
284
285
if (edited_resource.is_valid() || paste_valid) {
286
edit_menu->add_separator();
287
288
if (edited_resource.is_valid()) {
289
edit_menu->add_item(TTR("Copy"), OBJ_MENU_COPY);
290
}
291
292
if (paste_valid) {
293
edit_menu->add_item(TTR("Paste"), OBJ_MENU_PASTE);
294
edit_menu->add_item(TTRC("Paste as Unique"), OBJ_MENU_PASTE_AS_UNIQUE);
295
}
296
}
297
298
// Add options to convert existing resource to another type of resource.
299
if (is_editable() && edited_resource.is_valid()) {
300
Vector<Ref<EditorResourceConversionPlugin>> conversions = EditorNode::get_singleton()->find_resource_conversion_plugin_for_resource(edited_resource);
301
if (!conversions.is_empty()) {
302
edit_menu->add_separator();
303
}
304
int relative_id = 0;
305
for (const Ref<EditorResourceConversionPlugin> &conversion : conversions) {
306
String what = conversion->converts_to();
307
Ref<Texture2D> icon;
308
if (has_theme_icon(what, EditorStringName(EditorIcons))) {
309
icon = get_editor_theme_icon(what);
310
} else {
311
icon = get_editor_theme_icon(SNAME("Object"));
312
}
313
314
edit_menu->add_icon_item(icon, vformat(TTR("Convert to %s"), what), CONVERT_BASE_ID + relative_id);
315
relative_id++;
316
}
317
}
318
}
319
320
void EditorResourcePicker::_edit_menu_cbk(int p_which) {
321
switch (p_which) {
322
case OBJ_MENU_LOAD: {
323
List<String> extensions;
324
for (int i = 0; i < base_type.get_slice_count(","); i++) {
325
String base = base_type.get_slicec(',', i);
326
if (base == "Resource") {
327
base = "";
328
}
329
ResourceLoader::get_recognized_extensions_for_type(base, &extensions);
330
if (ScriptServer::is_global_class(base)) {
331
ResourceLoader::get_recognized_extensions_for_type(ScriptServer::get_global_class_native_base(base), &extensions);
332
}
333
}
334
335
HashSet<String> valid_extensions;
336
for (const String &E : extensions) {
337
valid_extensions.insert(E);
338
}
339
340
if (!file_dialog) {
341
file_dialog = memnew(EditorFileDialog);
342
file_dialog->set_file_mode(EditorFileDialog::FILE_MODE_OPEN_FILE);
343
add_child(file_dialog);
344
file_dialog->connect("file_selected", callable_mp(this, &EditorResourcePicker::_file_selected));
345
}
346
347
file_dialog->clear_filters();
348
for (const String &E : valid_extensions) {
349
file_dialog->add_filter("*." + E, E.to_upper());
350
}
351
352
file_dialog->popup_file_dialog();
353
} break;
354
355
case OBJ_MENU_QUICKLOAD: {
356
const Vector<String> &base_types_string = base_type.split(",");
357
358
Vector<StringName> base_types;
359
for (const String &type : base_types_string) {
360
base_types.push_back(type);
361
}
362
363
EditorNode::get_singleton()->get_quick_open_dialog()->popup_dialog(base_types, callable_mp(this, &EditorResourcePicker::_file_selected));
364
} break;
365
366
case OBJ_MENU_INSPECT: {
367
if (edited_resource.is_valid()) {
368
emit_signal(SNAME("resource_selected"), edited_resource, true);
369
}
370
} break;
371
372
case OBJ_MENU_CLEAR: {
373
edited_resource = Ref<Resource>();
374
_resource_changed();
375
} break;
376
377
case OBJ_MENU_MAKE_UNIQUE: {
378
if (edited_resource.is_null()) {
379
return;
380
}
381
382
Ref<Resource> unique_resource = edited_resource->duplicate();
383
ERR_FAIL_COND(unique_resource.is_null()); // duplicate() may fail.
384
385
edited_resource = unique_resource;
386
_resource_changed();
387
} break;
388
389
case OBJ_MENU_MAKE_UNIQUE_RECURSIVE: {
390
if (edited_resource.is_null()) {
391
return;
392
}
393
394
if (!duplicate_resources_dialog) {
395
duplicate_resources_dialog = memnew(ConfirmationDialog);
396
add_child(duplicate_resources_dialog);
397
duplicate_resources_dialog->set_title(TTR("Make Unique (Recursive)"));
398
duplicate_resources_dialog->connect(SceneStringName(confirmed), callable_mp(this, &EditorResourcePicker::_duplicate_selected_resources));
399
400
VBoxContainer *vb = memnew(VBoxContainer);
401
duplicate_resources_dialog->add_child(vb);
402
403
Label *label = memnew(Label(TTR("Select resources to make unique:")));
404
vb->add_child(label);
405
406
duplicate_resources_tree = memnew(Tree);
407
duplicate_resources_tree->set_accessibility_name(TTRC("Duplicate resources"));
408
duplicate_resources_tree->set_auto_translate_mode(AUTO_TRANSLATE_MODE_DISABLED);
409
vb->add_child(duplicate_resources_tree);
410
duplicate_resources_tree->set_columns(2);
411
duplicate_resources_tree->set_v_size_flags(SIZE_EXPAND_FILL);
412
}
413
414
duplicate_resources_tree->clear();
415
TreeItem *root = duplicate_resources_tree->create_item();
416
_gather_resources_to_duplicate(edited_resource, root);
417
418
duplicate_resources_dialog->reset_size();
419
duplicate_resources_dialog->popup_centered(Vector2(500, 400) * EDSCALE);
420
} break;
421
422
case OBJ_MENU_SAVE: {
423
if (edited_resource.is_null()) {
424
return;
425
}
426
EditorNode::get_singleton()->save_resource(edited_resource);
427
} break;
428
429
case OBJ_MENU_SAVE_AS: {
430
if (edited_resource.is_null()) {
431
return;
432
}
433
Callable resource_saved = callable_mp(this, &EditorResourcePicker::_resource_saved);
434
if (!EditorNode::get_singleton()->is_connected("resource_saved", resource_saved)) {
435
EditorNode::get_singleton()->connect("resource_saved", resource_saved);
436
}
437
EditorNode::get_singleton()->save_resource_as(edited_resource);
438
} break;
439
440
case OBJ_MENU_COPY: {
441
EditorSettings::get_singleton()->set_resource_clipboard(edited_resource);
442
} break;
443
444
case OBJ_MENU_PASTE: {
445
edited_resource = EditorSettings::get_singleton()->get_resource_clipboard();
446
bool make_unique = true;
447
448
// Automatically make resource unique if it belongs to another scene or resource.
449
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()) {
450
make_unique = false;
451
} else if (resource_owner) {
452
Resource *res = Object::cast_to<Resource>(resource_owner);
453
if (res && edited_resource->get_path().get_slice("::", 0) == res->get_path().get_slice("::", 0)) {
454
make_unique = false;
455
}
456
}
457
458
if (make_unique) {
459
_edit_menu_cbk(OBJ_MENU_MAKE_UNIQUE);
460
} else {
461
_resource_changed();
462
}
463
} break;
464
465
case OBJ_MENU_PASTE_AS_UNIQUE: {
466
edited_resource = EditorSettings::get_singleton()->get_resource_clipboard();
467
// Use the recursive version when using Paste as Unique.
468
// This will show up a dialog to select which resources to make unique.
469
_edit_menu_cbk(OBJ_MENU_MAKE_UNIQUE_RECURSIVE);
470
} break;
471
472
case OBJ_MENU_SHOW_IN_FILE_SYSTEM: {
473
FileSystemDock::get_singleton()->navigate_to_path(edited_resource->get_path());
474
} break;
475
476
default: {
477
// Allow subclasses to handle their own options first, only then fallback on the default branch logic.
478
if (handle_menu_selected(p_which)) {
479
break;
480
}
481
482
if (p_which >= CONVERT_BASE_ID) {
483
int to_type = p_which - CONVERT_BASE_ID;
484
Vector<Ref<EditorResourceConversionPlugin>> conversions = EditorNode::get_singleton()->find_resource_conversion_plugin_for_resource(edited_resource);
485
ERR_FAIL_INDEX(to_type, conversions.size());
486
487
edited_resource = conversions[to_type]->convert(edited_resource);
488
_resource_changed();
489
break;
490
}
491
492
ERR_FAIL_COND(inheritors_array.is_empty());
493
494
String intype = inheritors_array[p_which - TYPE_BASE_ID];
495
Variant obj;
496
497
if (ScriptServer::is_global_class(intype)) {
498
obj = EditorNode::get_editor_data().script_class_instance(intype);
499
} else {
500
obj = ClassDB::instantiate(intype);
501
}
502
503
if (!obj) {
504
obj = EditorNode::get_editor_data().instantiate_custom_type(intype, "Resource");
505
}
506
507
Resource *resp = Object::cast_to<Resource>(obj);
508
ERR_BREAK(!resp);
509
resp->set_path(_get_owner_path() + "::" + resp->generate_scene_unique_id()); // Assign a base path for built-in Resources.
510
511
EditorNode::get_editor_data().instantiate_object_properties(obj);
512
513
// Prevent freeing of the object until the end of the update of the resource (GH-88286).
514
Ref<Resource> old_edited_resource = edited_resource;
515
edited_resource = Ref<Resource>(resp);
516
_resource_changed();
517
} break;
518
}
519
}
520
521
void EditorResourcePicker::set_create_options(Object *p_menu_node) {
522
_ensure_resource_menu();
523
// If a subclass implements this method, use it to replace all create items.
524
if (GDVIRTUAL_CALL(_set_create_options, p_menu_node)) {
525
return;
526
}
527
528
// By default provide generic "New ..." options.
529
if (!base_type.is_empty()) {
530
int idx = 0;
531
532
_ensure_allowed_types();
533
HashSet<StringName> allowed_types = allowed_types_without_convert;
534
if (!allowed_types.is_empty()) {
535
edit_menu->add_separator(TTRC("New"));
536
}
537
538
for (const StringName &E : allowed_types) {
539
const String &t = E;
540
541
if (!ClassDB::can_instantiate(t)) {
542
continue;
543
}
544
545
inheritors_array.push_back(t);
546
547
Ref<Texture2D> icon = EditorNode::get_singleton()->get_class_icon(t, "Object");
548
int id = TYPE_BASE_ID + idx;
549
edit_menu->add_icon_item(icon, t, id);
550
edit_menu->set_item_auto_translate_mode(-1, AUTO_TRANSLATE_MODE_DISABLED);
551
552
HashMap<String, DocData::ClassDoc>::Iterator class_doc = EditorHelp::get_doc_data()->class_list.find(t);
553
if (class_doc) {
554
edit_menu->set_item_tooltip(-1, DTR(class_doc->value.brief_description));
555
}
556
557
idx++;
558
}
559
560
if (edit_menu->get_item_count()) {
561
edit_menu->add_separator();
562
}
563
}
564
}
565
566
bool EditorResourcePicker::handle_menu_selected(int p_which) {
567
bool success = false;
568
GDVIRTUAL_CALL(_handle_menu_selected, p_which, success);
569
return success;
570
}
571
572
void EditorResourcePicker::_button_draw() {
573
if (dropping) {
574
Color color = get_theme_color(SNAME("accent_color"), EditorStringName(Editor));
575
assign_button->draw_rect(Rect2(Point2(), assign_button->get_size()), color, false);
576
}
577
}
578
579
void EditorResourcePicker::_button_input(const Ref<InputEvent> &p_event) {
580
Ref<InputEventMouseButton> mb = p_event;
581
582
if (mb.is_valid() && mb->is_pressed() && mb->get_button_index() == MouseButton::RIGHT) {
583
// Only attempt to update and show the menu if we have
584
// a valid resource or the Picker is editable, as
585
// there will otherwise be nothing to display.
586
if (edited_resource.is_valid() || is_editable()) {
587
if (edit_menu && edit_menu->is_visible()) {
588
edit_button->set_pressed(false);
589
edit_menu->hide();
590
return;
591
}
592
593
_update_menu_items();
594
595
Vector2 pos = get_screen_position() + mb->get_position();
596
edit_menu->reset_size();
597
edit_menu->set_position(pos);
598
edit_menu->popup();
599
}
600
}
601
}
602
603
String EditorResourcePicker::_get_owner_path() const {
604
EditorProperty *property = Object::cast_to<EditorProperty>(get_parent());
605
if (!property) {
606
return String();
607
}
608
Object *obj = property->get_edited_object();
609
610
Node *node = Object::cast_to<Node>(obj);
611
if (node) {
612
if (node->get_scene_file_path().is_empty()) {
613
node = node->get_owner();
614
}
615
if (node) {
616
return node->get_scene_file_path();
617
}
618
return String();
619
}
620
621
Resource *res = Object::cast_to<Resource>(obj);
622
if (res && !res->is_built_in()) {
623
return res->get_path();
624
}
625
// TODO: It would be nice to handle deeper Resource nesting.
626
return String();
627
}
628
629
String EditorResourcePicker::_get_resource_type(const Ref<Resource> &p_resource) const {
630
if (p_resource.is_null()) {
631
return String();
632
}
633
String res_type = p_resource->get_class();
634
635
Ref<Script> res_script = p_resource->get_script();
636
if (res_script.is_null()) {
637
return res_type;
638
}
639
640
// TODO: Replace with EditorFileSystem when PR #60606 is merged to use cached resource type.
641
String script_type = EditorNode::get_editor_data().script_class_get_name(res_script->get_path());
642
if (!script_type.is_empty()) {
643
res_type = script_type;
644
}
645
return res_type;
646
}
647
648
static void _add_allowed_type(const StringName &p_type, List<StringName> *p_vector) {
649
if (p_vector->find(p_type)) {
650
// Already added.
651
return;
652
}
653
654
if (ClassDB::class_exists(p_type)) {
655
// Engine class,
656
657
if (!ClassDB::is_virtual(p_type)) {
658
p_vector->push_back(p_type);
659
}
660
661
LocalVector<StringName> inheriters;
662
ClassDB::get_inheriters_from_class(p_type, inheriters);
663
for (const StringName &S : inheriters) {
664
_add_allowed_type(S, p_vector);
665
}
666
} else {
667
// Script class.
668
p_vector->push_back(p_type);
669
}
670
671
List<StringName> inheriters;
672
ScriptServer::get_inheriters_list(p_type, &inheriters);
673
for (const StringName &S : inheriters) {
674
_add_allowed_type(S, p_vector);
675
}
676
}
677
678
void EditorResourcePicker::_ensure_allowed_types() const {
679
if (!allowed_types_without_convert.is_empty()) {
680
return;
681
}
682
683
List<StringName> final_allowed;
684
685
Vector<String> allowed_types = base_type.split(",");
686
int size = allowed_types.size();
687
688
for (const String &S : allowed_types) {
689
const String base = S.strip_edges();
690
if (base.begins_with("-")) {
691
final_allowed.erase(base.right(-1));
692
continue;
693
}
694
_add_allowed_type(base, &final_allowed);
695
}
696
697
for (const StringName &SN : final_allowed) {
698
allowed_types_without_convert.insert(SN);
699
}
700
701
allowed_types_with_convert = HashSet<StringName>(allowed_types_without_convert);
702
703
for (int i = 0; i < size; i++) {
704
const String base = allowed_types[i].strip_edges();
705
if (base == "BaseMaterial3D") {
706
allowed_types_with_convert.insert("Texture2D");
707
} else if (ClassDB::is_parent_class("ShaderMaterial", base)) {
708
allowed_types_with_convert.insert("Shader");
709
} else if (ClassDB::is_parent_class("ImageTexture", base)) {
710
allowed_types_with_convert.insert("Image");
711
}
712
}
713
}
714
715
bool EditorResourcePicker::_is_drop_valid(const Dictionary &p_drag_data) const {
716
{
717
const ObjectID source_picker = p_drag_data.get("source_picker", ObjectID());
718
if (source_picker == get_instance_id()) {
719
return false;
720
}
721
}
722
723
if (base_type.is_empty()) {
724
return true;
725
}
726
727
Dictionary drag_data = p_drag_data;
728
729
Ref<Resource> res;
730
if (drag_data.has("type") && String(drag_data["type"]) == "script_list_element") {
731
ScriptEditorBase *se = Object::cast_to<ScriptEditorBase>(drag_data["script_list_element"]);
732
if (se) {
733
res = se->get_edited_resource();
734
}
735
} else if (drag_data.has("type") && String(drag_data["type"]) == "resource") {
736
res = drag_data["resource"];
737
} else if (drag_data.has("type") && String(drag_data["type"]) == "files") {
738
Vector<String> files = drag_data["files"];
739
740
if (files.size() == 1) {
741
if (ResourceLoader::exists(files[0])) {
742
// TODO: Extract the typename of the dropped filepath's resource in a more performant way, without fully loading it.
743
res = ResourceLoader::load(files[0]);
744
}
745
}
746
}
747
748
_ensure_allowed_types();
749
HashSet<StringName> allowed_types = allowed_types_with_convert;
750
751
if (res.is_valid()) {
752
String res_type = _get_resource_type(res);
753
754
if (_is_type_valid(res_type, allowed_types)) {
755
return true;
756
}
757
758
if (res->get_script()) {
759
StringName custom_class = EditorNode::get_singleton()->get_object_custom_type_name(res->get_script());
760
if (_is_type_valid(custom_class, allowed_types)) {
761
return true;
762
}
763
}
764
}
765
766
return false;
767
}
768
769
bool EditorResourcePicker::_is_type_valid(const String &p_type_name, const HashSet<StringName> &p_allowed_types) const {
770
for (const StringName &E : p_allowed_types) {
771
String at = E;
772
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)) {
773
return true;
774
}
775
}
776
777
return false;
778
}
779
780
bool EditorResourcePicker::_is_custom_type_script() const {
781
Ref<Script> resource_as_script = edited_resource;
782
783
if (resource_as_script.is_valid() && resource_owner && resource_owner->has_meta(SceneStringName(_custom_type_script))) {
784
return true;
785
}
786
787
return false;
788
}
789
790
Variant EditorResourcePicker::get_drag_data_fw(const Point2 &p_point, Control *p_from) {
791
if (edited_resource.is_valid()) {
792
Dictionary drag_data = EditorNode::get_singleton()->drag_resource(edited_resource, p_from);
793
drag_data["source_picker"] = get_instance_id();
794
return drag_data;
795
}
796
797
return Variant();
798
}
799
800
bool EditorResourcePicker::can_drop_data_fw(const Point2 &p_point, const Variant &p_data, Control *p_from) const {
801
return editable && _is_drop_valid(p_data);
802
}
803
804
void EditorResourcePicker::drop_data_fw(const Point2 &p_point, const Variant &p_data, Control *p_from) {
805
ERR_FAIL_COND(!_is_drop_valid(p_data));
806
807
Dictionary drag_data = p_data;
808
809
Ref<Resource> dropped_resource;
810
if (drag_data.has("type") && String(drag_data["type"]) == "script_list_element") {
811
ScriptEditorBase *se = Object::cast_to<ScriptEditorBase>(drag_data["script_list_element"]);
812
if (se) {
813
dropped_resource = se->get_edited_resource();
814
}
815
} else if (drag_data.has("type") && String(drag_data["type"]) == "resource") {
816
dropped_resource = drag_data["resource"];
817
}
818
819
if (dropped_resource.is_null() && drag_data.has("type") && String(drag_data["type"]) == "files") {
820
Vector<String> files = drag_data["files"];
821
822
if (files.size() == 1) {
823
dropped_resource = ResourceLoader::load(files[0]);
824
}
825
}
826
827
if (dropped_resource.is_valid()) {
828
_ensure_allowed_types();
829
HashSet<StringName> allowed_types = allowed_types_without_convert;
830
831
String res_type = _get_resource_type(dropped_resource);
832
833
// If the accepted dropped resource is from the extended list, it requires conversion.
834
if (!_is_type_valid(res_type, allowed_types)) {
835
for (const StringName &E : allowed_types) {
836
String at = E;
837
838
if (at == "BaseMaterial3D" && Ref<Texture2D>(dropped_resource).is_valid()) {
839
// Use existing resource if possible and only replace its data.
840
Ref<StandardMaterial3D> mat = edited_resource;
841
if (mat.is_null()) {
842
mat.instantiate();
843
}
844
mat->set_texture(StandardMaterial3D::TextureParam::TEXTURE_ALBEDO, dropped_resource);
845
dropped_resource = mat;
846
break;
847
}
848
849
if (at == "ShaderMaterial" && Ref<Shader>(dropped_resource).is_valid()) {
850
Ref<ShaderMaterial> mat = edited_resource;
851
if (mat.is_null()) {
852
mat.instantiate();
853
}
854
mat->set_shader(dropped_resource);
855
dropped_resource = mat;
856
break;
857
}
858
859
if (at == "ImageTexture" && Ref<Image>(dropped_resource).is_valid()) {
860
Ref<ImageTexture> texture = edited_resource;
861
if (texture.is_null()) {
862
texture.instantiate();
863
}
864
texture->set_image(dropped_resource);
865
dropped_resource = texture;
866
break;
867
}
868
}
869
}
870
871
edited_resource = dropped_resource;
872
_resource_changed();
873
}
874
}
875
876
void EditorResourcePicker::_bind_methods() {
877
ClassDB::bind_method(D_METHOD("_update_resource_preview"), &EditorResourcePicker::_update_resource_preview);
878
879
ClassDB::bind_method(D_METHOD("set_base_type", "base_type"), &EditorResourcePicker::set_base_type);
880
ClassDB::bind_method(D_METHOD("get_base_type"), &EditorResourcePicker::get_base_type);
881
ClassDB::bind_method(D_METHOD("get_allowed_types"), &EditorResourcePicker::get_allowed_types);
882
ClassDB::bind_method(D_METHOD("set_edited_resource", "resource"), &EditorResourcePicker::set_edited_resource);
883
ClassDB::bind_method(D_METHOD("get_edited_resource"), &EditorResourcePicker::get_edited_resource);
884
ClassDB::bind_method(D_METHOD("set_toggle_mode", "enable"), &EditorResourcePicker::set_toggle_mode);
885
ClassDB::bind_method(D_METHOD("is_toggle_mode"), &EditorResourcePicker::is_toggle_mode);
886
ClassDB::bind_method(D_METHOD("set_toggle_pressed", "pressed"), &EditorResourcePicker::set_toggle_pressed);
887
ClassDB::bind_method(D_METHOD("set_editable", "enable"), &EditorResourcePicker::set_editable);
888
ClassDB::bind_method(D_METHOD("is_editable"), &EditorResourcePicker::is_editable);
889
890
GDVIRTUAL_BIND(_set_create_options, "menu_node");
891
GDVIRTUAL_BIND(_handle_menu_selected, "id");
892
893
ADD_PROPERTY(PropertyInfo(Variant::STRING, "base_type"), "set_base_type", "get_base_type");
894
ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "edited_resource", PROPERTY_HINT_RESOURCE_TYPE, "Resource", PROPERTY_USAGE_NONE), "set_edited_resource", "get_edited_resource");
895
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "editable"), "set_editable", "is_editable");
896
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "toggle_mode"), "set_toggle_mode", "is_toggle_mode");
897
898
ADD_SIGNAL(MethodInfo("resource_selected", PropertyInfo(Variant::OBJECT, "resource", PROPERTY_HINT_RESOURCE_TYPE, "Resource"), PropertyInfo(Variant::BOOL, "inspect")));
899
ADD_SIGNAL(MethodInfo("resource_changed", PropertyInfo(Variant::OBJECT, "resource", PROPERTY_HINT_RESOURCE_TYPE, "Resource")));
900
}
901
902
void EditorResourcePicker::_notification(int p_what) {
903
switch (p_what) {
904
case NOTIFICATION_ENTER_TREE: {
905
_update_resource();
906
[[fallthrough]];
907
}
908
case NOTIFICATION_THEME_CHANGED: {
909
const int icon_width = get_theme_constant(SNAME("class_icon_size"), EditorStringName(Editor));
910
assign_button->add_theme_constant_override("icon_max_width", icon_width);
911
if (edit_menu) {
912
edit_menu->add_theme_constant_override("icon_max_width", icon_width);
913
}
914
915
quick_load_button->set_button_icon(get_editor_theme_icon(SNAME("Load")));
916
edit_button->set_button_icon(get_theme_icon(SNAME("select_arrow"), SNAME("Tree")));
917
} break;
918
919
case NOTIFICATION_DRAW: {
920
draw_style_box(get_theme_stylebox(SceneStringName(panel), SNAME("Tree")), Rect2(Point2(), get_size()));
921
} break;
922
923
case NOTIFICATION_DRAG_BEGIN: {
924
if (editable && _is_drop_valid(get_viewport()->gui_get_drag_data())) {
925
dropping = true;
926
assign_button->queue_redraw();
927
}
928
} break;
929
930
case NOTIFICATION_DRAG_END: {
931
if (dropping) {
932
dropping = false;
933
assign_button->queue_redraw();
934
}
935
} break;
936
937
case NOTIFICATION_EXIT_TREE: {
938
Callable resource_saved = callable_mp(this, &EditorResourcePicker::_resource_saved);
939
if (EditorNode::get_singleton()->is_connected("resource_saved", resource_saved)) {
940
EditorNode::get_singleton()->disconnect("resource_saved", resource_saved);
941
}
942
} break;
943
}
944
}
945
946
void EditorResourcePicker::set_assign_button_min_size(const Size2i &p_size) {
947
assign_button_min_size = p_size;
948
assign_button->set_custom_minimum_size(assign_button_min_size);
949
}
950
951
void EditorResourcePicker::set_base_type(const String &p_base_type) {
952
base_type = p_base_type;
953
954
// There is a possibility that the new base type is conflicting with the existing value.
955
// Keep the value, but warn the user that there is a potential mistake.
956
if (!base_type.is_empty() && edited_resource.is_valid()) {
957
_ensure_allowed_types();
958
HashSet<StringName> allowed_types = allowed_types_with_convert;
959
960
StringName custom_class;
961
bool is_custom = false;
962
if (edited_resource->get_script()) {
963
custom_class = EditorNode::get_singleton()->get_object_custom_type_name(edited_resource->get_script());
964
is_custom = _is_type_valid(custom_class, allowed_types);
965
}
966
967
if (!is_custom && !_is_type_valid(edited_resource->get_class(), allowed_types)) {
968
String class_str = (custom_class == StringName() ? edited_resource->get_class() : vformat("%s (%s)", custom_class, edited_resource->get_class()));
969
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));
970
}
971
}
972
}
973
974
String EditorResourcePicker::get_base_type() const {
975
return base_type;
976
}
977
978
Vector<String> EditorResourcePicker::get_allowed_types() const {
979
_ensure_allowed_types();
980
HashSet<StringName> allowed_types = allowed_types_without_convert;
981
982
Vector<String> types;
983
types.resize(allowed_types.size());
984
985
int i = 0;
986
String *w = types.ptrw();
987
for (const StringName &E : allowed_types) {
988
w[i] = E;
989
i++;
990
}
991
992
return types;
993
}
994
995
void EditorResourcePicker::set_edited_resource(Ref<Resource> p_resource) {
996
if (p_resource.is_null()) {
997
edited_resource = Ref<Resource>();
998
_update_resource();
999
return;
1000
}
1001
1002
if (!base_type.is_empty()) {
1003
_ensure_allowed_types();
1004
HashSet<StringName> allowed_types = allowed_types_with_convert;
1005
1006
StringName custom_class;
1007
bool is_custom = false;
1008
if (p_resource->get_script()) {
1009
custom_class = EditorNode::get_singleton()->get_object_custom_type_name(p_resource->get_script());
1010
is_custom = _is_type_valid(custom_class, allowed_types);
1011
}
1012
1013
if (!is_custom && !_is_type_valid(p_resource->get_class(), allowed_types)) {
1014
String class_str = (custom_class == StringName() ? p_resource->get_class() : vformat("%s (%s)", custom_class, p_resource->get_class()));
1015
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));
1016
}
1017
}
1018
set_edited_resource_no_check(p_resource);
1019
}
1020
1021
void EditorResourcePicker::set_edited_resource_no_check(Ref<Resource> p_resource) {
1022
edited_resource = p_resource;
1023
_update_resource();
1024
}
1025
1026
Ref<Resource> EditorResourcePicker::get_edited_resource() {
1027
return edited_resource;
1028
}
1029
1030
void EditorResourcePicker::set_toggle_mode(bool p_enable) {
1031
assign_button->set_toggle_mode(p_enable);
1032
}
1033
1034
bool EditorResourcePicker::is_toggle_mode() const {
1035
return assign_button->is_toggle_mode();
1036
}
1037
1038
void EditorResourcePicker::set_toggle_pressed(bool p_pressed) {
1039
if (!is_toggle_mode()) {
1040
return;
1041
}
1042
1043
assign_button->set_pressed(p_pressed);
1044
}
1045
1046
bool EditorResourcePicker::is_toggle_pressed() const {
1047
return assign_button->is_pressed();
1048
}
1049
1050
void EditorResourcePicker::set_resource_owner(Object *p_object) {
1051
resource_owner = p_object;
1052
}
1053
1054
void EditorResourcePicker::set_editable(bool p_editable) {
1055
editable = p_editable;
1056
assign_button->set_disabled(!editable && edited_resource.is_null());
1057
quick_load_button->set_visible(editable && edited_resource.is_null());
1058
edit_button->set_visible(editable);
1059
}
1060
1061
bool EditorResourcePicker::is_editable() const {
1062
return editable;
1063
}
1064
1065
void EditorResourcePicker::_ensure_resource_menu() {
1066
if (edit_menu) {
1067
return;
1068
}
1069
edit_menu = memnew(PopupMenu);
1070
edit_menu->add_theme_constant_override("icon_max_width", get_theme_constant(SNAME("class_icon_size"), EditorStringName(Editor)));
1071
add_child(edit_menu);
1072
edit_menu->connect(SceneStringName(id_pressed), callable_mp(this, &EditorResourcePicker::_edit_menu_cbk));
1073
edit_menu->connect("popup_hide", callable_mp((BaseButton *)edit_button, &BaseButton::set_pressed).bind(false));
1074
}
1075
1076
void EditorResourcePicker::_gather_resources_to_duplicate(const Ref<Resource> p_resource, TreeItem *p_item, const String &p_property_name) const {
1077
p_item->set_cell_mode(0, TreeItem::CELL_MODE_CHECK);
1078
1079
String res_name = p_resource->get_name();
1080
if (res_name.is_empty() && !p_resource->is_built_in()) {
1081
res_name = p_resource->get_path().get_file();
1082
}
1083
1084
if (res_name.is_empty()) {
1085
p_item->set_text(0, p_resource->get_class());
1086
} else {
1087
p_item->set_text(0, vformat("%s (%s)", p_resource->get_class(), res_name));
1088
}
1089
1090
p_item->set_icon(0, EditorNode::get_singleton()->get_object_icon(p_resource.ptr()));
1091
p_item->set_editable(0, true);
1092
1093
Array meta = { p_resource };
1094
p_item->set_metadata(0, meta);
1095
1096
if (!p_property_name.is_empty()) {
1097
p_item->set_text(1, p_property_name);
1098
}
1099
1100
static Vector<String> unique_exceptions = { "Image", "Shader", "Mesh", "FontFile" };
1101
if (!unique_exceptions.has(p_resource->get_class())) {
1102
// Automatically select resource, unless it's something that shouldn't be duplicated.
1103
p_item->set_checked(0, true);
1104
}
1105
1106
List<PropertyInfo> plist;
1107
p_resource->get_property_list(&plist);
1108
1109
for (const PropertyInfo &E : plist) {
1110
if (!(E.usage & PROPERTY_USAGE_STORAGE) || E.type != Variant::OBJECT || E.hint != PROPERTY_HINT_RESOURCE_TYPE) {
1111
continue;
1112
}
1113
1114
Ref<Resource> res = p_resource->get(E.name);
1115
if (res.is_null()) {
1116
continue;
1117
}
1118
1119
TreeItem *child = p_item->create_child();
1120
_gather_resources_to_duplicate(res, child, E.name);
1121
1122
meta = child->get_metadata(0);
1123
// Remember property name.
1124
meta.append(E.name);
1125
1126
if ((E.usage & PROPERTY_USAGE_NEVER_DUPLICATE)) {
1127
// The resource can't be duplicated, but make it appear on the list anyway.
1128
child->set_checked(0, false);
1129
child->set_editable(0, false);
1130
}
1131
}
1132
}
1133
1134
void EditorResourcePicker::_duplicate_selected_resources() {
1135
for (TreeItem *item = duplicate_resources_tree->get_root(); item; item = item->get_next_in_tree()) {
1136
if (!item->is_checked(0)) {
1137
continue;
1138
}
1139
1140
Array meta = item->get_metadata(0);
1141
Ref<Resource> res = meta[0];
1142
Ref<Resource> unique_resource = res->duplicate();
1143
ERR_FAIL_COND(unique_resource.is_null()); // duplicate() may fail.
1144
meta[0] = unique_resource;
1145
1146
if (meta.size() == 1) { // Root.
1147
edited_resource = unique_resource;
1148
_resource_changed();
1149
} else {
1150
Array parent_meta = item->get_parent()->get_metadata(0);
1151
Ref<Resource> parent = parent_meta[0];
1152
parent->set(meta[1], unique_resource);
1153
}
1154
}
1155
}
1156
1157
EditorResourcePicker::EditorResourcePicker(bool p_hide_assign_button_controls) {
1158
assign_button = memnew(Button);
1159
assign_button->set_flat(true);
1160
assign_button->set_h_size_flags(SIZE_EXPAND_FILL);
1161
assign_button->set_accessibility_name(TTRC("Assign Resource"));
1162
assign_button->set_expand_icon(true);
1163
assign_button->set_clip_text(true);
1164
assign_button->set_auto_translate_mode(AUTO_TRANSLATE_MODE_DISABLED);
1165
SET_DRAG_FORWARDING_GCD(assign_button, EditorResourcePicker);
1166
add_child(assign_button);
1167
assign_button->connect(SceneStringName(pressed), callable_mp(this, &EditorResourcePicker::_resource_selected));
1168
assign_button->connect(SceneStringName(draw), callable_mp(this, &EditorResourcePicker::_button_draw));
1169
assign_button->connect(SceneStringName(gui_input), callable_mp(this, &EditorResourcePicker::_button_input));
1170
1171
if (!p_hide_assign_button_controls) {
1172
preview_rect = memnew(TextureRect);
1173
preview_rect->set_expand_mode(TextureRect::EXPAND_IGNORE_SIZE);
1174
preview_rect->set_anchors_and_offsets_preset(PRESET_FULL_RECT);
1175
preview_rect->set_offset(SIDE_TOP, 1);
1176
preview_rect->set_offset(SIDE_BOTTOM, -1);
1177
preview_rect->set_offset(SIDE_RIGHT, -1);
1178
preview_rect->set_texture_filter(TEXTURE_FILTER_NEAREST_WITH_MIPMAPS);
1179
assign_button->add_child(preview_rect);
1180
}
1181
1182
quick_load_button = memnew(Button);
1183
quick_load_button->set_tooltip_text(TTRC("Quick Load"));
1184
add_child(quick_load_button);
1185
quick_load_button->connect(SceneStringName(pressed), callable_mp(this, &EditorResourcePicker::_edit_menu_cbk).bind(OBJ_MENU_QUICKLOAD));
1186
1187
edit_button = memnew(Button);
1188
edit_button->set_flat(false);
1189
edit_button->set_toggle_mode(true);
1190
edit_button->set_action_mode(BaseButton::ACTION_MODE_BUTTON_PRESS);
1191
edit_button->set_accessibility_name(TTRC("Edit"));
1192
add_child(edit_button);
1193
edit_button->connect(SceneStringName(pressed), callable_mp(this, &EditorResourcePicker::_update_menu));
1194
edit_button->connect(SceneStringName(gui_input), callable_mp(this, &EditorResourcePicker::_button_input));
1195
1196
add_theme_constant_override("separation", 0);
1197
}
1198
1199
// EditorScriptPicker
1200
1201
void EditorScriptPicker::set_create_options(Object *p_menu_node) {
1202
PopupMenu *menu_node = Object::cast_to<PopupMenu>(p_menu_node);
1203
if (!menu_node) {
1204
return;
1205
}
1206
1207
if (!(script_owner && script_owner->has_meta(SceneStringName(_custom_type_script)))) {
1208
menu_node->add_icon_item(get_editor_theme_icon(SNAME("ScriptCreate")), TTR("New Script..."), OBJ_MENU_NEW_SCRIPT);
1209
}
1210
1211
if (script_owner) {
1212
Ref<Script> scr = script_owner->get_script();
1213
if (scr.is_valid()) {
1214
menu_node->add_icon_item(get_editor_theme_icon(SNAME("ScriptExtend")), TTR("Extend Script..."), OBJ_MENU_EXTEND_SCRIPT);
1215
}
1216
}
1217
menu_node->add_separator();
1218
}
1219
1220
bool EditorScriptPicker::handle_menu_selected(int p_which) {
1221
switch (p_which) {
1222
case OBJ_MENU_NEW_SCRIPT: {
1223
if (script_owner) {
1224
SceneTreeDock::get_singleton()->open_script_dialog(script_owner, false);
1225
}
1226
return true;
1227
}
1228
1229
case OBJ_MENU_EXTEND_SCRIPT: {
1230
if (script_owner) {
1231
SceneTreeDock::get_singleton()->open_script_dialog(script_owner, true);
1232
}
1233
return true;
1234
}
1235
}
1236
1237
return false;
1238
}
1239
1240
void EditorScriptPicker::set_script_owner(Node *p_owner) {
1241
script_owner = p_owner;
1242
}
1243
1244
Node *EditorScriptPicker::get_script_owner() const {
1245
return script_owner;
1246
}
1247
1248
void EditorScriptPicker::_bind_methods() {
1249
ClassDB::bind_method(D_METHOD("set_script_owner", "owner_node"), &EditorScriptPicker::set_script_owner);
1250
ClassDB::bind_method(D_METHOD("get_script_owner"), &EditorScriptPicker::get_script_owner);
1251
1252
ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "script_owner", PROPERTY_HINT_RESOURCE_TYPE, "Node", PROPERTY_USAGE_NONE), "set_script_owner", "get_script_owner");
1253
}
1254
1255
// EditorShaderPicker
1256
1257
void EditorShaderPicker::set_create_options(Object *p_menu_node) {
1258
PopupMenu *menu_node = Object::cast_to<PopupMenu>(p_menu_node);
1259
if (!menu_node) {
1260
return;
1261
}
1262
1263
menu_node->add_icon_item(get_editor_theme_icon(SNAME("Shader")), TTR("New Shader..."), OBJ_MENU_NEW_SHADER);
1264
menu_node->add_separator();
1265
}
1266
1267
bool EditorShaderPicker::handle_menu_selected(int p_which) {
1268
Ref<ShaderMaterial> ed_material = Ref<ShaderMaterial>(get_edited_material());
1269
1270
switch (p_which) {
1271
case OBJ_MENU_NEW_SHADER: {
1272
if (ed_material.is_valid()) {
1273
SceneTreeDock::get_singleton()->open_shader_dialog(ed_material, preferred_mode);
1274
return true;
1275
}
1276
} break;
1277
default:
1278
break;
1279
}
1280
return false;
1281
}
1282
1283
void EditorShaderPicker::set_edited_material(ShaderMaterial *p_material) {
1284
edited_material = p_material;
1285
}
1286
1287
ShaderMaterial *EditorShaderPicker::get_edited_material() const {
1288
return edited_material;
1289
}
1290
1291
void EditorShaderPicker::set_preferred_mode(int p_mode) {
1292
preferred_mode = p_mode;
1293
}
1294
1295
//////////////
1296
1297
void EditorAudioStreamPicker::_notification(int p_what) {
1298
switch (p_what) {
1299
case NOTIFICATION_READY:
1300
case NOTIFICATION_THEME_CHANGED: {
1301
_update_resource();
1302
} break;
1303
case NOTIFICATION_INTERNAL_PROCESS: {
1304
Ref<AudioStream> audio_stream = get_edited_resource();
1305
if (audio_stream.is_valid()) {
1306
if (audio_stream->get_length() > 0) {
1307
Ref<AudioStreamPreview> preview = AudioStreamPreviewGenerator::get_singleton()->generate_preview(audio_stream);
1308
if (preview.is_valid()) {
1309
if (preview->get_version() != last_preview_version) {
1310
stream_preview_rect->queue_redraw();
1311
last_preview_version = preview->get_version();
1312
}
1313
}
1314
}
1315
1316
uint64_t tagged_frame = audio_stream->get_tagged_frame();
1317
uint64_t diff_frames = AudioServer::get_singleton()->get_mixed_frames() - tagged_frame;
1318
uint64_t diff_msec = diff_frames * 1000 / AudioServer::get_singleton()->get_mix_rate();
1319
1320
if (diff_msec < 300) {
1321
uint32_t count = audio_stream->get_tagged_frame_count();
1322
1323
bool differ = false;
1324
1325
if (count != tagged_frame_offset_count) {
1326
differ = true;
1327
}
1328
float offsets[MAX_TAGGED_FRAMES];
1329
1330
for (uint32_t i = 0; i < MIN(count, uint32_t(MAX_TAGGED_FRAMES)); i++) {
1331
offsets[i] = audio_stream->get_tagged_frame_offset(i);
1332
if (offsets[i] != tagged_frame_offsets[i]) {
1333
differ = true;
1334
}
1335
}
1336
1337
if (differ) {
1338
tagged_frame_offset_count = count;
1339
for (uint32_t i = 0; i < count; i++) {
1340
tagged_frame_offsets[i] = offsets[i];
1341
}
1342
}
1343
1344
stream_preview_rect->queue_redraw();
1345
} else {
1346
if (tagged_frame_offset_count != 0) {
1347
stream_preview_rect->queue_redraw();
1348
}
1349
tagged_frame_offset_count = 0;
1350
}
1351
}
1352
} break;
1353
}
1354
}
1355
1356
void EditorAudioStreamPicker::_update_resource() {
1357
EditorResourcePicker::_update_resource();
1358
1359
Ref<Font> font = get_theme_font(SceneStringName(font), SNAME("Label"));
1360
int font_size = get_theme_font_size(SceneStringName(font_size), SNAME("Label"));
1361
Ref<AudioStream> audio_stream = get_edited_resource();
1362
if (audio_stream.is_valid() && audio_stream->get_length() > 0.0) {
1363
set_assign_button_min_size(Size2(1, font->get_height(font_size) * 3));
1364
} else {
1365
set_assign_button_min_size(Size2(1, font->get_height(font_size) * 1.5));
1366
}
1367
1368
stream_preview_rect->queue_redraw();
1369
}
1370
1371
void EditorAudioStreamPicker::_preview_draw() {
1372
Ref<AudioStream> audio_stream = get_edited_resource();
1373
if (audio_stream.is_null()) {
1374
get_assign_button()->set_text(TTR("<empty>"));
1375
return;
1376
}
1377
1378
int font_size = get_theme_font_size(SceneStringName(font_size), SNAME("Label"));
1379
1380
get_assign_button()->set_text("");
1381
1382
Size2i size = stream_preview_rect->get_size();
1383
Ref<Font> font = get_theme_font(SceneStringName(font), SNAME("Label"));
1384
1385
Rect2 rect(Point2(), size);
1386
1387
if (audio_stream->get_length() > 0 && size.width > 0) {
1388
rect.size.height *= 0.5;
1389
1390
Ref<AudioStreamPreview> preview = AudioStreamPreviewGenerator::get_singleton()->generate_preview(audio_stream);
1391
float preview_len = preview->get_length();
1392
1393
Vector<Vector2> points;
1394
points.resize(size.width * 2);
1395
1396
for (int i = 0; i < size.width; i++) {
1397
float ofs = i * preview_len / size.width;
1398
float ofs_n = (i + 1) * preview_len / size.width;
1399
float max = preview->get_max(ofs, ofs_n) * 0.5 + 0.5;
1400
float min = preview->get_min(ofs, ofs_n) * 0.5 + 0.5;
1401
1402
int idx = i;
1403
points.write[idx * 2 + 0] = Vector2(i + 1, rect.position.y + min * rect.size.y);
1404
points.write[idx * 2 + 1] = Vector2(i + 1, rect.position.y + max * rect.size.y);
1405
}
1406
1407
Vector<Color> colors = { get_theme_color(SNAME("contrast_color_2"), EditorStringName(Editor)) };
1408
1409
RS::get_singleton()->canvas_item_add_multiline(stream_preview_rect->get_canvas_item(), points, colors);
1410
1411
if (tagged_frame_offset_count) {
1412
Color accent = get_theme_color(SNAME("accent_color"), EditorStringName(Editor));
1413
1414
for (uint32_t i = 0; i < tagged_frame_offset_count; i++) {
1415
int x = CLAMP(tagged_frame_offsets[i] * size.width / preview_len, 0, size.width);
1416
if (x == 0) {
1417
continue; // Because some may always return 0, ignore offset 0.
1418
}
1419
stream_preview_rect->draw_rect(Rect2i(x, 0, 2, rect.size.height), accent);
1420
}
1421
}
1422
rect.position.y += rect.size.height;
1423
}
1424
1425
Ref<Texture2D> icon;
1426
Color icon_modulate(1, 1, 1, 1);
1427
1428
if (tagged_frame_offset_count > 0) {
1429
icon = get_editor_theme_icon(SNAME("Play"));
1430
if ((OS::get_singleton()->get_ticks_msec() % 500) > 250) {
1431
icon_modulate = Color(1, 0.5, 0.5, 1); // get_theme_color(SNAME("accent_color"), EditorStringName(Editor));
1432
}
1433
} else {
1434
icon = EditorNode::get_singleton()->get_object_icon(audio_stream.operator->(), "Object");
1435
}
1436
String text;
1437
if (!audio_stream->get_name().is_empty()) {
1438
text = audio_stream->get_name();
1439
} else if (audio_stream->get_path().is_resource_file()) {
1440
text = audio_stream->get_path().get_file();
1441
} else {
1442
text = audio_stream->get_class().replace_first("AudioStream", "");
1443
}
1444
1445
stream_preview_rect->draw_texture(icon, Point2i(EDSCALE * 4, rect.position.y + (rect.size.height - icon->get_height()) / 2), icon_modulate);
1446
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)));
1447
}
1448
1449
EditorAudioStreamPicker::EditorAudioStreamPicker() :
1450
EditorResourcePicker(true) {
1451
stream_preview_rect = memnew(Control);
1452
1453
stream_preview_rect->set_anchors_and_offsets_preset(PRESET_FULL_RECT);
1454
stream_preview_rect->set_offset(SIDE_TOP, 1);
1455
stream_preview_rect->set_offset(SIDE_BOTTOM, -1);
1456
stream_preview_rect->set_offset(SIDE_RIGHT, -1);
1457
stream_preview_rect->set_mouse_filter(MOUSE_FILTER_IGNORE);
1458
stream_preview_rect->connect(SceneStringName(draw), callable_mp(this, &EditorAudioStreamPicker::_preview_draw));
1459
1460
get_assign_button()->add_child(stream_preview_rect);
1461
get_assign_button()->move_child(stream_preview_rect, 0);
1462
set_process_internal(true);
1463
}
1464
1465