Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
godotengine
GitHub Repository: godotengine/godot
Path: blob/master/editor/gui/create_dialog.cpp
9903 views
1
/**************************************************************************/
2
/* create_dialog.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 "create_dialog.h"
32
33
#include "core/object/class_db.h"
34
#include "editor/editor_node.h"
35
#include "editor/editor_string_names.h"
36
#include "editor/file_system/editor_paths.h"
37
#include "editor/settings/editor_feature_profile.h"
38
#include "editor/settings/editor_settings.h"
39
#include "editor/themes/editor_scale.h"
40
41
void CreateDialog::popup_create(bool p_dont_clear, bool p_replace_mode, const String &p_current_type, const String &p_current_name) {
42
_fill_type_list();
43
44
icon_fallback = search_options->has_theme_icon(base_type, EditorStringName(EditorIcons)) ? base_type : "Object";
45
46
if (p_dont_clear) {
47
search_box->select_all();
48
} else {
49
search_box->clear();
50
}
51
52
if (p_replace_mode) {
53
search_box->set_text(p_current_type);
54
}
55
56
search_box->grab_focus();
57
_update_search();
58
59
if (p_replace_mode) {
60
set_title(vformat(TTR("Change Type of \"%s\""), p_current_name));
61
set_ok_button_text(TTR("Change"));
62
} else {
63
set_title(vformat(TTR("Create New %s"), base_type));
64
set_ok_button_text(TTR("Create"));
65
}
66
67
_load_favorites_and_history();
68
_save_and_update_favorite_list();
69
70
// Restore valid window bounds or pop up at default size.
71
Rect2 saved_size = EditorSettings::get_singleton()->get_project_metadata("dialog_bounds", "create_new_node", Rect2());
72
if (saved_size != Rect2()) {
73
popup(saved_size);
74
} else {
75
popup_centered_clamped(Size2(900, 700) * EDSCALE, 0.8);
76
}
77
}
78
79
void CreateDialog::for_inherit() {
80
allow_abstract_scripts = true;
81
}
82
83
void CreateDialog::_fill_type_list() {
84
List<StringName> complete_type_list;
85
ClassDB::get_class_list(&complete_type_list);
86
ScriptServer::get_global_class_list(&complete_type_list);
87
88
EditorData &ed = EditorNode::get_editor_data();
89
HashMap<String, DocData::ClassDoc> &class_docs_list = EditorHelp::get_doc_data()->class_list;
90
91
for (const StringName &type : complete_type_list) {
92
if (!_should_hide_type(type)) {
93
TypeInfo type_info;
94
type_info.type_name = type;
95
96
const DocData::ClassDoc *class_docs = class_docs_list.getptr(type);
97
if (class_docs) {
98
type_info.search_keywords = class_docs->keywords.split(",");
99
100
for (int i = 0; i < type_info.search_keywords.size(); i++) {
101
type_info.search_keywords.set(i, type_info.search_keywords[i].strip_edges());
102
}
103
}
104
105
type_info_list.push_back(type_info);
106
107
if (!ed.get_custom_types().has(type)) {
108
continue;
109
}
110
111
const Vector<EditorData::CustomType> &ct = ed.get_custom_types()[type];
112
for (int i = 0; i < ct.size(); i++) {
113
custom_type_parents[ct[i].name] = type;
114
custom_type_indices[ct[i].name] = i;
115
116
TypeInfo custom_type_info;
117
custom_type_info.type_name = ct[i].name;
118
type_info_list.push_back(custom_type_info);
119
}
120
}
121
}
122
123
struct TypeInfoCompare {
124
StringName::AlphCompare compare;
125
126
_FORCE_INLINE_ bool operator()(const TypeInfo &l, const TypeInfo &r) const {
127
return compare(l.type_name, r.type_name);
128
}
129
};
130
131
type_info_list.sort_custom<TypeInfoCompare>();
132
}
133
134
bool CreateDialog::_is_type_preferred(const String &p_type) const {
135
if (ClassDB::class_exists(p_type)) {
136
return ClassDB::is_parent_class(p_type, preferred_search_result_type);
137
}
138
139
return EditorNode::get_editor_data().script_class_is_parent(p_type, preferred_search_result_type);
140
}
141
142
void CreateDialog::_script_button_clicked(TreeItem *p_item, int p_column, int p_button_id, MouseButton p_mouse_button_index) {
143
if (p_mouse_button_index != MouseButton::LEFT) {
144
return;
145
}
146
// The id of opening-script button is 1.
147
if (p_button_id != 1) {
148
return;
149
}
150
151
String scr_path = ScriptServer::get_global_class_path(p_item->get_text(0));
152
Ref<Script> scr = ResourceLoader::load(scr_path, "Script");
153
ERR_FAIL_COND_MSG(scr.is_null(), vformat("Could not load the script from resource path: %s", scr_path));
154
EditorNode::get_singleton()->push_item_no_inspector(scr.ptr());
155
156
hide();
157
_cleanup();
158
}
159
160
bool CreateDialog::_is_class_disabled_by_feature_profile(const StringName &p_class) const {
161
Ref<EditorFeatureProfile> profile = EditorFeatureProfileManager::get_singleton()->get_current_profile();
162
163
return profile.is_valid() && profile->is_class_disabled(p_class);
164
}
165
166
bool CreateDialog::_should_hide_type(const StringName &p_type) const {
167
if (_is_class_disabled_by_feature_profile(p_type)) {
168
return true;
169
}
170
171
if (is_base_type_node && p_type.operator String().begins_with("Editor")) {
172
return true; // Do not show editor nodes.
173
}
174
175
if (ClassDB::class_exists(p_type)) {
176
if (!ClassDB::can_instantiate(p_type) || ClassDB::is_virtual(p_type)) {
177
return true; // Can't create abstract or virtual class.
178
}
179
180
if (!ClassDB::is_parent_class(p_type, base_type)) {
181
return true; // Wrong inheritance.
182
}
183
184
if (!ClassDB::is_class_exposed(p_type)) {
185
return true; // Unexposed types.
186
}
187
188
for (const StringName &E : type_blacklist) {
189
if (ClassDB::is_parent_class(p_type, E)) {
190
return true; // Parent type is blacklisted.
191
}
192
}
193
for (const StringName &F : custom_type_blocklist) {
194
if (ClassDB::is_parent_class(p_type, F)) {
195
return true; // Parent type is excluded in custom type blocklist.
196
}
197
}
198
} else {
199
if (!ScriptServer::is_global_class(p_type)) {
200
return true;
201
}
202
if (!EditorNode::get_editor_data().script_class_is_parent(p_type, base_type)) {
203
return true; // Wrong inheritance.
204
}
205
206
StringName native_type = ScriptServer::get_global_class_native_base(p_type);
207
if (ClassDB::class_exists(native_type)) {
208
if (!ClassDB::can_instantiate(native_type)) {
209
return true;
210
} else if (custom_type_blocklist.has(p_type) || custom_type_blocklist.has(native_type)) {
211
return true;
212
}
213
}
214
215
String script_path = ScriptServer::get_global_class_path(p_type);
216
if (script_path.begins_with("res://addons/")) {
217
int i = script_path.find_char('/', 13); // 13 is length of "res://addons/".
218
while (i > -1) {
219
const String plugin_path = script_path.substr(0, i).path_join("plugin.cfg");
220
if (FileAccess::exists(plugin_path)) {
221
return !EditorNode::get_singleton()->is_addon_plugin_enabled(plugin_path);
222
}
223
i = script_path.find_char('/', i + 1);
224
}
225
}
226
// Abstract scripts cannot be instantiated.
227
String path = ScriptServer::get_global_class_path(p_type);
228
Ref<Script> scr = ResourceLoader::load(path, "Script");
229
return scr.is_null() || (!allow_abstract_scripts && scr->is_abstract());
230
}
231
232
return false;
233
}
234
235
void CreateDialog::_update_search() {
236
search_options->clear();
237
search_options_types.clear();
238
239
TreeItem *root = search_options->create_item();
240
root->set_text(0, base_type);
241
root->set_icon(0, search_options->get_editor_theme_icon(icon_fallback));
242
search_options_types[base_type] = root;
243
_configure_search_option_item(root, base_type, ClassDB::class_exists(base_type) ? TypeCategory::CPP_TYPE : TypeCategory::OTHER_TYPE, "");
244
245
const String search_text = search_box->get_text();
246
247
float highest_score = 0.0f;
248
StringName best_match;
249
250
for (const TypeInfo &candidate : type_info_list) {
251
String match_keyword;
252
253
// First check if the name matches. If it does not, try the search keywords.
254
float score = _score_type(candidate.type_name, search_text);
255
if (score < 0.0f) {
256
for (const String &keyword : candidate.search_keywords) {
257
score = _score_type(keyword, search_text);
258
259
// Reduce the score of keywords, since they are an indirect match.
260
score *= 0.1f;
261
262
if (score >= 0.0f) {
263
match_keyword = keyword;
264
break;
265
}
266
}
267
}
268
269
// Search did not match.
270
if (score < 0.0f) {
271
continue;
272
}
273
274
_add_type(candidate.type_name, ClassDB::class_exists(candidate.type_name) ? TypeCategory::CPP_TYPE : TypeCategory::OTHER_TYPE, match_keyword);
275
276
if (score > highest_score) {
277
highest_score = score;
278
best_match = candidate.type_name;
279
}
280
}
281
282
// Select the best result.
283
if (search_text.is_empty()) {
284
select_type(base_type);
285
} else if (best_match != StringName()) {
286
select_type(best_match);
287
} else {
288
favorite->set_disabled(true);
289
help_bit->set_custom_text(String(), String(), vformat(TTR("No results for \"%s\"."), search_text.replace("[", "[lb]")));
290
get_ok_button()->set_disabled(true);
291
search_options->deselect_all();
292
}
293
}
294
295
void CreateDialog::_add_type(const StringName &p_type, TypeCategory p_type_category, const String &p_match_keyword) {
296
if (search_options_types.has(p_type)) {
297
return;
298
}
299
300
TypeCategory inherited_type = TypeCategory::OTHER_TYPE;
301
302
StringName inherits;
303
if (p_type_category == TypeCategory::CPP_TYPE) {
304
inherits = ClassDB::get_parent_class(p_type);
305
inherited_type = TypeCategory::CPP_TYPE;
306
} else {
307
if (p_type_category == TypeCategory::PATH_TYPE) {
308
ERR_FAIL_COND(!ResourceLoader::exists(p_type, "Script"));
309
Ref<Script> scr = ResourceLoader::load(p_type, "Script");
310
ERR_FAIL_COND(scr.is_null());
311
312
Ref<Script> base = scr->get_base_script();
313
if (base.is_null()) {
314
// Must be a native base type.
315
StringName extends = scr->get_instance_base_type();
316
if (extends == StringName()) {
317
// Not a valid script (has compile errors), we therefore ignore it as it can not be instantiated anyway (when selected).
318
return;
319
}
320
321
inherits = extends;
322
inherited_type = TypeCategory::CPP_TYPE;
323
} else {
324
inherits = base->get_global_name();
325
326
if (inherits == StringName()) {
327
inherits = base->get_path();
328
inherited_type = TypeCategory::PATH_TYPE;
329
}
330
}
331
} else if (ScriptServer::is_global_class(p_type)) {
332
inherits = ScriptServer::get_global_class_base(p_type);
333
bool is_native_class = ClassDB::class_exists(inherits);
334
inherited_type = is_native_class ? TypeCategory::CPP_TYPE : TypeCategory::OTHER_TYPE;
335
} else {
336
inherits = custom_type_parents[p_type];
337
if (ClassDB::class_exists(inherits)) {
338
inherited_type = TypeCategory::CPP_TYPE;
339
}
340
}
341
}
342
343
// Should never happen, but just in case...
344
ERR_FAIL_COND(inherits == StringName());
345
346
_add_type(inherits, inherited_type, "");
347
348
TreeItem *item = search_options->create_item(search_options_types[inherits]);
349
search_options_types[p_type] = item;
350
_configure_search_option_item(item, p_type, p_type_category, p_match_keyword);
351
}
352
353
void CreateDialog::_configure_search_option_item(TreeItem *r_item, const StringName &p_type, TypeCategory p_type_category, const String &p_match_keyword) {
354
bool script_type = ScriptServer::is_global_class(p_type);
355
bool is_abstract = false;
356
bool is_custom_type = false;
357
String type_name;
358
String text;
359
if (p_type_category == TypeCategory::CPP_TYPE) {
360
type_name = p_type;
361
text = p_type;
362
} else if (p_type_category == TypeCategory::PATH_TYPE) {
363
type_name = "\"" + p_type + "\"";
364
text = "\"" + p_type + "\"";
365
} else if (script_type) {
366
is_custom_type = true;
367
type_name = p_type;
368
text = p_type;
369
370
if (!allow_abstract_scripts) {
371
is_abstract = ScriptServer::is_global_class_abstract(p_type);
372
}
373
374
String tooltip = TTR("Script path: %s");
375
bool is_tool = ScriptServer::is_global_class_tool(p_type);
376
if (is_tool) {
377
tooltip = TTR("The script will run in the editor.") + "\n" + tooltip;
378
}
379
r_item->add_button(0, get_editor_theme_icon(SNAME("Script")), 1, false, vformat(tooltip, ScriptServer::get_global_class_path(p_type)));
380
if (is_tool) {
381
int button_index = r_item->get_button_count(0) - 1;
382
r_item->set_button_color(0, button_index, get_theme_color(SNAME("accent_color"), EditorStringName(Editor)));
383
}
384
} else {
385
is_custom_type = true;
386
type_name = custom_type_parents[p_type];
387
text = p_type;
388
}
389
390
if (!p_match_keyword.is_empty()) {
391
text += " - " + TTR(vformat("Matches the \"%s\" keyword.", p_match_keyword));
392
}
393
r_item->set_text(0, text);
394
395
Array meta;
396
meta.append(is_custom_type);
397
meta.append(type_name);
398
r_item->set_metadata(0, meta);
399
400
bool can_instantiate = (p_type_category == TypeCategory::CPP_TYPE && ClassDB::can_instantiate(p_type)) ||
401
(p_type_category == TypeCategory::OTHER_TYPE && !(!allow_abstract_scripts && is_abstract));
402
bool instantiable = can_instantiate && !(ClassDB::class_exists(p_type) && ClassDB::is_virtual(p_type));
403
404
r_item->set_meta(SNAME("__instantiable"), instantiable);
405
406
r_item->set_icon(0, EditorNode::get_singleton()->get_class_icon(p_type));
407
if (!instantiable) {
408
r_item->set_custom_color(0, search_options->get_theme_color(SNAME("font_disabled_color"), EditorStringName(Editor)));
409
}
410
411
HashMap<String, DocData::ClassDoc>::Iterator class_doc = EditorHelp::get_doc_data()->class_list.find(p_type);
412
413
bool is_deprecated = (class_doc && class_doc->value.is_deprecated);
414
bool is_experimental = (class_doc && class_doc->value.is_experimental);
415
416
if (is_deprecated) {
417
r_item->add_button(0, get_editor_theme_icon("StatusError"), 0, false, TTR("This class is marked as deprecated."));
418
} else if (is_experimental) {
419
r_item->add_button(0, get_editor_theme_icon("NodeWarning"), 0, false, TTR("This class is marked as experimental."));
420
}
421
422
if (!search_box->get_text().is_empty()) {
423
r_item->set_collapsed(false);
424
} else {
425
// Don't collapse the root node or an abstract node on the first tree level.
426
bool should_collapse = p_type != base_type && (r_item->get_parent()->get_text(0) != base_type || can_instantiate);
427
428
if (should_collapse && bool(EDITOR_GET("docks/scene_tree/start_create_dialog_fully_expanded"))) {
429
should_collapse = false; // Collapse all nodes anyway.
430
}
431
r_item->set_collapsed(should_collapse);
432
}
433
434
const String &description = DTR(class_doc ? class_doc->value.brief_description : "");
435
r_item->set_tooltip_text(0, description);
436
437
if (p_type_category == TypeCategory::OTHER_TYPE && !script_type) {
438
Ref<Texture2D> icon = EditorNode::get_editor_data().get_custom_types()[custom_type_parents[p_type]][custom_type_indices[p_type]].icon;
439
if (icon.is_valid()) {
440
r_item->set_icon(0, icon);
441
}
442
}
443
}
444
445
float CreateDialog::_score_type(const String &p_type, const String &p_search) const {
446
if (p_search.is_empty()) {
447
return 0.0f;
448
}
449
450
// Determine the best match for a non-empty search.
451
if (!p_search.is_subsequence_ofn(p_type)) {
452
return -1.0f;
453
}
454
455
if (p_type == p_search) {
456
// Always favor an exact match (case-sensitive), since clicking a favorite will set the search text to the type.
457
return 1.0f;
458
}
459
460
const String &type_name = p_type.get_slicec(' ', 0);
461
462
float inverse_length = 1.f / float(type_name.length());
463
464
// Favor types where search term is a substring close to the start of the type.
465
float w = 0.5f;
466
int pos = type_name.findn(p_search);
467
float score = (pos > -1) ? 1.0f - w * MIN(1, 3 * pos * inverse_length) : MAX(0.f, .9f - w);
468
469
// Favor shorter items: they resemble the search term more.
470
w = 0.9f;
471
score *= (1 - w) + w * MIN(1.0f, p_search.length() * inverse_length);
472
473
score *= _is_type_preferred(type_name) ? 1.0f : 0.9f;
474
475
// Add score for being a favorite type.
476
score *= favorite_list.has(type_name) ? 1.0f : 0.8f;
477
478
// Look through at most 5 recent items
479
bool in_recent = false;
480
constexpr int RECENT_COMPLETION_SIZE = 5;
481
for (int i = 0; i < MIN(RECENT_COMPLETION_SIZE - 1, recent->get_item_count()); i++) {
482
if (recent->get_item_text(i) == type_name) {
483
in_recent = true;
484
break;
485
}
486
}
487
score *= in_recent ? 1.0f : 0.9f;
488
489
return score;
490
}
491
492
void CreateDialog::_cleanup() {
493
type_info_list.clear();
494
favorite_list.clear();
495
favorites->clear();
496
recent->clear();
497
custom_type_parents.clear();
498
custom_type_indices.clear();
499
}
500
501
void CreateDialog::_confirmed() {
502
String selected_item = get_selected_type();
503
if (selected_item.is_empty()) {
504
return;
505
}
506
507
TreeItem *selected = search_options->get_selected();
508
if (!selected->get_meta("__instantiable", true)) {
509
return;
510
}
511
512
{
513
Ref<FileAccess> f = FileAccess::open(EditorPaths::get_singleton()->get_project_settings_dir().path_join("create_recent." + base_type), FileAccess::WRITE);
514
if (f.is_valid()) {
515
f->store_line(selected_item);
516
517
constexpr int RECENT_HISTORY_SIZE = 15;
518
for (int i = 0; i < MIN(RECENT_HISTORY_SIZE - 1, recent->get_item_count()); i++) {
519
if (recent->get_item_text(i) != selected_item) {
520
f->store_line(recent->get_item_text(i));
521
}
522
}
523
}
524
}
525
526
// To prevent, emitting an error from the transient window (shader dialog for example) hide this dialog before emitting the "create" signal.
527
hide();
528
529
emit_signal(SNAME("create"));
530
_cleanup();
531
}
532
533
void CreateDialog::_text_changed(const String &p_newtext) {
534
_update_search();
535
}
536
537
void CreateDialog::_sbox_input(const Ref<InputEvent> &p_event) {
538
// Redirect navigational key events to the tree.
539
Ref<InputEventKey> key = p_event;
540
if (key.is_valid()) {
541
if (key->is_action("ui_up", true) || key->is_action("ui_down", true) || key->is_action("ui_page_up") || key->is_action("ui_page_down")) {
542
search_options->gui_input(key);
543
search_box->accept_event();
544
} else if (key->is_action_pressed("ui_select", true)) {
545
TreeItem *ti = search_options->get_selected();
546
if (ti) {
547
ti->set_collapsed(!ti->is_collapsed());
548
}
549
search_box->accept_event();
550
}
551
}
552
}
553
554
void CreateDialog::_notification(int p_what) {
555
switch (p_what) {
556
case NOTIFICATION_ENTER_TREE: {
557
connect(SceneStringName(confirmed), callable_mp(this, &CreateDialog::_confirmed));
558
} break;
559
560
case NOTIFICATION_EXIT_TREE: {
561
disconnect(SceneStringName(confirmed), callable_mp(this, &CreateDialog::_confirmed));
562
} break;
563
564
case NOTIFICATION_VISIBILITY_CHANGED: {
565
if (is_visible()) {
566
callable_mp((Control *)search_box, &Control::grab_focus).call_deferred(); // Still not visible.
567
search_box->select_all();
568
} else {
569
EditorSettings::get_singleton()->set_project_metadata("dialog_bounds", "create_new_node", Rect2(get_position(), get_size()));
570
}
571
} break;
572
573
case NOTIFICATION_THEME_CHANGED: {
574
const int icon_width = get_theme_constant(SNAME("class_icon_size"), EditorStringName(Editor));
575
search_options->add_theme_constant_override("icon_max_width", icon_width);
576
favorites->add_theme_constant_override("icon_max_width", icon_width);
577
recent->set_fixed_icon_size(Size2(icon_width, icon_width));
578
579
search_box->set_right_icon(get_editor_theme_icon(SNAME("Search")));
580
favorite->set_button_icon(get_editor_theme_icon(SNAME("Favorites")));
581
} break;
582
}
583
}
584
585
void CreateDialog::select_type(const String &p_type, bool p_center_on_item) {
586
if (!search_options_types.has(p_type)) {
587
return;
588
}
589
590
TreeItem *to_select = search_options_types[p_type];
591
to_select->select(0);
592
search_options->scroll_to_item(to_select, p_center_on_item);
593
594
help_bit->parse_symbol("class|" + p_type + "|");
595
596
favorite->set_disabled(false);
597
favorite->set_pressed(favorite_list.has(p_type));
598
599
if (to_select->get_meta("__instantiable", true)) {
600
get_ok_button()->set_disabled(false);
601
get_ok_button()->set_tooltip_text(String());
602
} else {
603
get_ok_button()->set_disabled(true);
604
get_ok_button()->set_tooltip_text(TTR("The selected class can't be instantiated."));
605
}
606
}
607
608
void CreateDialog::select_base() {
609
if (search_options_types.is_empty()) {
610
_update_search();
611
}
612
select_type(base_type, false);
613
}
614
615
String CreateDialog::get_selected_type() {
616
TreeItem *selected = search_options->get_selected();
617
if (!selected) {
618
return String();
619
}
620
621
return selected->get_text(0);
622
}
623
624
void CreateDialog::set_base_type(const String &p_base) {
625
base_type = p_base;
626
is_base_type_node = ClassDB::is_parent_class(p_base, "Node");
627
}
628
629
Variant CreateDialog::instantiate_selected() {
630
TreeItem *selected = search_options->get_selected();
631
632
if (!selected) {
633
return Variant();
634
}
635
636
Array meta = selected->get_metadata(0).operator Array();
637
ERR_FAIL_COND_V(meta.size() != 2, Variant());
638
639
bool is_custom_type = meta[0].operator bool();
640
String type_name = meta[1].operator String();
641
Variant obj;
642
if (is_custom_type) {
643
if (ScriptServer::is_global_class(type_name)) {
644
obj = EditorNode::get_editor_data().script_class_instance(type_name);
645
Node *n = Object::cast_to<Node>(obj);
646
if (n) {
647
n->set_name(type_name);
648
}
649
} else {
650
obj = EditorNode::get_editor_data().instantiate_custom_type(selected->get_text(0), type_name);
651
}
652
} else {
653
obj = ClassDB::instantiate(type_name);
654
}
655
EditorNode::get_editor_data().instantiate_object_properties(obj);
656
657
return obj;
658
}
659
660
void CreateDialog::_item_selected() {
661
String name = get_selected_type();
662
select_type(name, false);
663
}
664
665
void CreateDialog::_hide_requested() {
666
_cancel_pressed(); // From AcceptDialog.
667
}
668
669
void CreateDialog::cancel_pressed() {
670
_cleanup();
671
}
672
673
void CreateDialog::_favorite_toggled() {
674
TreeItem *item = search_options->get_selected();
675
if (!item) {
676
return;
677
}
678
679
String name = item->get_text(0);
680
681
if (favorite_list.has(name)) {
682
favorite_list.erase(name);
683
favorite->set_pressed(false);
684
} else {
685
favorite_list.push_back(name);
686
favorite->set_pressed(true);
687
}
688
689
_save_and_update_favorite_list();
690
}
691
692
void CreateDialog::_history_selected(int p_idx) {
693
search_box->set_text(recent->get_item_text(p_idx).get_slicec(' ', 0));
694
favorites->deselect_all();
695
_update_search();
696
}
697
698
void CreateDialog::_favorite_selected() {
699
TreeItem *item = favorites->get_selected();
700
if (!item) {
701
return;
702
}
703
704
search_box->set_text(item->get_text(0).get_slicec(' ', 0));
705
recent->deselect_all();
706
_update_search();
707
}
708
709
void CreateDialog::_history_activated(int p_idx) {
710
_history_selected(p_idx);
711
_confirmed();
712
}
713
714
void CreateDialog::_favorite_activated() {
715
_favorite_selected();
716
_confirmed();
717
}
718
719
Variant CreateDialog::get_drag_data_fw(const Point2 &p_point, Control *p_from) {
720
TreeItem *ti = (p_point == Vector2(Math::INF, Math::INF)) ? favorites->get_selected() : favorites->get_item_at_position(p_point);
721
if (ti) {
722
Dictionary d;
723
d["type"] = "create_favorite_drag";
724
d["class"] = ti->get_text(0);
725
726
Button *tb = memnew(Button);
727
tb->set_flat(true);
728
tb->set_button_icon(ti->get_icon(0));
729
tb->set_text(ti->get_text(0));
730
tb->set_auto_translate_mode(AUTO_TRANSLATE_MODE_DISABLED);
731
favorites->set_drag_preview(tb);
732
733
return d;
734
}
735
736
return Variant();
737
}
738
739
bool CreateDialog::can_drop_data_fw(const Point2 &p_point, const Variant &p_data, Control *p_from) const {
740
Dictionary d = p_data;
741
if (d.has("type") && String(d["type"]) == "create_favorite_drag") {
742
favorites->set_drop_mode_flags(Tree::DROP_MODE_INBETWEEN);
743
return true;
744
}
745
746
return false;
747
}
748
749
void CreateDialog::drop_data_fw(const Point2 &p_point, const Variant &p_data, Control *p_from) {
750
Dictionary d = p_data;
751
752
TreeItem *ti = (p_point == Vector2(Math::INF, Math::INF)) ? favorites->get_selected() : favorites->get_item_at_position(p_point);
753
if (!ti) {
754
return;
755
}
756
757
String drop_at = ti->get_text(0);
758
int ds = (p_point == Vector2(Math::INF, Math::INF)) ? favorites->get_drop_section_at_position(favorites->get_item_rect(ti).position) : favorites->get_drop_section_at_position(p_point);
759
760
int drop_idx = favorite_list.find(drop_at);
761
if (drop_idx < 0) {
762
return;
763
}
764
765
String type = d["class"];
766
767
int from_idx = favorite_list.find(type);
768
if (from_idx < 0) {
769
return;
770
}
771
772
if (drop_idx == from_idx) {
773
ds = -1; //cause it will be gone
774
} else if (drop_idx > from_idx) {
775
drop_idx--;
776
}
777
778
favorite_list.remove_at(from_idx);
779
780
if (ds < 0) {
781
favorite_list.insert(drop_idx, type);
782
} else {
783
if (drop_idx >= favorite_list.size() - 1) {
784
favorite_list.push_back(type);
785
} else {
786
favorite_list.insert(drop_idx + 1, type);
787
}
788
}
789
790
_save_and_update_favorite_list();
791
}
792
793
void CreateDialog::_save_and_update_favorite_list() {
794
favorites->clear();
795
TreeItem *root = favorites->create_item();
796
797
{
798
Ref<FileAccess> f = FileAccess::open(EditorPaths::get_singleton()->get_project_settings_dir().path_join("favorites." + base_type), FileAccess::WRITE);
799
if (f.is_valid()) {
800
for (int i = 0; i < favorite_list.size(); i++) {
801
String l = favorite_list[i];
802
String name = l.get_slicec(' ', 0);
803
if (!EditorNode::get_editor_data().is_type_recognized(name)) {
804
continue;
805
}
806
f->store_line(l);
807
808
if (_is_class_disabled_by_feature_profile(name)) {
809
continue;
810
}
811
812
TreeItem *ti = favorites->create_item(root);
813
ti->set_text(0, l);
814
ti->set_icon(0, EditorNode::get_singleton()->get_class_icon(name));
815
}
816
}
817
}
818
819
emit_signal(SNAME("favorites_updated"));
820
}
821
822
void CreateDialog::_load_favorites_and_history() {
823
String dir = EditorPaths::get_singleton()->get_project_settings_dir();
824
Ref<FileAccess> f = FileAccess::open(dir.path_join("create_recent." + base_type), FileAccess::READ);
825
if (f.is_valid()) {
826
while (!f->eof_reached()) {
827
String l = f->get_line().strip_edges();
828
String name = l.get_slicec(' ', 0);
829
830
if (EditorNode::get_editor_data().is_type_recognized(name) && !_is_class_disabled_by_feature_profile(name)) {
831
recent->add_item(l, EditorNode::get_singleton()->get_class_icon(name));
832
}
833
}
834
}
835
836
f = FileAccess::open(dir.path_join("favorites." + base_type), FileAccess::READ);
837
if (f.is_valid()) {
838
while (!f->eof_reached()) {
839
String l = f->get_line().strip_edges();
840
841
if (!l.is_empty()) {
842
favorite_list.push_back(l);
843
}
844
}
845
}
846
}
847
848
void CreateDialog::_bind_methods() {
849
ADD_SIGNAL(MethodInfo("create"));
850
ADD_SIGNAL(MethodInfo("favorites_updated"));
851
}
852
853
CreateDialog::CreateDialog() {
854
base_type = "Object";
855
preferred_search_result_type = "";
856
857
type_blacklist.insert("PluginScript"); // PluginScript must be initialized before use, which is not possible here.
858
type_blacklist.insert("ScriptCreateDialog"); // This is an exposed editor Node that doesn't have an Editor prefix.
859
860
HSplitContainer *hsc = memnew(HSplitContainer);
861
add_child(hsc);
862
863
VSplitContainer *vsc = memnew(VSplitContainer);
864
hsc->add_child(vsc);
865
866
VBoxContainer *fav_vb = memnew(VBoxContainer);
867
fav_vb->set_custom_minimum_size(Size2(150, 100) * EDSCALE);
868
fav_vb->set_v_size_flags(Control::SIZE_EXPAND_FILL);
869
vsc->add_child(fav_vb);
870
871
favorites = memnew(Tree);
872
favorites->set_accessibility_name(TTRC("Favorites:"));
873
favorites->set_auto_translate_mode(AUTO_TRANSLATE_MODE_DISABLED);
874
favorites->set_hide_root(true);
875
favorites->set_hide_folding(true);
876
favorites->set_allow_reselect(true);
877
favorites->connect("cell_selected", callable_mp(this, &CreateDialog::_favorite_selected));
878
favorites->connect("item_activated", callable_mp(this, &CreateDialog::_favorite_activated));
879
favorites->add_theme_constant_override("draw_guides", 1);
880
favorites->set_theme_type_variation("TreeSecondary");
881
SET_DRAG_FORWARDING_GCD(favorites, CreateDialog);
882
fav_vb->add_margin_child(TTR("Favorites:"), favorites, true);
883
884
VBoxContainer *rec_vb = memnew(VBoxContainer);
885
vsc->add_child(rec_vb);
886
rec_vb->set_custom_minimum_size(Size2(150, 100) * EDSCALE);
887
rec_vb->set_v_size_flags(Control::SIZE_EXPAND_FILL);
888
889
recent = memnew(ItemList);
890
recent->set_accessibility_name(TTRC("Recent:"));
891
recent->set_auto_translate_mode(AUTO_TRANSLATE_MODE_DISABLED);
892
rec_vb->add_margin_child(TTR("Recent:"), recent, true);
893
recent->set_allow_reselect(true);
894
recent->connect(SceneStringName(item_selected), callable_mp(this, &CreateDialog::_history_selected));
895
recent->connect("item_activated", callable_mp(this, &CreateDialog::_history_activated));
896
recent->add_theme_constant_override("draw_guides", 1);
897
recent->set_theme_type_variation("ItemListSecondary");
898
899
VBoxContainer *vbc = memnew(VBoxContainer);
900
vbc->set_custom_minimum_size(Size2(300, 0) * EDSCALE);
901
vbc->set_h_size_flags(Control::SIZE_EXPAND_FILL);
902
hsc->add_child(vbc);
903
904
search_box = memnew(LineEdit);
905
search_box->set_accessibility_name(TTRC("Search"));
906
search_box->set_clear_button_enabled(true);
907
search_box->set_h_size_flags(Control::SIZE_EXPAND_FILL);
908
search_box->connect(SceneStringName(text_changed), callable_mp(this, &CreateDialog::_text_changed));
909
search_box->connect(SceneStringName(gui_input), callable_mp(this, &CreateDialog::_sbox_input));
910
911
HBoxContainer *search_hb = memnew(HBoxContainer);
912
search_hb->add_child(search_box);
913
914
favorite = memnew(Button);
915
favorite->set_toggle_mode(true);
916
favorite->set_tooltip_text(TTR("(Un)favorite selected item."));
917
favorite->connect(SceneStringName(pressed), callable_mp(this, &CreateDialog::_favorite_toggled));
918
search_hb->add_child(favorite);
919
vbc->add_margin_child(TTR("Search:"), search_hb);
920
921
search_options = memnew(Tree);
922
search_options->set_accessibility_name(TTRC("Matches:"));
923
search_options->set_auto_translate_mode(AUTO_TRANSLATE_MODE_DISABLED);
924
search_options->connect("item_activated", callable_mp(this, &CreateDialog::_confirmed));
925
search_options->connect("cell_selected", callable_mp(this, &CreateDialog::_item_selected));
926
search_options->connect("button_clicked", callable_mp(this, &CreateDialog::_script_button_clicked));
927
vbc->add_margin_child(TTR("Matches:"), search_options, true);
928
929
help_bit = memnew(EditorHelpBit);
930
help_bit->set_accessibility_name(TTRC("Description:"));
931
help_bit->set_content_height_limits(80 * EDSCALE, 80 * EDSCALE);
932
help_bit->connect("request_hide", callable_mp(this, &CreateDialog::_hide_requested));
933
vbc->add_margin_child(TTR("Description:"), help_bit);
934
935
register_text_enter(search_box);
936
set_hide_on_ok(false);
937
set_clamp_to_embedder(true);
938
}
939
940