Merge pull request #56772 from stijn-h/quick-open-redesign

Redesign Quick Open
This commit is contained in:
Rémi Verschelde 2024-10-02 15:00:37 +02:00
commit b070f4a49d
No known key found for this signature in database
GPG key ID: C3336907360768E1
18 changed files with 1269 additions and 463 deletions

View file

@ -714,6 +714,12 @@
If [code]true[/code], when saving a file, the editor will rename the old file to a different name, save a new file, then only remove the old file once the new file has been saved. This makes loss of data less likely to happen if the editor or operating system exits unexpectedly while saving (e.g. due to a crash or power outage).
[b]Note:[/b] On Windows, this feature can interact negatively with certain antivirus programs. In this case, you may have to set this to [code]false[/code] to prevent file locking issues.
</member>
<member name="filesystem/quick_open_dialog/default_display_mode" type="int" setter="" getter="">
If set to [code]Adaptive[/code], the dialog opens in list view or grid view depending on the requested type. If set to [code]Last Used[/code], the display mode will always open the way you last used it.
</member>
<member name="filesystem/quick_open_dialog/include_addons" type="bool" setter="" getter="">
If [code]true[/code], results will include files located in the [code]addons[/code] folder.
</member>
<member name="filesystem/tools/oidn/oidn_denoise_path" type="String" setter="" getter="">
The path to the directory containing the Open Image Denoise (OIDN) executable, used optionally for denoising lightmaps. It can be downloaded from [url=https://www.openimagedenoise.org/downloads.html]openimagedenoise.org[/url].
To enable this feature for your specific project, use [member ProjectSettings.rendering/lightmapping/denoising/denoiser].

View file

@ -94,7 +94,7 @@
#include "editor/editor_paths.h"
#include "editor/editor_properties.h"
#include "editor/editor_property_name_processor.h"
#include "editor/editor_quick_open.h"
#include "editor/editor_resource_picker.h"
#include "editor/editor_resource_preview.h"
#include "editor/editor_run.h"
#include "editor/editor_run_native.h"
@ -109,6 +109,7 @@
#include "editor/filesystem_dock.h"
#include "editor/gui/editor_bottom_panel.h"
#include "editor/gui/editor_file_dialog.h"
#include "editor/gui/editor_quick_open_dialog.h"
#include "editor/gui/editor_run_bar.h"
#include "editor/gui/editor_scene_tabs.h"
#include "editor/gui/editor_title_bar.h"
@ -2669,19 +2670,13 @@ void EditorNode::_menu_option_confirm(int p_option, bool p_confirmed) {
} break;
case FILE_QUICK_OPEN: {
quick_open->popup_dialog("Resource", true);
quick_open->set_title(TTR("Quick Open..."));
quick_open_dialog->popup_dialog({ "Resource" }, callable_mp(this, &EditorNode::_quick_opened));
} break;
case FILE_QUICK_OPEN_SCENE: {
quick_open->popup_dialog("PackedScene", true);
quick_open->set_title(TTR("Quick Open Scene..."));
quick_open_dialog->popup_dialog({ "PackedScene" }, callable_mp(this, &EditorNode::_quick_opened));
} break;
case FILE_QUICK_OPEN_SCRIPT: {
quick_open->popup_dialog("Script", true);
quick_open->set_title(TTR("Quick Open Script..."));
quick_open_dialog->popup_dialog({ "Script" }, callable_mp(this, &EditorNode::_quick_opened));
} break;
case FILE_OPEN_PREV: {
if (previous_scenes.is_empty()) {
@ -4599,17 +4594,11 @@ void EditorNode::_update_recent_scenes() {
recent_scenes->reset_size();
}
void EditorNode::_quick_opened() {
Vector<String> files = quick_open->get_selected_files();
bool open_scene_dialog = quick_open->get_base_type() == "PackedScene";
for (int i = 0; i < files.size(); i++) {
const String &res_path = files[i];
if (open_scene_dialog || ClassDB::is_parent_class(ResourceLoader::get_resource_type(res_path), "PackedScene")) {
open_request(res_path);
void EditorNode::_quick_opened(const String &p_file_path) {
if (ClassDB::is_parent_class(ResourceLoader::get_resource_type(p_file_path), "PackedScene")) {
open_request(p_file_path);
} else {
load_resource(res_path);
}
load_resource(p_file_path);
}
}
@ -7848,9 +7837,8 @@ EditorNode::EditorNode() {
open_imported->connect("custom_action", callable_mp(this, &EditorNode::_inherit_imported));
gui_base->add_child(open_imported);
quick_open = memnew(EditorQuickOpen);
gui_base->add_child(quick_open);
quick_open->connect("quick_open", callable_mp(this, &EditorNode::_quick_opened));
quick_open_dialog = memnew(EditorQuickOpenDialog);
gui_base->add_child(quick_open_dialog);
_update_recent_scenes();

View file

@ -81,7 +81,6 @@ class EditorLog;
class EditorMainScreen;
class EditorNativeShaderSourceVisualizer;
class EditorPluginList;
class EditorQuickOpen;
class EditorResourcePreview;
class EditorResourceConversionPlugin;
class EditorRunBar;
@ -90,6 +89,7 @@ class EditorSelectionHistory;
class EditorSettingsDialog;
class EditorTitleBar;
class ExportTemplateManager;
class EditorQuickOpenDialog;
class FBXImporterManager;
class FileSystemDock;
class HistoryDock;
@ -257,13 +257,13 @@ private:
EditorSelectionHistory editor_history;
EditorCommandPalette *command_palette = nullptr;
EditorQuickOpenDialog *quick_open_dialog = nullptr;
EditorExport *editor_export = nullptr;
EditorLog *log = nullptr;
EditorNativeShaderSourceVisualizer *native_shader_source_visualizer = nullptr;
EditorPluginList *editor_plugins_force_input_forwarding = nullptr;
EditorPluginList *editor_plugins_force_over = nullptr;
EditorPluginList *editor_plugins_over = nullptr;
EditorQuickOpen *quick_open = nullptr;
EditorResourcePreview *resource_preview = nullptr;
EditorSelection *editor_selection = nullptr;
EditorSettingsDialog *editor_settings_dialog = nullptr;
@ -572,7 +572,7 @@ private:
void _inherit_request(String p_file);
void _instantiate_request(const Vector<String> &p_files);
void _quick_opened();
void _quick_opened(const String &p_file_path);
void _open_command_palette();
void _project_run_started();
@ -913,6 +913,8 @@ public:
Dictionary drag_resource(const Ref<Resource> &p_res, Control *p_from);
Dictionary drag_files_and_dirs(const Vector<String> &p_paths, Control *p_from);
EditorQuickOpenDialog *get_quick_open_dialog() { return quick_open_dialog; }
void add_tool_menu_item(const String &p_name, const Callable &p_callback);
void add_tool_submenu_item(const String &p_name, PopupMenu *p_submenu);
void remove_tool_menu_item(const String &p_name);

View file

@ -1,308 +0,0 @@
/**************************************************************************/
/* editor_quick_open.cpp */
/**************************************************************************/
/* This file is part of: */
/* GODOT ENGINE */
/* https://godotengine.org */
/**************************************************************************/
/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
/* */
/* Permission is hereby granted, free of charge, to any person obtaining */
/* a copy of this software and associated documentation files (the */
/* "Software"), to deal in the Software without restriction, including */
/* without limitation the rights to use, copy, modify, merge, publish, */
/* distribute, sublicense, and/or sell copies of the Software, and to */
/* permit persons to whom the Software is furnished to do so, subject to */
/* the following conditions: */
/* */
/* The above copyright notice and this permission notice shall be */
/* included in all copies or substantial portions of the Software. */
/* */
/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */
/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
/**************************************************************************/
#include "editor_quick_open.h"
#include "core/os/keyboard.h"
#include "editor/editor_node.h"
#include "editor/editor_string_names.h"
#include "editor/themes/editor_scale.h"
Rect2i EditorQuickOpen::prev_rect = Rect2i();
bool EditorQuickOpen::was_showed = false;
void EditorQuickOpen::popup_dialog(const String &p_base, bool p_enable_multi, bool p_dont_clear) {
base_type = p_base;
allow_multi_select = p_enable_multi;
search_options->set_select_mode(allow_multi_select ? Tree::SELECT_MULTI : Tree::SELECT_SINGLE);
if (was_showed) {
popup(prev_rect);
} else {
popup_centered_clamped(Size2(600, 440) * EDSCALE, 0.8f);
}
EditorFileSystemDirectory *efsd = EditorFileSystem::get_singleton()->get_filesystem();
_build_search_cache(efsd);
if (p_dont_clear) {
search_box->select_all();
_update_search();
} else {
search_box->clear(); // This will emit text_changed.
}
search_box->grab_focus();
}
void EditorQuickOpen::_build_search_cache(EditorFileSystemDirectory *p_efsd) {
for (int i = 0; i < p_efsd->get_subdir_count(); i++) {
_build_search_cache(p_efsd->get_subdir(i));
}
Vector<String> base_types = base_type.split(",");
for (int i = 0; i < p_efsd->get_file_count(); i++) {
String file = p_efsd->get_file_path(i);
String engine_type = p_efsd->get_file_type(i);
String script_type = p_efsd->get_file_resource_script_class(i);
String actual_type = script_type.is_empty() ? engine_type : script_type;
// Iterate all possible base types.
for (String &parent_type : base_types) {
if (ClassDB::is_parent_class(engine_type, parent_type) || EditorNode::get_editor_data().script_class_is_parent(script_type, parent_type)) {
files.push_back(file.substr(6, file.length()));
// Store refs to used icons.
String ext = file.get_extension();
if (!icons.has(ext)) {
icons.insert(ext, EditorNode::get_singleton()->get_class_icon(actual_type, "Object"));
}
// Stop testing base types as soon as we got a match.
break;
}
}
}
}
void EditorQuickOpen::_update_search() {
const PackedStringArray search_tokens = search_box->get_text().to_lower().replace("/", " ").split(" ", false);
const bool empty_search = search_tokens.is_empty();
// Filter possible candidates.
Vector<Entry> entries;
for (int i = 0; i < files.size(); i++) {
Entry r;
r.path = files[i];
if (empty_search) {
entries.push_back(r);
} else {
r.score = _score_search_result(search_tokens, r.path.to_lower());
if (r.score > 0) {
entries.push_back(r);
}
}
}
// Display results
TreeItem *root = search_options->get_root();
root->clear_children();
if (entries.size() > 0) {
if (!empty_search) {
SortArray<Entry, EntryComparator> sorter;
sorter.sort(entries.ptrw(), entries.size());
}
const int class_icon_size = search_options->get_theme_constant(SNAME("class_icon_size"), EditorStringName(Editor));
const int entry_limit = MIN(entries.size(), 300);
for (int i = 0; i < entry_limit; i++) {
TreeItem *ti = search_options->create_item(root);
ti->set_text(0, entries[i].path);
ti->set_icon_max_width(0, class_icon_size);
ti->set_icon(0, *icons.lookup_ptr(entries[i].path.get_extension()));
}
TreeItem *to_select = root->get_first_child();
to_select->select(0);
to_select->set_as_cursor(0);
search_options->scroll_to_item(to_select);
get_ok_button()->set_disabled(false);
} else {
search_options->deselect_all();
get_ok_button()->set_disabled(true);
}
}
float EditorQuickOpen::_score_search_result(const PackedStringArray &p_search_tokens, const String &p_path) {
float score = 0.0f;
int prev_min_match_idx = -1;
for (const String &s : p_search_tokens) {
int min_match_idx = p_path.find(s);
if (min_match_idx == -1) {
return 0.0f;
}
float token_score = s.length();
int max_match_idx = p_path.rfind(s);
// Prioritize the actual file name over folder.
if (max_match_idx > p_path.rfind("/")) {
token_score *= 2.0f;
}
// Prioritize matches at the front of the path token.
if (min_match_idx == 0 || p_path.contains("/" + s)) {
token_score += 1.0f;
}
score += token_score;
// Prioritize tokens which appear in order.
if (prev_min_match_idx != -1 && max_match_idx > prev_min_match_idx) {
score += 1.0f;
}
prev_min_match_idx = min_match_idx;
}
return score;
}
void EditorQuickOpen::_confirmed() {
if (!search_options->get_selected()) {
return;
}
_cleanup();
hide();
emit_signal(SNAME("quick_open"));
}
void EditorQuickOpen::cancel_pressed() {
_cleanup();
}
void EditorQuickOpen::_cleanup() {
files.clear();
icons.clear();
}
void EditorQuickOpen::_text_changed(const String &p_newtext) {
_update_search();
}
void EditorQuickOpen::_sbox_input(const Ref<InputEvent> &p_event) {
// Redirect navigational key events to the tree.
Ref<InputEventKey> key = p_event;
if (key.is_valid()) {
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")) {
search_options->gui_input(key);
search_box->accept_event();
if (allow_multi_select) {
TreeItem *root = search_options->get_root();
if (!root->get_first_child()) {
return;
}
TreeItem *current = search_options->get_selected();
TreeItem *item = search_options->get_next_selected(root);
while (item) {
item->deselect(0);
item = search_options->get_next_selected(item);
}
current->select(0);
current->set_as_cursor(0);
}
}
}
}
String EditorQuickOpen::get_selected() const {
TreeItem *ti = search_options->get_selected();
if (!ti) {
return String();
}
return "res://" + ti->get_text(0);
}
Vector<String> EditorQuickOpen::get_selected_files() const {
Vector<String> selected_files;
TreeItem *item = search_options->get_next_selected(search_options->get_root());
while (item) {
selected_files.push_back("res://" + item->get_text(0));
item = search_options->get_next_selected(item);
}
return selected_files;
}
String EditorQuickOpen::get_base_type() const {
return base_type;
}
void EditorQuickOpen::_notification(int p_what) {
switch (p_what) {
case NOTIFICATION_ENTER_TREE: {
connect(SceneStringName(confirmed), callable_mp(this, &EditorQuickOpen::_confirmed));
search_box->set_clear_button_enabled(true);
} break;
case NOTIFICATION_VISIBILITY_CHANGED: {
if (!is_visible()) {
prev_rect = Rect2i(get_position(), get_size());
was_showed = true;
}
} break;
case NOTIFICATION_THEME_CHANGED: {
search_box->set_right_icon(get_editor_theme_icon(SNAME("Search")));
} break;
case NOTIFICATION_EXIT_TREE: {
disconnect(SceneStringName(confirmed), callable_mp(this, &EditorQuickOpen::_confirmed));
} break;
}
}
void EditorQuickOpen::_bind_methods() {
ADD_SIGNAL(MethodInfo("quick_open"));
}
EditorQuickOpen::EditorQuickOpen() {
VBoxContainer *vbc = memnew(VBoxContainer);
add_child(vbc);
search_box = memnew(LineEdit);
search_box->connect(SceneStringName(text_changed), callable_mp(this, &EditorQuickOpen::_text_changed));
search_box->connect(SceneStringName(gui_input), callable_mp(this, &EditorQuickOpen::_sbox_input));
vbc->add_margin_child(TTR("Search:"), search_box);
register_text_enter(search_box);
search_options = memnew(Tree);
search_options->set_auto_translate_mode(AUTO_TRANSLATE_MODE_DISABLED);
search_options->connect("item_activated", callable_mp(this, &EditorQuickOpen::_confirmed));
search_options->create_item();
search_options->set_hide_root(true);
search_options->set_hide_folding(true);
search_options->add_theme_constant_override("draw_guides", 1);
vbc->add_margin_child(TTR("Matches:"), search_options, true);
set_ok_button_text(TTR("Open"));
set_hide_on_ok(false);
}

View file

@ -1,89 +0,0 @@
/**************************************************************************/
/* editor_quick_open.h */
/**************************************************************************/
/* This file is part of: */
/* GODOT ENGINE */
/* https://godotengine.org */
/**************************************************************************/
/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
/* */
/* Permission is hereby granted, free of charge, to any person obtaining */
/* a copy of this software and associated documentation files (the */
/* "Software"), to deal in the Software without restriction, including */
/* without limitation the rights to use, copy, modify, merge, publish, */
/* distribute, sublicense, and/or sell copies of the Software, and to */
/* permit persons to whom the Software is furnished to do so, subject to */
/* the following conditions: */
/* */
/* The above copyright notice and this permission notice shall be */
/* included in all copies or substantial portions of the Software. */
/* */
/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */
/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
/**************************************************************************/
#ifndef EDITOR_QUICK_OPEN_H
#define EDITOR_QUICK_OPEN_H
#include "core/templates/oa_hash_map.h"
#include "editor/editor_file_system.h"
#include "scene/gui/dialogs.h"
#include "scene/gui/tree.h"
class EditorQuickOpen : public ConfirmationDialog {
GDCLASS(EditorQuickOpen, ConfirmationDialog);
static Rect2i prev_rect;
static bool was_showed;
LineEdit *search_box = nullptr;
Tree *search_options = nullptr;
String base_type;
bool allow_multi_select = false;
Vector<String> files;
OAHashMap<String, Ref<Texture2D>> icons;
struct Entry {
String path;
float score = 0;
};
struct EntryComparator {
_FORCE_INLINE_ bool operator()(const Entry &A, const Entry &B) const {
return A.score > B.score;
}
};
void _update_search();
void _build_search_cache(EditorFileSystemDirectory *p_efsd);
float _score_search_result(const PackedStringArray &p_search_tokens, const String &p_path);
void _confirmed();
virtual void cancel_pressed() override;
void _cleanup();
void _sbox_input(const Ref<InputEvent> &p_event);
void _text_changed(const String &p_newtext);
protected:
void _notification(int p_what);
static void _bind_methods();
public:
String get_base_type() const;
String get_selected() const;
Vector<String> get_selected_files() const;
void popup_dialog(const String &p_base, bool p_enable_multi = false, bool p_dontclear = false);
EditorQuickOpen();
};
#endif // EDITOR_QUICK_OPEN_H

View file

@ -33,12 +33,12 @@
#include "editor/audio_stream_preview.h"
#include "editor/editor_help.h"
#include "editor/editor_node.h"
#include "editor/editor_quick_open.h"
#include "editor/editor_resource_preview.h"
#include "editor/editor_settings.h"
#include "editor/editor_string_names.h"
#include "editor/filesystem_dock.h"
#include "editor/gui/editor_file_dialog.h"
#include "editor/gui/editor_quick_open_dialog.h"
#include "editor/plugins/editor_resource_conversion_plugin.h"
#include "editor/plugins/script_editor_plugin.h"
#include "editor/scene_tree_dock.h"
@ -171,10 +171,6 @@ void EditorResourcePicker::_file_selected(const String &p_path) {
_update_resource();
}
void EditorResourcePicker::_file_quick_selected() {
_file_selected(quick_open->get_selected());
}
void EditorResourcePicker::_resource_saved(Object *p_resource) {
if (edited_resource.is_valid() && p_resource == edited_resource.ptr()) {
emit_signal(SNAME("resource_changed"), edited_resource);
@ -339,14 +335,14 @@ void EditorResourcePicker::_edit_menu_cbk(int p_which) {
} break;
case OBJ_MENU_QUICKLOAD: {
if (!quick_open) {
quick_open = memnew(EditorQuickOpen);
add_child(quick_open);
quick_open->connect("quick_open", callable_mp(this, &EditorResourcePicker::_file_quick_selected));
const Vector<String> &base_types_string = base_type.split(",");
Vector<StringName> base_types;
for (const String &type : base_types_string) {
base_types.push_back(type);
}
quick_open->popup_dialog(base_type);
quick_open->set_title(TTR("Resource"));
EditorNode::get_singleton()->get_quick_open_dialog()->popup_dialog(base_types, callable_mp(this, &EditorResourcePicker::_file_selected));
} break;
case OBJ_MENU_INSPECT: {

View file

@ -36,7 +36,6 @@
class Button;
class ConfirmationDialog;
class EditorFileDialog;
class EditorQuickOpen;
class PopupMenu;
class TextureRect;
class Tree;
@ -59,7 +58,6 @@ class EditorResourcePicker : public HBoxContainer {
TextureRect *preview_rect = nullptr;
Button *edit_button = nullptr;
EditorFileDialog *file_dialog = nullptr;
EditorQuickOpen *quick_open = nullptr;
ConfirmationDialog *duplicate_resources_dialog = nullptr;
Tree *duplicate_resources_tree = nullptr;
@ -88,7 +86,6 @@ class EditorResourcePicker : public HBoxContainer {
void _update_resource_preview(const String &p_path, const Ref<Texture2D> &p_preview, const Ref<Texture2D> &p_small_preview, ObjectID p_obj);
void _resource_selected();
void _file_quick_selected();
void _file_selected(const String &p_path);
void _resource_saved(Object *p_resource);

View file

@ -414,6 +414,24 @@ void EditorResourcePreview::_update_thumbnail_sizes() {
}
}
EditorResourcePreview::PreviewItem EditorResourcePreview::get_resource_preview_if_available(const String &p_path) {
PreviewItem item;
{
MutexLock lock(preview_mutex);
HashMap<String, EditorResourcePreview::Item>::Iterator I = cache.find(p_path);
if (!I) {
return item;
}
EditorResourcePreview::Item &cached_item = I->value;
item.preview = cached_item.preview;
item.small_preview = cached_item.small_preview;
}
preview_sem.post();
return item;
}
void EditorResourcePreview::queue_edited_resource_preview(const Ref<Resource> &p_res, Object *p_receiver, const StringName &p_receiver_func, const Variant &p_userdata) {
ERR_FAIL_NULL(p_receiver);
ERR_FAIL_COND(!p_res.is_valid());

View file

@ -128,12 +128,19 @@ protected:
public:
static EditorResourcePreview *get_singleton();
struct PreviewItem {
Ref<Texture2D> preview;
Ref<Texture2D> small_preview;
};
// p_receiver_func callback has signature (String p_path, Ref<Texture2D> p_preview, Ref<Texture2D> p_preview_small, Variant p_userdata)
// p_preview will be null if there was an error
void queue_resource_preview(const String &p_path, Object *p_receiver, const StringName &p_receiver_func, const Variant &p_userdata);
void queue_edited_resource_preview(const Ref<Resource> &p_res, Object *p_receiver, const StringName &p_receiver_func, const Variant &p_userdata);
const Dictionary get_preview_metadata(const String &p_path) const;
PreviewItem get_resource_preview_if_available(const String &p_path);
void add_preview_generator(const Ref<EditorResourcePreviewGenerator> &p_generator);
void remove_preview_generator(const Ref<EditorResourcePreviewGenerator> &p_generator);
void check_for_invalidation(const String &p_path);

View file

@ -602,6 +602,10 @@ void EditorSettings::_load_defaults(Ref<ConfigFile> p_extra_config) {
EDITOR_SETTING(Variant::INT, PROPERTY_HINT_ENUM, "filesystem/file_dialog/display_mode", 0, "Thumbnails,List")
EDITOR_SETTING(Variant::INT, PROPERTY_HINT_RANGE, "filesystem/file_dialog/thumbnail_size", 64, "32,128,16")
// Quick Open dialog
_initial_set("filesystem/quick_open_dialog/include_addons", false);
EDITOR_SETTING(Variant::INT, PROPERTY_HINT_ENUM, "filesystem/quick_open_dialog/default_display_mode", 0, "Adaptive,Last Used")
// Import (for glft module)
EDITOR_SETTING_USAGE(Variant::STRING, PROPERTY_HINT_GLOBAL_FILE, "filesystem/import/blender/blender_path", "", "", PROPERTY_USAGE_DEFAULT | PROPERTY_USAGE_RESTART_IF_CHANGED | PROPERTY_USAGE_EDITOR_BASIC_SETTING)
EDITOR_SETTING_USAGE(Variant::INT, PROPERTY_HINT_RANGE, "filesystem/import/blender/rpc_port", 6011, "0,65535,1", PROPERTY_USAGE_DEFAULT | PROPERTY_USAGE_RESTART_IF_CHANGED)

View file

@ -0,0 +1,961 @@
/**************************************************************************/
/* editor_quick_open_dialog.cpp */
/**************************************************************************/
/* This file is part of: */
/* GODOT ENGINE */
/* https://godotengine.org */
/**************************************************************************/
/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
/* */
/* Permission is hereby granted, free of charge, to any person obtaining */
/* a copy of this software and associated documentation files (the */
/* "Software"), to deal in the Software without restriction, including */
/* without limitation the rights to use, copy, modify, merge, publish, */
/* distribute, sublicense, and/or sell copies of the Software, and to */
/* permit persons to whom the Software is furnished to do so, subject to */
/* the following conditions: */
/* */
/* The above copyright notice and this permission notice shall be */
/* included in all copies or substantial portions of the Software. */
/* */
/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */
/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
/**************************************************************************/
#include "editor_quick_open_dialog.h"
#include "editor/editor_file_system.h"
#include "editor/editor_node.h"
#include "editor/editor_resource_preview.h"
#include "editor/editor_settings.h"
#include "editor/editor_string_names.h"
#include "editor/themes/editor_scale.h"
#include "scene/gui/center_container.h"
#include "scene/gui/check_button.h"
#include "scene/gui/flow_container.h"
#include "scene/gui/margin_container.h"
#include "scene/gui/panel_container.h"
#include "scene/gui/separator.h"
#include "scene/gui/texture_rect.h"
#include "scene/gui/tree.h"
EditorQuickOpenDialog::EditorQuickOpenDialog() {
VBoxContainer *vbc = memnew(VBoxContainer);
vbc->add_theme_constant_override("separation", 0);
add_child(vbc);
{
// Search bar
MarginContainer *mc = memnew(MarginContainer);
mc->add_theme_constant_override("margin_top", 6);
mc->add_theme_constant_override("margin_bottom", 6);
mc->add_theme_constant_override("margin_left", 1);
mc->add_theme_constant_override("margin_right", 1);
vbc->add_child(mc);
search_box = memnew(LineEdit);
search_box->set_h_size_flags(Control::SIZE_EXPAND_FILL);
search_box->set_placeholder(TTR("Search files..."));
search_box->set_clear_button_enabled(true);
mc->add_child(search_box);
}
{
container = memnew(QuickOpenResultContainer);
container->connect("result_clicked", callable_mp(this, &EditorQuickOpenDialog::ok_pressed));
vbc->add_child(container);
}
search_box->connect(SceneStringName(text_changed), callable_mp(this, &EditorQuickOpenDialog::_search_box_text_changed));
search_box->connect(SceneStringName(gui_input), callable_mp(container, &QuickOpenResultContainer::handle_search_box_input));
register_text_enter(search_box);
get_ok_button()->hide();
}
String EditorQuickOpenDialog::get_dialog_title(const Vector<StringName> &p_base_types) {
if (p_base_types.size() > 1) {
return TTR("Select Resource");
}
if (p_base_types[0] == SNAME("PackedScene")) {
return TTR("Select Scene");
}
return TTR("Select") + " " + p_base_types[0];
}
void EditorQuickOpenDialog::popup_dialog(const Vector<StringName> &p_base_types, const Callable &p_item_selected_callback) {
ERR_FAIL_COND(p_base_types.is_empty());
ERR_FAIL_COND(!p_item_selected_callback.is_valid());
item_selected_callback = p_item_selected_callback;
container->init(p_base_types);
get_ok_button()->set_disabled(container->has_nothing_selected());
set_title(get_dialog_title(p_base_types));
popup_centered_clamped(Size2(710, 650) * EDSCALE, 0.8f);
search_box->grab_focus();
}
void EditorQuickOpenDialog::ok_pressed() {
item_selected_callback.call(container->get_selected());
container->save_selected_item();
container->cleanup();
search_box->clear();
hide();
}
void EditorQuickOpenDialog::cancel_pressed() {
container->cleanup();
search_box->clear();
}
void EditorQuickOpenDialog::_search_box_text_changed(const String &p_query) {
container->update_results(p_query.to_lower());
get_ok_button()->set_disabled(container->has_nothing_selected());
}
//------------------------- Result Container
QuickOpenResultContainer::QuickOpenResultContainer() {
set_h_size_flags(Control::SIZE_EXPAND_FILL);
set_v_size_flags(Control::SIZE_EXPAND_FILL);
add_theme_constant_override("separation", 0);
{
// Results section
panel_container = memnew(PanelContainer);
panel_container->set_v_size_flags(Control::SIZE_EXPAND_FILL);
add_child(panel_container);
{
// No search results
no_results_container = memnew(CenterContainer);
no_results_container->set_h_size_flags(Control::SIZE_EXPAND_FILL);
no_results_container->set_v_size_flags(Control::SIZE_EXPAND_FILL);
panel_container->add_child(no_results_container);
no_results_label = memnew(Label);
no_results_label->add_theme_font_size_override(SceneStringName(font_size), 24 * EDSCALE);
no_results_container->add_child(no_results_label);
no_results_container->hide();
}
{
// Search results
scroll_container = memnew(ScrollContainer);
scroll_container->set_h_size_flags(Control::SIZE_EXPAND_FILL);
scroll_container->set_v_size_flags(Control::SIZE_EXPAND_FILL);
scroll_container->set_horizontal_scroll_mode(ScrollContainer::SCROLL_MODE_DISABLED);
scroll_container->hide();
panel_container->add_child(scroll_container);
list = memnew(VBoxContainer);
list->set_h_size_flags(Control::SIZE_EXPAND_FILL);
list->hide();
scroll_container->add_child(list);
grid = memnew(HFlowContainer);
grid->set_h_size_flags(Control::SIZE_EXPAND_FILL);
grid->set_v_size_flags(Control::SIZE_EXPAND_FILL);
grid->add_theme_constant_override("v_separation", 18);
grid->add_theme_constant_override("h_separation", 4);
grid->hide();
scroll_container->add_child(grid);
}
}
{
// Bottom bar
HBoxContainer *bottom_bar = memnew(HBoxContainer);
add_child(bottom_bar);
file_details_path = memnew(Label);
file_details_path->set_h_size_flags(Control::SIZE_EXPAND_FILL);
file_details_path->set_horizontal_alignment(HorizontalAlignment::HORIZONTAL_ALIGNMENT_CENTER);
file_details_path->set_text_overrun_behavior(TextServer::OVERRUN_TRIM_ELLIPSIS);
bottom_bar->add_child(file_details_path);
{
HBoxContainer *hbc = memnew(HBoxContainer);
hbc->add_theme_constant_override("separation", 3);
bottom_bar->add_child(hbc);
include_addons_toggle = memnew(CheckButton);
include_addons_toggle->set_flat(true);
include_addons_toggle->set_focus_mode(Control::FOCUS_NONE);
include_addons_toggle->set_default_cursor_shape(CURSOR_POINTING_HAND);
include_addons_toggle->set_tooltip_text(TTR("Include files from addons"));
include_addons_toggle->connect(SceneStringName(toggled), callable_mp(this, &QuickOpenResultContainer::_toggle_include_addons));
hbc->add_child(include_addons_toggle);
VSeparator *vsep = memnew(VSeparator);
vsep->set_v_size_flags(Control::SIZE_SHRINK_CENTER);
vsep->set_custom_minimum_size(Size2i(0, 14 * EDSCALE));
hbc->add_child(vsep);
display_mode_toggle = memnew(Button);
display_mode_toggle->set_flat(true);
display_mode_toggle->set_focus_mode(Control::FOCUS_NONE);
display_mode_toggle->set_default_cursor_shape(CURSOR_POINTING_HAND);
display_mode_toggle->connect(SceneStringName(pressed), callable_mp(this, &QuickOpenResultContainer::_toggle_display_mode));
hbc->add_child(display_mode_toggle);
}
}
// Creating and deleting nodes while searching is slow, so we allocate
// a bunch of result nodes and fill in the content based on result ranking.
result_items.resize(TOTAL_ALLOCATED_RESULT_ITEMS);
for (int i = 0; i < TOTAL_ALLOCATED_RESULT_ITEMS; i++) {
QuickOpenResultItem *item = memnew(QuickOpenResultItem);
item->connect(SceneStringName(gui_input), callable_mp(this, &QuickOpenResultContainer::_item_input).bind(i));
result_items.write[i] = item;
}
}
QuickOpenResultContainer::~QuickOpenResultContainer() {
for (QuickOpenResultItem *E : result_items) {
memdelete(E);
}
}
void QuickOpenResultContainer::init(const Vector<StringName> &p_base_types) {
base_types = p_base_types;
const int display_mode_behavior = EDITOR_GET("filesystem/quick_open_dialog/default_display_mode");
const bool adaptive_display_mode = (display_mode_behavior == 0);
if (adaptive_display_mode) {
_set_display_mode(get_adaptive_display_mode(p_base_types));
}
const bool include_addons = EDITOR_GET("filesystem/quick_open_dialog/include_addons");
include_addons_toggle->set_pressed_no_signal(include_addons);
_create_initial_results(include_addons);
}
void QuickOpenResultContainer::_create_initial_results(bool p_include_addons) {
file_type_icons.insert("__default_icon", get_editor_theme_icon(SNAME("Object")));
_find_candidates_in_folder(EditorFileSystem::get_singleton()->get_filesystem(), p_include_addons);
max_total_results = MIN(candidates.size(), TOTAL_ALLOCATED_RESULT_ITEMS);
file_type_icons.clear();
update_results(query);
}
void QuickOpenResultContainer::_find_candidates_in_folder(EditorFileSystemDirectory *p_directory, bool p_include_addons) {
for (int i = 0; i < p_directory->get_subdir_count(); i++) {
if (p_include_addons || p_directory->get_name() != "addons") {
_find_candidates_in_folder(p_directory->get_subdir(i), p_include_addons);
}
}
for (int i = 0; i < p_directory->get_file_count(); i++) {
String file_path = p_directory->get_file_path(i);
const StringName engine_type = p_directory->get_file_type(i);
const StringName script_type = p_directory->get_file_resource_script_class(i);
const bool is_engine_type = script_type == StringName();
const StringName &actual_type = is_engine_type ? engine_type : script_type;
for (const StringName &parent_type : base_types) {
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));
if (is_valid) {
Candidate c;
c.file_name = file_path.get_file();
c.file_directory = file_path.get_base_dir();
EditorResourcePreview::PreviewItem item = EditorResourcePreview::get_singleton()->get_resource_preview_if_available(file_path);
if (item.preview.is_valid()) {
c.thumbnail = item.preview;
} else if (file_type_icons.has(actual_type)) {
c.thumbnail = *file_type_icons.lookup_ptr(actual_type);
} else if (has_theme_icon(actual_type, EditorStringName(EditorIcons))) {
c.thumbnail = get_editor_theme_icon(actual_type);
file_type_icons.insert(actual_type, c.thumbnail);
} else {
c.thumbnail = *file_type_icons.lookup_ptr("__default_icon");
}
candidates.push_back(c);
break; // Stop testing base types as soon as we get a match.
}
}
}
}
void QuickOpenResultContainer::update_results(const String &p_query) {
query = p_query;
int relevant_candidates = _sort_candidates(p_query);
_update_result_items(MIN(relevant_candidates, max_total_results), 0);
}
int QuickOpenResultContainer::_sort_candidates(const String &p_query) {
if (p_query.is_empty()) {
return 0;
}
const PackedStringArray search_tokens = p_query.to_lower().replace("/", " ").split(" ", false);
if (search_tokens.is_empty()) {
return 0;
}
// First, we assign a score to each candidate.
int num_relevant_candidates = 0;
for (Candidate &c : candidates) {
c.score = 0;
int prev_token_match_pos = -1;
for (const String &token : search_tokens) {
const int file_pos = c.file_name.findn(token);
const int dir_pos = c.file_directory.findn(token);
const bool file_match = file_pos > -1;
const bool dir_match = dir_pos > -1;
if (!file_match && !dir_match) {
c.score = -1.0f;
break;
}
float token_score = file_match ? 0.6f : 0.1999f;
// Add bias for shorter filenames/paths: they resemble the query more.
const String &matched_string = file_match ? c.file_name : c.file_directory;
int matched_string_token_pos = file_match ? file_pos : dir_pos;
token_score += 0.1f * (1.0f - ((float)matched_string_token_pos / (float)matched_string.length()));
// Add bias if the match happened in the file name, not the extension.
if (file_match) {
int ext_pos = matched_string.rfind(".");
if (ext_pos == -1 || ext_pos > matched_string_token_pos) {
token_score += 0.1f;
}
}
// Add bias if token is in order.
{
int candidate_string_token_pos = file_match ? (c.file_directory.length() + file_pos) : dir_pos;
if (prev_token_match_pos != -1 && candidate_string_token_pos > prev_token_match_pos) {
token_score += 0.2f;
}
prev_token_match_pos = candidate_string_token_pos;
}
c.score += token_score;
}
if (c.score > 0.0f) {
num_relevant_candidates++;
}
}
// Now we will sort the candidates based on score, resolving ties by favoring:
// 1. Shorter file length.
// 2. Shorter directory length.
// 3. Lower alphabetic order.
struct CandidateComparator {
_FORCE_INLINE_ bool operator()(const Candidate &p_a, const Candidate &p_b) const {
if (!Math::is_equal_approx(p_a.score, p_b.score)) {
return p_a.score > p_b.score;
}
if (p_a.file_name.length() != p_b.file_name.length()) {
return p_a.file_name.length() < p_b.file_name.length();
}
if (p_a.file_directory.length() != p_b.file_directory.length()) {
return p_a.file_directory.length() < p_b.file_directory.length();
}
return p_a.file_name < p_b.file_name;
}
};
candidates.sort_custom<CandidateComparator>();
return num_relevant_candidates;
}
void QuickOpenResultContainer::_update_result_items(int p_new_visible_results_count, int p_new_selection_index) {
List<Candidate> *type_history = nullptr;
showing_history = false;
if (query.is_empty()) {
if (candidates.size() <= SHOW_ALL_FILES_THRESHOLD) {
p_new_visible_results_count = candidates.size();
} else {
p_new_visible_results_count = 0;
if (base_types.size() == 1) {
type_history = selected_history.lookup_ptr(base_types[0]);
if (type_history) {
p_new_visible_results_count = type_history->size();
showing_history = true;
}
}
}
}
// Only need to update items that were not hidden in previous update.
int num_items_needing_updates = MAX(num_visible_results, p_new_visible_results_count);
num_visible_results = p_new_visible_results_count;
for (int i = 0; i < num_items_needing_updates; i++) {
QuickOpenResultItem *item = result_items[i];
if (i < num_visible_results) {
if (type_history) {
const Candidate &c = type_history->get(i);
item->set_content(c.thumbnail, c.file_name, c.file_directory);
} else {
const Candidate &c = candidates[i];
item->set_content(c.thumbnail, c.file_name, c.file_directory);
}
} else {
item->reset();
}
};
const bool any_results = num_visible_results > 0;
_select_item(any_results ? p_new_selection_index : -1);
scroll_container->set_visible(any_results);
no_results_container->set_visible(!any_results);
if (!any_results) {
if (candidates.is_empty()) {
no_results_label->set_text(TTR("No files found for this type"));
} else if (query.is_empty()) {
no_results_label->set_text(TTR("Start searching to find files..."));
} else {
no_results_label->set_text(TTR("No results found"));
}
}
}
void QuickOpenResultContainer::handle_search_box_input(const Ref<InputEvent> &p_ie) {
if (num_visible_results < 0) {
return;
}
Ref<InputEventKey> key_event = p_ie;
if (key_event.is_valid() && key_event->is_pressed()) {
bool move_selection = false;
switch (key_event->get_keycode()) {
case Key::UP:
case Key::DOWN:
case Key::PAGEUP:
case Key::PAGEDOWN: {
move_selection = true;
} break;
case Key::LEFT:
case Key::RIGHT: {
// Both grid and the search box use left/right keys. By default, grid will take it.
// It would be nice if we could check for ALT to give the event to the searchbox cursor.
// However, if you press ALT, the searchbox also denies the input.
move_selection = (content_display_mode == QuickOpenDisplayMode::GRID);
} break;
default:
break; // Let the event through so it will reach the search box.
}
if (move_selection) {
_move_selection_index(key_event->get_keycode());
queue_redraw();
accept_event();
}
}
}
void QuickOpenResultContainer::_move_selection_index(Key p_key) {
const int max_index = num_visible_results - 1;
int idx = selection_index;
if (content_display_mode == QuickOpenDisplayMode::LIST) {
if (p_key == Key::UP) {
idx = (idx == 0) ? max_index : (idx - 1);
} else if (p_key == Key::DOWN) {
idx = (idx == max_index) ? 0 : (idx + 1);
} else if (p_key == Key::PAGEUP) {
idx = (idx == 0) ? idx : MAX(idx - 10, 0);
} else if (p_key == Key::PAGEDOWN) {
idx = (idx == max_index) ? idx : MIN(idx + 10, max_index);
}
} else {
int column_count = grid->get_line_max_child_count();
if (p_key == Key::LEFT) {
idx = (idx == 0) ? max_index : (idx - 1);
} else if (p_key == Key::RIGHT) {
idx = (idx == max_index) ? 0 : (idx + 1);
} else if (p_key == Key::UP) {
idx = (idx == 0) ? max_index : MAX(idx - column_count, 0);
} else if (p_key == Key::DOWN) {
idx = (idx == max_index) ? 0 : MIN(idx + column_count, max_index);
} else if (p_key == Key::PAGEUP) {
idx = (idx == 0) ? idx : MAX(idx - (3 * column_count), 0);
} else if (p_key == Key::PAGEDOWN) {
idx = (idx == max_index) ? idx : MIN(idx + (3 * column_count), max_index);
}
}
_select_item(idx);
}
void QuickOpenResultContainer::_select_item(int p_index) {
if (!has_nothing_selected()) {
result_items[selection_index]->highlight_item(false);
}
selection_index = p_index;
if (has_nothing_selected()) {
file_details_path->set_text("");
return;
}
result_items[selection_index]->highlight_item(true);
file_details_path->set_text(get_selected() + (showing_history ? TTR(" (recently opened)") : ""));
const QuickOpenResultItem *item = result_items[selection_index];
// Copied from Tree.
const int selected_position = item->get_position().y;
const int selected_size = item->get_size().y;
const int scroll_window_size = scroll_container->get_size().y;
const int scroll_position = scroll_container->get_v_scroll();
if (selected_position <= scroll_position) {
scroll_container->set_v_scroll(selected_position);
} else if (selected_position + selected_size > scroll_position + scroll_window_size) {
scroll_container->set_v_scroll(selected_position + selected_size - scroll_window_size);
}
}
void QuickOpenResultContainer::_item_input(const Ref<InputEvent> &p_ev, int p_index) {
Ref<InputEventMouseButton> mb = p_ev;
if (mb.is_valid() && mb->is_pressed() && mb->get_button_index() == MouseButton::LEFT) {
_select_item(p_index);
emit_signal(SNAME("result_clicked"));
}
}
void QuickOpenResultContainer::_toggle_include_addons(bool p_pressed) {
EditorSettings::get_singleton()->set("filesystem/quick_open_dialog/include_addons", p_pressed);
cleanup();
_create_initial_results(p_pressed);
}
void QuickOpenResultContainer::_toggle_display_mode() {
QuickOpenDisplayMode new_display_mode = (content_display_mode == QuickOpenDisplayMode::LIST) ? QuickOpenDisplayMode::GRID : QuickOpenDisplayMode::LIST;
_set_display_mode(new_display_mode);
}
void QuickOpenResultContainer::_set_display_mode(QuickOpenDisplayMode p_display_mode) {
content_display_mode = p_display_mode;
const bool first_time = !list->is_visible() && !grid->is_visible();
if (!first_time) {
const bool show_list = (content_display_mode == QuickOpenDisplayMode::LIST);
if ((show_list && list->is_visible()) || (!show_list && grid->is_visible())) {
return;
}
}
hide();
// Move result item nodes from one container to the other.
CanvasItem *prev_root;
CanvasItem *next_root;
if (content_display_mode == QuickOpenDisplayMode::LIST) {
prev_root = Object::cast_to<CanvasItem>(grid);
next_root = Object::cast_to<CanvasItem>(list);
} else {
prev_root = Object::cast_to<CanvasItem>(list);
next_root = Object::cast_to<CanvasItem>(grid);
}
prev_root->hide();
for (QuickOpenResultItem *item : result_items) {
item->set_display_mode(content_display_mode);
if (!first_time) {
prev_root->remove_child(item);
}
next_root->add_child(item);
}
next_root->show();
show();
_update_result_items(num_visible_results, selection_index);
if (content_display_mode == QuickOpenDisplayMode::LIST) {
display_mode_toggle->set_icon(get_editor_theme_icon(SNAME("FileThumbnail")));
display_mode_toggle->set_tooltip_text(TTR("Grid view"));
} else {
display_mode_toggle->set_icon(get_editor_theme_icon(SNAME("FileList")));
display_mode_toggle->set_tooltip_text(TTR("List view"));
}
}
bool QuickOpenResultContainer::has_nothing_selected() const {
return selection_index < 0;
}
String QuickOpenResultContainer::get_selected() const {
ERR_FAIL_COND_V_MSG(has_nothing_selected(), String(), "Tried to get selected file, but nothing was selected.");
if (showing_history) {
const List<Candidate> *type_history = selected_history.lookup_ptr(base_types[0]);
const Candidate &c = type_history->get(selection_index);
return c.file_directory.path_join(c.file_name);
} else {
const Candidate &c = candidates[selection_index];
return c.file_directory.path_join(c.file_name);
}
}
QuickOpenDisplayMode QuickOpenResultContainer::get_adaptive_display_mode(const Vector<StringName> &p_base_types) {
static const Vector<StringName> grid_preferred_types = {
"Font",
"Texture2D",
"Material",
"Mesh"
};
for (const StringName &type : grid_preferred_types) {
for (const StringName &base_type : p_base_types) {
if (base_type == type || ClassDB::is_parent_class(base_type, type))
return QuickOpenDisplayMode::GRID;
}
}
return QuickOpenDisplayMode::LIST;
}
void QuickOpenResultContainer::save_selected_item() {
if (base_types.size() > 1) {
// Getting the type of the file and checking which base type it belongs to should be possible.
// However, for now these are not supported, and we don't record this.
return;
}
if (showing_history) {
// Selecting from history, so already added.
return;
}
const StringName &base_type = base_types[0];
List<Candidate> *type_history = selected_history.lookup_ptr(base_type);
if (!type_history) {
selected_history.insert(base_type, List<Candidate>());
type_history = selected_history.lookup_ptr(base_type);
} else {
const Candidate &selected = candidates[selection_index];
for (const Candidate &candidate : *type_history) {
if (candidate.file_directory == selected.file_directory && candidate.file_name == selected.file_name) {
return;
}
}
if (type_history->size() > 8) {
type_history->pop_back();
}
}
type_history->push_front(candidates[selection_index]);
}
void QuickOpenResultContainer::cleanup() {
num_visible_results = 0;
candidates.clear();
_select_item(-1);
for (QuickOpenResultItem *item : result_items) {
item->reset();
}
}
void QuickOpenResultContainer::_notification(int p_what) {
switch (p_what) {
case NOTIFICATION_THEME_CHANGED: {
Color text_color = get_theme_color("font_readonly_color", EditorStringName(Editor));
file_details_path->add_theme_color_override(SceneStringName(font_color), text_color);
no_results_label->add_theme_color_override(SceneStringName(font_color), text_color);
panel_container->add_theme_style_override(SceneStringName(panel), get_theme_stylebox(SNAME("QuickOpenBackgroundPanel"), EditorStringName(EditorStyles)));
if (content_display_mode == QuickOpenDisplayMode::LIST) {
display_mode_toggle->set_icon(get_editor_theme_icon(SNAME("FileThumbnail")));
} else {
display_mode_toggle->set_icon(get_editor_theme_icon(SNAME("FileList")));
}
} break;
}
}
void QuickOpenResultContainer::_bind_methods() {
ADD_SIGNAL(MethodInfo("result_clicked"));
}
//------------------------- Result Item
QuickOpenResultItem::QuickOpenResultItem() {
set_focus_mode(FocusMode::FOCUS_ALL);
_set_enabled(false);
set_default_cursor_shape(CURSOR_POINTING_HAND);
list_item = memnew(QuickOpenResultListItem);
list_item->hide();
add_child(list_item);
grid_item = memnew(QuickOpenResultGridItem);
grid_item->hide();
add_child(grid_item);
}
void QuickOpenResultItem::set_display_mode(QuickOpenDisplayMode p_display_mode) {
if (p_display_mode == QuickOpenDisplayMode::LIST) {
grid_item->hide();
list_item->show();
} else {
list_item->hide();
grid_item->show();
}
queue_redraw();
}
void QuickOpenResultItem::set_content(const Ref<Texture2D> &p_thumbnail, const String &p_file, const String &p_file_directory) {
_set_enabled(true);
if (list_item->is_visible()) {
list_item->set_content(p_thumbnail, p_file, p_file_directory);
} else {
grid_item->set_content(p_thumbnail, p_file);
}
}
void QuickOpenResultItem::reset() {
_set_enabled(false);
is_hovering = false;
is_selected = false;
if (list_item->is_visible()) {
list_item->reset();
} else {
grid_item->reset();
}
}
void QuickOpenResultItem::highlight_item(bool p_enabled) {
is_selected = p_enabled;
if (list_item->is_visible()) {
if (p_enabled) {
list_item->highlight_item(highlighted_font_color);
} else {
list_item->remove_highlight();
}
} else {
if (p_enabled) {
grid_item->highlight_item(highlighted_font_color);
} else {
grid_item->remove_highlight();
}
}
queue_redraw();
}
void QuickOpenResultItem::_set_enabled(bool p_enabled) {
set_visible(p_enabled);
set_process(p_enabled);
set_process_input(p_enabled);
}
void QuickOpenResultItem::_notification(int p_what) {
switch (p_what) {
case NOTIFICATION_MOUSE_ENTER:
case NOTIFICATION_MOUSE_EXIT: {
is_hovering = is_visible() && p_what == NOTIFICATION_MOUSE_ENTER;
queue_redraw();
} break;
case NOTIFICATION_THEME_CHANGED: {
selected_stylebox = get_theme_stylebox("selected", "Tree");
hovering_stylebox = get_theme_stylebox("hover", "Tree");
highlighted_font_color = get_theme_color("font_focus_color", EditorStringName(Editor));
} break;
case NOTIFICATION_DRAW: {
if (is_selected) {
draw_style_box(selected_stylebox, Rect2(Point2(), get_size()));
} else if (is_hovering) {
draw_style_box(hovering_stylebox, Rect2(Point2(), get_size()));
}
} break;
}
}
//----------------- List item
QuickOpenResultListItem::QuickOpenResultListItem() {
set_h_size_flags(Control::SIZE_EXPAND_FILL);
add_theme_constant_override("separation", 4 * EDSCALE);
{
image_container = memnew(MarginContainer);
image_container->add_theme_constant_override("margin_top", 2 * EDSCALE);
image_container->add_theme_constant_override("margin_bottom", 2 * EDSCALE);
image_container->add_theme_constant_override("margin_left", CONTAINER_MARGIN * EDSCALE);
image_container->add_theme_constant_override("margin_right", 0);
add_child(image_container);
thumbnail = memnew(TextureRect);
thumbnail->set_h_size_flags(Control::SIZE_SHRINK_CENTER);
thumbnail->set_v_size_flags(Control::SIZE_SHRINK_CENTER);
thumbnail->set_expand_mode(TextureRect::EXPAND_IGNORE_SIZE);
thumbnail->set_stretch_mode(TextureRect::StretchMode::STRETCH_SCALE);
image_container->add_child(thumbnail);
}
{
text_container = memnew(VBoxContainer);
text_container->add_theme_constant_override("separation", -6 * EDSCALE);
text_container->set_h_size_flags(Control::SIZE_EXPAND_FILL);
text_container->set_v_size_flags(Control::SIZE_FILL);
add_child(text_container);
name = memnew(Label);
name->set_h_size_flags(Control::SIZE_EXPAND_FILL);
name->set_text_overrun_behavior(TextServer::OVERRUN_TRIM_ELLIPSIS);
name->set_horizontal_alignment(HorizontalAlignment::HORIZONTAL_ALIGNMENT_LEFT);
text_container->add_child(name);
path = memnew(Label);
path->set_h_size_flags(Control::SIZE_EXPAND_FILL);
path->set_text_overrun_behavior(TextServer::OVERRUN_TRIM_ELLIPSIS);
path->add_theme_font_size_override(SceneStringName(font_size), 12 * EDSCALE);
text_container->add_child(path);
}
}
void QuickOpenResultListItem::set_content(const Ref<Texture2D> &p_thumbnail, const String &p_file, const String &p_file_directory) {
thumbnail->set_texture(p_thumbnail);
name->set_text(p_file);
path->set_text(p_file_directory);
const int max_size = 32 * EDSCALE;
bool uses_icon = p_thumbnail->get_width() < max_size;
if (uses_icon) {
thumbnail->set_custom_minimum_size(p_thumbnail->get_size());
int margin_needed = (max_size - p_thumbnail->get_width()) / 2;
image_container->add_theme_constant_override("margin_left", CONTAINER_MARGIN + margin_needed);
image_container->add_theme_constant_override("margin_right", margin_needed);
} else {
thumbnail->set_custom_minimum_size(Size2i(max_size, max_size));
image_container->add_theme_constant_override("margin_left", CONTAINER_MARGIN);
image_container->add_theme_constant_override("margin_right", 0);
}
}
void QuickOpenResultListItem::reset() {
name->set_text("");
thumbnail->set_texture(nullptr);
path->set_text("");
}
void QuickOpenResultListItem::highlight_item(const Color &p_color) {
name->add_theme_color_override(SceneStringName(font_color), p_color);
}
void QuickOpenResultListItem::remove_highlight() {
name->remove_theme_color_override(SceneStringName(font_color));
}
void QuickOpenResultListItem::_notification(int p_what) {
switch (p_what) {
case NOTIFICATION_THEME_CHANGED: {
path->add_theme_color_override(SceneStringName(font_color), get_theme_color("font_disabled_color", EditorStringName(Editor)));
} break;
}
}
//--------------- Grid Item
QuickOpenResultGridItem::QuickOpenResultGridItem() {
set_h_size_flags(Control::SIZE_FILL);
set_v_size_flags(Control::SIZE_EXPAND_FILL);
add_theme_constant_override("separation", -2 * EDSCALE);
thumbnail = memnew(TextureRect);
thumbnail->set_h_size_flags(Control::SIZE_SHRINK_CENTER);
thumbnail->set_v_size_flags(Control::SIZE_SHRINK_CENTER);
thumbnail->set_custom_minimum_size(Size2i(80 * EDSCALE, 64 * EDSCALE));
add_child(thumbnail);
name = memnew(Label);
name->set_h_size_flags(Control::SIZE_EXPAND_FILL);
name->set_text_overrun_behavior(TextServer::OVERRUN_TRIM_ELLIPSIS);
name->set_horizontal_alignment(HorizontalAlignment::HORIZONTAL_ALIGNMENT_CENTER);
name->add_theme_font_size_override(SceneStringName(font_size), 13 * EDSCALE);
add_child(name);
}
void QuickOpenResultGridItem::set_content(const Ref<Texture2D> &p_thumbnail, const String &p_file) {
thumbnail->set_texture(p_thumbnail);
const String &file_name = p_file.get_basename();
name->set_text(file_name);
name->set_tooltip_text(file_name);
bool uses_icon = p_thumbnail->get_width() < (32 * EDSCALE);
if (uses_icon || p_thumbnail->get_height() <= thumbnail->get_custom_minimum_size().y) {
thumbnail->set_expand_mode(TextureRect::EXPAND_KEEP_SIZE);
thumbnail->set_stretch_mode(TextureRect::StretchMode::STRETCH_KEEP_CENTERED);
} else {
thumbnail->set_expand_mode(TextureRect::EXPAND_FIT_WIDTH_PROPORTIONAL);
thumbnail->set_stretch_mode(TextureRect::StretchMode::STRETCH_SCALE);
}
}
void QuickOpenResultGridItem::reset() {
name->set_text("");
thumbnail->set_texture(nullptr);
}
void QuickOpenResultGridItem::highlight_item(const Color &p_color) {
name->add_theme_color_override(SceneStringName(font_color), p_color);
}
void QuickOpenResultGridItem::remove_highlight() {
name->remove_theme_color_override(SceneStringName(font_color));
}

View file

@ -0,0 +1,230 @@
/**************************************************************************/
/* editor_quick_open_dialog.h */
/**************************************************************************/
/* This file is part of: */
/* GODOT ENGINE */
/* https://godotengine.org */
/**************************************************************************/
/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
/* */
/* Permission is hereby granted, free of charge, to any person obtaining */
/* a copy of this software and associated documentation files (the */
/* "Software"), to deal in the Software without restriction, including */
/* without limitation the rights to use, copy, modify, merge, publish, */
/* distribute, sublicense, and/or sell copies of the Software, and to */
/* permit persons to whom the Software is furnished to do so, subject to */
/* the following conditions: */
/* */
/* The above copyright notice and this permission notice shall be */
/* included in all copies or substantial portions of the Software. */
/* */
/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */
/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
/**************************************************************************/
#ifndef EDITOR_QUICK_OPEN_DIALOG_H
#define EDITOR_QUICK_OPEN_DIALOG_H
#include "core/templates/oa_hash_map.h"
#include "scene/gui/dialogs.h"
class Button;
class CenterContainer;
class CheckButton;
class EditorFileSystemDirectory;
class LineEdit;
class HFlowContainer;
class MarginContainer;
class PanelContainer;
class ScrollContainer;
class StringName;
class Texture2D;
class TextureRect;
class VBoxContainer;
class QuickOpenResultItem;
enum class QuickOpenDisplayMode {
GRID,
LIST,
};
class QuickOpenResultContainer : public VBoxContainer {
GDCLASS(QuickOpenResultContainer, VBoxContainer)
public:
void init(const Vector<StringName> &p_base_types);
void handle_search_box_input(const Ref<InputEvent> &p_ie);
void update_results(const String &p_query);
bool has_nothing_selected() const;
String get_selected() const;
void save_selected_item();
void cleanup();
QuickOpenResultContainer();
~QuickOpenResultContainer();
protected:
void _notification(int p_what);
private:
static const int TOTAL_ALLOCATED_RESULT_ITEMS = 100;
static const int SHOW_ALL_FILES_THRESHOLD = 30;
struct Candidate {
String file_name;
String file_directory;
Ref<Texture2D> thumbnail;
float score = 0;
};
Vector<StringName> base_types;
Vector<Candidate> candidates;
OAHashMap<StringName, List<Candidate>> selected_history;
String query;
int selection_index = -1;
int num_visible_results = 0;
int max_total_results = 0;
bool showing_history = false;
QuickOpenDisplayMode content_display_mode = QuickOpenDisplayMode::LIST;
Vector<QuickOpenResultItem *> result_items;
ScrollContainer *scroll_container = nullptr;
VBoxContainer *list = nullptr;
HFlowContainer *grid = nullptr;
PanelContainer *panel_container = nullptr;
CenterContainer *no_results_container = nullptr;
Label *no_results_label = nullptr;
Label *file_details_path = nullptr;
Button *display_mode_toggle = nullptr;
CheckButton *include_addons_toggle = nullptr;
OAHashMap<StringName, Ref<Texture2D>> file_type_icons;
static QuickOpenDisplayMode get_adaptive_display_mode(const Vector<StringName> &p_base_types);
void _create_initial_results(bool p_include_addons);
void _find_candidates_in_folder(EditorFileSystemDirectory *p_directory, bool p_include_addons);
int _sort_candidates(const String &p_query);
void _update_result_items(int p_new_visible_results_count, int p_new_selection_index);
void _move_selection_index(Key p_key);
void _select_item(int p_index);
void _item_input(const Ref<InputEvent> &p_ev, int p_index);
void _set_display_mode(QuickOpenDisplayMode p_display_mode);
void _toggle_display_mode();
void _toggle_include_addons(bool p_pressed);
static void _bind_methods();
};
class QuickOpenResultGridItem : public VBoxContainer {
GDCLASS(QuickOpenResultGridItem, VBoxContainer)
public:
QuickOpenResultGridItem();
void set_content(const Ref<Texture2D> &p_thumbnail, const String &p_file_name);
void reset();
void highlight_item(const Color &p_color);
void remove_highlight();
private:
TextureRect *thumbnail = nullptr;
Label *name = nullptr;
};
class QuickOpenResultListItem : public HBoxContainer {
GDCLASS(QuickOpenResultListItem, HBoxContainer)
public:
QuickOpenResultListItem();
void set_content(const Ref<Texture2D> &p_thumbnail, const String &p_file_name, const String &p_file_directory);
void reset();
void highlight_item(const Color &p_color);
void remove_highlight();
protected:
void _notification(int p_what);
private:
static const int CONTAINER_MARGIN = 8;
MarginContainer *image_container = nullptr;
VBoxContainer *text_container = nullptr;
TextureRect *thumbnail = nullptr;
Label *name = nullptr;
Label *path = nullptr;
};
class QuickOpenResultItem : public HBoxContainer {
GDCLASS(QuickOpenResultItem, HBoxContainer)
public:
QuickOpenResultItem();
void set_content(const Ref<Texture2D> &p_thumbnail, const String &p_file_name, const String &p_file_directory);
void set_display_mode(QuickOpenDisplayMode p_display_mode);
void reset();
void highlight_item(bool p_enabled);
protected:
void _notification(int p_what);
private:
QuickOpenResultListItem *list_item = nullptr;
QuickOpenResultGridItem *grid_item = nullptr;
Ref<StyleBox> selected_stylebox;
Ref<StyleBox> hovering_stylebox;
Color highlighted_font_color;
bool is_hovering = false;
bool is_selected = false;
void _set_enabled(bool p_enabled);
};
class EditorQuickOpenDialog : public AcceptDialog {
GDCLASS(EditorQuickOpenDialog, AcceptDialog);
public:
void popup_dialog(const Vector<StringName> &p_base_types, const Callable &p_item_selected_callback);
EditorQuickOpenDialog();
protected:
virtual void cancel_pressed() override;
virtual void ok_pressed() override;
private:
static String get_dialog_title(const Vector<StringName> &p_base_types);
LineEdit *search_box = nullptr;
QuickOpenResultContainer *container = nullptr;
Callable item_selected_callback;
void _search_box_text_changed(const String &p_query);
};
#endif // EDITOR_QUICK_OPEN_DIALOG_H

View file

@ -34,10 +34,10 @@
#include "editor/debugger/editor_debugger_node.h"
#include "editor/editor_command_palette.h"
#include "editor/editor_node.h"
#include "editor/editor_quick_open.h"
#include "editor/editor_run_native.h"
#include "editor/editor_settings.h"
#include "editor/editor_string_names.h"
#include "editor/gui/editor_quick_open_dialog.h"
#include "scene/gui/box_container.h"
#include "scene/gui/button.h"
#include "scene/gui/panel_container.h"
@ -121,16 +121,15 @@ void EditorRunBar::_write_movie_toggled(bool p_enabled) {
}
}
void EditorRunBar::_quick_run_selected() {
play_custom_scene(quick_run->get_selected());
void EditorRunBar::_quick_run_selected(const String &p_file_path) {
play_custom_scene(p_file_path);
}
void EditorRunBar::_play_custom_pressed() {
if (editor_run.get_status() == EditorRun::STATUS_STOP || current_mode != RunMode::RUN_CUSTOM) {
stop_playing();
quick_run->popup_dialog("PackedScene", true);
quick_run->set_title(TTR("Quick Run Scene..."));
EditorNode::get_singleton()->get_quick_open_dialog()->popup_dialog({ "PackedScene" }, callable_mp(this, &EditorRunBar::_quick_run_selected));
play_custom_scene_button->set_pressed(false);
} else {
// Reload if already running a custom scene.
@ -446,8 +445,4 @@ EditorRunBar::EditorRunBar() {
write_movie_button->set_focus_mode(Control::FOCUS_NONE);
write_movie_button->set_tooltip_text(TTR("Enable Movie Maker mode.\nThe project will run at stable FPS and the visual and audio output will be recorded to a video file."));
write_movie_button->connect(SceneStringName(toggled), callable_mp(this, &EditorRunBar::_write_movie_toggled));
quick_run = memnew(EditorQuickOpen);
add_child(quick_run);
quick_run->connect("quick_open", callable_mp(this, &EditorRunBar::_quick_run_selected));
}

View file

@ -37,7 +37,6 @@
class Button;
class EditorRunNative;
class EditorQuickOpen;
class PanelContainer;
class HBoxContainer;
@ -68,8 +67,6 @@ class EditorRunBar : public MarginContainer {
PanelContainer *write_movie_panel = nullptr;
Button *write_movie_button = nullptr;
EditorQuickOpen *quick_run = nullptr;
RunMode current_mode = RunMode::STOPPED;
String run_custom_filename;
String run_current_filename;
@ -78,7 +75,7 @@ class EditorRunBar : public MarginContainer {
void _update_play_buttons();
void _write_movie_toggled(bool p_enabled);
void _quick_run_selected();
void _quick_run_selected(const String &p_file_path);
void _play_current_pressed();
void _play_custom_pressed();

View file

@ -37,15 +37,16 @@
#include "core/os/keyboard.h"
#include "editor/debugger/editor_debugger_node.h"
#include "editor/editor_feature_profile.h"
#include "editor/editor_file_system.h"
#include "editor/editor_main_screen.h"
#include "editor/editor_node.h"
#include "editor/editor_paths.h"
#include "editor/editor_quick_open.h"
#include "editor/editor_settings.h"
#include "editor/editor_string_names.h"
#include "editor/editor_undo_redo_manager.h"
#include "editor/filesystem_dock.h"
#include "editor/gui/editor_file_dialog.h"
#include "editor/gui/editor_quick_open_dialog.h"
#include "editor/inspector_dock.h"
#include "editor/multi_node_edit.h"
#include "editor/node_dock.h"
@ -73,8 +74,8 @@ void SceneTreeDock::_nodes_drag_begin() {
pending_click_select = nullptr;
}
void SceneTreeDock::_quick_open() {
instantiate_scenes(quick_open->get_selected_files(), scene_tree->get_selected());
void SceneTreeDock::_quick_open(const String &p_file_path) {
instantiate_scenes({ p_file_path }, scene_tree->get_selected());
}
void SceneTreeDock::_inspect_hovered_node() {
@ -609,8 +610,7 @@ void SceneTreeDock::_tool_selected(int p_tool, bool p_confirm_override) {
break;
}
quick_open->popup_dialog("PackedScene", true);
quick_open->set_title(TTR("Instantiate Child Scene"));
EditorNode::get_singleton()->get_quick_open_dialog()->popup_dialog({ "PackedScene" }, callable_mp(this, &SceneTreeDock::_quick_open));
if (!p_confirm_override) {
emit_signal(SNAME("add_node_used"));
}
@ -4694,10 +4694,6 @@ SceneTreeDock::SceneTreeDock(Node *p_scene_root, EditorSelection *p_editor_selec
accept = memnew(AcceptDialog);
add_child(accept);
quick_open = memnew(EditorQuickOpen);
add_child(quick_open);
quick_open->connect("quick_open", callable_mp(this, &SceneTreeDock::_quick_open));
set_process_shortcut_input(true);
delete_dialog = memnew(ConfirmationDialog);

View file

@ -39,7 +39,6 @@
class CheckBox;
class EditorData;
class EditorSelection;
class EditorQuickOpen;
class MenuButton;
class ReparentDialog;
class ShaderCreateDialog;
@ -159,7 +158,6 @@ class SceneTreeDock : public VBoxContainer {
ConfirmationDialog *placeholder_editable_instance_remove_dialog = nullptr;
ReparentDialog *reparent_dialog = nullptr;
EditorQuickOpen *quick_open = nullptr;
EditorFileDialog *new_scene_from_dialog = nullptr;
enum FilterMenuItems {
@ -267,7 +265,7 @@ class SceneTreeDock : public VBoxContainer {
void _nodes_dragged(const Array &p_nodes, NodePath p_to, int p_type);
void _files_dropped(const Vector<String> &p_files, NodePath p_to, int p_type);
void _script_dropped(const String &p_file, NodePath p_to);
void _quick_open();
void _quick_open(const String &p_file_path);
void _tree_rmb(const Vector2 &p_menu_pos);
void _update_tree_menu();

View file

@ -261,6 +261,7 @@ void FlowContainer::_resort() {
}
cached_size = (vertical ? ofs.x : ofs.y) + line_height;
cached_line_count = lines_data.size();
cached_line_max_child_count = lines_data.size() > 0 ? lines_data[0].child_count : 0;
}
Size2 FlowContainer::get_minimum_size() const {
@ -339,6 +340,10 @@ int FlowContainer::get_line_count() const {
return cached_line_count;
}
int FlowContainer::get_line_max_child_count() const {
return cached_line_max_child_count;
}
void FlowContainer::set_alignment(AlignmentMode p_alignment) {
if (alignment == p_alignment) {
return;

View file

@ -52,6 +52,8 @@ public:
private:
int cached_size = 0;
int cached_line_count = 0;
int cached_line_max_child_count = 0;
int cached_items_on_last_row = 0;
bool vertical = false;
bool reverse_fill = false;
@ -74,6 +76,7 @@ protected:
public:
int get_line_count() const;
int get_line_max_child_count() const;
void set_alignment(AlignmentMode p_alignment);
AlignmentMode get_alignment() const;