summaryrefslogtreecommitdiffstats
path: root/editor
diff options
context:
space:
mode:
Diffstat (limited to 'editor')
-rw-r--r--editor/animation_track_editor.cpp22
-rw-r--r--editor/debugger/script_editor_debugger.cpp10
-rw-r--r--editor/debugger/script_editor_debugger.h2
-rw-r--r--editor/editor_audio_buses.cpp20
-rw-r--r--editor/editor_feature_profile.cpp6
-rw-r--r--editor/editor_inspector.cpp402
-rw-r--r--editor/editor_inspector.h40
-rw-r--r--editor/editor_run.cpp5
-rw-r--r--editor/editor_settings.cpp50
-rw-r--r--editor/editor_settings.h3
-rw-r--r--editor/filesystem_dock.cpp28
-rw-r--r--editor/filesystem_dock.h2
-rw-r--r--editor/gui/editor_bottom_panel.cpp42
-rw-r--r--editor/gui/editor_bottom_panel.h7
-rw-r--r--editor/gui/editor_quick_open_dialog.cpp582
-rw-r--r--editor/gui/editor_quick_open_dialog.h78
-rw-r--r--editor/gui/editor_toaster.cpp65
-rw-r--r--editor/gui/editor_toaster.h7
-rw-r--r--editor/gui/scene_tree_editor.cpp37
-rw-r--r--editor/gui/scene_tree_editor.h2
-rw-r--r--editor/icons/Unfavorite.svg1
-rw-r--r--editor/import/3d/resource_importer_obj.cpp4
-rw-r--r--editor/import/3d/resource_importer_obj.h3
-rw-r--r--editor/import/3d/resource_importer_scene.cpp5
-rw-r--r--editor/import/3d/resource_importer_scene.h2
-rw-r--r--editor/import/3d/scene_import_settings.cpp1
-rw-r--r--editor/import/audio_stream_import_settings.cpp4
-rw-r--r--editor/import/resource_importer_bitmask.h2
-rw-r--r--editor/import/resource_importer_bmfont.h2
-rw-r--r--editor/import/resource_importer_csv_translation.h2
-rw-r--r--editor/import/resource_importer_dynamic_font.h2
-rw-r--r--editor/import/resource_importer_image.h2
-rw-r--r--editor/import/resource_importer_imagefont.h2
-rw-r--r--editor/import/resource_importer_layered_texture.h2
-rw-r--r--editor/import/resource_importer_shader_file.h2
-rw-r--r--editor/import/resource_importer_texture.h2
-rw-r--r--editor/import/resource_importer_texture_atlas.h2
-rw-r--r--editor/import/resource_importer_wav.cpp4
-rw-r--r--editor/import/resource_importer_wav.h2
-rw-r--r--editor/plugins/game_view_plugin.cpp11
-rw-r--r--editor/plugins/gdextension_export_plugin.h45
-rw-r--r--editor/plugins/mesh_instance_3d_editor_plugin.cpp44
-rw-r--r--editor/plugins/mesh_instance_3d_editor_plugin.h2
-rw-r--r--editor/plugins/node_3d_editor_plugin.cpp2
-rw-r--r--editor/plugins/visual_shader_editor_plugin.cpp2
-rw-r--r--editor/scene_tree_dock.cpp3
46 files changed, 1133 insertions, 432 deletions
diff --git a/editor/animation_track_editor.cpp b/editor/animation_track_editor.cpp
index c210ad0761..bc79b14d4a 100644
--- a/editor/animation_track_editor.cpp
+++ b/editor/animation_track_editor.cpp
@@ -5297,6 +5297,28 @@ void AnimationTrackEditor::_add_track(int p_type) {
return;
}
adding_track_type = p_type;
+ Vector<StringName> valid_types;
+ switch (adding_track_type) {
+ case Animation::TYPE_BLEND_SHAPE: {
+ // Blend Shape is a property of MeshInstance3D.
+ valid_types.push_back(SNAME("MeshInstance3D"));
+ } break;
+ case Animation::TYPE_POSITION_3D:
+ case Animation::TYPE_ROTATION_3D:
+ case Animation::TYPE_SCALE_3D: {
+ // 3D Properties come from nodes inheriting Node3D.
+ valid_types.push_back(SNAME("Node3D"));
+ } break;
+ case Animation::TYPE_AUDIO: {
+ valid_types.push_back(SNAME("AudioStreamPlayer"));
+ valid_types.push_back(SNAME("AudioStreamPlayer2D"));
+ valid_types.push_back(SNAME("AudioStreamPlayer3D"));
+ } break;
+ case Animation::TYPE_ANIMATION: {
+ valid_types.push_back(SNAME("AnimationPlayer"));
+ } break;
+ }
+ pick_track->set_valid_types(valid_types);
pick_track->popup_scenetree_dialog(nullptr, root_node);
pick_track->get_filter_line_edit()->clear();
pick_track->get_filter_line_edit()->grab_focus();
diff --git a/editor/debugger/script_editor_debugger.cpp b/editor/debugger/script_editor_debugger.cpp
index b78aad1721..da59450dd0 100644
--- a/editor/debugger/script_editor_debugger.cpp
+++ b/editor/debugger/script_editor_debugger.cpp
@@ -1033,6 +1033,9 @@ void ScriptEditorDebugger::start(Ref<RemoteDebuggerPeer> p_peer) {
_update_buttons_state();
emit_signal(SNAME("started"));
+ Array quit_keys = DebuggerMarshalls::serialize_key_shortcut(ED_GET_SHORTCUT("editor/stop_running_project"));
+ _put_msg("scene:setup_scene", quit_keys);
+
if (EditorSettings::get_singleton()->get_project_metadata("debug_options", "autostart_profiler", false)) {
profiler->set_profiling(true);
}
@@ -1170,6 +1173,12 @@ String ScriptEditorDebugger::get_var_value(const String &p_var) const {
return inspector->get_stack_variable(p_var);
}
+void ScriptEditorDebugger::_resources_reimported(const PackedStringArray &p_resources) {
+ Array msg;
+ msg.push_back(p_resources);
+ _put_msg("scene:reload_cached_files", msg);
+}
+
int ScriptEditorDebugger::_get_node_path_cache(const NodePath &p_path) {
const int *r = node_path_cache.getptr(p_path);
if (r) {
@@ -1818,6 +1827,7 @@ ScriptEditorDebugger::ScriptEditorDebugger() {
tabs->connect("tab_changed", callable_mp(this, &ScriptEditorDebugger::_tab_changed));
InspectorDock::get_inspector_singleton()->connect("object_id_selected", callable_mp(this, &ScriptEditorDebugger::_remote_object_selected));
+ EditorFileSystem::get_singleton()->connect("resources_reimported", callable_mp(this, &ScriptEditorDebugger::_resources_reimported));
{ //debugger
VBoxContainer *vbc = memnew(VBoxContainer);
diff --git a/editor/debugger/script_editor_debugger.h b/editor/debugger/script_editor_debugger.h
index 1908b1e5a7..06a968e141 100644
--- a/editor/debugger/script_editor_debugger.h
+++ b/editor/debugger/script_editor_debugger.h
@@ -198,6 +198,8 @@ private:
void _video_mem_request();
void _video_mem_export();
+ void _resources_reimported(const PackedStringArray &p_resources);
+
int _get_node_path_cache(const NodePath &p_path);
int _get_res_path_cache(const String &p_path);
diff --git a/editor/editor_audio_buses.cpp b/editor/editor_audio_buses.cpp
index 084ecf7229..0649272216 100644
--- a/editor/editor_audio_buses.cpp
+++ b/editor/editor_audio_buses.cpp
@@ -91,17 +91,26 @@ void EditorAudioBus::_notification(int p_what) {
Color mute_color = EditorThemeManager::is_dark_theme() ? Color(1.0, 0.16, 0.16) : Color(2.35, 1.03, 1.03);
Color bypass_color = EditorThemeManager::is_dark_theme() ? Color(0.13, 0.8, 1.0) : Color(1.03, 2.04, 2.35);
float darkening_factor = EditorThemeManager::is_dark_theme() ? 0.15 : 0.65;
+ Color solo_color_darkened = solo_color.darkened(darkening_factor);
+ Color mute_color_darkened = mute_color.darkened(darkening_factor);
+ Color bypass_color_darkened = bypass_color.darkened(darkening_factor);
- Ref<StyleBoxFlat>(solo->get_theme_stylebox(SceneStringName(pressed)))->set_border_color(solo_color.darkened(darkening_factor));
- Ref<StyleBoxFlat>(mute->get_theme_stylebox(SceneStringName(pressed)))->set_border_color(mute_color.darkened(darkening_factor));
- Ref<StyleBoxFlat>(bypass->get_theme_stylebox(SceneStringName(pressed)))->set_border_color(bypass_color.darkened(darkening_factor));
+ Ref<StyleBoxFlat>(solo->get_theme_stylebox(SceneStringName(pressed)))->set_border_color(solo_color_darkened);
+ Ref<StyleBoxFlat>(mute->get_theme_stylebox(SceneStringName(pressed)))->set_border_color(mute_color_darkened);
+ Ref<StyleBoxFlat>(bypass->get_theme_stylebox(SceneStringName(pressed)))->set_border_color(bypass_color_darkened);
+ Ref<StyleBoxFlat>(solo->get_theme_stylebox("hover_pressed"))->set_border_color(solo_color_darkened);
+ Ref<StyleBoxFlat>(mute->get_theme_stylebox("hover_pressed"))->set_border_color(mute_color_darkened);
+ Ref<StyleBoxFlat>(bypass->get_theme_stylebox("hover_pressed"))->set_border_color(bypass_color_darkened);
solo->set_button_icon(get_editor_theme_icon(SNAME("AudioBusSolo")));
solo->add_theme_color_override("icon_pressed_color", solo_color);
+ solo->add_theme_color_override("icon_hover_pressed_color", solo_color_darkened);
mute->set_button_icon(get_editor_theme_icon(SNAME("AudioBusMute")));
mute->add_theme_color_override("icon_pressed_color", mute_color);
+ mute->add_theme_color_override("icon_hover_pressed_color", mute_color_darkened);
bypass->set_button_icon(get_editor_theme_icon(SNAME("AudioBusBypass")));
bypass->add_theme_color_override("icon_pressed_color", bypass_color);
+ bypass->add_theme_color_override("icon_hover_pressed_color", bypass_color_darkened);
bus_options->set_button_icon(get_editor_theme_icon(SNAME("GuiTabMenuHl")));
@@ -841,13 +850,18 @@ EditorAudioBus::EditorAudioBus(EditorAudioBuses *p_buses, bool p_is_master) {
child->begin_bulk_theme_override();
child->add_theme_style_override(CoreStringName(normal), sbempty);
child->add_theme_style_override("hover", sbempty);
+ child->add_theme_style_override("hover_mirrored", sbempty);
child->add_theme_style_override("focus", sbempty);
+ child->add_theme_style_override("focus_mirrored", sbempty);
Ref<StyleBoxFlat> sbflat = memnew(StyleBoxFlat);
sbflat->set_content_margin_all(0);
sbflat->set_bg_color(Color(1, 1, 1, 0));
sbflat->set_border_width(Side::SIDE_BOTTOM, Math::round(3 * EDSCALE));
child->add_theme_style_override(SceneStringName(pressed), sbflat);
+ child->add_theme_style_override("pressed_mirrored", sbflat);
+ child->add_theme_style_override("hover_pressed", sbflat);
+ child->add_theme_style_override("hover_pressed_mirrored", sbflat);
child->end_bulk_theme_override();
}
diff --git a/editor/editor_feature_profile.cpp b/editor/editor_feature_profile.cpp
index 7ffbc39d75..9cf10c0ecb 100644
--- a/editor/editor_feature_profile.cpp
+++ b/editor/editor_feature_profile.cpp
@@ -43,37 +43,37 @@
const char *EditorFeatureProfile::feature_names[FEATURE_MAX] = {
TTRC("3D Editor"),
TTRC("Script Editor"),
- TTRC("Game View"),
TTRC("Asset Library"),
TTRC("Scene Tree Editing"),
TTRC("Node Dock"),
TTRC("FileSystem Dock"),
TTRC("Import Dock"),
TTRC("History Dock"),
+ TTRC("Game View"),
};
const char *EditorFeatureProfile::feature_descriptions[FEATURE_MAX] = {
TTRC("Allows to view and edit 3D scenes."),
TTRC("Allows to edit scripts using the integrated script editor."),
- TTRC("Provides tools for selecting and debugging nodes at runtime."),
TTRC("Provides built-in access to the Asset Library."),
TTRC("Allows editing the node hierarchy in the Scene dock."),
TTRC("Allows to work with signals and groups of the node selected in the Scene dock."),
TTRC("Allows to browse the local file system via a dedicated dock."),
TTRC("Allows to configure import settings for individual assets. Requires the FileSystem dock to function."),
TTRC("Provides an overview of the editor's and each scene's undo history."),
+ TTRC("Provides tools for selecting and debugging nodes at runtime."),
};
const char *EditorFeatureProfile::feature_identifiers[FEATURE_MAX] = {
"3d",
"script",
- "game",
"asset_lib",
"scene_tree",
"node_dock",
"filesystem_dock",
"import_dock",
"history_dock",
+ "game",
};
void EditorFeatureProfile::set_disable_class(const StringName &p_class, bool p_disabled) {
diff --git a/editor/editor_inspector.cpp b/editor/editor_inspector.cpp
index 91dd8e5019..1c23ce8ede 100644
--- a/editor/editor_inspector.cpp
+++ b/editor/editor_inspector.cpp
@@ -931,10 +931,19 @@ float EditorProperty::get_name_split_ratio() const {
return split_ratio;
}
+void EditorProperty::set_favoritable(bool p_favoritable) {
+ can_favorite = p_favoritable;
+}
+
+bool EditorProperty::is_favoritable() const {
+ return can_favorite;
+}
+
void EditorProperty::set_object_and_property(Object *p_object, const StringName &p_property) {
object = p_object;
property = p_property;
- _update_pin_flags();
+
+ _update_flags();
}
static bool _is_value_potential_override(Node *p_node, const String &p_property) {
@@ -953,12 +962,14 @@ static bool _is_value_potential_override(Node *p_node, const String &p_property)
}
}
-void EditorProperty::_update_pin_flags() {
+void EditorProperty::_update_flags() {
can_pin = false;
pin_hidden = true;
+
if (read_only) {
return;
}
+
if (Node *node = Object::cast_to<Node>(object)) {
// Avoid errors down the road by ignoring nodes which are not part of a scene
if (!node->get_owner()) {
@@ -1034,6 +1045,10 @@ void EditorProperty::menu_option(int p_option) {
case MENU_COPY_PROPERTY_PATH: {
DisplayServer::get_singleton()->clipboard_set(property_path);
} break;
+ case MENU_FAVORITE_PROPERTY: {
+ emit_signal(SNAME("property_favorited"), property, !favorited);
+ queue_redraw();
+ } break;
case MENU_PIN_VALUE: {
emit_signal(SNAME("property_pinned"), property, !pinned);
queue_redraw();
@@ -1091,6 +1106,7 @@ void EditorProperty::_bind_methods() {
ADD_SIGNAL(MethodInfo("property_deleted", PropertyInfo(Variant::STRING_NAME, "property")));
ADD_SIGNAL(MethodInfo("property_keyed_with_value", PropertyInfo(Variant::STRING_NAME, "property"), PropertyInfo(Variant::NIL, "value", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NIL_IS_VARIANT)));
ADD_SIGNAL(MethodInfo("property_checked", PropertyInfo(Variant::STRING_NAME, "property"), PropertyInfo(Variant::BOOL, "checked")));
+ ADD_SIGNAL(MethodInfo("property_favorited", PropertyInfo(Variant::STRING_NAME, "property"), PropertyInfo(Variant::BOOL, "favorited")));
ADD_SIGNAL(MethodInfo("property_pinned", PropertyInfo(Variant::STRING_NAME, "property"), PropertyInfo(Variant::BOOL, "pinned")));
ADD_SIGNAL(MethodInfo("property_can_revert_changed", PropertyInfo(Variant::STRING_NAME, "property"), PropertyInfo(Variant::BOOL, "can_revert")));
ADD_SIGNAL(MethodInfo("resource_selected", PropertyInfo(Variant::STRING, "path"), PropertyInfo(Variant::OBJECT, "resource", PROPERTY_HINT_RESOURCE_TYPE, "Resource")));
@@ -1129,8 +1145,21 @@ void EditorProperty::_update_popup() {
menu->set_item_disabled(MENU_PASTE_VALUE, is_read_only());
menu->set_item_disabled(MENU_COPY_PROPERTY_PATH, internal);
- if (!pin_hidden) {
+ if (can_favorite || !pin_hidden) {
menu->add_separator();
+ }
+
+ if (can_favorite) {
+ if (favorited) {
+ menu->add_icon_item(get_editor_theme_icon(SNAME("Unfavorite")), TTR("Unfavorite Property"), MENU_FAVORITE_PROPERTY);
+ menu->set_item_tooltip(menu->get_item_index(MENU_FAVORITE_PROPERTY), TTR("Make this property be put back at its original place."));
+ } else {
+ menu->add_icon_item(get_editor_theme_icon(SNAME("Favorites")), TTR("Favorite Property"), MENU_FAVORITE_PROPERTY);
+ menu->set_item_tooltip(menu->get_item_index(MENU_FAVORITE_PROPERTY), TTR("Make this property be placed at the top for all objects of this class."));
+ }
+ }
+
+ if (!pin_hidden) {
if (can_pin) {
menu->add_icon_check_item(get_editor_theme_icon(SNAME("Pin")), TTR("Pin Value"), MENU_PIN_VALUE);
menu->set_item_checked(menu->get_item_index(MENU_PIN_VALUE), pinned);
@@ -1219,9 +1248,14 @@ void EditorInspectorPlugin::_bind_methods() {
void EditorInspectorCategory::_notification(int p_what) {
switch (p_what) {
- case NOTIFICATION_ENTER_TREE:
case NOTIFICATION_THEME_CHANGED: {
- menu->set_item_icon(menu->get_item_index(MENU_OPEN_DOCS), get_editor_theme_icon(SNAME("Help")));
+ if (menu) {
+ if (is_favorite) {
+ menu->set_item_icon(menu->get_item_index(EditorInspector::MENU_UNFAVORITE_ALL), get_editor_theme_icon(SNAME("Unfavorite")));
+ } else {
+ menu->set_item_icon(menu->get_item_index(MENU_OPEN_DOCS), get_editor_theme_icon(SNAME("Help")));
+ }
+ }
} break;
case NOTIFICATION_DRAW: {
Ref<StyleBox> sb = get_theme_stylebox(SNAME("bg"));
@@ -1278,6 +1312,15 @@ Control *EditorInspectorCategory::make_custom_tooltip(const String &p_text) cons
return memnew(Control); // Make the standard tooltip invisible.
}
+void EditorInspectorCategory::set_as_favorite(EditorInspector *p_for_inspector) {
+ is_favorite = true;
+
+ menu = memnew(PopupMenu);
+ menu->add_item(TTR("Unfavorite All"), EditorInspector::MENU_UNFAVORITE_ALL);
+ add_child(menu);
+ menu->connect(SceneStringName(id_pressed), callable_mp(p_for_inspector, &EditorInspector::_handle_menu_option));
+}
+
Size2 EditorInspectorCategory::get_minimum_size() const {
Ref<Font> font = get_theme_font(SNAME("bold"), EditorStringName(EditorFonts));
int font_size = get_theme_font_size(SNAME("bold_size"), EditorStringName(EditorFonts));
@@ -1306,7 +1349,7 @@ void EditorInspectorCategory::_handle_menu_option(int p_option) {
}
void EditorInspectorCategory::gui_input(const Ref<InputEvent> &p_event) {
- if (doc_class_name.is_empty()) {
+ if (!is_favorite && doc_class_name.is_empty()) {
return;
}
@@ -1315,20 +1358,21 @@ void EditorInspectorCategory::gui_input(const Ref<InputEvent> &p_event) {
return;
}
- menu->set_item_disabled(menu->get_item_index(MENU_OPEN_DOCS), !EditorHelp::get_doc_data()->class_list.has(doc_class_name));
+ if (!is_favorite) {
+ if (!menu) {
+ menu = memnew(PopupMenu);
+ menu->add_icon_item(get_editor_theme_icon(SNAME("Help")), TTR("Open Documentation"), MENU_OPEN_DOCS);
+ add_child(menu);
+ menu->connect(SceneStringName(id_pressed), callable_mp(this, &EditorInspectorCategory::_handle_menu_option));
+ }
+ menu->set_item_disabled(menu->get_item_index(MENU_OPEN_DOCS), !EditorHelp::get_doc_data()->class_list.has(doc_class_name));
+ }
menu->set_position(get_screen_position() + mb_event->get_position());
menu->reset_size();
menu->popup();
}
-EditorInspectorCategory::EditorInspectorCategory() {
- menu = memnew(PopupMenu);
- menu->connect(SceneStringName(id_pressed), callable_mp(this, &EditorInspectorCategory::_handle_menu_option));
- menu->add_item(TTR("Open Documentation"), MENU_OPEN_DOCS);
- add_child(menu);
-}
-
////////////////////////////////////////////////
////////////////////////////////////////////////
@@ -1622,6 +1666,10 @@ void EditorInspectorSection::gui_input(const Ref<InputEvent> &p_event) {
}
}
+String EditorInspectorSection::get_section() const {
+ return section;
+}
+
VBoxContainer *EditorInspectorSection::get_vbox() {
return vbox;
}
@@ -2675,7 +2723,13 @@ String EditorInspector::get_selected_path() const {
void EditorInspector::_parse_added_editors(VBoxContainer *current_vbox, EditorInspectorSection *p_section, Ref<EditorInspectorPlugin> ped) {
for (const EditorInspectorPlugin::AddedEditor &F : ped->added_editors) {
EditorProperty *ep = Object::cast_to<EditorProperty>(F.property_editor);
- current_vbox->add_child(F.property_editor);
+
+ if (ep && current_favorites.has(F.properties[0])) {
+ ep->favorited = true;
+ favorites_vbox->add_child(F.property_editor);
+ } else {
+ current_vbox->add_child(F.property_editor);
+ }
if (ep) {
ep->object = object;
@@ -2684,6 +2738,7 @@ void EditorInspector::_parse_added_editors(VBoxContainer *current_vbox, EditorIn
ep->connect("property_deleted", callable_mp(this, &EditorInspector::_property_deleted), CONNECT_DEFERRED);
ep->connect("property_keyed_with_value", callable_mp(this, &EditorInspector::_property_keyed_with_value));
ep->connect("property_checked", callable_mp(this, &EditorInspector::_property_checked));
+ ep->connect("property_favorited", callable_mp(this, &EditorInspector::_set_property_favorited), CONNECT_DEFERRED);
ep->connect("property_pinned", callable_mp(this, &EditorInspector::_property_pinned));
ep->connect("selected", callable_mp(this, &EditorInspector::_property_selected));
ep->connect("multiple_properties_changed", callable_mp(this, &EditorInspector::_multiple_properties_changed));
@@ -2727,7 +2782,7 @@ void EditorInspector::_parse_added_editors(VBoxContainer *current_vbox, EditorIn
ep->set_read_only(read_only);
ep->update_property();
- ep->_update_pin_flags();
+ ep->_update_flags();
ep->update_editor_property_status();
ep->set_deletable(deletable_properties);
ep->update_cache();
@@ -2837,6 +2892,7 @@ void EditorInspector::update_tree() {
String subgroup;
String subgroup_base;
int section_depth = 0;
+ bool disable_favorite = false;
VBoxContainer *category_vbox = nullptr;
List<PropertyInfo> plist;
@@ -2844,13 +2900,17 @@ void EditorInspector::update_tree() {
HashMap<VBoxContainer *, HashMap<String, VBoxContainer *>> vbox_per_path;
HashMap<String, EditorInspectorArray *> editor_inspector_array_per_prefix;
+ HashMap<String, HashMap<String, LocalVector<EditorProperty *>>> favorites_to_add;
Color sscolor = get_theme_color(SNAME("prop_subsection"), EditorStringName(Editor));
// Get the lists of editors to add the beginning.
for (Ref<EditorInspectorPlugin> &ped : valid_plugins) {
ped->parse_begin(object);
- _parse_added_editors(main_vbox, nullptr, ped);
+ _parse_added_editors(begin_vbox, nullptr, ped);
+ }
+ if (begin_vbox->get_child_count()) {
+ begin_vbox->show();
}
StringName doc_name;
@@ -2897,6 +2957,7 @@ void EditorInspector::update_tree() {
subgroup = "";
subgroup_base = "";
section_depth = 0;
+ disable_favorite = false;
vbox_per_path.clear();
editor_inspector_array_per_prefix.clear();
@@ -2960,6 +3021,11 @@ void EditorInspector::update_tree() {
} else {
category_icon = EditorNode::get_singleton()->get_object_icon(scr.ptr(), "Object");
}
+
+ // Property favorites aren't compatible with built-in scripts.
+ if (scr->is_built_in()) {
+ disable_favorite = true;
+ }
}
}
@@ -3058,6 +3124,11 @@ void EditorInspector::update_tree() {
}
}
+ // Don't allow to favorite array items.
+ if (!disable_favorite) {
+ disable_favorite = !array_prefix.is_empty();
+ }
+
if (!array_prefix.is_empty()) {
path = path.trim_prefix(array_prefix);
int char_index = path.find("/");
@@ -3449,6 +3520,7 @@ void EditorInspector::update_tree() {
ep->set_draw_warning(draw_warning);
ep->set_use_folding(use_folding);
+ ep->set_favoritable(can_favorite && !disable_favorite);
ep->set_checkable(checkable);
ep->set_checked(checked);
ep->set_keying(keying);
@@ -3456,7 +3528,12 @@ void EditorInspector::update_tree() {
ep->set_deletable(deletable_properties || p.name.begins_with("metadata/"));
}
- current_vbox->add_child(editors[i].property_editor);
+ if (ep && ep->is_favoritable() && current_favorites.has(p.name)) {
+ ep->favorited = true;
+ favorites_to_add[group][subgroup].push_back(ep);
+ } else {
+ current_vbox->add_child(editors[i].property_editor);
+ }
if (ep) {
// Eventually, set other properties/signals after the property editor got added to the tree.
@@ -3465,6 +3542,7 @@ void EditorInspector::update_tree() {
ep->connect("property_keyed", callable_mp(this, &EditorInspector::_property_keyed));
ep->connect("property_deleted", callable_mp(this, &EditorInspector::_property_deleted), CONNECT_DEFERRED);
ep->connect("property_keyed_with_value", callable_mp(this, &EditorInspector::_property_keyed_with_value));
+ ep->connect("property_favorited", callable_mp(this, &EditorInspector::_set_property_favorited), CONNECT_DEFERRED);
ep->connect("property_checked", callable_mp(this, &EditorInspector::_property_checked));
ep->connect("property_pinned", callable_mp(this, &EditorInspector::_property_pinned));
ep->connect("selected", callable_mp(this, &EditorInspector::_property_selected));
@@ -3495,7 +3573,7 @@ void EditorInspector::update_tree() {
ep->set_internal(p.usage & PROPERTY_USAGE_INTERNAL);
ep->update_property();
- ep->_update_pin_flags();
+ ep->_update_flags();
ep->update_editor_property_status();
ep->update_cache();
@@ -3506,6 +3584,79 @@ void EditorInspector::update_tree() {
}
}
+ if (!current_favorites.is_empty()) {
+ favorites_section->show();
+
+ // Organize the favorited properties in their sections, to keep context and differentiate from others with the same name.
+ bool is_localized = property_name_style == EditorPropertyNameProcessor::STYLE_LOCALIZED;
+ for (const KeyValue<String, HashMap<String, LocalVector<EditorProperty *>>> &KV : favorites_to_add) {
+ String section_name = KV.key;
+ String label;
+ String tooltip;
+ VBoxContainer *parent_vbox = favorites_vbox;
+ if (!section_name.is_empty()) {
+ if (is_localized) {
+ label = EditorPropertyNameProcessor::get_singleton()->translate_group_name(section_name);
+ tooltip = section_name;
+ } else {
+ label = section_name;
+ tooltip = EditorPropertyNameProcessor::get_singleton()->translate_group_name(section_name);
+ }
+
+ EditorInspectorSection *section = memnew(EditorInspectorSection);
+ favorites_groups_vbox->add_child(section);
+ parent_vbox = section->get_vbox();
+ section->setup("", section_name, object, sscolor, false);
+ section->set_tooltip_text(tooltip);
+ }
+
+ for (const KeyValue<String, LocalVector<EditorProperty *>> &KV2 : KV.value) {
+ section_name = KV2.key;
+ VBoxContainer *vbox = parent_vbox;
+ if (!section_name.is_empty()) {
+ if (is_localized) {
+ label = EditorPropertyNameProcessor::get_singleton()->translate_group_name(section_name);
+ tooltip = section_name;
+ } else {
+ label = section_name;
+ tooltip = EditorPropertyNameProcessor::get_singleton()->translate_group_name(section_name);
+ }
+
+ EditorInspectorSection *section = memnew(EditorInspectorSection);
+ vbox->add_child(section);
+ vbox = section->get_vbox();
+ section->setup("", section_name, object, sscolor, false);
+ section->set_tooltip_text(tooltip);
+ }
+
+ for (EditorProperty *ep : KV2.value) {
+ vbox->add_child(ep);
+ }
+ }
+ }
+
+ // Show a separator if there's no category to clearly divide the properties.
+ favorites_separator->hide();
+ if (main_vbox->get_child_count() > 0) {
+ EditorInspectorCategory *category = Object::cast_to<EditorInspectorCategory>(main_vbox->get_child(0));
+ if (!category) {
+ favorites_separator->show();
+ }
+ }
+
+ // Clean up empty sections.
+ for (List<EditorInspectorSection *>::Element *I = sections.back(); I; I = I->prev()) {
+ EditorInspectorSection *section = I->get();
+ if (section->get_vbox()->get_child_count() == 0) {
+ I = I->prev();
+
+ sections.erase(section);
+ vbox_per_path[main_vbox].erase(section->get_section());
+ memdelete(section);
+ }
+ }
+ }
+
if (!hide_metadata && !object->call("_hide_metadata_from_inspector")) {
// Add 4px of spacing between the "Add Metadata" button and the content above it.
Control *spacer = memnew(Control);
@@ -3548,6 +3699,19 @@ void EditorInspector::update_property(const String &p_prop) {
}
void EditorInspector::_clear(bool p_hide_plugins) {
+ begin_vbox->hide();
+ while (begin_vbox->get_child_count()) {
+ memdelete(begin_vbox->get_child(0));
+ }
+
+ favorites_section->hide();
+ while (favorites_vbox->get_child_count()) {
+ memdelete(favorites_vbox->get_child(0));
+ }
+ while (favorites_groups_vbox->get_child_count()) {
+ memdelete(favorites_groups_vbox->get_child(0));
+ }
+
while (main_vbox->get_child_count()) {
memdelete(main_vbox->get_child(0));
}
@@ -3594,6 +3758,10 @@ void EditorInspector::edit(Object *p_object) {
update_scroll_request = scroll_cache[object->get_instance_id()]; //done this way because wait until full size is accommodated
}
object->connect(CoreStringName(property_list_changed), callable_mp(this, &EditorInspector::_changed_callback));
+
+ can_favorite = Object::cast_to<Node>(object) || Object::cast_to<Resource>(object);
+ _update_current_favorites();
+
update_tree();
}
@@ -4088,10 +4256,164 @@ void EditorInspector::_node_removed(Node *p_node) {
}
}
+void EditorInspector::_update_current_favorites() {
+ current_favorites.clear();
+ if (!can_favorite) {
+ return;
+ }
+
+ HashMap<String, PackedStringArray> favorites = EditorSettings::get_singleton()->get_favorite_properties();
+
+ // Fetch script properties.
+ Ref<Script> scr = object->get_script();
+ if (scr.is_valid()) {
+ List<PropertyInfo> plist;
+ // FIXME: Only properties from a saved script will be available, unsaved ones will be ignored.
+ // Can cause a little wonkiness, while nothing serious, would be nice to find a way to get
+ // unsaved ones without needing to get the entire property list of an object.
+ scr->get_script_property_list(&plist);
+
+ String path;
+ HashMap<String, LocalVector<String>> props;
+
+ for (PropertyInfo &p : plist) {
+ if (p.usage & PROPERTY_USAGE_CATEGORY) {
+ path = favorites.has(p.hint_string) ? p.hint_string : String();
+ } else if (p.usage & PROPERTY_USAGE_SCRIPT_VARIABLE && !path.is_empty()) {
+ props[path].push_back(p.name);
+ }
+ }
+
+ // Add favorited properties while removing invalid ones.
+ bool invalid_props = false;
+ for (const KeyValue<String, LocalVector<String>> &KV : props) {
+ path = KV.key;
+ for (int i = 0; i < favorites[path].size(); i++) {
+ String prop = favorites[path][i];
+ if (KV.value.has(prop)) {
+ current_favorites.append(prop);
+ } else {
+ invalid_props = true;
+ favorites[path].erase(prop);
+ i--;
+ }
+ }
+
+ if (favorites[path].is_empty()) {
+ favorites.erase(path);
+ }
+ }
+
+ if (invalid_props) {
+ EditorSettings::get_singleton()->set_favorite_properties(favorites);
+ }
+ }
+
+ // Fetch built-in properties.
+ StringName class_name = object->get_class_name();
+ for (const KeyValue<String, PackedStringArray> &KV : favorites) {
+ if (ClassDB::is_parent_class(class_name, KV.key)) {
+ current_favorites.append_array(KV.value);
+ }
+ }
+}
+
+void EditorInspector::_set_property_favorited(const String &p_path, bool p_favorited) {
+ if (!object) {
+ return;
+ }
+
+ StringName class_name = object->get_class_name();
+ while (!class_name.is_empty()) {
+ bool has_prop = ClassDB::has_property(class_name, p_path, true);
+ if (has_prop) {
+ break;
+ }
+
+ class_name = ClassDB::get_parent_class_nocheck(class_name);
+ }
+
+ if (class_name.is_empty()) {
+ Ref<Script> scr = object->get_script();
+ if (scr.is_valid()) {
+ List<PropertyInfo> plist;
+ scr->get_script_property_list(&plist);
+
+ String path;
+ for (PropertyInfo &p : plist) {
+ if (p.usage & PROPERTY_USAGE_CATEGORY) {
+ path = p.hint_string;
+ } else if (p.usage & PROPERTY_USAGE_SCRIPT_VARIABLE && p.name == p_path) {
+ class_name = path;
+ break;
+ }
+ }
+ }
+
+ ERR_FAIL_COND_MSG(class_name.is_empty(), "Can't favorite invalid property. If said property was from a script and recently renamed, try saving it first.");
+ }
+
+ HashMap<String, PackedStringArray> favorites = EditorSettings::get_singleton()->get_favorite_properties();
+ if (p_favorited) {
+ current_favorites.append(p_path);
+ favorites[class_name].append(p_path);
+ } else {
+ current_favorites.erase(p_path);
+
+ if (favorites.has(class_name) && favorites[class_name].has(p_path)) {
+ if (favorites[class_name].size() > 1) {
+ favorites[class_name].erase(p_path);
+ } else {
+ favorites.erase(class_name);
+ }
+ }
+ }
+ EditorSettings::get_singleton()->set_favorite_properties(favorites);
+
+ update_tree();
+}
+
+void EditorInspector::_clear_current_favorites() {
+ current_favorites.clear();
+
+ HashMap<String, PackedStringArray> favorites = EditorSettings::get_singleton()->get_favorite_properties();
+
+ Ref<Script> scr = object->get_script();
+ if (scr.is_valid()) {
+ List<PropertyInfo> plist;
+ scr->get_script_property_list(&plist);
+
+ for (PropertyInfo &p : plist) {
+ if (p.usage & PROPERTY_USAGE_CATEGORY && favorites.has(p.hint_string)) {
+ favorites.erase(p.hint_string);
+ }
+ }
+ }
+
+ StringName class_name = object->get_class_name();
+ while (class_name) {
+ if (favorites.has(class_name)) {
+ favorites.erase(class_name);
+ }
+
+ class_name = ClassDB::get_parent_class(class_name);
+ }
+
+ EditorSettings::get_singleton()->set_favorite_properties(favorites);
+ update_tree();
+}
+
void EditorInspector::_notification(int p_what) {
switch (p_what) {
case NOTIFICATION_THEME_CHANGED: {
- main_vbox->add_theme_constant_override("separation", get_theme_constant(SNAME("v_separation"), SNAME("EditorInspector")));
+ favorites_category->icon = get_editor_theme_icon(SNAME("Favorites"));
+
+ int separation = get_theme_constant(SNAME("v_separation"), SNAME("EditorInspector"));
+ base_vbox->add_theme_constant_override("separation", separation);
+ begin_vbox->add_theme_constant_override("separation", separation);
+ favorites_section->add_theme_constant_override("separation", separation);
+ favorites_groups_vbox->add_theme_constant_override("separation", separation);
+ main_vbox->add_theme_constant_override("separation", separation);
} break;
case NOTIFICATION_READY: {
@@ -4189,6 +4511,7 @@ void EditorInspector::_notification(int p_what) {
void EditorInspector::_changed_callback() {
//this is called when property change is notified via notify_property_list_changed()
if (object != nullptr) {
+ _update_current_favorites();
_edit_request_change(object, String());
}
}
@@ -4278,6 +4601,14 @@ void EditorInspector::_add_meta_confirm() {
undo_redo->commit_action();
}
+void EditorInspector::_handle_menu_option(int p_option) {
+ switch (p_option) {
+ case MENU_UNFAVORITE_ALL:
+ _clear_current_favorites();
+ break;
+ }
+}
+
void EditorInspector::_bind_methods() {
ClassDB::bind_method("_edit_request_change", &EditorInspector::_edit_request_change);
ClassDB::bind_method("get_selected_path", &EditorInspector::get_selected_path);
@@ -4296,9 +4627,36 @@ void EditorInspector::_bind_methods() {
EditorInspector::EditorInspector() {
object = nullptr;
+
+ base_vbox = memnew(VBoxContainer);
+ base_vbox->set_h_size_flags(SIZE_EXPAND_FILL);
+ add_child(base_vbox);
+
+ begin_vbox = memnew(VBoxContainer);
+ base_vbox->add_child(begin_vbox);
+ begin_vbox->hide();
+
+ favorites_section = memnew(VBoxContainer);
+ base_vbox->add_child(favorites_section);
+ favorites_section->hide();
+
+ favorites_category = memnew(EditorInspectorCategory);
+ favorites_category->set_as_favorite(this);
+ favorites_section->add_child(favorites_category);
+ favorites_category->label = TTR("Favorites");
+
+ favorites_vbox = memnew(VBoxContainer);
+ favorites_section->add_child(favorites_vbox);
+ favorites_groups_vbox = memnew(VBoxContainer);
+ favorites_section->add_child(favorites_groups_vbox);
+
+ favorites_separator = memnew(HSeparator);
+ favorites_section->add_child(favorites_separator);
+ favorites_separator->hide();
+
main_vbox = memnew(VBoxContainer);
- main_vbox->set_h_size_flags(SIZE_EXPAND_FILL);
- add_child(main_vbox);
+ base_vbox->add_child(main_vbox);
+
set_horizontal_scroll_mode(SCROLL_MODE_DISABLED);
set_follow_focus(true);
diff --git a/editor/editor_inspector.h b/editor/editor_inspector.h
index 0309213b76..2e4633ccea 100644
--- a/editor/editor_inspector.h
+++ b/editor/editor_inspector.h
@@ -41,6 +41,7 @@ class Button;
class ConfirmationDialog;
class EditorInspector;
class EditorValidationPanel;
+class HSeparator;
class LineEdit;
class MarginContainer;
class OptionButton;
@@ -64,6 +65,7 @@ public:
MENU_COPY_VALUE,
MENU_PASTE_VALUE,
MENU_COPY_PROPERTY_PATH,
+ MENU_FAVORITE_PROPERTY,
MENU_PIN_VALUE,
MENU_OPEN_DOCUMENTATION,
};
@@ -112,6 +114,9 @@ private:
bool pin_hidden = false;
bool pinned = false;
+ bool can_favorite = false;
+ bool favorited = false;
+
bool use_folding = false;
bool draw_top_bg = true;
@@ -134,7 +139,7 @@ private:
GDVIRTUAL0(_update_property)
GDVIRTUAL1(_set_read_only, bool)
- void _update_pin_flags();
+ void _update_flags();
protected:
bool has_borders = false;
@@ -218,6 +223,9 @@ public:
void set_name_split_ratio(float p_ratio);
float get_name_split_ratio() const;
+ void set_favoritable(bool p_favoritable);
+ bool is_favoritable() const;
+
void set_object_and_property(Object *p_object, const StringName &p_property);
virtual Control *make_custom_tooltip(const String &p_text) const override;
@@ -285,6 +293,7 @@ class EditorInspectorCategory : public Control {
String label;
String doc_class_name;
PopupMenu *menu = nullptr;
+ bool is_favorite = false;
void _handle_menu_option(int p_option);
@@ -293,10 +302,10 @@ protected:
virtual void gui_input(const Ref<InputEvent> &p_event) override;
public:
+ void set_as_favorite(EditorInspector *p_for_inspector);
+
virtual Size2 get_minimum_size() const override;
virtual Control *make_custom_tooltip(const String &p_text) const override;
-
- EditorInspectorCategory();
};
class EditorInspectorSection : public Container {
@@ -331,6 +340,7 @@ public:
virtual Size2 get_minimum_size() const override;
void setup(const String &p_section, const String &p_label, Object *p_object, const Color &p_bg_color, bool p_foldable, int p_indent_depth = 0, int p_level = 1);
+ String get_section() const;
VBoxContainer *get_vbox();
void unfold();
void fold();
@@ -480,13 +490,31 @@ public:
class EditorInspector : public ScrollContainer {
GDCLASS(EditorInspector, ScrollContainer);
+ friend class EditorInspectorCategory;
+
enum {
MAX_PLUGINS = 1024
};
static Ref<EditorInspectorPlugin> inspector_plugins[MAX_PLUGINS];
static int inspector_plugin_count;
+ // Right-click context menu options.
+ enum ClassMenuOption {
+ MENU_UNFAVORITE_ALL,
+ };
+
+ bool can_favorite = false;
+ PackedStringArray current_favorites;
+ VBoxContainer *favorites_section = nullptr;
+ EditorInspectorCategory *favorites_category = nullptr;
+ VBoxContainer *favorites_vbox = nullptr;
+ VBoxContainer *favorites_groups_vbox = nullptr;
+ HSeparator *favorites_separator = nullptr;
+
EditorInspector *root_inspector = nullptr;
+
+ VBoxContainer *base_vbox = nullptr;
+ VBoxContainer *begin_vbox = nullptr;
VBoxContainer *main_vbox = nullptr;
// Map used to cache the instantiated editors.
@@ -557,6 +585,10 @@ class EditorInspector : public ScrollContainer {
void _property_selected(const String &p_path, int p_focusable);
void _object_id_selected(const String &p_path, ObjectID p_id);
+ void _update_current_favorites();
+ void _set_property_favorited(const String &p_path, bool p_favorited);
+ void _clear_current_favorites();
+
void _node_removed(Node *p_node);
HashMap<StringName, int> per_array_page;
@@ -584,6 +616,8 @@ class EditorInspector : public ScrollContainer {
void _add_meta_confirm();
void _show_add_meta_dialog();
+ void _handle_menu_option(int p_option);
+
protected:
static void _bind_methods();
void _notification(int p_what);
diff --git a/editor/editor_run.cpp b/editor/editor_run.cpp
index d5135f4198..caed02ae58 100644
--- a/editor/editor_run.cpp
+++ b/editor/editor_run.cpp
@@ -223,11 +223,6 @@ Error EditorRun::run(const String &p_scene, const String &p_write_movie) {
args.push_back(p_scene);
}
- // Pass the debugger stop shortcut to the running instance(s).
- String shortcut;
- VariantWriter::write_to_string(ED_GET_SHORTCUT("editor/stop_running_project"), shortcut);
- OS::get_singleton()->set_environment("__GODOT_EDITOR_STOP_SHORTCUT__", shortcut);
-
String exec = OS::get_singleton()->get_executable_path();
int instance_count = RunInstancesDialog::get_singleton()->get_instance_count();
for (int i = 0; i < instance_count; i++) {
diff --git a/editor/editor_settings.cpp b/editor/editor_settings.cpp
index 12a7c3a2ff..d41cb64c6c 100644
--- a/editor/editor_settings.cpp
+++ b/editor/editor_settings.cpp
@@ -602,6 +602,10 @@ void EditorSettings::_load_defaults(Ref<ConfigFile> p_extra_config) {
EDITOR_SETTING(Variant::INT, PROPERTY_HINT_RANGE, "filesystem/file_dialog/thumbnail_size", 64, "32,128,16")
// Quick Open dialog
+ EDITOR_SETTING_USAGE(Variant::INT, PROPERTY_HINT_RANGE, "filesystem/quick_open_dialog/max_results", 100, "0,10000,1", PROPERTY_USAGE_DEFAULT)
+ _initial_set("filesystem/quick_open_dialog/show_search_highlight", true);
+ _initial_set("filesystem/quick_open_dialog/enable_fuzzy_matching", true);
+ EDITOR_SETTING_USAGE(Variant::INT, PROPERTY_HINT_RANGE, "filesystem/quick_open_dialog/max_fuzzy_misses", 2, "0,10,1", PROPERTY_USAGE_DEFAULT)
_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")
@@ -1492,10 +1496,26 @@ void EditorSettings::set_favorites(const Vector<String> &p_favorites) {
}
}
+void EditorSettings::set_favorite_properties(const HashMap<String, PackedStringArray> &p_favorite_properties) {
+ favorite_properties = p_favorite_properties;
+ String favorite_properties_file = EditorPaths::get_singleton()->get_project_settings_dir().path_join("favorite_properties");
+
+ Ref<ConfigFile> cf;
+ cf.instantiate();
+ for (const KeyValue<String, PackedStringArray> &kv : p_favorite_properties) {
+ cf->set_value(kv.key, "properties", kv.value);
+ }
+ cf->save(favorite_properties_file);
+}
+
Vector<String> EditorSettings::get_favorites() const {
return favorites;
}
+HashMap<String, PackedStringArray> EditorSettings::get_favorite_properties() const {
+ return favorite_properties;
+}
+
void EditorSettings::set_recent_dirs(const Vector<String> &p_recent_dirs) {
recent_dirs = p_recent_dirs;
String recent_dirs_file;
@@ -1518,23 +1538,51 @@ Vector<String> EditorSettings::get_recent_dirs() const {
void EditorSettings::load_favorites_and_recent_dirs() {
String favorites_file;
+ String favorite_properties_file;
String recent_dirs_file;
if (Engine::get_singleton()->is_project_manager_hint()) {
favorites_file = EditorPaths::get_singleton()->get_config_dir().path_join("favorite_dirs");
+ favorite_properties_file = EditorPaths::get_singleton()->get_config_dir().path_join("favorite_properties");
recent_dirs_file = EditorPaths::get_singleton()->get_config_dir().path_join("recent_dirs");
} else {
favorites_file = EditorPaths::get_singleton()->get_project_settings_dir().path_join("favorites");
+ favorite_properties_file = EditorPaths::get_singleton()->get_project_settings_dir().path_join("favorite_properties");
recent_dirs_file = EditorPaths::get_singleton()->get_project_settings_dir().path_join("recent_dirs");
}
+
+ /// File Favorites
+
Ref<FileAccess> f = FileAccess::open(favorites_file, FileAccess::READ);
if (f.is_valid()) {
String line = f->get_line().strip_edges();
while (!line.is_empty()) {
- favorites.push_back(line);
+ favorites.append(line);
line = f->get_line().strip_edges();
}
}
+ /// Inspector Favorites
+
+ Ref<ConfigFile> cf;
+ cf.instantiate();
+ if (cf->load(favorite_properties_file) == OK) {
+ List<String> secs;
+ cf->get_sections(&secs);
+
+ for (String &E : secs) {
+ PackedStringArray properties = PackedStringArray(cf->get_value(E, "properties"));
+ if (EditorNode::get_editor_data().is_type_recognized(E) || ResourceLoader::exists(E, "Script")) {
+ for (const String &property : properties) {
+ if (!favorite_properties[E].has(property)) {
+ favorite_properties[E].push_back(property);
+ }
+ }
+ }
+ }
+ }
+
+ /// Recent Directories
+
f = FileAccess::open(recent_dirs_file, FileAccess::READ);
if (f.is_valid()) {
String line = f->get_line().strip_edges();
diff --git a/editor/editor_settings.h b/editor/editor_settings.h
index d1ccedfe6c..3c8a4de866 100644
--- a/editor/editor_settings.h
+++ b/editor/editor_settings.h
@@ -102,6 +102,7 @@ private:
HashMap<String, List<Ref<InputEvent>>> builtin_action_overrides;
Vector<String> favorites;
+ HashMap<String, PackedStringArray> favorite_properties;
Vector<String> recent_dirs;
bool save_changed_setting = true;
@@ -176,6 +177,8 @@ public:
void set_favorites(const Vector<String> &p_favorites);
Vector<String> get_favorites() const;
+ void set_favorite_properties(const HashMap<String, PackedStringArray> &p_favorite_properties);
+ HashMap<String, PackedStringArray> get_favorite_properties() const;
void set_recent_dirs(const Vector<String> &p_recent_dirs);
Vector<String> get_recent_dirs() const;
void load_favorites_and_recent_dirs();
diff --git a/editor/filesystem_dock.cpp b/editor/filesystem_dock.cpp
index 9b08d21bdc..a468d71568 100644
--- a/editor/filesystem_dock.cpp
+++ b/editor/filesystem_dock.cpp
@@ -1641,21 +1641,27 @@ String FileSystemDock::_get_unique_name(const FileOrFolder &p_entry, const Strin
return new_path;
}
-void FileSystemDock::_update_favorites_list_after_move(const HashMap<String, String> &p_files_renames, const HashMap<String, String> &p_folders_renames) const {
- Vector<String> favorites_list = EditorSettings::get_singleton()->get_favorites();
- Vector<String> new_favorites;
-
- for (const String &old_path : favorites_list) {
+void FileSystemDock::_update_favorites_after_move(const HashMap<String, String> &p_files_renames, const HashMap<String, String> &p_folders_renames) const {
+ Vector<String> favorite_files = EditorSettings::get_singleton()->get_favorites();
+ Vector<String> new_favorite_files;
+ for (const String &old_path : favorite_files) {
if (p_folders_renames.has(old_path)) {
- new_favorites.push_back(p_folders_renames[old_path]);
+ new_favorite_files.push_back(p_folders_renames[old_path]);
} else if (p_files_renames.has(old_path)) {
- new_favorites.push_back(p_files_renames[old_path]);
+ new_favorite_files.push_back(p_files_renames[old_path]);
} else {
- new_favorites.push_back(old_path);
+ new_favorite_files.push_back(old_path);
}
}
+ EditorSettings::get_singleton()->set_favorites(new_favorite_files);
- EditorSettings::get_singleton()->set_favorites(new_favorites);
+ HashMap<String, PackedStringArray> favorite_properties = EditorSettings::get_singleton()->get_favorite_properties();
+ for (const KeyValue<String, String> &KV : p_files_renames) {
+ if (favorite_properties.has(KV.key)) {
+ favorite_properties.replace_key(KV.key, KV.value);
+ }
+ }
+ EditorSettings::get_singleton()->set_favorite_properties(favorite_properties);
}
void FileSystemDock::_make_scene_confirm() {
@@ -1798,7 +1804,7 @@ void FileSystemDock::_rename_operation_confirm() {
_update_resource_paths_after_move(file_renames, uids);
_update_dependencies_after_move(file_renames, file_owners);
_update_project_settings_after_move(file_renames, folder_renames);
- _update_favorites_list_after_move(file_renames, folder_renames);
+ _update_favorites_after_move(file_renames, folder_renames);
EditorSceneTabs::get_singleton()->set_current_tab(current_tab);
@@ -1959,7 +1965,7 @@ void FileSystemDock::_move_operation_confirm(const String &p_to_path, bool p_cop
_update_resource_paths_after_move(file_renames, uids);
_update_dependencies_after_move(file_renames, file_owners);
_update_project_settings_after_move(file_renames, folder_renames);
- _update_favorites_list_after_move(file_renames, folder_renames);
+ _update_favorites_after_move(file_renames, folder_renames);
EditorSceneTabs::get_singleton()->set_current_tab(current_tab);
diff --git a/editor/filesystem_dock.h b/editor/filesystem_dock.h
index 72d5ac3a98..fe83129c07 100644
--- a/editor/filesystem_dock.h
+++ b/editor/filesystem_dock.h
@@ -275,7 +275,7 @@ private:
void _before_move(HashMap<String, ResourceUID::ID> &r_uids, HashSet<String> &r_file_owners) const;
void _update_dependencies_after_move(const HashMap<String, String> &p_renames, const HashSet<String> &p_file_owners) const;
void _update_resource_paths_after_move(const HashMap<String, String> &p_renames, const HashMap<String, ResourceUID::ID> &p_uids) const;
- void _update_favorites_list_after_move(const HashMap<String, String> &p_files_renames, const HashMap<String, String> &p_folders_renames) const;
+ void _update_favorites_after_move(const HashMap<String, String> &p_files_renames, const HashMap<String, String> &p_folders_renames) const;
void _update_project_settings_after_move(const HashMap<String, String> &p_renames, const HashMap<String, String> &p_folders_renames);
String _get_unique_name(const FileOrFolder &p_entry, const String &p_at_path);
diff --git a/editor/gui/editor_bottom_panel.cpp b/editor/gui/editor_bottom_panel.cpp
index 2eb899f085..3cc1e37be0 100644
--- a/editor/gui/editor_bottom_panel.cpp
+++ b/editor/gui/editor_bottom_panel.cpp
@@ -31,35 +31,35 @@
#include "editor_bottom_panel.h"
#include "editor/debugger/editor_debugger_node.h"
-#include "editor/editor_about.h"
#include "editor/editor_command_palette.h"
#include "editor/editor_node.h"
#include "editor/editor_string_names.h"
-#include "editor/engine_update_label.h"
#include "editor/gui/editor_toaster.h"
#include "editor/gui/editor_version_button.h"
#include "editor/themes/editor_scale.h"
#include "scene/gui/box_container.h"
#include "scene/gui/button.h"
+#include "scene/gui/split_container.h"
void EditorBottomPanel::_notification(int p_what) {
switch (p_what) {
case NOTIFICATION_THEME_CHANGED: {
+ pin_button->set_button_icon(get_editor_theme_icon(SNAME("Pin")));
expand_button->set_button_icon(get_editor_theme_icon(SNAME("ExpandBottomDock")));
} break;
}
}
-void EditorBottomPanel::_switch_by_control(bool p_visible, Control *p_control) {
+void EditorBottomPanel::_switch_by_control(bool p_visible, Control *p_control, bool p_ignore_lock) {
for (int i = 0; i < items.size(); i++) {
if (items[i].control == p_control) {
- _switch_to_item(p_visible, i);
+ _switch_to_item(p_visible, i, p_ignore_lock);
return;
}
}
}
-void EditorBottomPanel::_switch_to_item(bool p_visible, int p_idx) {
+void EditorBottomPanel::_switch_to_item(bool p_visible, int p_idx, bool p_ignore_lock) {
ERR_FAIL_INDEX(p_idx, items.size());
if (items[p_idx].control->is_visible() == p_visible) {
@@ -70,6 +70,10 @@ void EditorBottomPanel::_switch_to_item(bool p_visible, int p_idx) {
ERR_FAIL_NULL(center_split);
if (p_visible) {
+ if (!p_ignore_lock && lock_panel_switching && pin_button->is_visible()) {
+ return;
+ }
+
for (int i = 0; i < items.size(); i++) {
items[i].button->set_pressed_no_signal(i == p_idx);
items[i].control->set_visible(i == p_idx);
@@ -80,18 +84,23 @@ void EditorBottomPanel::_switch_to_item(bool p_visible, int p_idx) {
} else {
add_theme_style_override(SceneStringName(panel), get_theme_stylebox(SNAME("BottomPanel"), EditorStringName(EditorStyles)));
}
+
center_split->set_dragger_visibility(SplitContainer::DRAGGER_VISIBLE);
center_split->set_collapsed(false);
+ pin_button->show();
+
+ expand_button->show();
if (expand_button->is_pressed()) {
EditorNode::get_top_split()->hide();
}
- expand_button->show();
} else {
add_theme_style_override(SceneStringName(panel), get_theme_stylebox(SNAME("BottomPanel"), EditorStringName(EditorStyles)));
items[p_idx].button->set_pressed_no_signal(false);
items[p_idx].control->set_visible(false);
center_split->set_dragger_visibility(SplitContainer::DRAGGER_HIDDEN);
center_split->set_collapsed(true);
+ pin_button->hide();
+
expand_button->hide();
if (expand_button->is_pressed()) {
EditorNode::get_top_split()->show();
@@ -101,13 +110,17 @@ void EditorBottomPanel::_switch_to_item(bool p_visible, int p_idx) {
last_opened_control = items[p_idx].control;
}
+void EditorBottomPanel::_pin_button_toggled(bool p_pressed) {
+ lock_panel_switching = p_pressed;
+}
+
void EditorBottomPanel::_expand_button_toggled(bool p_pressed) {
EditorNode::get_top_split()->set_visible(!p_pressed);
}
bool EditorBottomPanel::_button_drag_hover(const Vector2 &, const Variant &, Button *p_button, Control *p_control) {
if (!p_button->is_pressed()) {
- _switch_by_control(true, p_control);
+ _switch_by_control(true, p_control, true);
}
return false;
}
@@ -149,7 +162,7 @@ void EditorBottomPanel::load_layout_from_config(Ref<ConfigFile> p_config_file, c
Button *EditorBottomPanel::add_item(String p_text, Control *p_item, const Ref<Shortcut> &p_shortcut, bool p_at_front) {
Button *tb = memnew(Button);
tb->set_theme_type_variation("BottomPanelButton");
- tb->connect(SceneStringName(toggled), callable_mp(this, &EditorBottomPanel::_switch_by_control).bind(p_item));
+ tb->connect(SceneStringName(toggled), callable_mp(this, &EditorBottomPanel::_switch_by_control).bind(p_item, true));
tb->set_drag_forwarding(Callable(), callable_mp(this, &EditorBottomPanel::_button_drag_hover).bind(tb, p_item), Callable());
tb->set_text(p_text);
tb->set_shortcut(p_shortcut);
@@ -231,10 +244,10 @@ void EditorBottomPanel::toggle_last_opened_bottom_panel() {
// Select by control instead of index, so that the last bottom panel is opened correctly
// if it's been reordered since.
if (last_opened_control) {
- _switch_by_control(!last_opened_control->is_visible(), last_opened_control);
+ _switch_by_control(!last_opened_control->is_visible(), last_opened_control, true);
} else {
// Open the first panel in the list if no panel was opened this session.
- _switch_to_item(true, 0);
+ _switch_to_item(true, 0, true);
}
}
@@ -263,10 +276,17 @@ EditorBottomPanel::EditorBottomPanel() {
Control *h_spacer = memnew(Control);
bottom_hbox->add_child(h_spacer);
+ pin_button = memnew(Button);
+ bottom_hbox->add_child(pin_button);
+ pin_button->hide();
+ pin_button->set_theme_type_variation("FlatMenuButton");
+ pin_button->set_toggle_mode(true);
+ pin_button->set_tooltip_text(TTR("Pin Bottom Panel Switching"));
+ pin_button->connect(SceneStringName(toggled), callable_mp(this, &EditorBottomPanel::_pin_button_toggled));
+
expand_button = memnew(Button);
bottom_hbox->add_child(expand_button);
expand_button->hide();
- expand_button->set_flat(false);
expand_button->set_theme_type_variation("FlatMenuButton");
expand_button->set_toggle_mode(true);
expand_button->set_shortcut(ED_SHORTCUT_AND_COMMAND("editor/bottom_panel_expand", TTR("Expand Bottom Panel"), KeyModifierMask::SHIFT | Key::F12));
diff --git a/editor/gui/editor_bottom_panel.h b/editor/gui/editor_bottom_panel.h
index 3d44b3750a..950f0e2570 100644
--- a/editor/gui/editor_bottom_panel.h
+++ b/editor/gui/editor_bottom_panel.h
@@ -49,16 +49,19 @@ class EditorBottomPanel : public PanelContainer {
};
Vector<BottomPanelItem> items;
+ bool lock_panel_switching = false;
VBoxContainer *item_vbox = nullptr;
HBoxContainer *bottom_hbox = nullptr;
HBoxContainer *button_hbox = nullptr;
EditorToaster *editor_toaster = nullptr;
+ Button *pin_button = nullptr;
Button *expand_button = nullptr;
Control *last_opened_control = nullptr;
- void _switch_by_control(bool p_visible, Control *p_control);
- void _switch_to_item(bool p_visible, int p_idx);
+ void _switch_by_control(bool p_visible, Control *p_control, bool p_ignore_lock = false);
+ void _switch_to_item(bool p_visible, int p_idx, bool p_ignore_lock = false);
+ void _pin_button_toggled(bool p_pressed);
void _expand_button_toggled(bool p_pressed);
bool _button_drag_hover(const Vector2 &, const Variant &, Button *p_button, Control *p_control);
diff --git a/editor/gui/editor_quick_open_dialog.cpp b/editor/gui/editor_quick_open_dialog.cpp
index b8f3b259ca..44e7b3e483 100644
--- a/editor/gui/editor_quick_open_dialog.cpp
+++ b/editor/gui/editor_quick_open_dialog.cpp
@@ -30,6 +30,7 @@
#include "editor_quick_open_dialog.h"
+#include "core/string/fuzzy_search.h"
#include "editor/editor_file_system.h"
#include "editor/editor_node.h"
#include "editor/editor_resource_preview.h"
@@ -45,6 +46,55 @@
#include "scene/gui/texture_rect.h"
#include "scene/gui/tree.h"
+void HighlightedLabel::draw_substr_rects(const Vector2i &p_substr, Vector2 p_offset, int p_line_limit, int line_spacing) {
+ for (int i = get_lines_skipped(); i < p_line_limit; i++) {
+ RID line = get_line_rid(i);
+ Vector<Vector2> ranges = TS->shaped_text_get_selection(line, p_substr.x, p_substr.x + p_substr.y);
+ Rect2 line_rect = get_line_rect(i);
+ for (const Vector2 &range : ranges) {
+ Rect2 rect = Rect2(Point2(range.x, 0) + line_rect.position, Size2(range.y - range.x, line_rect.size.y));
+ rect.position = p_offset + line_rect.position;
+ rect.position.x += range.x;
+ rect.size = Size2(range.y - range.x, line_rect.size.y);
+ rect.size.x = MIN(rect.size.x, line_rect.size.x - range.x);
+ if (rect.size.x > 0) {
+ draw_rect(rect, Color(1, 1, 1, 0.07), true);
+ draw_rect(rect, Color(0.5, 0.7, 1.0, 0.4), false, 1);
+ }
+ }
+ p_offset.y += line_spacing + TS->shaped_text_get_ascent(line) + TS->shaped_text_get_descent(line);
+ }
+}
+
+void HighlightedLabel::add_highlight(const Vector2i &p_interval) {
+ if (p_interval.y > 0) {
+ highlights.append(p_interval);
+ queue_redraw();
+ }
+}
+
+void HighlightedLabel::reset_highlights() {
+ highlights.clear();
+ queue_redraw();
+}
+
+void HighlightedLabel::_notification(int p_notification) {
+ if (p_notification == NOTIFICATION_DRAW) {
+ if (highlights.is_empty()) {
+ return;
+ }
+
+ Vector2 offset;
+ int line_limit;
+ int line_spacing;
+ get_layout_data(offset, line_limit, line_spacing);
+
+ for (const Vector2i &substr : highlights) {
+ draw_substr_rects(substr, offset, line_limit, line_spacing);
+ }
+ }
+}
+
EditorQuickOpenDialog::EditorQuickOpenDialog() {
VBoxContainer *vbc = memnew(VBoxContainer);
vbc->add_theme_constant_override("separation", 0);
@@ -100,7 +150,7 @@ void EditorQuickOpenDialog::popup_dialog(const Vector<StringName> &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);
+ popup_centered_clamped(Size2(780, 650) * EDSCALE, 0.8f);
search_box->grab_focus();
}
@@ -119,13 +169,18 @@ void EditorQuickOpenDialog::cancel_pressed() {
}
void EditorQuickOpenDialog::_search_box_text_changed(const String &p_query) {
- container->update_results(p_query.to_lower());
-
+ container->set_query_and_update(p_query);
get_ok_button()->set_disabled(container->has_nothing_selected());
}
//------------------------- Result Container
+void style_button(Button *p_button) {
+ p_button->set_flat(true);
+ p_button->set_focus_mode(Control::FOCUS_NONE);
+ p_button->set_default_cursor_shape(Control::CURSOR_POINTING_HAND);
+}
+
QuickOpenResultContainer::QuickOpenResultContainer() {
set_h_size_flags(Control::SIZE_EXPAND_FILL);
set_v_size_flags(Control::SIZE_EXPAND_FILL);
@@ -175,91 +230,107 @@ QuickOpenResultContainer::QuickOpenResultContainer() {
}
{
- // Bottom bar
- HBoxContainer *bottom_bar = memnew(HBoxContainer);
- add_child(bottom_bar);
-
+ // Selected filepath
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);
+ 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);
- }
+ {
+ // Bottom bar
+ HBoxContainer *bottom_bar = memnew(HBoxContainer);
+ bottom_bar->set_h_size_flags(Control::SIZE_EXPAND_FILL);
+ bottom_bar->set_alignment(ALIGNMENT_END);
+ bottom_bar->add_theme_constant_override("separation", 3);
+ add_child(bottom_bar);
+
+ fuzzy_search_toggle = memnew(CheckButton);
+ style_button(fuzzy_search_toggle);
+ fuzzy_search_toggle->set_text(TTR("Fuzzy Search"));
+ fuzzy_search_toggle->set_tooltip_text(TTR("Enable fuzzy matching"));
+ fuzzy_search_toggle->connect(SceneStringName(toggled), callable_mp(this, &QuickOpenResultContainer::_toggle_fuzzy_search));
+ bottom_bar->add_child(fuzzy_search_toggle);
+
+ include_addons_toggle = memnew(CheckButton);
+ style_button(include_addons_toggle);
+ include_addons_toggle->set_text(TTR("Addons"));
+ include_addons_toggle->set_tooltip_text(TTR("Include files from addons"));
+ include_addons_toggle->connect(SceneStringName(toggled), callable_mp(this, &QuickOpenResultContainer::_toggle_include_addons));
+ bottom_bar->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));
+ bottom_bar->add_child(vsep);
+
+ display_mode_toggle = memnew(Button);
+ style_button(display_mode_toggle);
+ display_mode_toggle->connect(SceneStringName(pressed), callable_mp(this, &QuickOpenResultContainer::_toggle_display_mode));
+ bottom_bar->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++) {
+void QuickOpenResultContainer::_ensure_result_vector_capacity() {
+ int target_size = EDITOR_GET("filesystem/quick_open_dialog/max_results");
+ int initial_size = result_items.size();
+ for (int i = target_size; i < initial_size; i++) {
+ result_items[i]->queue_free();
+ }
+ result_items.resize(target_size);
+ for (int i = initial_size; i < target_size; 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() {
- if (never_opened) {
- for (QuickOpenResultItem *E : result_items) {
- memdelete(E);
+ if (!never_opened) {
+ _layout_result_item(item);
}
}
}
void QuickOpenResultContainer::init(const Vector<StringName> &p_base_types) {
+ _ensure_result_vector_capacity();
base_types = p_base_types;
- never_opened = false;
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));
+ } else if (never_opened) {
+ int last = EditorSettings::get_singleton()->get_project_metadata("quick_open_dialog", "last_mode", (int)QuickOpenDisplayMode::LIST);
+ _set_display_mode((QuickOpenDisplayMode)last);
}
+ const bool fuzzy_matching = EDITOR_GET("filesystem/quick_open_dialog/enable_fuzzy_matching");
const bool include_addons = EDITOR_GET("filesystem/quick_open_dialog/include_addons");
+ fuzzy_search_toggle->set_pressed_no_signal(fuzzy_matching);
include_addons_toggle->set_pressed_no_signal(include_addons);
+ never_opened = false;
- _create_initial_results(include_addons);
+ const bool enable_highlights = EDITOR_GET("filesystem/quick_open_dialog/show_search_highlight");
+ for (QuickOpenResultItem *E : result_items) {
+ E->enable_highlights = enable_highlights;
+ }
+
+ _create_initial_results();
}
-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);
+void QuickOpenResultContainer::_create_initial_results() {
file_type_icons.clear();
-
- update_results(query);
+ file_type_icons.insert("__default_icon", get_editor_theme_icon(SNAME("Object")));
+ filepaths.clear();
+ filetypes.clear();
+ _find_filepaths_in_folder(EditorFileSystem::get_singleton()->get_filesystem(), include_addons_toggle->is_pressed());
+ max_total_results = MIN(filepaths.size(), result_items.size());
+ update_results();
}
-void QuickOpenResultContainer::_find_candidates_in_folder(EditorFileSystemDirectory *p_directory, bool p_include_addons) {
+void QuickOpenResultContainer::_find_filepaths_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);
+ _find_filepaths_in_folder(p_directory->get_subdir(i), p_include_addons);
}
}
@@ -276,146 +347,91 @@ void QuickOpenResultContainer::_find_candidates_in_folder(EditorFileSystemDirect
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);
-
+ filepaths.append(file_path);
+ filetypes.insert(file_path, actual_type);
break; // Stop testing base types as soon as we get a match.
}
}
}
}
-void QuickOpenResultContainer::update_results(const String &p_query) {
+void QuickOpenResultContainer::set_query_and_update(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;
+ update_results();
+}
+
+void QuickOpenResultContainer::_setup_candidate(QuickOpenResultCandidate &candidate, const String &filepath) {
+ StringName actual_type = *filetypes.lookup_ptr(filepath);
+ candidate.file_path = filepath;
+ candidate.result = nullptr;
+
+ EditorResourcePreview::PreviewItem item = EditorResourcePreview::get_singleton()->get_resource_preview_if_available(filepath);
+ if (item.preview.is_valid()) {
+ candidate.thumbnail = item.preview;
+ } else if (file_type_icons.has(actual_type)) {
+ candidate.thumbnail = *file_type_icons.lookup_ptr(actual_type);
+ } else if (has_theme_icon(actual_type, EditorStringName(EditorIcons))) {
+ candidate.thumbnail = get_editor_theme_icon(actual_type);
+ file_type_icons.insert(actual_type, candidate.thumbnail);
+ } else {
+ candidate.thumbnail = *file_type_icons.lookup_ptr("__default_icon");
}
+}
- const PackedStringArray search_tokens = p_query.to_lower().replace("/", " ").split(" ", false);
+void QuickOpenResultContainer::_setup_candidate(QuickOpenResultCandidate &p_candidate, const FuzzySearchResult &p_result) {
+ _setup_candidate(p_candidate, p_result.target);
+ p_candidate.result = &p_result;
+}
- if (search_tokens.is_empty()) {
- return 0;
+void QuickOpenResultContainer::update_results() {
+ showing_history = false;
+ candidates.clear();
+ if (query.is_empty()) {
+ _use_default_candidates();
+ } else {
+ _score_and_sort_candidates();
}
+ _update_result_items(MIN(candidates.size(), max_total_results), 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;
+void QuickOpenResultContainer::_use_default_candidates() {
+ if (filepaths.size() <= SHOW_ALL_FILES_THRESHOLD) {
+ candidates.resize(filepaths.size());
+ QuickOpenResultCandidate *candidates_write = candidates.ptrw();
+ for (const String &filepath : filepaths) {
+ _setup_candidate(*candidates_write++, filepath);
}
-
- if (c.score > 0.0f) {
- num_relevant_candidates++;
+ } else if (base_types.size() == 1) {
+ Vector<QuickOpenResultCandidate> *history = selected_history.lookup_ptr(base_types[0]);
+ if (history) {
+ showing_history = true;
+ candidates.append_array(*history);
}
}
-
- // 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;
+void QuickOpenResultContainer::_update_fuzzy_search_results() {
+ FuzzySearch fuzzy_search;
+ fuzzy_search.start_offset = 6; // Don't match against "res://" at the start of each filepath.
+ fuzzy_search.set_query(query);
+ fuzzy_search.max_results = max_total_results;
+ bool fuzzy_matching = EDITOR_GET("filesystem/quick_open_dialog/enable_fuzzy_matching");
+ int max_misses = EDITOR_GET("filesystem/quick_open_dialog/max_fuzzy_misses");
+ fuzzy_search.allow_subsequences = fuzzy_matching;
+ fuzzy_search.max_misses = fuzzy_matching ? max_misses : 0;
+ fuzzy_search.search_all(filepaths, search_results);
+}
- 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;
- }
- }
- }
+void QuickOpenResultContainer::_score_and_sort_candidates() {
+ _update_fuzzy_search_results();
+ candidates.resize(search_results.size());
+ QuickOpenResultCandidate *candidates_write = candidates.ptrw();
+ for (const FuzzySearchResult &result : search_results) {
+ _setup_candidate(*candidates_write++, result);
}
+}
+void QuickOpenResultContainer::_update_result_items(int p_new_visible_results_count, int p_new_selection_index) {
// 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;
@@ -424,13 +440,7 @@ void QuickOpenResultContainer::_update_result_items(int p_new_visible_results_co
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);
- }
+ item->set_content(candidates[i]);
} else {
item->reset();
}
@@ -443,7 +453,7 @@ void QuickOpenResultContainer::_update_result_items(int p_new_visible_results_co
no_results_container->set_visible(!any_results);
if (!any_results) {
- if (candidates.is_empty()) {
+ if (filepaths.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..."));
@@ -471,10 +481,12 @@ void QuickOpenResultContainer::handle_search_box_input(const Ref<InputEvent> &p_
} 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);
+ if (content_display_mode == QuickOpenDisplayMode::GRID) {
+ // Maybe strip off the shift modifier to allow non-selecting navigation by character?
+ if (key_event->get_modifiers_mask() == 0) {
+ move_selection = true;
+ }
+ }
} break;
default:
break; // Let the event through so it will reach the search box.
@@ -489,6 +501,10 @@ void QuickOpenResultContainer::handle_search_box_input(const Ref<InputEvent> &p_
}
void QuickOpenResultContainer::_move_selection_index(Key p_key) {
+ // Don't move selection if there are no results.
+ if (num_visible_results <= 0) {
+ return;
+ }
const int max_index = num_visible_results - 1;
int idx = selection_index;
@@ -562,11 +578,15 @@ void QuickOpenResultContainer::_item_input(const Ref<InputEvent> &p_ev, int p_in
}
}
+void QuickOpenResultContainer::_toggle_fuzzy_search(bool p_pressed) {
+ EditorSettings::get_singleton()->set("filesystem/quick_open_dialog/enable_fuzzy_matching", p_pressed);
+ update_results();
+}
+
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);
+ _create_initial_results();
}
void QuickOpenResultContainer::_toggle_display_mode() {
@@ -574,41 +594,41 @@ void QuickOpenResultContainer::_toggle_display_mode() {
_set_display_mode(new_display_mode);
}
-void QuickOpenResultContainer::_set_display_mode(QuickOpenDisplayMode p_display_mode) {
- content_display_mode = p_display_mode;
+CanvasItem *QuickOpenResultContainer::_get_result_root() {
+ if (content_display_mode == QuickOpenDisplayMode::LIST) {
+ return list;
+ } else {
+ return grid;
+ }
+}
- const bool show_list = (content_display_mode == QuickOpenDisplayMode::LIST);
- if ((show_list && list->is_visible()) || (!show_list && grid->is_visible())) {
- return;
+void QuickOpenResultContainer::_layout_result_item(QuickOpenResultItem *item) {
+ item->set_display_mode(content_display_mode);
+ Node *parent = item->get_parent();
+ if (parent) {
+ parent->remove_child(item);
}
+ _get_result_root()->add_child(item);
+}
- hide();
+void QuickOpenResultContainer::_set_display_mode(QuickOpenDisplayMode p_display_mode) {
+ CanvasItem *prev_root = _get_result_root();
- // 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);
+ if (prev_root->is_visible() && content_display_mode == p_display_mode) {
+ return;
}
- const bool first_time = !list->is_visible() && !grid->is_visible();
+ content_display_mode = p_display_mode;
+ CanvasItem *next_root = _get_result_root();
+
+ EditorSettings::get_singleton()->set_project_metadata("quick_open_dialog", "last_mode", (int)content_display_mode);
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->show();
- next_root->add_child(item);
+ for (QuickOpenResultItem *item : result_items) {
+ _layout_result_item(item);
}
- next_root->show();
- show();
_update_result_items(num_visible_results, selection_index);
@@ -627,16 +647,7 @@ bool QuickOpenResultContainer::has_nothing_selected() const {
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);
- }
+ return candidates[selection_index].file_path;
}
QuickOpenDisplayMode QuickOpenResultContainer::get_adaptive_display_mode(const Vector<StringName> &p_base_types) {
@@ -665,32 +676,27 @@ void QuickOpenResultContainer::save_selected_item() {
return;
}
- if (showing_history) {
- // Selecting from history, so already added.
- return;
- }
-
const StringName &base_type = base_types[0];
+ const QuickOpenResultCandidate &selected = candidates[selection_index];
+ Vector<QuickOpenResultCandidate> *type_history = selected_history.lookup_ptr(base_type);
- List<Candidate> *type_history = selected_history.lookup_ptr(base_type);
if (!type_history) {
- selected_history.insert(base_type, List<Candidate>());
+ selected_history.insert(base_type, Vector<QuickOpenResultCandidate>());
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;
+ for (int i = 0; i < type_history->size(); i++) {
+ if (selected.file_path == type_history->get(i).file_path) {
+ type_history->remove_at(i);
+ break;
}
}
-
- if (type_history->size() > 8) {
- type_history->pop_back();
- }
}
- type_history->push_front(candidates[selection_index]);
+ type_history->insert(0, selected);
+ type_history->ptrw()->result = nullptr;
+ if (type_history->size() > MAX_HISTORY_SIZE) {
+ type_history->resize(MAX_HISTORY_SIZE);
+ }
}
void QuickOpenResultContainer::cleanup() {
@@ -744,36 +750,35 @@ QuickOpenResultItem::QuickOpenResultItem() {
void QuickOpenResultItem::set_display_mode(QuickOpenDisplayMode p_display_mode) {
if (p_display_mode == QuickOpenDisplayMode::LIST) {
grid_item->hide();
+ grid_item->reset();
list_item->show();
} else {
list_item->hide();
+ list_item->reset();
grid_item->show();
}
queue_redraw();
}
-void QuickOpenResultItem::set_content(const Ref<Texture2D> &p_thumbnail, const String &p_file, const String &p_file_directory) {
+void QuickOpenResultItem::set_content(const QuickOpenResultCandidate &p_candidate) {
_set_enabled(true);
if (list_item->is_visible()) {
- list_item->set_content(p_thumbnail, p_file, p_file_directory);
+ list_item->set_content(p_candidate, enable_highlights);
} else {
- grid_item->set_content(p_thumbnail, p_file);
+ grid_item->set_content(p_candidate, enable_highlights);
}
+
+ queue_redraw();
}
void QuickOpenResultItem::reset() {
_set_enabled(false);
-
is_hovering = false;
is_selected = false;
-
- if (list_item->is_visible()) {
- list_item->reset();
- } else {
- grid_item->reset();
- }
+ list_item->reset();
+ grid_item->reset();
}
void QuickOpenResultItem::highlight_item(bool p_enabled) {
@@ -826,6 +831,22 @@ void QuickOpenResultItem::_notification(int p_what) {
//----------------- List item
+static Vector2i _get_path_interval(const Vector2i &p_interval, int p_dir_index) {
+ if (p_interval.x >= p_dir_index || p_interval.y < 1) {
+ return { -1, -1 };
+ }
+ return { p_interval.x, MIN(p_interval.x + p_interval.y, p_dir_index) - p_interval.x };
+}
+
+static Vector2i _get_name_interval(const Vector2i &p_interval, int p_dir_index) {
+ if (p_interval.x + p_interval.y <= p_dir_index || p_interval.y < 1) {
+ return { -1, -1 };
+ }
+ int first_name_idx = p_dir_index + 1;
+ int start = MAX(p_interval.x, first_name_idx);
+ return { start - first_name_idx, p_interval.y - start + p_interval.x };
+}
+
QuickOpenResultListItem::QuickOpenResultListItem() {
set_h_size_flags(Control::SIZE_EXPAND_FILL);
add_theme_constant_override("separation", 4 * EDSCALE);
@@ -853,13 +874,13 @@ QuickOpenResultListItem::QuickOpenResultListItem() {
text_container->set_v_size_flags(Control::SIZE_FILL);
add_child(text_container);
- name = memnew(Label);
+ name = memnew(HighlightedLabel);
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 = memnew(HighlightedLabel);
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);
@@ -867,18 +888,29 @@ QuickOpenResultListItem::QuickOpenResultListItem() {
}
}
-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);
+void QuickOpenResultListItem::set_content(const QuickOpenResultCandidate &p_candidate, bool p_highlight) {
+ thumbnail->set_texture(p_candidate.thumbnail);
+ name->set_text(p_candidate.file_path.get_file());
+ path->set_text(p_candidate.file_path.get_base_dir());
+ name->reset_highlights();
+ path->reset_highlights();
+
+ if (p_highlight && p_candidate.result != nullptr) {
+ for (const FuzzyTokenMatch &match : p_candidate.result->token_matches) {
+ for (const Vector2i &interval : match.substrings) {
+ path->add_highlight(_get_path_interval(interval, p_candidate.result->dir_index));
+ name->add_highlight(_get_name_interval(interval, p_candidate.result->dir_index));
+ }
+ }
+ }
const int max_size = 32 * EDSCALE;
- bool uses_icon = p_thumbnail->get_width() < max_size;
+ bool uses_icon = p_candidate.thumbnail->get_width() < max_size;
if (uses_icon) {
- thumbnail->set_custom_minimum_size(p_thumbnail->get_size());
+ thumbnail->set_custom_minimum_size(p_candidate.thumbnail->get_size());
- int margin_needed = (max_size - p_thumbnail->get_width()) / 2;
+ int margin_needed = (max_size - p_candidate.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 {
@@ -889,9 +921,11 @@ void QuickOpenResultListItem::set_content(const Ref<Texture2D> &p_thumbnail, con
}
void QuickOpenResultListItem::reset() {
- name->set_text("");
thumbnail->set_texture(nullptr);
+ name->set_text("");
path->set_text("");
+ name->reset_highlights();
+ path->reset_highlights();
}
void QuickOpenResultListItem::highlight_item(const Color &p_color) {
@@ -920,10 +954,10 @@ QuickOpenResultGridItem::QuickOpenResultGridItem() {
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));
+ thumbnail->set_custom_minimum_size(Size2i(120 * EDSCALE, 64 * EDSCALE));
add_child(thumbnail);
- name = memnew(Label);
+ name = memnew(HighlightedLabel);
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);
@@ -931,16 +965,23 @@ QuickOpenResultGridItem::QuickOpenResultGridItem() {
add_child(name);
}
-void QuickOpenResultGridItem::set_content(const Ref<Texture2D> &p_thumbnail, const String &p_file) {
- thumbnail->set_texture(p_thumbnail);
+void QuickOpenResultGridItem::set_content(const QuickOpenResultCandidate &p_candidate, bool p_highlight) {
+ thumbnail->set_texture(p_candidate.thumbnail);
+ name->set_text(p_candidate.file_path.get_file());
+ name->set_tooltip_text(p_candidate.file_path);
+ name->reset_highlights();
- const String &file_name = p_file.get_basename();
- name->set_text(file_name);
- name->set_tooltip_text(file_name);
+ if (p_highlight && p_candidate.result != nullptr) {
+ for (const FuzzyTokenMatch &match : p_candidate.result->token_matches) {
+ for (const Vector2i &interval : match.substrings) {
+ name->add_highlight(_get_name_interval(interval, p_candidate.result->dir_index));
+ }
+ }
+ }
- bool uses_icon = p_thumbnail->get_width() < (32 * EDSCALE);
+ bool uses_icon = p_candidate.thumbnail->get_width() < (32 * EDSCALE);
- if (uses_icon || p_thumbnail->get_height() <= thumbnail->get_custom_minimum_size().y) {
+ if (uses_icon || p_candidate.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 {
@@ -950,8 +991,9 @@ void QuickOpenResultGridItem::set_content(const Ref<Texture2D> &p_thumbnail, con
}
void QuickOpenResultGridItem::reset() {
- name->set_text("");
thumbnail->set_texture(nullptr);
+ name->set_text("");
+ name->reset_highlights();
}
void QuickOpenResultGridItem::highlight_item(const Color &p_color) {
diff --git a/editor/gui/editor_quick_open_dialog.h b/editor/gui/editor_quick_open_dialog.h
index 49257aed6b..3b3f927527 100644
--- a/editor/gui/editor_quick_open_dialog.h
+++ b/editor/gui/editor_quick_open_dialog.h
@@ -48,6 +48,8 @@ class Texture2D;
class TextureRect;
class VBoxContainer;
+class FuzzySearchResult;
+
class QuickOpenResultItem;
enum class QuickOpenDisplayMode {
@@ -55,13 +57,35 @@ enum class QuickOpenDisplayMode {
LIST,
};
+struct QuickOpenResultCandidate {
+ String file_path;
+ Ref<Texture2D> thumbnail;
+ const FuzzySearchResult *result = nullptr;
+};
+
+class HighlightedLabel : public Label {
+ GDCLASS(HighlightedLabel, Label)
+
+ Vector<Vector2i> highlights;
+
+ void draw_substr_rects(const Vector2i &p_substr, Vector2 p_offset, int p_line_limit, int line_spacing);
+
+public:
+ void add_highlight(const Vector2i &p_interval);
+ void reset_highlights();
+
+protected:
+ void _notification(int p_notification);
+};
+
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);
+ void set_query_and_update(const String &p_query);
+ void update_results();
bool has_nothing_selected() const;
String get_selected() const;
@@ -70,27 +94,21 @@ public:
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;
- };
+ static constexpr int SHOW_ALL_FILES_THRESHOLD = 30;
+ static constexpr int MAX_HISTORY_SIZE = 20;
+ Vector<FuzzySearchResult> search_results;
Vector<StringName> base_types;
- Vector<Candidate> candidates;
+ Vector<String> filepaths;
+ OAHashMap<String, StringName> filetypes;
+ Vector<QuickOpenResultCandidate> candidates;
- OAHashMap<StringName, List<Candidate>> selected_history;
+ OAHashMap<StringName, Vector<QuickOpenResultCandidate>> selected_history;
String query;
int selection_index = -1;
@@ -114,15 +132,21 @@ private:
Label *file_details_path = nullptr;
Button *display_mode_toggle = nullptr;
CheckButton *include_addons_toggle = nullptr;
+ CheckButton *fuzzy_search_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);
+ void _ensure_result_vector_capacity();
+ void _create_initial_results();
+ void _find_filepaths_in_folder(EditorFileSystemDirectory *p_directory, bool p_include_addons);
- int _sort_candidates(const String &p_query);
+ void _setup_candidate(QuickOpenResultCandidate &p_candidate, const String &p_filepath);
+ void _setup_candidate(QuickOpenResultCandidate &p_candidate, const FuzzySearchResult &p_result);
+ void _update_fuzzy_search_results();
+ void _use_default_candidates();
+ void _score_and_sort_candidates();
void _update_result_items(int p_new_visible_results_count, int p_new_selection_index);
void _move_selection_index(Key p_key);
@@ -130,9 +154,12 @@ private:
void _item_input(const Ref<InputEvent> &p_ev, int p_index);
+ CanvasItem *_get_result_root();
+ void _layout_result_item(QuickOpenResultItem *p_item);
void _set_display_mode(QuickOpenDisplayMode p_display_mode);
void _toggle_display_mode();
void _toggle_include_addons(bool p_pressed);
+ void _toggle_fuzzy_search(bool p_pressed);
static void _bind_methods();
};
@@ -143,14 +170,14 @@ class QuickOpenResultGridItem : public VBoxContainer {
public:
QuickOpenResultGridItem();
- void set_content(const Ref<Texture2D> &p_thumbnail, const String &p_file_name);
void reset();
+ void set_content(const QuickOpenResultCandidate &p_candidate, bool p_highlight);
void highlight_item(const Color &p_color);
void remove_highlight();
private:
TextureRect *thumbnail = nullptr;
- Label *name = nullptr;
+ HighlightedLabel *name = nullptr;
};
class QuickOpenResultListItem : public HBoxContainer {
@@ -159,8 +186,8 @@ class QuickOpenResultListItem : public HBoxContainer {
public:
QuickOpenResultListItem();
- void set_content(const Ref<Texture2D> &p_thumbnail, const String &p_file_name, const String &p_file_directory);
void reset();
+ void set_content(const QuickOpenResultCandidate &p_candidate, bool p_highlight);
void highlight_item(const Color &p_color);
void remove_highlight();
@@ -174,8 +201,8 @@ private:
VBoxContainer *text_container = nullptr;
TextureRect *thumbnail = nullptr;
- Label *name = nullptr;
- Label *path = nullptr;
+ HighlightedLabel *name = nullptr;
+ HighlightedLabel *path = nullptr;
};
class QuickOpenResultItem : public HBoxContainer {
@@ -184,10 +211,11 @@ class QuickOpenResultItem : public 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();
+ bool enable_highlights = true;
+ void reset();
+ void set_content(const QuickOpenResultCandidate &p_candidate);
+ void set_display_mode(QuickOpenDisplayMode p_display_mode);
void highlight_item(bool p_enabled);
protected:
diff --git a/editor/gui/editor_toaster.cpp b/editor/gui/editor_toaster.cpp
index cc439d56b3..4ebd1922a7 100644
--- a/editor/gui/editor_toaster.cpp
+++ b/editor/gui/editor_toaster.cpp
@@ -108,7 +108,6 @@ void EditorToaster::_notification(int p_what) {
}
} break;
- case NOTIFICATION_ENTER_TREE:
case NOTIFICATION_THEME_CHANGED: {
if (vbox_container->is_visible()) {
main_button->set_button_icon(get_editor_theme_icon(SNAME("Notification")));
@@ -134,9 +133,6 @@ void EditorToaster::_notification(int p_what) {
error_panel_style_progress->set_bg_color(get_theme_color(SNAME("base_color"), EditorStringName(Editor)).lightened(0.03));
error_panel_style_progress->set_border_color(get_theme_color(SNAME("error_color"), EditorStringName(Editor)));
-
- main_button->queue_redraw();
- disable_notifications_button->queue_redraw();
} break;
case NOTIFICATION_TRANSFORM_CHANGED: {
@@ -243,6 +239,7 @@ void EditorToaster::_auto_hide_or_free_toasts() {
main_button->set_tooltip_text(TTR("No notifications."));
main_button->set_modulate(Color(0.5, 0.5, 0.5));
main_button->set_disabled(true);
+ set_process_internal(false);
} else {
main_button->set_tooltip_text(TTR("Show notifications."));
main_button->set_modulate(Color(1, 1, 1));
@@ -361,6 +358,9 @@ Control *EditorToaster::popup(Control *p_control, Severity p_severity, double p_
}
panel->set_modulate(Color(1, 1, 1, 0));
panel->connect(SceneStringName(draw), callable_mp(this, &EditorToaster::_draw_progress).bind(panel));
+ panel->connect(SceneStringName(theme_changed), callable_mp(this, &EditorToaster::_toast_theme_changed).bind(panel));
+
+ Toast &toast = toasts[panel];
// Horizontal container.
HBoxContainer *hbox_container = memnew(HBoxContainer);
@@ -375,20 +375,20 @@ Control *EditorToaster::popup(Control *p_control, Severity p_severity, double p_
if (p_time > 0.0) {
Button *close_button = memnew(Button);
close_button->set_flat(true);
- close_button->set_button_icon(get_editor_theme_icon(SNAME("Close")));
close_button->connect(SceneStringName(pressed), callable_mp(this, &EditorToaster::close).bind(panel));
- close_button->connect(SceneStringName(theme_changed), callable_mp(this, &EditorToaster::_close_button_theme_changed).bind(close_button));
hbox_container->add_child(close_button);
+
+ toast.close_button = close_button;
}
- toasts[panel].severity = p_severity;
+ toast.severity = p_severity;
if (p_time > 0.0) {
- toasts[panel].duration = p_time;
- toasts[panel].remaining_time = p_time;
+ toast.duration = p_time;
+ toast.remaining_time = p_time;
} else {
- toasts[panel].duration = -1.0;
+ toast.duration = -1.0;
}
- toasts[panel].popped = true;
+ toast.popped = true;
vbox_container->add_child(panel);
_auto_hide_or_free_toasts();
_update_vbox_position();
@@ -406,7 +406,7 @@ void EditorToaster::popup_str(const String &p_message, Severity p_severity, cons
// Since "_popup_str" adds nodes to the tree, and since the "add_child" method is not
// thread-safe, it's better to defer the call to the next cycle to be thread-safe.
is_processing_error = true;
- MessageQueue::get_main_singleton()->push_callable(callable_mp(this, &EditorToaster::_popup_str).bind(p_message, p_severity, p_tooltip));
+ callable_mp(this, &EditorToaster::_popup_str).call_deferred(p_message, p_severity, p_tooltip);
is_processing_error = false;
}
@@ -433,19 +433,22 @@ void EditorToaster::_popup_str(const String &p_message, Severity p_severity, con
hb->add_child(count_label);
control = popup(hb, p_severity, default_message_duration, p_tooltip);
- toasts[control].message = p_message;
- toasts[control].tooltip = p_tooltip;
- toasts[control].count = 1;
- toasts[control].message_label = label;
- toasts[control].message_count_label = count_label;
+
+ Toast &toast = toasts[control];
+ toast.message = p_message;
+ toast.tooltip = p_tooltip;
+ toast.count = 1;
+ toast.message_label = label;
+ toast.message_count_label = count_label;
} else {
- if (toasts[control].popped) {
- toasts[control].count += 1;
+ Toast &toast = toasts[control];
+ if (toast.popped) {
+ toast.count += 1;
} else {
- toasts[control].count = 1;
+ toast.count = 1;
}
- toasts[control].remaining_time = toasts[control].duration;
- toasts[control].popped = true;
+ toast.remaining_time = toast.duration;
+ toast.popped = true;
control->show();
vbox_container->move_child(control, vbox_container->get_child_count());
_auto_hide_or_free_toasts();
@@ -480,6 +483,16 @@ void EditorToaster::_popup_str(const String &p_message, Severity p_severity, con
vbox_container->reset_size();
is_processing_error = false;
+ set_process_internal(true);
+}
+
+void EditorToaster::_toast_theme_changed(Control *p_control) {
+ ERR_FAIL_COND(!toasts.has(p_control));
+
+ Toast &toast = toasts[p_control];
+ if (toast.close_button) {
+ toast.close_button->set_button_icon(get_editor_theme_icon(SNAME("Close")));
+ }
}
void EditorToaster::close(Control *p_control) {
@@ -488,20 +501,12 @@ void EditorToaster::close(Control *p_control) {
toasts[p_control].popped = false;
}
-void EditorToaster::_close_button_theme_changed(Control *p_close_button) {
- Button *close_button = Object::cast_to<Button>(p_close_button);
- if (close_button) {
- close_button->set_button_icon(get_editor_theme_icon(SNAME("Close")));
- }
-}
-
EditorToaster *EditorToaster::get_singleton() {
return singleton;
}
EditorToaster::EditorToaster() {
set_notify_transform(true);
- set_process_internal(true);
// VBox.
vbox_container = memnew(VBoxContainer);
diff --git a/editor/gui/editor_toaster.h b/editor/gui/editor_toaster.h
index 4bf32d94ba..35a4337746 100644
--- a/editor/gui/editor_toaster.h
+++ b/editor/gui/editor_toaster.h
@@ -31,8 +31,6 @@
#ifndef EDITOR_TOASTER_H
#define EDITOR_TOASTER_H
-#include "core/string/ustring.h"
-#include "core/templates/local_vector.h"
#include "scene/gui/box_container.h"
class Button;
@@ -76,6 +74,9 @@ private:
real_t remaining_time = 0.0;
bool popped = false;
+ // Buttons
+ Button *close_button = nullptr;
+
// Messages
String message;
String tooltip;
@@ -101,7 +102,7 @@ private:
void _set_notifications_enabled(bool p_enabled);
void _repop_old();
void _popup_str(const String &p_message, Severity p_severity, const String &p_tooltip);
- void _close_button_theme_changed(Control *p_close_button);
+ void _toast_theme_changed(Control *p_control);
protected:
static EditorToaster *singleton;
diff --git a/editor/gui/scene_tree_editor.cpp b/editor/gui/scene_tree_editor.cpp
index 2e36b66025..0ada9aa8b2 100644
--- a/editor/gui/scene_tree_editor.cpp
+++ b/editor/gui/scene_tree_editor.cpp
@@ -1684,24 +1684,30 @@ void SceneTreeDialog::_show_all_nodes_changed(bool p_button_pressed) {
}
void SceneTreeDialog::set_valid_types(const Vector<StringName> &p_valid) {
- if (p_valid.is_empty()) {
- return;
+ if (allowed_types_hbox) {
+ allowed_types_hbox->queue_free();
+ allowed_types_hbox = nullptr;
+ valid_type_icons.clear();
}
tree->set_valid_types(p_valid);
- HBoxContainer *hbox = memnew(HBoxContainer);
- content->add_child(hbox);
- content->move_child(hbox, 0);
+ if (p_valid.is_empty()) {
+ return;
+ }
+
+ allowed_types_hbox = memnew(HBoxContainer);
+ content->add_child(allowed_types_hbox);
+ content->move_child(allowed_types_hbox, 0);
{
Label *label = memnew(Label);
- hbox->add_child(label);
+ allowed_types_hbox->add_child(label);
label->set_text(TTR("Allowed:"));
}
HFlowContainer *hflow = memnew(HFlowContainer);
- hbox->add_child(hflow);
+ allowed_types_hbox->add_child(hflow);
hflow->set_h_size_flags(Control::SIZE_EXPAND_FILL);
for (const StringName &type : p_valid) {
@@ -1735,6 +1741,9 @@ void SceneTreeDialog::set_valid_types(const Vector<StringName> &p_valid) {
}
show_all_nodes->show();
+ if (is_inside_tree()) {
+ _update_valid_type_icons();
+ }
}
void SceneTreeDialog::_notification(int p_what) {
@@ -1753,11 +1762,7 @@ void SceneTreeDialog::_notification(int p_what) {
} break;
case NOTIFICATION_THEME_CHANGED: {
- filter->set_right_icon(get_editor_theme_icon(SNAME("Search")));
- for (TextureRect *trect : valid_type_icons) {
- trect->set_custom_minimum_size(Vector2(get_theme_constant(SNAME("class_icon_size"), EditorStringName(Editor)), 0));
- trect->set_texture(trect->get_meta("icon"));
- }
+ _update_valid_type_icons();
} break;
case NOTIFICATION_EXIT_TREE: {
@@ -1766,6 +1771,14 @@ void SceneTreeDialog::_notification(int p_what) {
}
}
+void SceneTreeDialog::_update_valid_type_icons() {
+ filter->set_right_icon(get_editor_theme_icon(SNAME("Search")));
+ for (TextureRect *trect : valid_type_icons) {
+ trect->set_custom_minimum_size(Vector2(get_theme_constant(SNAME("class_icon_size"), EditorStringName(Editor)), 0));
+ trect->set_texture(trect->get_meta("icon"));
+ }
+}
+
void SceneTreeDialog::_cancel() {
hide();
}
diff --git a/editor/gui/scene_tree_editor.h b/editor/gui/scene_tree_editor.h
index e623c8405d..eed6d4b954 100644
--- a/editor/gui/scene_tree_editor.h
+++ b/editor/gui/scene_tree_editor.h
@@ -199,6 +199,7 @@ class SceneTreeDialog : public ConfirmationDialog {
LineEdit *filter = nullptr;
CheckButton *show_all_nodes = nullptr;
LocalVector<TextureRect *> valid_type_icons;
+ HBoxContainer *allowed_types_hbox = nullptr;
void _select();
void _cancel();
@@ -208,6 +209,7 @@ class SceneTreeDialog : public ConfirmationDialog {
void _show_all_nodes_changed(bool p_button_pressed);
protected:
+ void _update_valid_type_icons();
void _notification(int p_what);
static void _bind_methods();
diff --git a/editor/icons/Unfavorite.svg b/editor/icons/Unfavorite.svg
new file mode 100644
index 0000000000..78f1b90fd0
--- /dev/null
+++ b/editor/icons/Unfavorite.svg
@@ -0,0 +1 @@
+<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16"><path fill="#e0e0e0" d="M 8 1.6992188 L 5.6269531 5.796875 L 1 6.8945312 L 4.2363281 10.302734 L 3.8769531 14.976562 L 8.0175781 12.998047 L 12.173828 14.941406 L 11.777344 10.287109 L 15 6.8945312 L 10.373047 5.796875 L 8 1.6992188 z M 8 4.2773438 L 9.4882812 6.8457031 L 12.388672 7.5332031 L 10.369141 9.6601562 L 10.617188 12.576172 L 8.0097656 11.359375 L 5.4160156 12.599609 L 5.640625 9.6699219 L 3.6113281 7.5332031 L 6.5117188 6.8457031 L 8 4.2773438 z"/></svg> \ No newline at end of file
diff --git a/editor/import/3d/resource_importer_obj.cpp b/editor/import/3d/resource_importer_obj.cpp
index e77f5ec2b1..3669844207 100644
--- a/editor/import/3d/resource_importer_obj.cpp
+++ b/editor/import/3d/resource_importer_obj.cpp
@@ -535,8 +535,6 @@ static Error _parse_obj(const String &p_path, List<Ref<ImporterMesh>> &r_meshes,
}
}
- mesh->optimize_indices_for_cache();
-
if (p_generate_lods) {
// Use normal merge/split angles that match the defaults used for 3D scene importing.
mesh->generate_lods(60.0f, {});
@@ -546,6 +544,8 @@ static Error _parse_obj(const String &p_path, List<Ref<ImporterMesh>> &r_meshes,
mesh->create_shadow_mesh();
}
+ mesh->optimize_indices();
+
if (p_single_mesh && mesh->get_surface_count() > 0) {
r_meshes.push_back(mesh);
}
diff --git a/editor/import/3d/resource_importer_obj.h b/editor/import/3d/resource_importer_obj.h
index faf0f336c0..9d299bc31a 100644
--- a/editor/import/3d/resource_importer_obj.h
+++ b/editor/import/3d/resource_importer_obj.h
@@ -63,9 +63,6 @@ public:
virtual Error import(const String &p_source_file, const String &p_save_path, const HashMap<StringName, Variant> &p_options, List<String> *r_platform_variants, List<String> *r_gen_files = nullptr, Variant *r_metadata = nullptr) override;
- // Threaded import can currently cause deadlocks, see GH-48265.
- virtual bool can_import_threaded() const override { return false; }
-
ResourceImporterOBJ();
};
diff --git a/editor/import/3d/resource_importer_scene.cpp b/editor/import/3d/resource_importer_scene.cpp
index 58af558e7b..edf7aa66f0 100644
--- a/editor/import/3d/resource_importer_scene.cpp
+++ b/editor/import/3d/resource_importer_scene.cpp
@@ -2567,8 +2567,6 @@ Node *ResourceImporterScene::_generate_meshes(Node *p_node, const Dictionary &p_
}
}
- src_mesh_node->get_mesh()->optimize_indices_for_cache();
-
if (generate_lods) {
Array skin_pose_transform_array = _get_skinned_pose_transforms(src_mesh_node);
src_mesh_node->get_mesh()->generate_lods(merge_angle, skin_pose_transform_array);
@@ -2578,6 +2576,8 @@ Node *ResourceImporterScene::_generate_meshes(Node *p_node, const Dictionary &p_
src_mesh_node->get_mesh()->create_shadow_mesh();
}
+ src_mesh_node->get_mesh()->optimize_indices();
+
if (!save_to_file.is_empty()) {
Ref<Mesh> existing = ResourceCache::get_ref(save_to_file);
if (existing.is_valid()) {
@@ -2622,6 +2622,7 @@ Node *ResourceImporterScene::_generate_meshes(Node *p_node, const Dictionary &p_
mesh_node->set_layer_mask(src_mesh_node->get_layer_mask());
mesh_node->set_cast_shadows_setting(src_mesh_node->get_cast_shadows_setting());
+ mesh_node->set_visible(src_mesh_node->is_visible());
mesh_node->set_visibility_range_begin(src_mesh_node->get_visibility_range_begin());
mesh_node->set_visibility_range_begin_margin(src_mesh_node->get_visibility_range_begin_margin());
mesh_node->set_visibility_range_end(src_mesh_node->get_visibility_range_end());
diff --git a/editor/import/3d/resource_importer_scene.h b/editor/import/3d/resource_importer_scene.h
index fe757dc2a3..daeab2ae03 100644
--- a/editor/import/3d/resource_importer_scene.h
+++ b/editor/import/3d/resource_importer_scene.h
@@ -304,8 +304,6 @@ public:
virtual bool has_advanced_options() const override;
virtual void show_advanced_options(const String &p_path) override;
- virtual bool can_import_threaded() const override { return false; }
-
ResourceImporterScene(const String &p_scene_import_type = "PackedScene", bool p_singleton = false);
~ResourceImporterScene();
diff --git a/editor/import/3d/scene_import_settings.cpp b/editor/import/3d/scene_import_settings.cpp
index 645b7fda88..945c1811d7 100644
--- a/editor/import/3d/scene_import_settings.cpp
+++ b/editor/import/3d/scene_import_settings.cpp
@@ -368,6 +368,7 @@ void SceneImportSettingsDialog::_fill_scene(Node *p_node, TreeItem *p_parent_ite
mesh_node->set_transform(src_mesh_node->get_transform());
mesh_node->set_skin(src_mesh_node->get_skin());
mesh_node->set_skeleton_path(src_mesh_node->get_skeleton_path());
+ mesh_node->set_visible(src_mesh_node->is_visible());
if (src_mesh_node->get_mesh().is_valid()) {
Ref<ImporterMesh> editor_mesh = src_mesh_node->get_mesh();
mesh_node->set_mesh(editor_mesh->get_mesh());
diff --git a/editor/import/audio_stream_import_settings.cpp b/editor/import/audio_stream_import_settings.cpp
index 0367e5e3f3..dd45806385 100644
--- a/editor/import/audio_stream_import_settings.cpp
+++ b/editor/import/audio_stream_import_settings.cpp
@@ -580,12 +580,10 @@ AudioStreamImportSettingsDialog::AudioStreamImportSettingsDialog() {
bar_beats_edit->set_max(32);
bar_beats_edit->connect(SceneStringName(value_changed), callable_mp(this, &AudioStreamImportSettingsDialog::_settings_changed).unbind(1));
interactive_hb->add_child(bar_beats_edit);
- interactive_hb->add_spacer();
main_vbox->add_margin_child(TTR("Music Playback:"), interactive_hb);
color_rect = memnew(ColorRect);
- main_vbox->add_margin_child(TTR("Preview:"), color_rect);
-
+ main_vbox->add_margin_child(TTR("Preview:"), color_rect, true);
color_rect->set_custom_minimum_size(Size2(600, 200) * EDSCALE);
color_rect->set_v_size_flags(Control::SIZE_EXPAND_FILL);
diff --git a/editor/import/resource_importer_bitmask.h b/editor/import/resource_importer_bitmask.h
index 8963c8d918..30564bf0fe 100644
--- a/editor/import/resource_importer_bitmask.h
+++ b/editor/import/resource_importer_bitmask.h
@@ -50,6 +50,8 @@ public:
virtual bool get_option_visibility(const String &p_path, const String &p_option, const HashMap<StringName, Variant> &p_options) const override;
virtual Error import(const String &p_source_file, const String &p_save_path, const HashMap<StringName, Variant> &p_options, List<String> *r_platform_variants, List<String> *r_gen_files = nullptr, Variant *r_metadata = nullptr) override;
+ virtual bool can_import_threaded() const override { return true; }
+
ResourceImporterBitMap();
~ResourceImporterBitMap();
};
diff --git a/editor/import/resource_importer_bmfont.h b/editor/import/resource_importer_bmfont.h
index d31cd03736..48f036ff13 100644
--- a/editor/import/resource_importer_bmfont.h
+++ b/editor/import/resource_importer_bmfont.h
@@ -50,6 +50,8 @@ public:
virtual Error import(const String &p_source_file, const String &p_save_path, const HashMap<StringName, Variant> &p_options, List<String> *r_platform_variants, List<String> *r_gen_files = nullptr, Variant *r_metadata = nullptr) override;
+ virtual bool can_import_threaded() const override { return true; }
+
ResourceImporterBMFont();
};
diff --git a/editor/import/resource_importer_csv_translation.h b/editor/import/resource_importer_csv_translation.h
index c6b05eb043..9c83719ed1 100644
--- a/editor/import/resource_importer_csv_translation.h
+++ b/editor/import/resource_importer_csv_translation.h
@@ -51,6 +51,8 @@ public:
virtual Error import(const String &p_source_file, const String &p_save_path, const HashMap<StringName, Variant> &p_options, List<String> *r_platform_variants, List<String> *r_gen_files = nullptr, Variant *r_metadata = nullptr) override;
+ virtual bool can_import_threaded() const override { return true; }
+
ResourceImporterCSVTranslation();
};
diff --git a/editor/import/resource_importer_dynamic_font.h b/editor/import/resource_importer_dynamic_font.h
index de89e6b76f..7c7a16cf92 100644
--- a/editor/import/resource_importer_dynamic_font.h
+++ b/editor/import/resource_importer_dynamic_font.h
@@ -60,6 +60,8 @@ public:
virtual Error import(const String &p_source_file, const String &p_save_path, const HashMap<StringName, Variant> &p_options, List<String> *r_platform_variants, List<String> *r_gen_files = nullptr, Variant *r_metadata = nullptr) override;
+ virtual bool can_import_threaded() const override { return true; }
+
ResourceImporterDynamicFont();
};
diff --git a/editor/import/resource_importer_image.h b/editor/import/resource_importer_image.h
index 1490ab30d5..dd395009c1 100644
--- a/editor/import/resource_importer_image.h
+++ b/editor/import/resource_importer_image.h
@@ -52,6 +52,8 @@ public:
virtual Error import(const String &p_source_file, const String &p_save_path, const HashMap<StringName, Variant> &p_options, List<String> *r_platform_variants, List<String> *r_gen_files = nullptr, Variant *r_metadata = nullptr) override;
+ virtual bool can_import_threaded() const override { return true; }
+
ResourceImporterImage();
};
diff --git a/editor/import/resource_importer_imagefont.h b/editor/import/resource_importer_imagefont.h
index 065351c361..6b30a3cd6e 100644
--- a/editor/import/resource_importer_imagefont.h
+++ b/editor/import/resource_importer_imagefont.h
@@ -50,6 +50,8 @@ public:
virtual Error import(const String &p_source_file, const String &p_save_path, const HashMap<StringName, Variant> &p_options, List<String> *r_platform_variants, List<String> *r_gen_files = nullptr, Variant *r_metadata = nullptr) override;
+ virtual bool can_import_threaded() const override { return true; }
+
ResourceImporterImageFont();
};
diff --git a/editor/import/resource_importer_layered_texture.h b/editor/import/resource_importer_layered_texture.h
index 26495eed8d..d8b5bc2d14 100644
--- a/editor/import/resource_importer_layered_texture.h
+++ b/editor/import/resource_importer_layered_texture.h
@@ -117,6 +117,8 @@ public:
virtual bool are_import_settings_valid(const String &p_path, const Dictionary &p_meta) const override;
virtual String get_import_settings_string() const override;
+ virtual bool can_import_threaded() const override { return true; }
+
void set_mode(Mode p_mode) { mode = p_mode; }
ResourceImporterLayeredTexture(bool p_singleton = false);
diff --git a/editor/import/resource_importer_shader_file.h b/editor/import/resource_importer_shader_file.h
index aefc967989..b28dea36d6 100644
--- a/editor/import/resource_importer_shader_file.h
+++ b/editor/import/resource_importer_shader_file.h
@@ -51,6 +51,8 @@ public:
virtual Error import(const String &p_source_file, const String &p_save_path, const HashMap<StringName, Variant> &p_options, List<String> *r_platform_variants, List<String> *r_gen_files = nullptr, Variant *r_metadata = nullptr) override;
+ virtual bool can_import_threaded() const override { return true; }
+
ResourceImporterShaderFile();
};
diff --git a/editor/import/resource_importer_texture.h b/editor/import/resource_importer_texture.h
index 6d74c4e2f9..6c87cd0abb 100644
--- a/editor/import/resource_importer_texture.h
+++ b/editor/import/resource_importer_texture.h
@@ -102,6 +102,8 @@ public:
virtual Error import(const String &p_source_file, const String &p_save_path, const HashMap<StringName, Variant> &p_options, List<String> *r_platform_variants, List<String> *r_gen_files = nullptr, Variant *r_metadata = nullptr) override;
+ virtual bool can_import_threaded() const override { return true; }
+
void update_imports();
virtual bool are_import_settings_valid(const String &p_path, const Dictionary &p_meta) const override;
diff --git a/editor/import/resource_importer_texture_atlas.h b/editor/import/resource_importer_texture_atlas.h
index 0f2b10424c..e4ad9ac230 100644
--- a/editor/import/resource_importer_texture_atlas.h
+++ b/editor/import/resource_importer_texture_atlas.h
@@ -67,6 +67,8 @@ public:
virtual Error import(const String &p_source_file, const String &p_save_path, const HashMap<StringName, Variant> &p_options, List<String> *r_platform_variants, List<String> *r_gen_files = nullptr, Variant *r_metadata = nullptr) override;
virtual Error import_group_file(const String &p_group_file, const HashMap<String, HashMap<StringName, Variant>> &p_source_file_options, const HashMap<String, String> &p_base_paths) override;
+ virtual bool can_import_threaded() const override { return true; }
+
ResourceImporterTextureAtlas();
};
diff --git a/editor/import/resource_importer_wav.cpp b/editor/import/resource_importer_wav.cpp
index 339e7921b3..2654952e8a 100644
--- a/editor/import/resource_importer_wav.cpp
+++ b/editor/import/resource_importer_wav.cpp
@@ -213,9 +213,7 @@ Error ResourceImporterWAV::import(const String &p_source_file, const String &p_s
frames = remaining_bytes;
}
- if (format_channels == 0) {
- ERR_FAIL_COND_V(format_channels == 0, ERR_INVALID_DATA);
- }
+ ERR_FAIL_COND_V(format_channels == 0, ERR_INVALID_DATA);
frames /= format_channels;
frames /= (format_bits >> 3);
diff --git a/editor/import/resource_importer_wav.h b/editor/import/resource_importer_wav.h
index 47af37ba41..2253756554 100644
--- a/editor/import/resource_importer_wav.h
+++ b/editor/import/resource_importer_wav.h
@@ -142,6 +142,8 @@ public:
virtual Error import(const String &p_source_file, const String &p_save_path, const HashMap<StringName, Variant> &p_options, List<String> *r_platform_variants, List<String> *r_gen_files = nullptr, Variant *r_metadata = nullptr) override;
+ virtual bool can_import_threaded() const override { return true; }
+
ResourceImporterWAV();
};
diff --git a/editor/plugins/game_view_plugin.cpp b/editor/plugins/game_view_plugin.cpp
index f45af72e90..5c1f81ee94 100644
--- a/editor/plugins/game_view_plugin.cpp
+++ b/editor/plugins/game_view_plugin.cpp
@@ -30,6 +30,7 @@
#include "game_view_plugin.h"
+#include "core/debugger/debugger_marshalls.h"
#include "editor/editor_main_screen.h"
#include "editor/editor_node.h"
#include "editor/editor_settings.h"
@@ -40,7 +41,15 @@
#include "scene/gui/separator.h"
void GameViewDebugger::_session_started(Ref<EditorDebuggerSession> p_session) {
- p_session->send_message("scene:runtime_node_select_setup", Array());
+ Array setup_data;
+ Dictionary settings;
+ settings["editors/panning/2d_editor_panning_scheme"] = EDITOR_GET("editors/panning/2d_editor_panning_scheme");
+ settings["editors/panning/simple_panning"] = EDITOR_GET("editors/panning/simple_panning");
+ settings["editors/panning/warped_mouse_panning"] = EDITOR_GET("editors/panning/warped_mouse_panning");
+ settings["editors/panning/2d_editor_pan_speed"] = EDITOR_GET("editors/panning/2d_editor_pan_speed");
+ settings["canvas_item_editor/pan_view"] = DebuggerMarshalls::serialize_key_shortcut(ED_GET_SHORTCUT("canvas_item_editor/pan_view"));
+ setup_data.append(settings);
+ p_session->send_message("scene:runtime_node_select_setup", setup_data);
Array type;
type.append(node_type);
diff --git a/editor/plugins/gdextension_export_plugin.h b/editor/plugins/gdextension_export_plugin.h
index ad6b534235..c8ed05c6b7 100644
--- a/editor/plugins/gdextension_export_plugin.h
+++ b/editor/plugins/gdextension_export_plugin.h
@@ -77,12 +77,14 @@ void GDExtensionExportPlugin::_export_file(const String &p_path, const String &p
HashSet<String> archs;
HashSet<String> features_wo_arch;
+ Vector<String> features_vector;
for (const String &tag : p_features) {
if (all_archs.has(tag)) {
archs.insert(tag);
} else {
features_wo_arch.insert(tag);
}
+ features_vector.append(tag);
}
if (archs.is_empty()) {
@@ -90,11 +92,22 @@ void GDExtensionExportPlugin::_export_file(const String &p_path, const String &p
}
HashSet<String> libs_added;
+ struct FoundLibInfo {
+ int count = 0;
+ Vector<String> libs;
+ };
+ HashMap<String, FoundLibInfo> libs_found;
+ for (const String &arch_tag : archs) {
+ if (arch_tag != "universal") {
+ libs_found[arch_tag] = FoundLibInfo();
+ }
+ }
for (const String &arch_tag : archs) {
PackedStringArray tags;
String library_path = GDExtensionLibraryLoader::find_extension_library(
p_path, config, [features_wo_arch, arch_tag](const String &p_feature) { return features_wo_arch.has(p_feature) || (p_feature == arch_tag); }, &tags);
+
if (libs_added.has(library_path)) {
continue; // Universal library, already added for another arch, do not duplicate.
}
@@ -122,15 +135,19 @@ void GDExtensionExportPlugin::_export_file(const String &p_path, const String &p
String linker_flags = "-Wl,-U,_" + entry_symbol;
add_ios_linker_flags(linker_flags);
}
- } else {
- Vector<String> features_vector;
- for (const String &E : p_features) {
- features_vector.append(E);
- }
- if (get_export_platform().is_valid()) {
- get_export_platform()->add_message(EditorExportPlatform::EXPORT_MESSAGE_WARNING, TTR("GDExtension"), vformat(TTR("No suitable library found for GDExtension: \"%s\". Possible feature flags for your platform: %s"), p_path, String(", ").join(features_vector)));
+
+ // Update found library info.
+ if (arch_tag == "universal") {
+ for (const String &sub_arch_tag : archs) {
+ if (sub_arch_tag != "universal") {
+ libs_found[sub_arch_tag].count++;
+ libs_found[sub_arch_tag].libs.push_back(library_path);
+ }
+ }
+ } else {
+ libs_found[arch_tag].count++;
+ libs_found[arch_tag].libs.push_back(library_path);
}
- return;
}
Vector<SharedObject> dependencies_shared_objects = GDExtensionLibraryLoader::find_extension_dependencies(p_path, config, [p_features](String p_feature) { return p_features.has(p_feature); });
@@ -138,6 +155,18 @@ void GDExtensionExportPlugin::_export_file(const String &p_path, const String &p
_add_shared_object(shared_object);
}
}
+
+ for (const KeyValue<String, FoundLibInfo> &E : libs_found) {
+ if (E.value.count == 0) {
+ if (get_export_platform().is_valid()) {
+ get_export_platform()->add_message(EditorExportPlatform::EXPORT_MESSAGE_WARNING, TTR("GDExtension"), vformat(TTR("No \"%s\" library found for GDExtension: \"%s\". Possible feature flags for your platform: %s"), E.key, p_path, String(", ").join(features_vector)));
+ }
+ } else if (E.value.count > 1) {
+ if (get_export_platform().is_valid()) {
+ get_export_platform()->add_message(EditorExportPlatform::EXPORT_MESSAGE_WARNING, TTR("GDExtension"), vformat(TTR("Multiple \"%s\" libraries found for GDExtension: \"%s\": \"%s\"."), E.key, p_path, String(", ").join(E.value.libs)));
+ }
+ }
+ }
}
#endif // GDEXTENSION_EXPORT_PLUGIN_H
diff --git a/editor/plugins/mesh_instance_3d_editor_plugin.cpp b/editor/plugins/mesh_instance_3d_editor_plugin.cpp
index 48dcd2e6fc..fdc222e64f 100644
--- a/editor/plugins/mesh_instance_3d_editor_plugin.cpp
+++ b/editor/plugins/mesh_instance_3d_editor_plugin.cpp
@@ -40,6 +40,7 @@
#include "scene/3d/physics/collision_shape_3d.h"
#include "scene/3d/physics/physics_body_3d.h"
#include "scene/3d/physics/static_body_3d.h"
+#include "scene/gui/aspect_ratio_container.h"
#include "scene/gui/box_container.h"
#include "scene/gui/dialogs.h"
#include "scene/gui/menu_button.h"
@@ -445,10 +446,43 @@ void MeshInstance3DEditor::_debug_uv_draw() {
}
debug_uv->set_clip_contents(true);
- debug_uv->draw_rect(Rect2(Vector2(), debug_uv->get_size()), get_theme_color(SNAME("dark_color_3"), EditorStringName(Editor)));
+ debug_uv->draw_rect(
+ Rect2(Vector2(), debug_uv->get_size()),
+ get_theme_color(SNAME("dark_color_3"), EditorStringName(Editor)));
+
+ // Draw an outline to represent the UV2's beginning and end area (useful on Black OLED theme).
+ // Top-left coordinate needs to be `(1, 1)` to prevent `clip_contents` from clipping the top and left lines.
+ debug_uv->draw_rect(
+ Rect2(Vector2(1, 1), debug_uv->get_size() - Vector2(1, 1)),
+ get_theme_color(SNAME("mono_color"), EditorStringName(Editor)) * Color(1, 1, 1, 0.125),
+ false,
+ Math::round(EDSCALE));
+
+ for (int x = 1; x <= 7; x++) {
+ debug_uv->draw_line(
+ Vector2(debug_uv->get_size().x * 0.125 * x, 0),
+ Vector2(debug_uv->get_size().x * 0.125 * x, debug_uv->get_size().y),
+ get_theme_color(SNAME("mono_color"), EditorStringName(Editor)) * Color(1, 1, 1, 0.125),
+ Math::round(EDSCALE));
+ }
+
+ for (int y = 1; y <= 7; y++) {
+ debug_uv->draw_line(
+ Vector2(0, debug_uv->get_size().y * 0.125 * y),
+ Vector2(debug_uv->get_size().x, debug_uv->get_size().y * 0.125 * y),
+ get_theme_color(SNAME("mono_color"), EditorStringName(Editor)) * Color(1, 1, 1, 0.125),
+ Math::round(EDSCALE));
+ }
+
debug_uv->draw_set_transform(Vector2(), 0, debug_uv->get_size());
+
// Use a translucent color to allow overlapping triangles to be visible.
- debug_uv->draw_multiline(uv_lines, get_theme_color(SNAME("mono_color"), EditorStringName(Editor)) * Color(1, 1, 1, 0.5));
+ // Divide line width by the drawing scale set above, so that line width is consistent regardless of dialog size.
+ // Aspect ratio is preserved by the parent AspectRatioContainer, so we only need to check the X size which is always equal to Y.
+ debug_uv->draw_multiline(
+ uv_lines,
+ get_theme_color(SNAME("mono_color"), EditorStringName(Editor)) * Color(1, 1, 1, 0.5),
+ Math::round(EDSCALE) / debug_uv->get_size().x);
}
void MeshInstance3DEditor::_create_navigation_mesh() {
@@ -613,10 +647,14 @@ MeshInstance3DEditor::MeshInstance3DEditor() {
debug_uv_dialog = memnew(AcceptDialog);
debug_uv_dialog->set_title(TTR("UV Channel Debug"));
add_child(debug_uv_dialog);
+
+ debug_uv_arc = memnew(AspectRatioContainer);
+ debug_uv_dialog->add_child(debug_uv_arc);
+
debug_uv = memnew(Control);
debug_uv->set_custom_minimum_size(Size2(600, 600) * EDSCALE);
debug_uv->connect(SceneStringName(draw), callable_mp(this, &MeshInstance3DEditor::_debug_uv_draw));
- debug_uv_dialog->add_child(debug_uv);
+ debug_uv_arc->add_child(debug_uv);
navigation_mesh_dialog = memnew(ConfirmationDialog);
navigation_mesh_dialog->set_title(TTR("Create NavigationMesh"));
diff --git a/editor/plugins/mesh_instance_3d_editor_plugin.h b/editor/plugins/mesh_instance_3d_editor_plugin.h
index c982df9c5f..569ecd4fff 100644
--- a/editor/plugins/mesh_instance_3d_editor_plugin.h
+++ b/editor/plugins/mesh_instance_3d_editor_plugin.h
@@ -36,6 +36,7 @@
#include "scene/gui/option_button.h"
class AcceptDialog;
+class AspectRatioContainer;
class ConfirmationDialog;
class MenuButton;
class SpinBox;
@@ -79,6 +80,7 @@ class MeshInstance3DEditor : public Control {
AcceptDialog *err_dialog = nullptr;
AcceptDialog *debug_uv_dialog = nullptr;
+ AspectRatioContainer *debug_uv_arc = nullptr;
Control *debug_uv = nullptr;
Vector<Vector2> uv_lines;
diff --git a/editor/plugins/node_3d_editor_plugin.cpp b/editor/plugins/node_3d_editor_plugin.cpp
index b877c0b83e..5afe01025d 100644
--- a/editor/plugins/node_3d_editor_plugin.cpp
+++ b/editor/plugins/node_3d_editor_plugin.cpp
@@ -8480,7 +8480,7 @@ void Node3DEditor::clear() {
}
for (uint32_t i = 0; i < VIEWPORTS_COUNT; i++) {
- viewports[i]->view_menu->get_popup()->set_item_checked(view_menu->get_popup()->get_item_index(Node3DEditorViewport::VIEW_AUDIO_LISTENER), i == 0);
+ viewports[i]->view_menu->get_popup()->set_item_checked(viewports[i]->view_menu->get_popup()->get_item_index(Node3DEditorViewport::VIEW_AUDIO_LISTENER), i == 0);
viewports[i]->viewport->set_as_audio_listener_3d(i == 0);
}
diff --git a/editor/plugins/visual_shader_editor_plugin.cpp b/editor/plugins/visual_shader_editor_plugin.cpp
index 05929c5f0d..9c1befa144 100644
--- a/editor/plugins/visual_shader_editor_plugin.cpp
+++ b/editor/plugins/visual_shader_editor_plugin.cpp
@@ -1472,7 +1472,7 @@ void VisualShaderGraphPlugin::disconnect_nodes(VisualShader::Type p_type, int p_
if (visual_shader.is_valid() && visual_shader->get_shader_type() == p_type) {
graph->disconnect_node(itos(p_from_node), p_from_port, itos(p_to_node), p_to_port);
- for (const List<VisualShader::Connection>::Element *E = connections.front(); E; E = E->next()) {
+ for (List<VisualShader::Connection>::Element *E = connections.front(); E; E = E->next()) {
if (E->get().from_node == p_from_node && E->get().from_port == p_from_port && E->get().to_node == p_to_node && E->get().to_port == p_to_port) {
connections.erase(E);
break;
diff --git a/editor/scene_tree_dock.cpp b/editor/scene_tree_dock.cpp
index 19b7122a0d..87ba2e6875 100644
--- a/editor/scene_tree_dock.cpp
+++ b/editor/scene_tree_dock.cpp
@@ -126,7 +126,8 @@ void SceneTreeDock::input(const Ref<InputEvent> &p_event) {
Ref<InputEventMouseButton> mb = p_event;
if (mb.is_valid() && (mb->get_button_index() == MouseButton::LEFT || mb->get_button_index() == MouseButton::RIGHT)) {
- if (mb->is_pressed() && scene_tree->get_rect().has_point(scene_tree->get_local_mouse_position())) {
+ Tree *tree = scene_tree->get_scene_tree();
+ if (mb->is_pressed() && tree->get_rect().has_point(tree->get_local_mouse_position())) {
tree_clicked = true;
} else if (!mb->is_pressed()) {
tree_clicked = false;