Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
godotengine
GitHub Repository: godotengine/godot
Path: blob/master/editor/gui/editor_quick_open_dialog.cpp
20941 views
1
/**************************************************************************/
2
/* editor_quick_open_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 "editor_quick_open_dialog.h"
32
33
#include "core/config/project_settings.h"
34
#include "core/string/fuzzy_search.h"
35
#include "editor/docks/filesystem_dock.h"
36
#include "editor/editor_node.h"
37
#include "editor/editor_string_names.h"
38
#include "editor/editor_undo_redo_manager.h"
39
#include "editor/file_system/editor_file_system.h"
40
#include "editor/file_system/editor_paths.h"
41
#include "editor/gui/editor_toaster.h"
42
#include "editor/inspector/editor_resource_preview.h"
43
#include "editor/inspector/multi_node_edit.h"
44
#include "editor/settings/editor_settings.h"
45
#include "editor/themes/editor_scale.h"
46
#include "scene/gui/center_container.h"
47
#include "scene/gui/check_button.h"
48
#include "scene/gui/flow_container.h"
49
#include "scene/gui/line_edit.h"
50
#include "scene/gui/margin_container.h"
51
#include "scene/gui/panel_container.h"
52
#include "scene/gui/separator.h"
53
#include "scene/gui/texture_rect.h"
54
#include "scene/gui/tree.h"
55
56
void HighlightedLabel::draw_substr_rects(const Vector2i &p_substr, Vector2 p_offset, int p_line_limit, int line_spacing) {
57
for (int i = get_lines_skipped(); i < p_line_limit; i++) {
58
RID line = get_line_rid(i);
59
Vector<Vector2> ranges = TS->shaped_text_get_selection(line, p_substr.x, p_substr.x + p_substr.y);
60
Rect2 line_rect = get_line_rect(i);
61
for (const Vector2 &range : ranges) {
62
Rect2 rect = Rect2(Point2(range.x, 0) + line_rect.position, Size2(range.y - range.x, line_rect.size.y));
63
rect.position = p_offset + line_rect.position;
64
rect.position.x += range.x;
65
rect.size = Size2(range.y - range.x, line_rect.size.y);
66
rect.size.x = MIN(rect.size.x, line_rect.size.x - range.x);
67
if (rect.size.x > 0) {
68
draw_rect(rect, Color(1, 1, 1, 0.07), true);
69
draw_rect(rect, Color(0.5, 0.7, 1.0, 0.4), false, 1);
70
}
71
}
72
p_offset.y += line_spacing + TS->shaped_text_get_ascent(line) + TS->shaped_text_get_descent(line);
73
}
74
}
75
76
void HighlightedLabel::add_highlight(const Vector2i &p_interval) {
77
if (p_interval.y > 0) {
78
highlights.append(p_interval);
79
queue_redraw();
80
}
81
}
82
83
void HighlightedLabel::reset_highlights() {
84
highlights.clear();
85
queue_redraw();
86
}
87
88
void HighlightedLabel::_notification(int p_notification) {
89
if (p_notification == NOTIFICATION_DRAW) {
90
if (highlights.is_empty()) {
91
return;
92
}
93
94
Vector2 offset;
95
int line_limit;
96
int line_spacing;
97
get_layout_data(offset, line_limit, line_spacing);
98
99
for (const Vector2i &substr : highlights) {
100
draw_substr_rects(substr, offset, line_limit, line_spacing);
101
}
102
}
103
}
104
105
EditorQuickOpenDialog::EditorQuickOpenDialog() {
106
VBoxContainer *vbc = memnew(VBoxContainer);
107
vbc->add_theme_constant_override("separation", 0);
108
add_child(vbc);
109
110
{
111
// Search bar
112
MarginContainer *mc = memnew(MarginContainer);
113
mc->add_theme_constant_override("margin_top", 6);
114
mc->add_theme_constant_override("margin_bottom", 6);
115
mc->add_theme_constant_override("margin_left", 1);
116
mc->add_theme_constant_override("margin_right", 1);
117
vbc->add_child(mc);
118
119
search_box = memnew(LineEdit);
120
search_box->set_h_size_flags(Control::SIZE_EXPAND_FILL);
121
search_box->set_placeholder(TTR("Search files..."));
122
search_box->set_accessibility_name(TTRC("Search"));
123
search_box->set_clear_button_enabled(true);
124
mc->add_child(search_box);
125
}
126
127
{
128
container = memnew(QuickOpenResultContainer);
129
container->connect("selection_changed", callable_mp(this, &EditorQuickOpenDialog::selection_changed));
130
container->connect("result_clicked", callable_mp(this, &EditorQuickOpenDialog::item_pressed));
131
vbc->add_child(container);
132
}
133
134
search_box->connect(SceneStringName(text_changed), callable_mp(this, &EditorQuickOpenDialog::_search_box_text_changed));
135
search_box->connect(SceneStringName(gui_input), callable_mp(container, &QuickOpenResultContainer::handle_search_box_input));
136
register_text_enter(search_box);
137
get_ok_button()->hide();
138
}
139
140
String EditorQuickOpenDialog::get_dialog_title(const Vector<StringName> &p_base_types) {
141
if (p_base_types.size() > 1) {
142
return TTR("Select Resource");
143
}
144
145
if (p_base_types[0] == SNAME("PackedScene")) {
146
return TTR("Select Scene");
147
}
148
149
return TTR("Select") + " " + p_base_types[0];
150
}
151
152
void EditorQuickOpenDialog::popup_dialog(const Vector<StringName> &p_base_types, const Callable &p_item_selected_callback) {
153
ERR_FAIL_COND(p_base_types.is_empty());
154
ERR_FAIL_COND(!p_item_selected_callback.is_valid());
155
156
property_object = nullptr;
157
property_path = "";
158
item_selected_callback = p_item_selected_callback;
159
160
container->init(p_base_types);
161
container->set_instant_preview_toggle_visible(false);
162
_finish_dialog_setup(p_base_types);
163
}
164
165
void EditorQuickOpenDialog::popup_dialog_for_property(const Vector<StringName> &p_base_types, Object *p_obj, const StringName &p_path, const Callable &p_item_selected_callback) {
166
ERR_FAIL_NULL(p_obj);
167
ERR_FAIL_COND(p_base_types.is_empty());
168
ERR_FAIL_COND(!p_item_selected_callback.is_valid());
169
170
property_object = p_obj;
171
property_path = p_path;
172
item_selected_callback = p_item_selected_callback;
173
initial_property_value = property_object->get(property_path);
174
175
// Reset this, so that the property isn't updated immediately upon opening
176
// the window.
177
initial_selection_performed = false;
178
179
container->init(p_base_types);
180
container->set_instant_preview_toggle_visible(true);
181
_finish_dialog_setup(p_base_types);
182
}
183
184
void EditorQuickOpenDialog::_finish_dialog_setup(const Vector<StringName> &p_base_types) {
185
get_ok_button()->set_disabled(container->has_nothing_selected());
186
set_title(get_dialog_title(p_base_types));
187
popup_centered_clamped(Size2(780, 650) * EDSCALE, 0.8f);
188
search_box->grab_focus();
189
}
190
191
void EditorQuickOpenDialog::ok_pressed() {
192
container->save_selected_item();
193
194
update_property();
195
container->cleanup();
196
search_box->clear();
197
hide();
198
}
199
200
bool EditorQuickOpenDialog::_is_instant_preview_active() const {
201
return property_object != nullptr && container->is_instant_preview_enabled();
202
}
203
204
void EditorQuickOpenDialog::selection_changed() {
205
if (!_is_instant_preview_active()) {
206
return;
207
}
208
209
// This prevents the property from being changed the first time the Quick Open
210
// window is opened.
211
if (!initial_selection_performed) {
212
initial_selection_performed = true;
213
} else {
214
preview_property();
215
}
216
}
217
218
void EditorQuickOpenDialog::item_pressed(bool p_double_click) {
219
// A double-click should always be taken as a "confirm" action.
220
if (p_double_click) {
221
ok_pressed();
222
return;
223
}
224
225
// Single-clicks should be taken as a "confirm" action only if Instant Preview
226
// isn't currently enabled, or the property object is null for some reason.
227
if (!_is_instant_preview_active()) {
228
ok_pressed();
229
}
230
}
231
232
void EditorQuickOpenDialog::preview_property() {
233
ERR_FAIL_COND(container->get_selected() == ResourceUID::INVALID_ID);
234
String path = container->get_selected_path();
235
236
Ref<Resource> loaded_resource = ResourceLoader::load(path);
237
ERR_FAIL_COND_MSG(loaded_resource.is_null(), "Cannot load resource from path '" + path + "'.");
238
239
Resource *res = Object::cast_to<Resource>(property_object);
240
if (res) {
241
HashSet<Resource *> resources_found;
242
resources_found.insert(res);
243
if (EditorNode::find_recursive_resources(loaded_resource, resources_found)) {
244
EditorToaster::get_singleton()->popup_str(TTR("Recursion detected, Instant Preview failed."), EditorToaster::SEVERITY_ERROR);
245
loaded_resource = Ref<Resource>();
246
}
247
}
248
249
// MultiNodeEdit has adding to the undo/redo stack baked into its set function.
250
// As such, we have to specifically call a version of its setter that doesn't
251
// create undo/redo actions.
252
property_object->set_block_signals(true);
253
if (Object::cast_to<MultiNodeEdit>(property_object)) {
254
Object::cast_to<MultiNodeEdit>(property_object)->_set_impl(property_path, loaded_resource, "", false);
255
} else {
256
property_object->set(property_path, loaded_resource);
257
}
258
property_object->set_block_signals(false);
259
}
260
261
void EditorQuickOpenDialog::update_property() {
262
// Set the property back to the initial value first, so that the undo action
263
// has the correct object.
264
if (property_object) {
265
if (Object::cast_to<MultiNodeEdit>(property_object)) {
266
Object::cast_to<MultiNodeEdit>(property_object)->_set_impl(property_path, initial_property_value, "", false);
267
} else {
268
property_object->set(property_path, initial_property_value);
269
}
270
}
271
272
if (!item_selected_callback.is_valid()) {
273
String err_msg = "The callback provided to the Quick Open dialog was invalid.";
274
if (_is_instant_preview_active()) {
275
err_msg += " Try disabling \"Instant Preview\" as a workaround.";
276
}
277
ERR_FAIL_MSG(err_msg);
278
}
279
280
item_selected_callback.call(container->get_selected_path());
281
}
282
283
void EditorQuickOpenDialog::cancel_pressed() {
284
if (property_object) {
285
if (Object::cast_to<MultiNodeEdit>(property_object)) {
286
Object::cast_to<MultiNodeEdit>(property_object)->_set_impl(property_path, initial_property_value, "", false);
287
} else {
288
property_object->set(property_path, initial_property_value);
289
}
290
}
291
container->cleanup();
292
search_box->clear();
293
}
294
295
void EditorQuickOpenDialog::_search_box_text_changed(const String &p_query) {
296
container->set_query_and_update(p_query);
297
get_ok_button()->set_disabled(container->has_nothing_selected());
298
}
299
300
//------------------------- Result Container
301
302
void style_button(Button *p_button) {
303
p_button->set_flat(true);
304
p_button->set_focus_mode(Control::FOCUS_ACCESSIBILITY);
305
}
306
307
QuickOpenResultContainer::QuickOpenResultContainer() {
308
set_h_size_flags(Control::SIZE_EXPAND_FILL);
309
set_v_size_flags(Control::SIZE_EXPAND_FILL);
310
add_theme_constant_override("separation", 0);
311
history_file.instantiate();
312
313
{
314
// Results section
315
panel_container = memnew(PanelContainer);
316
panel_container->set_v_size_flags(Control::SIZE_EXPAND_FILL);
317
add_child(panel_container);
318
319
{
320
// No search results
321
no_results_container = memnew(CenterContainer);
322
no_results_container->set_h_size_flags(Control::SIZE_EXPAND_FILL);
323
no_results_container->set_v_size_flags(Control::SIZE_EXPAND_FILL);
324
panel_container->add_child(no_results_container);
325
326
no_results_label = memnew(Label);
327
no_results_label->set_focus_mode(FOCUS_ACCESSIBILITY);
328
no_results_label->add_theme_font_size_override(SceneStringName(font_size), 24 * EDSCALE);
329
no_results_container->add_child(no_results_label);
330
no_results_container->hide();
331
}
332
333
{
334
MarginContainer *mc = memnew(MarginContainer);
335
mc->set_theme_type_variation("NoBorderHorizontalWindow");
336
mc->set_h_size_flags(Control::SIZE_EXPAND_FILL);
337
mc->set_v_size_flags(Control::SIZE_EXPAND_FILL);
338
panel_container->add_child(mc);
339
340
// Search results
341
scroll_container = memnew(ScrollContainer);
342
scroll_container->set_horizontal_scroll_mode(ScrollContainer::SCROLL_MODE_DISABLED);
343
scroll_container->set_scroll_hint_mode(ScrollContainer::SCROLL_HINT_MODE_ALL);
344
scroll_container->hide();
345
panel_container->add_child(scroll_container);
346
347
list = memnew(VBoxContainer);
348
list->set_h_size_flags(Control::SIZE_EXPAND_FILL);
349
list->add_theme_constant_override(SNAME("separation"), 0);
350
list->hide();
351
scroll_container->add_child(list);
352
353
grid = memnew(HFlowContainer);
354
grid->set_h_size_flags(Control::SIZE_EXPAND_FILL);
355
grid->set_v_size_flags(Control::SIZE_EXPAND_FILL);
356
grid->add_theme_constant_override(SNAME("v_separation"), 0);
357
grid->add_theme_constant_override(SNAME("h_separation"), 0);
358
grid->hide();
359
scroll_container->add_child(grid);
360
361
file_context_menu = memnew(PopupMenu);
362
file_context_menu->add_item(TTR("Show in FileSystem"), FILE_SHOW_IN_FILESYSTEM);
363
file_context_menu->add_item(TTR("Show in File Manager"), FILE_SHOW_IN_FILE_MANAGER);
364
file_context_menu->connect(SceneStringName(id_pressed), callable_mp(this, &QuickOpenResultContainer::_menu_option));
365
file_context_menu->hide();
366
scroll_container->add_child(file_context_menu);
367
}
368
}
369
370
{
371
// Selected filepath
372
file_details_path = memnew(Label);
373
file_details_path->set_focus_mode(FOCUS_ACCESSIBILITY);
374
file_details_path->set_h_size_flags(Control::SIZE_EXPAND_FILL);
375
file_details_path->set_horizontal_alignment(HorizontalAlignment::HORIZONTAL_ALIGNMENT_CENTER);
376
file_details_path->set_text_overrun_behavior(TextServer::OVERRUN_TRIM_ELLIPSIS);
377
add_child(file_details_path);
378
}
379
380
{
381
// Bottom bar
382
HBoxContainer *bottom_bar = memnew(HBoxContainer);
383
bottom_bar->set_h_size_flags(Control::SIZE_EXPAND_FILL);
384
bottom_bar->set_alignment(ALIGNMENT_END);
385
bottom_bar->add_theme_constant_override("separation", 3);
386
add_child(bottom_bar);
387
388
instant_preview_toggle = memnew(CheckButton);
389
style_button(instant_preview_toggle);
390
instant_preview_toggle->set_text(TTRC("Instant Preview"));
391
instant_preview_toggle->set_tooltip_text(TTRC("Selected resource will be previewed in the editor before accepting."));
392
instant_preview_toggle->connect(SceneStringName(toggled), callable_mp(this, &QuickOpenResultContainer::_toggle_instant_preview));
393
bottom_bar->add_child(instant_preview_toggle);
394
395
fuzzy_search_toggle = memnew(CheckButton);
396
style_button(fuzzy_search_toggle);
397
fuzzy_search_toggle->set_text(TTR("Fuzzy Search"));
398
fuzzy_search_toggle->set_tooltip_text(TTRC("Include approximate matches."));
399
fuzzy_search_toggle->connect(SceneStringName(toggled), callable_mp(this, &QuickOpenResultContainer::_toggle_fuzzy_search));
400
bottom_bar->add_child(fuzzy_search_toggle);
401
402
include_addons_toggle = memnew(CheckButton);
403
style_button(include_addons_toggle);
404
include_addons_toggle->set_text(TTR("Addons"));
405
include_addons_toggle->set_tooltip_text(TTR("Include files from addons"));
406
include_addons_toggle->connect(SceneStringName(toggled), callable_mp(this, &QuickOpenResultContainer::_toggle_include_addons));
407
bottom_bar->add_child(include_addons_toggle);
408
409
VSeparator *vsep = memnew(VSeparator);
410
vsep->set_v_size_flags(Control::SIZE_SHRINK_CENTER);
411
vsep->set_custom_minimum_size(Size2i(0, 14 * EDSCALE));
412
bottom_bar->add_child(vsep);
413
414
display_mode_toggle = memnew(Button);
415
display_mode_toggle->set_accessibility_name(TTRC("Display Mode"));
416
style_button(display_mode_toggle);
417
display_mode_toggle->connect(SceneStringName(pressed), callable_mp(this, &QuickOpenResultContainer::_toggle_display_mode));
418
bottom_bar->add_child(display_mode_toggle);
419
}
420
}
421
422
void QuickOpenResultContainer::_menu_option(int p_option) {
423
ERR_FAIL_COND(get_selected() == ResourceUID::INVALID_ID);
424
String selected_path = get_selected_path();
425
426
switch (p_option) {
427
case FILE_SHOW_IN_FILESYSTEM: {
428
FileSystemDock::get_singleton()->navigate_to_path(selected_path);
429
} break;
430
case FILE_SHOW_IN_FILE_MANAGER: {
431
String dir = ProjectSettings::get_singleton()->globalize_path(selected_path);
432
OS::get_singleton()->shell_show_in_file_manager(dir, true);
433
} break;
434
}
435
}
436
437
void QuickOpenResultContainer::_ensure_result_vector_capacity() {
438
int target_size = EDITOR_GET("filesystem/quick_open_dialog/max_results");
439
int initial_size = result_items.size();
440
for (int i = target_size; i < initial_size; i++) {
441
result_items[i]->queue_free();
442
}
443
result_items.resize(target_size);
444
for (int i = initial_size; i < target_size; i++) {
445
QuickOpenResultItem *item = memnew(QuickOpenResultItem);
446
item->connect(SceneStringName(gui_input), callable_mp(this, &QuickOpenResultContainer::_item_input).bind(i));
447
result_items.write[i] = item;
448
if (!never_opened) {
449
_layout_result_item(item);
450
}
451
}
452
}
453
454
void QuickOpenResultContainer::init(const Vector<StringName> &p_base_types) {
455
_ensure_result_vector_capacity();
456
base_types = p_base_types;
457
458
const int display_mode_behavior = EDITOR_GET("filesystem/quick_open_dialog/default_display_mode");
459
const bool adaptive_display_mode = (display_mode_behavior == 0);
460
const bool first_open = never_opened;
461
462
if (adaptive_display_mode) {
463
_set_display_mode(get_adaptive_display_mode(p_base_types));
464
} else if (never_opened) {
465
int last = EditorSettings::get_singleton()->get_project_metadata("quick_open_dialog", "last_mode", (int)QuickOpenDisplayMode::LIST);
466
_set_display_mode((QuickOpenDisplayMode)last);
467
}
468
469
const bool do_instant_preview = EDITOR_GET("filesystem/quick_open_dialog/instant_preview");
470
const bool fuzzy_matching = EDITOR_GET("filesystem/quick_open_dialog/enable_fuzzy_matching");
471
const bool include_addons = EDITOR_GET("filesystem/quick_open_dialog/include_addons");
472
instant_preview_toggle->set_pressed_no_signal(do_instant_preview);
473
fuzzy_search_toggle->set_pressed_no_signal(fuzzy_matching);
474
include_addons_toggle->set_pressed_no_signal(include_addons);
475
never_opened = false;
476
477
const bool enable_highlights = EDITOR_GET("filesystem/quick_open_dialog/show_search_highlight");
478
for (QuickOpenResultItem *E : result_items) {
479
E->enable_highlights = enable_highlights;
480
}
481
482
bool history_modified = false;
483
484
if (first_open && history_file->load(_get_cache_file_path()) == OK) {
485
// Load history when opening for the first time.
486
file_type_icons.insert(SNAME("__default_icon"), get_editor_theme_icon(SNAME("Object")));
487
488
Vector<String> history_keys = history_file->get_section_keys("selected_history");
489
for (const String &type : history_keys) {
490
const StringName type_name = type;
491
const PackedStringArray history_uids = history_file->get_value("selected_history", type);
492
493
PackedStringArray cleaned_text_uids;
494
cleaned_text_uids.resize(history_uids.size());
495
496
Vector<ResourceUID::ID> cleaned_ids;
497
cleaned_ids.resize(history_uids.size());
498
499
{
500
String *text_write = cleaned_text_uids.ptrw();
501
ResourceUID::ID *id_write = cleaned_ids.ptrw();
502
int i = 0;
503
for (String uid : history_uids) {
504
#ifndef DISABLE_DEPRECATED
505
if (!uid.begins_with("uid://")) {
506
// uid might be a path here, if config was written by older editor version
507
ResourceUID::ID id = EditorFileSystem::get_singleton()->get_file_uid(uid);
508
if (id == ResourceUID::INVALID_ID) {
509
continue;
510
}
511
uid = ResourceUID::get_singleton()->id_to_text(id);
512
}
513
#endif
514
515
ResourceUID::ID id = ResourceUID::get_singleton()->text_to_id(uid);
516
if (id == ResourceUID::INVALID_ID || !ResourceUID::get_singleton()->has_id(id)) {
517
continue;
518
}
519
520
filetypes.insert(id, type_name);
521
text_write[i] = uid;
522
id_write[i] = id;
523
i++;
524
}
525
526
cleaned_text_uids.resize(i);
527
selected_history.insert(type, cleaned_ids);
528
529
if (i < history_uids.size()) {
530
// Some paths removed, need to update history.
531
if (i == 0) {
532
history_file->erase_section_key("selected_history", type);
533
} else {
534
history_file->set_value("selected_history", type, cleaned_text_uids);
535
}
536
history_modified = true;
537
}
538
}
539
}
540
} else if (!first_open && base_types.size() == 1) {
541
const StringName &type = base_types[0];
542
Vector<ResourceUID::ID> *history = selected_history.getptr(type);
543
544
if (history) {
545
Vector<ResourceUID::ID> clean_history;
546
547
for (const ResourceUID::ID &uid : *history) {
548
if (ResourceUID::get_singleton()->has_id(uid)) {
549
clean_history.push_back(uid);
550
} else {
551
history_modified = true;
552
}
553
}
554
555
if (clean_history.is_empty()) {
556
selected_history.erase(type);
557
} else if (history_modified) {
558
*history = clean_history;
559
}
560
}
561
}
562
563
if (history_modified) {
564
history_file->save(_get_cache_file_path());
565
}
566
567
_create_initial_results();
568
}
569
570
void QuickOpenResultContainer::_sort_uids(int p_max_results) {
571
struct FilepathComparator {
572
bool operator()(const ResourceUID::ID &p_lhs, const ResourceUID::ID &p_rhs) const {
573
String lhs_path = ResourceUID::get_singleton()->get_id_path(p_lhs);
574
String rhs_path = ResourceUID::get_singleton()->get_id_path(p_rhs);
575
576
// Sort on (length, alphanumeric) to prioritize shorter filepaths
577
return lhs_path.length() == rhs_path.length() ? lhs_path < rhs_path : lhs_path.length() < rhs_path.length();
578
}
579
};
580
581
SortArray<ResourceUID::ID, FilepathComparator> sorter{};
582
583
if ((int)uids.size() > p_max_results) {
584
sorter.partial_sort(0, uids.size(), p_max_results, uids.ptr());
585
} else {
586
sorter.sort(uids.ptr(), uids.size());
587
}
588
}
589
590
void QuickOpenResultContainer::_create_initial_results() {
591
file_type_icons.clear();
592
file_type_icons.insert(SNAME("__default_icon"), get_editor_theme_icon(SNAME("Object")));
593
uids.clear();
594
filetypes.clear();
595
history_set.clear();
596
597
Vector<ResourceUID::ID> *history = _get_history();
598
if (history) {
599
for (const ResourceUID::ID &uid : *history) {
600
history_set.insert(uid);
601
}
602
}
603
604
_find_uids_in_folder(EditorFileSystem::get_singleton()->get_filesystem(), include_addons_toggle->is_pressed());
605
_sort_uids(result_items.size());
606
max_total_results = MIN(uids.size(), result_items.size());
607
update_results();
608
}
609
610
void QuickOpenResultContainer::_find_uids_in_folder(EditorFileSystemDirectory *p_directory, bool p_include_addons) {
611
for (int i = 0; i < p_directory->get_subdir_count(); i++) {
612
if (p_include_addons || p_directory->get_name() != "addons") {
613
_find_uids_in_folder(p_directory->get_subdir(i), p_include_addons);
614
}
615
}
616
617
for (int i = 0; i < p_directory->get_file_count(); i++) {
618
ResourceUID::ID uid = p_directory->get_file_uid(i);
619
if (uid == ResourceUID::INVALID_ID) {
620
continue;
621
}
622
623
const StringName engine_type = p_directory->get_file_type(i);
624
const StringName script_type = p_directory->get_file_resource_script_class(i);
625
626
const bool is_engine_type = script_type == StringName();
627
const StringName &actual_type = is_engine_type ? engine_type : script_type;
628
629
for (const StringName &parent_type : base_types) {
630
bool is_valid = ClassDB::is_parent_class(engine_type, parent_type) || (!is_engine_type && EditorNode::get_editor_data().script_class_is_parent(script_type, parent_type));
631
632
if (is_valid) {
633
uids.push_back(uid);
634
filetypes.insert(uid, actual_type);
635
break; // Stop testing base types as soon as we get a match.
636
}
637
}
638
}
639
}
640
641
void QuickOpenResultContainer::set_query_and_update(const String &p_query) {
642
query = p_query;
643
update_results();
644
}
645
646
Vector<ResourceUID::ID> *QuickOpenResultContainer::_get_history() {
647
if (base_types.size() == 1) {
648
return selected_history.getptr(base_types[0]);
649
}
650
return nullptr;
651
}
652
653
QuickOpenResultCandidate QuickOpenResultCandidate::from_uid(const ResourceUID::ID &p_uid, bool &r_success) {
654
if (p_uid == ResourceUID::INVALID_ID || !ResourceUID::get_singleton()->has_id(p_uid)) {
655
r_success = false;
656
return QuickOpenResultCandidate();
657
}
658
659
QuickOpenResultCandidate candidate;
660
candidate.uid = p_uid;
661
candidate.result = nullptr;
662
r_success = true;
663
return candidate;
664
}
665
666
QuickOpenResultCandidate QuickOpenResultCandidate::from_result(const FuzzySearchResult &p_result, bool &r_success) {
667
ResourceUID::ID uid = EditorFileSystem::get_singleton()->get_file_uid(p_result.target);
668
669
QuickOpenResultCandidate candidate = from_uid(uid, r_success);
670
if (!r_success) {
671
return QuickOpenResultCandidate();
672
}
673
674
candidate.result = &p_result;
675
return candidate;
676
}
677
678
void QuickOpenResultContainer::_add_candidate(QuickOpenResultCandidate &p_candidate) {
679
ERR_FAIL_COND(!ResourceUID::get_singleton()->has_id(p_candidate.uid));
680
681
StringName actual_type;
682
{
683
StringName *actual_type_ptr = filetypes.getptr(p_candidate.uid);
684
if (actual_type_ptr) {
685
actual_type = *actual_type_ptr;
686
} else {
687
ERR_PRINT(vformat("EditorQuickOpenDialog: No type for path %s.", ResourceUID::get_singleton()->get_id_path(p_candidate.uid)));
688
}
689
}
690
691
String file_path = ResourceUID::get_singleton()->get_id_path(p_candidate.uid);
692
EditorResourcePreview::PreviewItem item = EditorResourcePreview::get_singleton()->get_resource_preview_if_available(file_path);
693
if (item.preview.is_valid()) {
694
p_candidate.thumbnail = item.preview;
695
} else if (file_type_icons.has(actual_type)) {
696
p_candidate.thumbnail = *file_type_icons.getptr(actual_type);
697
} else if (has_theme_icon(actual_type, EditorStringName(EditorIcons))) {
698
p_candidate.thumbnail = get_editor_theme_icon(actual_type);
699
file_type_icons.insert(actual_type, p_candidate.thumbnail);
700
} else {
701
p_candidate.thumbnail = *file_type_icons.getptr(SNAME("__default_icon"));
702
}
703
704
candidates.push_back(p_candidate);
705
candidates_uids.insert(p_candidate.uid);
706
}
707
708
void QuickOpenResultContainer::update_results() {
709
candidates.clear();
710
candidates_uids.clear();
711
712
if (query.is_empty()) {
713
_use_default_candidates();
714
} else {
715
_score_and_sort_candidates();
716
}
717
718
_update_result_items(MIN(candidates.size(), max_total_results), 0);
719
}
720
721
void QuickOpenResultContainer::_use_default_candidates() {
722
HashSet<ResourceUID::ID> existing_uids;
723
724
Vector<ResourceUID::ID> *history = _get_history();
725
if (history) {
726
for (const ResourceUID::ID &uid : *history) {
727
bool success;
728
QuickOpenResultCandidate candidate = QuickOpenResultCandidate::from_uid(uid, success);
729
if (!success) {
730
continue;
731
}
732
_add_candidate(candidate);
733
}
734
}
735
736
for (const ResourceUID::ID &uid : uids) {
737
if (candidates.size() >= max_total_results) {
738
break;
739
}
740
if (candidates_uids.has(uid)) {
741
continue;
742
}
743
744
bool success;
745
QuickOpenResultCandidate candidate = QuickOpenResultCandidate::from_uid(uid, success);
746
if (!success) {
747
continue;
748
}
749
750
_add_candidate(candidate);
751
}
752
}
753
754
void QuickOpenResultContainer::_update_fuzzy_search_results() {
755
FuzzySearch fuzzy_search;
756
fuzzy_search.start_offset = 6; // Don't match against "res://" at the start of each filepath.
757
fuzzy_search.set_query(query);
758
fuzzy_search.max_results = max_total_results;
759
bool fuzzy_matching = EDITOR_GET("filesystem/quick_open_dialog/enable_fuzzy_matching");
760
int max_misses = EDITOR_GET("filesystem/quick_open_dialog/max_fuzzy_misses");
761
fuzzy_search.allow_subsequences = fuzzy_matching;
762
fuzzy_search.max_misses = fuzzy_matching ? max_misses : 0;
763
764
PackedStringArray paths;
765
paths.reserve_exact(uids.size());
766
767
for (const ResourceUID::ID &uid : uids) {
768
paths.push_back(ResourceUID::get_singleton()->get_id_path(uid));
769
}
770
771
fuzzy_search.search_all(paths, search_results);
772
}
773
774
void QuickOpenResultContainer::_score_and_sort_candidates() {
775
_update_fuzzy_search_results();
776
777
for (const FuzzySearchResult &result : search_results) {
778
bool success;
779
QuickOpenResultCandidate candidate = QuickOpenResultCandidate::from_result(result, success);
780
if (!success) {
781
continue;
782
}
783
784
_add_candidate(candidate);
785
}
786
}
787
788
void QuickOpenResultContainer::_update_result_items(int p_new_visible_results_count, int p_new_selection_index) {
789
// Only need to update items that were not hidden in previous update.
790
int num_items_needing_updates = MAX(num_visible_results, p_new_visible_results_count);
791
num_visible_results = p_new_visible_results_count;
792
793
for (int i = 0; i < num_items_needing_updates; i++) {
794
QuickOpenResultItem *item = result_items[i];
795
796
if (i < num_visible_results) {
797
item->set_content(candidates[i]);
798
} else {
799
item->reset();
800
}
801
};
802
803
const bool any_results = num_visible_results > 0;
804
_select_item(any_results ? p_new_selection_index : -1);
805
806
scroll_container->set_visible(any_results);
807
no_results_container->set_visible(!any_results);
808
809
if (!any_results) {
810
if (uids.is_empty()) {
811
no_results_label->set_text(TTR("No files found for this type"));
812
} else {
813
no_results_label->set_text(TTR("No results found"));
814
}
815
}
816
}
817
818
void QuickOpenResultContainer::handle_search_box_input(const Ref<InputEvent> &p_ie) {
819
if (num_visible_results < 0) {
820
return;
821
}
822
823
Ref<InputEventKey> key_event = p_ie;
824
if (key_event.is_valid() && key_event->is_pressed()) {
825
bool move_selection = false;
826
827
switch (key_event->get_keycode()) {
828
case Key::UP:
829
case Key::DOWN:
830
case Key::PAGEUP:
831
case Key::PAGEDOWN: {
832
move_selection = true;
833
} break;
834
case Key::LEFT:
835
case Key::RIGHT: {
836
if (content_display_mode == QuickOpenDisplayMode::GRID) {
837
// Maybe strip off the shift modifier to allow non-selecting navigation by character?
838
if (key_event->get_modifiers_mask().is_empty()) {
839
move_selection = true;
840
}
841
}
842
} break;
843
default:
844
break; // Let the event through so it will reach the search box.
845
}
846
847
if (move_selection) {
848
_move_selection_index(key_event->get_keycode());
849
queue_redraw();
850
accept_event();
851
}
852
}
853
}
854
855
void QuickOpenResultContainer::_move_selection_index(Key p_key) {
856
// Don't move selection if there are no results.
857
if (num_visible_results <= 0) {
858
return;
859
}
860
const int max_index = num_visible_results - 1;
861
862
int idx = selection_index;
863
if (content_display_mode == QuickOpenDisplayMode::LIST) {
864
if (p_key == Key::UP) {
865
idx = (idx == 0) ? max_index : (idx - 1);
866
} else if (p_key == Key::DOWN) {
867
idx = (idx == max_index) ? 0 : (idx + 1);
868
} else if (p_key == Key::PAGEUP) {
869
idx = (idx == 0) ? idx : MAX(idx - 10, 0);
870
} else if (p_key == Key::PAGEDOWN) {
871
idx = (idx == max_index) ? idx : MIN(idx + 10, max_index);
872
}
873
} else {
874
int column_count = grid->get_line_max_child_count();
875
876
if (p_key == Key::LEFT) {
877
idx = (idx == 0) ? max_index : (idx - 1);
878
} else if (p_key == Key::RIGHT) {
879
idx = (idx == max_index) ? 0 : (idx + 1);
880
} else if (p_key == Key::UP) {
881
idx = (idx == 0) ? max_index : MAX(idx - column_count, 0);
882
} else if (p_key == Key::DOWN) {
883
idx = (idx == max_index) ? 0 : MIN(idx + column_count, max_index);
884
} else if (p_key == Key::PAGEUP) {
885
idx = (idx == 0) ? idx : MAX(idx - (3 * column_count), 0);
886
} else if (p_key == Key::PAGEDOWN) {
887
idx = (idx == max_index) ? idx : MIN(idx + (3 * column_count), max_index);
888
}
889
}
890
891
_select_item(idx);
892
}
893
894
void QuickOpenResultContainer::_select_item(int p_index) {
895
if (!has_nothing_selected()) {
896
result_items[selection_index]->highlight_item(false);
897
}
898
899
selection_index = p_index;
900
901
if (has_nothing_selected()) {
902
file_details_path->set_text("");
903
return;
904
}
905
906
result_items[selection_index]->highlight_item(true);
907
bool in_history = history_set.has(candidates[selection_index].uid);
908
file_details_path->set_text(get_selected_path() + (in_history ? TTR(" (recently opened)") : ""));
909
910
emit_signal(SNAME("selection_changed"));
911
912
const QuickOpenResultItem *item = result_items[selection_index];
913
914
// Copied from Tree.
915
const int selected_position = item->get_position().y;
916
const int selected_size = item->get_size().y;
917
const int scroll_window_size = scroll_container->get_size().y;
918
const int scroll_position = scroll_container->get_v_scroll();
919
920
if (selected_position <= scroll_position) {
921
scroll_container->set_v_scroll(selected_position);
922
} else if (selected_position + selected_size > scroll_position + scroll_window_size) {
923
scroll_container->set_v_scroll(selected_position + selected_size - scroll_window_size);
924
}
925
}
926
927
void QuickOpenResultContainer::_item_input(const Ref<InputEvent> &p_ev, int p_index) {
928
Ref<InputEventMouseButton> mb = p_ev;
929
930
if (mb.is_valid() && mb->is_pressed()) {
931
if (mb->get_button_index() == MouseButton::LEFT) {
932
_select_item(p_index);
933
emit_signal(SNAME("result_clicked"), mb->is_double_click());
934
} else if (mb->get_button_index() == MouseButton::RIGHT) {
935
_select_item(p_index);
936
file_context_menu->set_position(result_items[p_index]->get_screen_position() + mb->get_position());
937
file_context_menu->reset_size();
938
file_context_menu->popup();
939
}
940
}
941
}
942
943
void QuickOpenResultContainer::_toggle_instant_preview(bool p_pressed) {
944
EditorSettings::get_singleton()->set("filesystem/quick_open_dialog/instant_preview", p_pressed);
945
}
946
947
void QuickOpenResultContainer::_toggle_fuzzy_search(bool p_pressed) {
948
EditorSettings::get_singleton()->set("filesystem/quick_open_dialog/enable_fuzzy_matching", p_pressed);
949
update_results();
950
}
951
952
String QuickOpenResultContainer::_get_cache_file_path() const {
953
return EditorPaths::get_singleton()->get_project_settings_dir().path_join("quick_open_dialog_cache.cfg");
954
}
955
956
void QuickOpenResultContainer::_toggle_include_addons(bool p_pressed) {
957
EditorSettings::get_singleton()->set("filesystem/quick_open_dialog/include_addons", p_pressed);
958
cleanup();
959
_create_initial_results();
960
}
961
962
void QuickOpenResultContainer::_toggle_display_mode() {
963
QuickOpenDisplayMode new_display_mode = (content_display_mode == QuickOpenDisplayMode::LIST) ? QuickOpenDisplayMode::GRID : QuickOpenDisplayMode::LIST;
964
_set_display_mode(new_display_mode);
965
}
966
967
CanvasItem *QuickOpenResultContainer::_get_result_root() {
968
if (content_display_mode == QuickOpenDisplayMode::LIST) {
969
return list;
970
} else {
971
return grid;
972
}
973
}
974
975
void QuickOpenResultContainer::_layout_result_item(QuickOpenResultItem *item) {
976
item->set_display_mode(content_display_mode);
977
Node *parent = item->get_parent();
978
if (parent) {
979
parent->remove_child(item);
980
}
981
_get_result_root()->add_child(item);
982
}
983
984
void QuickOpenResultContainer::_set_display_mode(QuickOpenDisplayMode p_display_mode) {
985
CanvasItem *prev_root = _get_result_root();
986
987
if (prev_root->is_visible() && content_display_mode == p_display_mode) {
988
return;
989
}
990
991
content_display_mode = p_display_mode;
992
CanvasItem *next_root = _get_result_root();
993
994
EditorSettings::get_singleton()->set_project_metadata("quick_open_dialog", "last_mode", (int)content_display_mode);
995
996
prev_root->hide();
997
next_root->show();
998
999
for (QuickOpenResultItem *item : result_items) {
1000
_layout_result_item(item);
1001
}
1002
1003
_update_result_items(num_visible_results, selection_index);
1004
1005
if (content_display_mode == QuickOpenDisplayMode::LIST) {
1006
display_mode_toggle->set_button_icon(get_editor_theme_icon(SNAME("FileThumbnail")));
1007
display_mode_toggle->set_tooltip_text(TTR("Grid view"));
1008
} else {
1009
display_mode_toggle->set_button_icon(get_editor_theme_icon(SNAME("FileList")));
1010
display_mode_toggle->set_tooltip_text(TTR("List view"));
1011
}
1012
}
1013
1014
bool QuickOpenResultContainer::has_nothing_selected() const {
1015
return selection_index < 0;
1016
}
1017
1018
ResourceUID::ID QuickOpenResultContainer::get_selected() const {
1019
ERR_FAIL_COND_V_MSG(has_nothing_selected(), ResourceUID::INVALID_ID, "Tried to get selected file, but nothing was selected.");
1020
return candidates[selection_index].uid;
1021
}
1022
1023
String QuickOpenResultContainer::get_selected_path() const {
1024
ERR_FAIL_COND_V_MSG(has_nothing_selected(), "", "Tried to get selected file path, but nothing was selected.");
1025
String path = ResourceUID::get_singleton()->get_id_path(candidates[selection_index].uid);
1026
ERR_FAIL_COND_V_MSG(path.is_empty(), "", "Failed to get selected file path.");
1027
return path;
1028
}
1029
1030
QuickOpenDisplayMode QuickOpenResultContainer::get_adaptive_display_mode(const Vector<StringName> &p_base_types) {
1031
static const Vector<StringName> grid_preferred_types = {
1032
StringName("Font", true),
1033
StringName("Texture2D", true),
1034
StringName("Material", true),
1035
StringName("Mesh", true),
1036
};
1037
1038
for (const StringName &type : grid_preferred_types) {
1039
for (const StringName &base_type : p_base_types) {
1040
if (base_type == type || ClassDB::is_parent_class(base_type, type)) {
1041
return QuickOpenDisplayMode::GRID;
1042
}
1043
}
1044
}
1045
1046
return QuickOpenDisplayMode::LIST;
1047
}
1048
1049
String _get_uid_string(const String &p_filepath) {
1050
ResourceUID::ID id = EditorFileSystem::get_singleton()->get_file_uid(p_filepath);
1051
return id == ResourceUID::INVALID_ID ? p_filepath : ResourceUID::get_singleton()->id_to_text(id);
1052
}
1053
1054
bool QuickOpenResultContainer::is_instant_preview_enabled() const {
1055
return instant_preview_toggle && instant_preview_toggle->is_visible() && instant_preview_toggle->is_pressed();
1056
}
1057
1058
void QuickOpenResultContainer::set_instant_preview_toggle_visible(bool p_visible) {
1059
instant_preview_toggle->set_visible(p_visible);
1060
}
1061
1062
void QuickOpenResultContainer::save_selected_item() {
1063
if (base_types.size() > 1) {
1064
// Getting the type of the file and checking which base type it belongs to should be possible.
1065
// However, for now these are not supported, and we don't record this.
1066
return;
1067
}
1068
1069
const StringName &base_type = base_types[0];
1070
ResourceUID::ID selected = get_selected();
1071
Vector<ResourceUID::ID> *type_history = selected_history.getptr(base_type);
1072
1073
if (!type_history) {
1074
selected_history.insert(base_type, Vector<ResourceUID::ID>());
1075
type_history = selected_history.getptr(base_type);
1076
} else {
1077
for (int i = 0; i < type_history->size(); i++) {
1078
if (selected == type_history->get(i)) {
1079
type_history->remove_at(i);
1080
break;
1081
}
1082
}
1083
}
1084
1085
history_set.insert(selected);
1086
type_history->insert(0, selected);
1087
if (type_history->size() > MAX_HISTORY_SIZE) {
1088
type_history->resize(MAX_HISTORY_SIZE);
1089
}
1090
1091
PackedStringArray history_uids;
1092
history_uids.resize(type_history->size());
1093
{
1094
String *uids_write = history_uids.ptrw();
1095
1096
int i = 0;
1097
for (const ResourceUID::ID &uid : *type_history) {
1098
uids_write[i] = ResourceUID::get_singleton()->id_to_text(uid);
1099
i++;
1100
}
1101
}
1102
history_file->set_value("selected_history", base_type, history_uids);
1103
history_file->save(_get_cache_file_path());
1104
}
1105
1106
void QuickOpenResultContainer::cleanup() {
1107
num_visible_results = 0;
1108
candidates.clear();
1109
history_set.clear();
1110
_select_item(-1);
1111
1112
for (QuickOpenResultItem *item : result_items) {
1113
item->reset();
1114
}
1115
}
1116
1117
void QuickOpenResultContainer::_notification(int p_what) {
1118
switch (p_what) {
1119
case NOTIFICATION_THEME_CHANGED: {
1120
Color text_color = get_theme_color("font_readonly_color", EditorStringName(Editor));
1121
file_details_path->add_theme_color_override(SceneStringName(font_color), text_color);
1122
no_results_label->add_theme_color_override(SceneStringName(font_color), text_color);
1123
1124
file_context_menu->set_item_icon(FILE_SHOW_IN_FILESYSTEM, get_editor_theme_icon(SNAME("ShowInFileSystem")));
1125
file_context_menu->set_item_icon(FILE_SHOW_IN_FILE_MANAGER, get_editor_theme_icon(SNAME("Filesystem")));
1126
1127
panel_container->add_theme_style_override(SceneStringName(panel), get_theme_stylebox(SceneStringName(panel), SNAME("Tree")));
1128
1129
if (content_display_mode == QuickOpenDisplayMode::LIST) {
1130
display_mode_toggle->set_button_icon(get_editor_theme_icon(SNAME("FileThumbnail")));
1131
} else {
1132
display_mode_toggle->set_button_icon(get_editor_theme_icon(SNAME("FileList")));
1133
}
1134
} break;
1135
}
1136
}
1137
1138
void QuickOpenResultContainer::_bind_methods() {
1139
ADD_SIGNAL(MethodInfo("selection_changed"));
1140
ADD_SIGNAL(MethodInfo("result_clicked", PropertyInfo(Variant::BOOL, "double_click")));
1141
}
1142
1143
//------------------------- Result Item
1144
1145
QuickOpenResultItem::QuickOpenResultItem() {
1146
set_focus_mode(FocusMode::FOCUS_NONE);
1147
_set_enabled(false);
1148
1149
list_item = memnew(QuickOpenResultListItem);
1150
list_item->hide();
1151
add_child(list_item);
1152
1153
grid_item = memnew(QuickOpenResultGridItem);
1154
grid_item->hide();
1155
add_child(grid_item);
1156
}
1157
1158
void QuickOpenResultItem::set_display_mode(QuickOpenDisplayMode p_display_mode) {
1159
if (p_display_mode == QuickOpenDisplayMode::LIST) {
1160
grid_item->hide();
1161
grid_item->reset();
1162
list_item->show();
1163
} else {
1164
list_item->hide();
1165
list_item->reset();
1166
grid_item->show();
1167
}
1168
1169
queue_redraw();
1170
}
1171
1172
void QuickOpenResultItem::set_content(const QuickOpenResultCandidate &p_candidate) {
1173
_set_enabled(true);
1174
1175
if (list_item->is_visible()) {
1176
list_item->set_content(p_candidate, enable_highlights);
1177
} else {
1178
grid_item->set_content(p_candidate, enable_highlights);
1179
}
1180
1181
queue_redraw();
1182
}
1183
1184
void QuickOpenResultItem::reset() {
1185
_set_enabled(false);
1186
is_hovering = false;
1187
is_selected = false;
1188
list_item->reset();
1189
grid_item->reset();
1190
}
1191
1192
void QuickOpenResultItem::highlight_item(bool p_enabled) {
1193
is_selected = p_enabled;
1194
1195
if (list_item->is_visible()) {
1196
if (p_enabled) {
1197
list_item->highlight_item(highlighted_font_color);
1198
} else {
1199
list_item->remove_highlight();
1200
}
1201
} else {
1202
if (p_enabled) {
1203
grid_item->highlight_item(highlighted_font_color);
1204
} else {
1205
grid_item->remove_highlight();
1206
}
1207
}
1208
1209
queue_redraw();
1210
}
1211
1212
void QuickOpenResultItem::_set_enabled(bool p_enabled) {
1213
set_visible(p_enabled);
1214
set_process(p_enabled);
1215
set_process_input(p_enabled);
1216
}
1217
1218
void QuickOpenResultItem::_notification(int p_what) {
1219
switch (p_what) {
1220
case NOTIFICATION_MOUSE_ENTER:
1221
case NOTIFICATION_MOUSE_EXIT: {
1222
is_hovering = is_visible() && p_what == NOTIFICATION_MOUSE_ENTER;
1223
queue_redraw();
1224
} break;
1225
case NOTIFICATION_THEME_CHANGED: {
1226
selected_stylebox = get_theme_stylebox("selected", "Tree");
1227
hovering_stylebox = get_theme_stylebox(SNAME("hovered"), "Tree");
1228
highlighted_font_color = get_theme_color("font_focus_color", EditorStringName(Editor));
1229
} break;
1230
case NOTIFICATION_DRAW: {
1231
if (is_selected) {
1232
draw_style_box(selected_stylebox, Rect2(Point2(), get_size()));
1233
} else if (is_hovering) {
1234
draw_style_box(hovering_stylebox, Rect2(Point2(), get_size()));
1235
}
1236
} break;
1237
}
1238
}
1239
1240
//----------------- List item
1241
1242
static Vector2i _get_path_interval(const Vector2i &p_interval, int p_dir_index) {
1243
if (p_interval.x >= p_dir_index || p_interval.y < 1) {
1244
return { -1, -1 };
1245
}
1246
return { p_interval.x, MIN(p_interval.x + p_interval.y, p_dir_index) - p_interval.x };
1247
}
1248
1249
static Vector2i _get_name_interval(const Vector2i &p_interval, int p_dir_index) {
1250
if (p_interval.x + p_interval.y <= p_dir_index || p_interval.y < 1) {
1251
return { -1, -1 };
1252
}
1253
int first_name_idx = p_dir_index + 1;
1254
int start = MAX(p_interval.x, first_name_idx);
1255
return { start - first_name_idx, p_interval.y - start + p_interval.x };
1256
}
1257
1258
QuickOpenResultListItem::QuickOpenResultListItem() {
1259
set_h_size_flags(Control::SIZE_EXPAND_FILL);
1260
add_theme_constant_override("margin_left", 6 * EDSCALE);
1261
add_theme_constant_override("margin_right", 6 * EDSCALE);
1262
1263
hbc = memnew(HBoxContainer);
1264
hbc->add_theme_constant_override(SNAME("separation"), 4 * EDSCALE);
1265
add_child(hbc);
1266
1267
const int max_size = 36 * EDSCALE;
1268
1269
thumbnail = memnew(TextureRect);
1270
thumbnail->set_h_size_flags(Control::SIZE_SHRINK_CENTER);
1271
thumbnail->set_v_size_flags(Control::SIZE_SHRINK_CENTER);
1272
thumbnail->set_expand_mode(TextureRect::EXPAND_IGNORE_SIZE);
1273
thumbnail->set_stretch_mode(TextureRect::STRETCH_KEEP_ASPECT_CENTERED);
1274
thumbnail->set_custom_minimum_size(Size2i(max_size, max_size));
1275
hbc->add_child(thumbnail);
1276
1277
text_container = memnew(VBoxContainer);
1278
text_container->add_theme_constant_override(SNAME("separation"), -7 * EDSCALE);
1279
text_container->set_h_size_flags(Control::SIZE_EXPAND_FILL);
1280
text_container->set_v_size_flags(Control::SIZE_FILL);
1281
hbc->add_child(text_container);
1282
1283
name = memnew(HighlightedLabel);
1284
name->set_h_size_flags(Control::SIZE_EXPAND_FILL);
1285
name->set_text_overrun_behavior(TextServer::OVERRUN_TRIM_ELLIPSIS);
1286
name->set_horizontal_alignment(HorizontalAlignment::HORIZONTAL_ALIGNMENT_LEFT);
1287
text_container->add_child(name);
1288
1289
path = memnew(HighlightedLabel);
1290
path->set_h_size_flags(Control::SIZE_EXPAND_FILL);
1291
path->set_text_overrun_behavior(TextServer::OVERRUN_TRIM_ELLIPSIS);
1292
path->add_theme_font_size_override(SceneStringName(font_size), 12 * EDSCALE);
1293
text_container->add_child(path);
1294
}
1295
1296
void QuickOpenResultListItem::set_content(const QuickOpenResultCandidate &p_candidate, bool p_highlight) {
1297
thumbnail->set_texture(p_candidate.thumbnail);
1298
1299
String file_path = ResourceUID::get_singleton()->get_id_path(p_candidate.uid);
1300
name->set_text(file_path.get_file());
1301
path->set_text(file_path.get_base_dir());
1302
name->reset_highlights();
1303
path->reset_highlights();
1304
1305
if (p_highlight && p_candidate.result != nullptr) {
1306
for (const FuzzyTokenMatch &match : p_candidate.result->token_matches) {
1307
for (const Vector2i &interval : match.substrings) {
1308
path->add_highlight(_get_path_interval(interval, p_candidate.result->dir_index));
1309
name->add_highlight(_get_name_interval(interval, p_candidate.result->dir_index));
1310
}
1311
}
1312
}
1313
}
1314
1315
void QuickOpenResultListItem::reset() {
1316
thumbnail->set_texture(nullptr);
1317
name->set_text("");
1318
path->set_text("");
1319
name->reset_highlights();
1320
path->reset_highlights();
1321
}
1322
1323
void QuickOpenResultListItem::highlight_item(const Color &p_color) {
1324
name->add_theme_color_override(SceneStringName(font_color), p_color);
1325
}
1326
1327
void QuickOpenResultListItem::remove_highlight() {
1328
name->remove_theme_color_override(SceneStringName(font_color));
1329
}
1330
1331
void QuickOpenResultListItem::_notification(int p_what) {
1332
switch (p_what) {
1333
case NOTIFICATION_THEME_CHANGED: {
1334
path->add_theme_color_override(SceneStringName(font_color), get_theme_color("font_disabled_color", EditorStringName(Editor)));
1335
} break;
1336
}
1337
}
1338
1339
//--------------- Grid Item
1340
1341
QuickOpenResultGridItem::QuickOpenResultGridItem() {
1342
set_custom_minimum_size(Size2i(120 * EDSCALE, 0));
1343
add_theme_constant_override("margin_top", 6 * EDSCALE);
1344
add_theme_constant_override("margin_left", 2 * EDSCALE);
1345
add_theme_constant_override("margin_right", 2 * EDSCALE);
1346
1347
vbc = memnew(VBoxContainer);
1348
vbc->set_h_size_flags(Control::SIZE_FILL);
1349
vbc->set_v_size_flags(Control::SIZE_EXPAND_FILL);
1350
vbc->add_theme_constant_override(SNAME("separation"), 0);
1351
add_child(vbc);
1352
1353
const int max_size = 64 * EDSCALE;
1354
1355
thumbnail = memnew(TextureRect);
1356
thumbnail->set_h_size_flags(Control::SIZE_SHRINK_CENTER);
1357
thumbnail->set_v_size_flags(Control::SIZE_SHRINK_CENTER);
1358
thumbnail->set_custom_minimum_size(Size2i(max_size, max_size));
1359
vbc->add_child(thumbnail);
1360
1361
name = memnew(HighlightedLabel);
1362
name->set_h_size_flags(Control::SIZE_EXPAND_FILL);
1363
name->set_text_overrun_behavior(TextServer::OVERRUN_TRIM_ELLIPSIS);
1364
name->set_horizontal_alignment(HorizontalAlignment::HORIZONTAL_ALIGNMENT_CENTER);
1365
name->add_theme_font_size_override(SceneStringName(font_size), 13 * EDSCALE);
1366
vbc->add_child(name);
1367
}
1368
1369
void QuickOpenResultGridItem::set_content(const QuickOpenResultCandidate &p_candidate, bool p_highlight) {
1370
thumbnail->set_texture(p_candidate.thumbnail);
1371
1372
String file_path = ResourceUID::get_singleton()->get_id_path(p_candidate.uid);
1373
name->set_text(file_path.get_file());
1374
name->set_tooltip_text(file_path);
1375
name->reset_highlights();
1376
1377
if (p_highlight && p_candidate.result != nullptr) {
1378
for (const FuzzyTokenMatch &match : p_candidate.result->token_matches) {
1379
for (const Vector2i &interval : match.substrings) {
1380
name->add_highlight(_get_name_interval(interval, p_candidate.result->dir_index));
1381
}
1382
}
1383
}
1384
1385
bool uses_icon = p_candidate.thumbnail->get_width() < (32 * EDSCALE);
1386
1387
if (uses_icon || p_candidate.thumbnail->get_height() <= thumbnail->get_custom_minimum_size().y) {
1388
thumbnail->set_expand_mode(TextureRect::EXPAND_KEEP_SIZE);
1389
thumbnail->set_stretch_mode(TextureRect::StretchMode::STRETCH_KEEP_CENTERED);
1390
} else {
1391
thumbnail->set_expand_mode(TextureRect::EXPAND_FIT_WIDTH_PROPORTIONAL);
1392
thumbnail->set_stretch_mode(TextureRect::StretchMode::STRETCH_SCALE);
1393
}
1394
}
1395
1396
void QuickOpenResultGridItem::reset() {
1397
thumbnail->set_texture(nullptr);
1398
name->set_text("");
1399
name->reset_highlights();
1400
}
1401
1402
void QuickOpenResultGridItem::highlight_item(const Color &p_color) {
1403
name->add_theme_color_override(SceneStringName(font_color), p_color);
1404
}
1405
1406
void QuickOpenResultGridItem::remove_highlight() {
1407
name->remove_theme_color_override(SceneStringName(font_color));
1408
}
1409
1410