diff --git a/doc/classes/EditorSettings.xml b/doc/classes/EditorSettings.xml index 748a62111449..a5097521dc45 100644 --- a/doc/classes/EditorSettings.xml +++ b/doc/classes/EditorSettings.xml @@ -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. + + 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. + + + If [code]true[/code], results will include files located in the [code]addons[/code] folder. + 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]. diff --git a/editor/editor_node.cpp b/editor/editor_node.cpp index 3bae9ae984e2..2853ebc4994c 100644 --- a/editor/editor_node.cpp +++ b/editor/editor_node.cpp @@ -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 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); - } else { - load_resource(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(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(); diff --git a/editor/editor_node.h b/editor/editor_node.h index 36332e3d782b..55caed4bb415 100644 --- a/editor/editor_node.h +++ b/editor/editor_node.h @@ -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 @@ class EditorNode : public Node { 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 @@ class EditorNode : public Node { void _inherit_request(String p_file); void _instantiate_request(const Vector &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 @@ class EditorNode : public Node { Dictionary drag_resource(const Ref &p_res, Control *p_from); Dictionary drag_files_and_dirs(const Vector &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); diff --git a/editor/editor_quick_open.cpp b/editor/editor_quick_open.cpp deleted file mode 100644 index bd30fc28d113..000000000000 --- a/editor/editor_quick_open.cpp +++ /dev/null @@ -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 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 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 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 &p_event) { - // Redirect navigational key events to the tree. - Ref 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 EditorQuickOpen::get_selected_files() const { - Vector 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); -} diff --git a/editor/editor_quick_open.h b/editor/editor_quick_open.h deleted file mode 100644 index 815cc0c8fe9b..000000000000 --- a/editor/editor_quick_open.h +++ /dev/null @@ -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 files; - OAHashMap> 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 &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 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 diff --git a/editor/editor_resource_picker.cpp b/editor/editor_resource_picker.cpp index a81db5fdaa16..e4ae2a620277 100644 --- a/editor/editor_resource_picker.cpp +++ b/editor/editor_resource_picker.cpp @@ -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 &base_types_string = base_type.split(","); + + Vector 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: { diff --git a/editor/editor_resource_picker.h b/editor/editor_resource_picker.h index 05e392da2c77..c39d9af764f7 100644 --- a/editor/editor_resource_picker.h +++ b/editor/editor_resource_picker.h @@ -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 &p_preview, const Ref &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); diff --git a/editor/editor_resource_preview.cpp b/editor/editor_resource_preview.cpp index a50b2f3dccc8..581a01a5ed0b 100644 --- a/editor/editor_resource_preview.cpp +++ b/editor/editor_resource_preview.cpp @@ -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::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 &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()); diff --git a/editor/editor_resource_preview.h b/editor/editor_resource_preview.h index 57b6e4cedb44..88cd753a5837 100644 --- a/editor/editor_resource_preview.h +++ b/editor/editor_resource_preview.h @@ -128,12 +128,19 @@ class EditorResourcePreview : public Node { public: static EditorResourcePreview *get_singleton(); + struct PreviewItem { + Ref preview; + Ref small_preview; + }; + // p_receiver_func callback has signature (String p_path, Ref p_preview, Ref 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 &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 &p_generator); void remove_preview_generator(const Ref &p_generator); void check_for_invalidation(const String &p_path); diff --git a/editor/editor_settings.cpp b/editor/editor_settings.cpp index 6e8312e8c55a..ceaffb64c498 100644 --- a/editor/editor_settings.cpp +++ b/editor/editor_settings.cpp @@ -602,6 +602,10 @@ void EditorSettings::_load_defaults(Ref 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) diff --git a/editor/gui/editor_quick_open_dialog.cpp b/editor/gui/editor_quick_open_dialog.cpp new file mode 100644 index 000000000000..d24d37b302fa --- /dev/null +++ b/editor/gui/editor_quick_open_dialog.cpp @@ -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 &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 &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 &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(); + + return num_relevant_candidates; +} + +void QuickOpenResultContainer::_update_result_items(int p_new_visible_results_count, int p_new_selection_index) { + List *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 &p_ie) { + if (num_visible_results < 0) { + return; + } + + Ref 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 &p_ev, int p_index) { + Ref 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(grid); + next_root = Object::cast_to(list); + } else { + prev_root = Object::cast_to(list); + next_root = Object::cast_to(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 *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 &p_base_types) { + static const Vector 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 *type_history = selected_history.lookup_ptr(base_type); + if (!type_history) { + selected_history.insert(base_type, List()); + 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 &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 &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 &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)); +} diff --git a/editor/gui/editor_quick_open_dialog.h b/editor/gui/editor_quick_open_dialog.h new file mode 100644 index 000000000000..d2177375dc16 --- /dev/null +++ b/editor/gui/editor_quick_open_dialog.h @@ -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 &p_base_types); + void handle_search_box_input(const Ref &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 thumbnail; + float score = 0; + }; + + Vector base_types; + Vector candidates; + + OAHashMap> 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 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> file_type_icons; + + static QuickOpenDisplayMode get_adaptive_display_mode(const Vector &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 &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 &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 &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 &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 selected_stylebox; + Ref 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 &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 &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 diff --git a/editor/gui/editor_run_bar.cpp b/editor/gui/editor_run_bar.cpp index 9050ee0cd4b9..908b1e671953 100644 --- a/editor/gui/editor_run_bar.cpp +++ b/editor/gui/editor_run_bar.cpp @@ -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)); } diff --git a/editor/gui/editor_run_bar.h b/editor/gui/editor_run_bar.h index 1cb999612a0c..d8238aa2c541 100644 --- a/editor/gui/editor_run_bar.h +++ b/editor/gui/editor_run_bar.h @@ -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(); diff --git a/editor/scene_tree_dock.cpp b/editor/scene_tree_dock.cpp index 82fb0865d258..bcab0c2883c7 100644 --- a/editor/scene_tree_dock.cpp +++ b/editor/scene_tree_dock.cpp @@ -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); diff --git a/editor/scene_tree_dock.h b/editor/scene_tree_dock.h index d0cdaf8dd14b..05ad0f36e486 100644 --- a/editor/scene_tree_dock.h +++ b/editor/scene_tree_dock.h @@ -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 &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(); diff --git a/scene/gui/flow_container.cpp b/scene/gui/flow_container.cpp index d32c75fbcdcf..90d5b6b36d4f 100644 --- a/scene/gui/flow_container.cpp +++ b/scene/gui/flow_container.cpp @@ -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; diff --git a/scene/gui/flow_container.h b/scene/gui/flow_container.h index 65ebc89c7844..6a00e5b0e54a 100644 --- a/scene/gui/flow_container.h +++ b/scene/gui/flow_container.h @@ -52,6 +52,8 @@ class FlowContainer : public Container { 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 @@ class FlowContainer : public Container { public: int get_line_count() const; + int get_line_max_child_count() const; void set_alignment(AlignmentMode p_alignment); AlignmentMode get_alignment() const;