diff --git a/doc/classes/EditorSettings.xml b/doc/classes/EditorSettings.xml index ab5b9cb2353..f69bfffb5e8 100644 --- a/doc/classes/EditorSettings.xml +++ b/doc/classes/EditorSettings.xml @@ -798,6 +798,9 @@ If [code]true[/code], results will include files located in the [code]addons[/code] folder. + + If [code]true[/code], highlighting a resource will preview it quickly without confirming the selection or closing the dialog. + The number of missed query characters allowed in a match when fuzzy matching is enabled. For example, with the default value of [code]2[/code], [code]"normal"[/code] would match [code]"narmal"[/code] and [code]"norma"[/code] but not [code]"nor"[/code]. diff --git a/editor/gui/editor_quick_open_dialog.cpp b/editor/gui/editor_quick_open_dialog.cpp index da5ee7c923d..32346b0f9ba 100644 --- a/editor/gui/editor_quick_open_dialog.cpp +++ b/editor/gui/editor_quick_open_dialog.cpp @@ -35,9 +35,11 @@ #include "editor/docks/filesystem_dock.h" #include "editor/editor_node.h" #include "editor/editor_string_names.h" +#include "editor/editor_undo_redo_manager.h" #include "editor/file_system/editor_file_system.h" #include "editor/file_system/editor_paths.h" #include "editor/inspector/editor_resource_preview.h" +#include "editor/inspector/multi_node_edit.h" #include "editor/settings/editor_settings.h" #include "editor/themes/editor_scale.h" #include "scene/gui/center_container.h" @@ -123,7 +125,8 @@ EditorQuickOpenDialog::EditorQuickOpenDialog() { { container = memnew(QuickOpenResultContainer); - container->connect("result_clicked", callable_mp(this, &EditorQuickOpenDialog::ok_pressed)); + container->connect("selection_changed", callable_mp(this, &EditorQuickOpenDialog::selection_changed)); + container->connect("result_clicked", callable_mp(this, &EditorQuickOpenDialog::item_pressed)); vbc->add_child(container); } @@ -149,26 +152,117 @@ void EditorQuickOpenDialog::popup_dialog(const Vector &p_base_types, ERR_FAIL_COND(p_base_types.is_empty()); ERR_FAIL_COND(!p_item_selected_callback.is_valid()); + property_object = nullptr; + property_path = ""; item_selected_callback = p_item_selected_callback; container->init(p_base_types); - get_ok_button()->set_disabled(container->has_nothing_selected()); + container->set_instant_preview_toggle_visible(false); + _finish_dialog_setup(p_base_types); +} +void EditorQuickOpenDialog::popup_dialog_for_property(const Vector &p_base_types, Object *p_obj, const StringName &p_path, const Callable &p_item_selected_callback) { + ERR_FAIL_NULL(p_obj); + ERR_FAIL_COND(p_base_types.is_empty()); + ERR_FAIL_COND(!p_item_selected_callback.is_valid()); + + property_object = p_obj; + property_path = p_path; + item_selected_callback = p_item_selected_callback; + initial_property_value = property_object->get(property_path); + + // Reset this, so that the property isn't updated immediately upon opening + // the window. + initial_selection_performed = false; + + container->init(p_base_types); + container->set_instant_preview_toggle_visible(true); + _finish_dialog_setup(p_base_types); +} + +void EditorQuickOpenDialog::_finish_dialog_setup(const Vector &p_base_types) { + get_ok_button()->set_disabled(container->has_nothing_selected()); set_title(get_dialog_title(p_base_types)); popup_centered_clamped(Size2(780, 650) * EDSCALE, 0.8f); search_box->grab_focus(); } void EditorQuickOpenDialog::ok_pressed() { - item_selected_callback.call(container->get_selected()); - - container->save_selected_item(); + update_property(); container->cleanup(); search_box->clear(); hide(); } +bool EditorQuickOpenDialog::_is_instant_preview_active() const { + return property_object != nullptr && container->is_instant_preview_enabled(); +} + +void EditorQuickOpenDialog::selection_changed() { + if (!_is_instant_preview_active()) { + return; + } + + // This prevents the property from being changed the first time the Quick Open + // window is opened. + if (!initial_selection_performed) { + initial_selection_performed = true; + } else { + preview_property(); + } +} + +void EditorQuickOpenDialog::item_pressed(bool p_double_click) { + // A double-click should always be taken as a "confirm" action. + if (p_double_click) { + container->save_selected_item(); + ok_pressed(); + return; + } + + // Single-clicks should be taken as a "confirm" action only if Instant Preview + // isn't currently enabled, or the property object is null for some reason. + if (!_is_instant_preview_active()) { + container->save_selected_item(); + ok_pressed(); + } +} + +void EditorQuickOpenDialog::preview_property() { + Ref loaded_resource = ResourceLoader::load(container->get_selected()); + ERR_FAIL_COND_MSG(loaded_resource.is_null(), "Cannot load resource from path '" + container->get_selected() + "'."); + + // MultiNodeEdit has adding to the undo/redo stack baked into its set function. + // As such, we have to specifically call a version of its setter that doesn't + // create undo/redo actions. + if (Object::cast_to(property_object)) { + Object::cast_to(property_object)->_set_impl(property_path, loaded_resource, "", false); + } else { + property_object->set(property_path, loaded_resource); + } +} + +void EditorQuickOpenDialog::update_property() { + // Set the property back to the initial value first, so that the undo action + // has the correct object. + if (property_object) { + if (Object::cast_to(property_object)) { + Object::cast_to(property_object)->_set_impl(property_path, initial_property_value, "", false); + } else { + property_object->set(property_path, initial_property_value); + } + } + item_selected_callback.call(container->get_selected()); +} + void EditorQuickOpenDialog::cancel_pressed() { + if (property_object) { + if (Object::cast_to(property_object)) { + Object::cast_to(property_object)->_set_impl(property_path, initial_property_value, "", false); + } else { + property_object->set(property_path, initial_property_value); + } + } container->cleanup(); search_box->clear(); } @@ -262,6 +356,13 @@ QuickOpenResultContainer::QuickOpenResultContainer() { bottom_bar->add_theme_constant_override("separation", 3); add_child(bottom_bar); + instant_preview_toggle = memnew(CheckButton); + style_button(instant_preview_toggle); + instant_preview_toggle->set_text(TTRC("Instant Preview")); + instant_preview_toggle->set_tooltip_text(TTRC("Selected resource will be previewed in the editor before accepting.")); + instant_preview_toggle->connect(SceneStringName(toggled), callable_mp(this, &QuickOpenResultContainer::_toggle_instant_preview)); + bottom_bar->add_child(instant_preview_toggle); + fuzzy_search_toggle = memnew(CheckButton); style_button(fuzzy_search_toggle); fuzzy_search_toggle->set_text(TTR("Fuzzy Search")); @@ -333,8 +434,10 @@ void QuickOpenResultContainer::init(const Vector &p_base_types) { _set_display_mode((QuickOpenDisplayMode)last); } + const bool do_instant_preview = EDITOR_GET("filesystem/quick_open_dialog/instant_preview"); 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"); + instant_preview_toggle->set_pressed_no_signal(do_instant_preview); fuzzy_search_toggle->set_pressed_no_signal(fuzzy_matching); include_addons_toggle->set_pressed_no_signal(include_addons); never_opened = false; @@ -689,6 +792,8 @@ void QuickOpenResultContainer::_select_item(int p_index) { bool in_history = history_set.has(candidates[selection_index].file_path); file_details_path->set_text(get_selected() + (in_history ? TTR(" (recently opened)") : "")); + emit_signal(SNAME("selection_changed")); + const QuickOpenResultItem *item = result_items[selection_index]; // Copied from Tree. @@ -710,7 +815,7 @@ void QuickOpenResultContainer::_item_input(const Ref &p_ev, int p_in if (mb.is_valid() && mb->is_pressed()) { if (mb->get_button_index() == MouseButton::LEFT) { _select_item(p_index); - emit_signal(SNAME("result_clicked")); + emit_signal(SNAME("result_clicked"), mb->is_double_click()); } else if (mb->get_button_index() == MouseButton::RIGHT) { _select_item(p_index); file_context_menu->set_position(result_items[p_index]->get_screen_position() + mb->get_position()); @@ -720,6 +825,10 @@ void QuickOpenResultContainer::_item_input(const Ref &p_ev, int p_in } } +void QuickOpenResultContainer::_toggle_instant_preview(bool p_pressed) { + EditorSettings::get_singleton()->set("filesystem/quick_open_dialog/instant_preview", p_pressed); +} + void QuickOpenResultContainer::_toggle_fuzzy_search(bool p_pressed) { EditorSettings::get_singleton()->set("filesystem/quick_open_dialog/enable_fuzzy_matching", p_pressed); update_results(); @@ -820,6 +929,14 @@ String _get_uid_string(const String &p_filepath) { return id == ResourceUID::INVALID_ID ? p_filepath : ResourceUID::get_singleton()->id_to_text(id); } +bool QuickOpenResultContainer::is_instant_preview_enabled() const { + return instant_preview_toggle && instant_preview_toggle->is_visible() && instant_preview_toggle->is_pressed(); +} + +void QuickOpenResultContainer::set_instant_preview_toggle_visible(bool p_visible) { + instant_preview_toggle->set_visible(p_visible); +} + void QuickOpenResultContainer::save_selected_item() { if (base_types.size() > 1) { // Getting the type of the file and checking which base type it belongs to should be possible. @@ -895,13 +1012,14 @@ void QuickOpenResultContainer::_notification(int p_what) { } void QuickOpenResultContainer::_bind_methods() { - ADD_SIGNAL(MethodInfo("result_clicked")); + ADD_SIGNAL(MethodInfo("selection_changed")); + ADD_SIGNAL(MethodInfo("result_clicked", PropertyInfo(Variant::BOOL, "double_click"))); } //------------------------- Result Item QuickOpenResultItem::QuickOpenResultItem() { - set_focus_mode(FocusMode::FOCUS_ALL); + set_focus_mode(FocusMode::FOCUS_NONE); _set_enabled(false); set_default_cursor_shape(CURSOR_POINTING_HAND); diff --git a/editor/gui/editor_quick_open_dialog.h b/editor/gui/editor_quick_open_dialog.h index c0953e06f5d..025b0f4e1ed 100644 --- a/editor/gui/editor_quick_open_dialog.h +++ b/editor/gui/editor_quick_open_dialog.h @@ -97,6 +97,9 @@ public: bool has_nothing_selected() const; String get_selected() const; + bool is_instant_preview_enabled() const; + void set_instant_preview_toggle_visible(bool p_visible); + void save_selected_item(); void cleanup(); @@ -139,6 +142,7 @@ private: Label *file_details_path = nullptr; Button *display_mode_toggle = nullptr; + CheckButton *instant_preview_toggle = nullptr; CheckButton *include_addons_toggle = nullptr; CheckButton *fuzzy_search_toggle = nullptr; @@ -168,6 +172,7 @@ private: void _layout_result_item(QuickOpenResultItem *p_item); void _set_display_mode(QuickOpenDisplayMode p_display_mode); void _toggle_display_mode(); + void _toggle_instant_preview(bool p_pressed); void _toggle_include_addons(bool p_pressed); void _toggle_fuzzy_search(bool p_pressed); void _menu_option(int p_option); @@ -252,11 +257,14 @@ class EditorQuickOpenDialog : public AcceptDialog { public: void popup_dialog(const Vector &p_base_types, const Callable &p_item_selected_callback); + void popup_dialog_for_property(const Vector &p_base_types, Object *p_obj, const StringName &p_path, const Callable &p_item_selected_callback); EditorQuickOpenDialog(); protected: virtual void cancel_pressed() override; virtual void ok_pressed() override; + void item_pressed(bool p_double_click); + void selection_changed(); private: static String get_dialog_title(const Vector &p_base_types); @@ -266,5 +274,14 @@ private: Callable item_selected_callback; + Object *property_object = nullptr; + StringName property_path; + Variant initial_property_value; + bool initial_selection_performed = false; + bool _is_instant_preview_active() const; void _search_box_text_changed(const String &p_query); + void _finish_dialog_setup(const Vector &p_base_types); + + void preview_property(); + void update_property(); }; diff --git a/editor/inspector/editor_properties.cpp b/editor/inspector/editor_properties.cpp index c1ca8d35267..c2a4dbe7b2b 100644 --- a/editor/inspector/editor_properties.cpp +++ b/editor/inspector/editor_properties.cpp @@ -3472,6 +3472,7 @@ void EditorPropertyResource::setup(Object *p_object, const String &p_path, const resource_picker->set_base_type(p_base_type); resource_picker->set_resource_owner(p_object); + resource_picker->set_property_path(p_path); resource_picker->set_editable(true); resource_picker->set_h_size_flags(SIZE_EXPAND_FILL); add_child(resource_picker); diff --git a/editor/inspector/editor_resource_picker.cpp b/editor/inspector/editor_resource_picker.cpp index fde30e2077b..12724ed22bb 100644 --- a/editor/inspector/editor_resource_picker.cpp +++ b/editor/inspector/editor_resource_picker.cpp @@ -379,7 +379,13 @@ void EditorResourcePicker::_edit_menu_cbk(int p_which) { base_types.push_back(type); } - EditorNode::get_singleton()->get_quick_open_dialog()->popup_dialog(base_types, callable_mp(this, &EditorResourcePicker::_file_selected)); + EditorQuickOpenDialog *quick_open = EditorNode::get_singleton()->get_quick_open_dialog(); + if (resource_owner) { + quick_open->popup_dialog_for_property(base_types, resource_owner, property_path, callable_mp(this, &EditorResourcePicker::_file_selected)); + } else { + quick_open->popup_dialog(base_types, callable_mp(this, &EditorResourcePicker::_file_selected)); + } + } break; case OBJ_MENU_INSPECT: { diff --git a/editor/inspector/editor_resource_picker.h b/editor/inspector/editor_resource_picker.h index 0a1f34d8e52..58b6b926130 100644 --- a/editor/inspector/editor_resource_picker.h +++ b/editor/inspector/editor_resource_picker.h @@ -84,6 +84,7 @@ class EditorResourcePicker : public HBoxContainer { }; Object *resource_owner = nullptr; + StringName property_path; PopupMenu *edit_menu = nullptr; @@ -144,6 +145,7 @@ public: bool is_toggle_pressed() const; void set_resource_owner(Object *p_object); + void set_property_path(const StringName &p_path) { property_path = p_path; } void set_editable(bool p_editable); bool is_editable() const; diff --git a/editor/inspector/multi_node_edit.cpp b/editor/inspector/multi_node_edit.cpp index 1b32a2890bb..de778409560 100644 --- a/editor/inspector/multi_node_edit.cpp +++ b/editor/inspector/multi_node_edit.cpp @@ -38,7 +38,7 @@ bool MultiNodeEdit::_set(const StringName &p_name, const Variant &p_value) { return _set_impl(p_name, p_value, ""); } -bool MultiNodeEdit::_set_impl(const StringName &p_name, const Variant &p_value, const String &p_field) { +bool MultiNodeEdit::_set_impl(const StringName &p_name, const Variant &p_value, const String &p_field, bool p_undo_redo) { Node *es = EditorNode::get_singleton()->get_edited_scene(); if (!es) { return false; @@ -59,7 +59,10 @@ bool MultiNodeEdit::_set_impl(const StringName &p_name, const Variant &p_value, EditorUndoRedoManager *ur = EditorUndoRedoManager::get_singleton(); - ur->create_action(vformat(TTR("Set %s on %d nodes"), name, get_node_count()), UndoRedo::MERGE_ENDS); + if (p_undo_redo) { + ur->create_action(vformat(TTR("Set %s on %d nodes"), name, get_node_count()), UndoRedo::MERGE_ENDS); + } + for (const NodePath &E : nodes) { Node *n = es->get_node_or_null(E); if (!n) { @@ -71,7 +74,12 @@ bool MultiNodeEdit::_set_impl(const StringName &p_name, const Variant &p_value, if (node_path_target) { path = n->get_path_to(node_path_target); } - ur->add_do_property(n, name, path); + + if (p_undo_redo) { + ur->add_do_property(n, name, path); + } else { + n->set(name, path); + } } else { Variant new_value; if (p_field.is_empty()) { @@ -81,13 +89,22 @@ bool MultiNodeEdit::_set_impl(const StringName &p_name, const Variant &p_value, // only one field new_value = fieldwise_assign(n->get(name), p_value, p_field); } - ur->add_do_property(n, name, new_value); + + if (p_undo_redo) { + ur->add_do_property(n, name, new_value); + } else { + n->set(name, new_value); + } } - ur->add_undo_property(n, name, n->get(name)); + if (p_undo_redo) { + ur->add_undo_property(n, name, n->get(name)); + } } - ur->commit_action(); + if (p_undo_redo) { + ur->commit_action(); + } return true; } diff --git a/editor/inspector/multi_node_edit.h b/editor/inspector/multi_node_edit.h index bfc8d1d0b9f..9426cf066e5 100644 --- a/editor/inspector/multi_node_edit.h +++ b/editor/inspector/multi_node_edit.h @@ -35,6 +35,8 @@ class MultiNodeEdit : public RefCounted { GDCLASS(MultiNodeEdit, RefCounted); + friend class EditorQuickOpenDialog; + LocalVector nodes; bool notify_property_list_changed_pending = false; struct PLData { @@ -42,7 +44,7 @@ class MultiNodeEdit : public RefCounted { PropertyInfo info; }; - bool _set_impl(const StringName &p_name, const Variant &p_value, const String &p_field); + bool _set_impl(const StringName &p_name, const Variant &p_value, const String &p_field, bool p_undo_redo = true); void _queue_notify_property_list_changed(); void _notify_property_list_changed(); diff --git a/editor/settings/editor_settings.cpp b/editor/settings/editor_settings.cpp index 4e75b774e94..5e2143402d3 100644 --- a/editor/settings/editor_settings.cpp +++ b/editor/settings/editor_settings.cpp @@ -659,6 +659,7 @@ void EditorSettings::_load_defaults(Ref p_extra_config) { // 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/instant_preview", false); _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)