You've already forked godot
mirror of
https://github.com/godotengine/godot.git
synced 2025-11-05 12:10:55 +00:00
Add Instant Preview to Quick Open dialog
Add toggle for instant preview Always keep search box selected so that keyboard navigation works Add default setting for Instant Preview Directly set property value for resource via Quick Load menu (no undo/redo or dirty-scene functionality yet) Add undo/redo functionality Update class reference Update doc/classes/EditorSettings.xml Co-authored-by: Micky <66727710+Mickeon@users.noreply.github.com> Slight improvement(?) to wording of setting Allow previewing without committing change Address various suggestions/improvements Only allow Instant Preview to be used if Quick Open menu is being used to modify a property Only allow property-based Quick Load when resource to modify is defined (otherwise default to old behavior) Apply suggestions from code review Co-authored-by: Tomasz Chabora <kobewi4e@gmail.com> Address comments/suggestions Get rid of duplicated code and use original callback strategy (Attempt to) fix Instant Preview for editing multiple nodes at once and undo/redo stack for single nodes Fix cancelling Quick Open when multiple nodes are selected Prevent initially selected item in Quick Open dialog from overwriting the currently selected property Apply suggestions from code review Co-authored-by: Tomasz Chabora <kobewi4e@gmail.com> Co-authored-by: A Thousand Ships <96648715+AThousandShips@users.noreply.github.com> Make a few changes/improvements based on feedback - Combine some duplicated code into `_finish_dialog_setup()` - Move `ERR_FAIL_NULL(p_obj);` to top of checks - Fix renaming of `is_instant_preview_enabled()` across code, and remove now-redundant conditions where it is used - Make `EditorResourcePicker::property_path` be `StringName` not `String`
This commit is contained in:
@@ -795,6 +795,9 @@
|
||||
<member name="filesystem/quick_open_dialog/include_addons" type="bool" setter="" getter="">
|
||||
If [code]true[/code], results will include files located in the [code]addons[/code] folder.
|
||||
</member>
|
||||
<member name="filesystem/quick_open_dialog/instant_preview" type="bool" setter="" getter="">
|
||||
If [code]true[/code], highlighting a resource will preview it quickly without confirming the selection or closing the dialog.
|
||||
</member>
|
||||
<member name="filesystem/quick_open_dialog/max_fuzzy_misses" type="int" setter="" getter="">
|
||||
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].
|
||||
</member>
|
||||
|
||||
@@ -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<StringName> &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<StringName> &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<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(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<Resource> 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<MultiNodeEdit>(property_object)) {
|
||||
Object::cast_to<MultiNodeEdit>(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<MultiNodeEdit>(property_object)) {
|
||||
Object::cast_to<MultiNodeEdit>(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<MultiNodeEdit>(property_object)) {
|
||||
Object::cast_to<MultiNodeEdit>(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<StringName> &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;
|
||||
@@ -679,6 +782,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.
|
||||
@@ -700,7 +805,7 @@ void QuickOpenResultContainer::_item_input(const Ref<InputEvent> &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());
|
||||
@@ -710,6 +815,10 @@ void QuickOpenResultContainer::_item_input(const Ref<InputEvent> &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();
|
||||
@@ -810,6 +919,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.
|
||||
@@ -885,13 +1002,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);
|
||||
|
||||
|
||||
@@ -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<StringName> &p_base_types, const Callable &p_item_selected_callback);
|
||||
void popup_dialog_for_property(const Vector<StringName> &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<StringName> &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<StringName> &p_base_types);
|
||||
|
||||
void preview_property();
|
||||
void update_property();
|
||||
};
|
||||
|
||||
@@ -3441,6 +3441,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);
|
||||
|
||||
@@ -361,7 +361,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: {
|
||||
|
||||
@@ -83,6 +83,7 @@ class EditorResourcePicker : public HBoxContainer {
|
||||
};
|
||||
|
||||
Object *resource_owner = nullptr;
|
||||
StringName property_path;
|
||||
|
||||
PopupMenu *edit_menu = nullptr;
|
||||
|
||||
@@ -143,6 +144,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;
|
||||
|
||||
@@ -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();
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
if (p_undo_redo) {
|
||||
ur->add_do_property(n, name, new_value);
|
||||
} else {
|
||||
n->set(name, new_value);
|
||||
}
|
||||
}
|
||||
|
||||
if (p_undo_redo) {
|
||||
ur->add_undo_property(n, name, n->get(name));
|
||||
}
|
||||
}
|
||||
|
||||
if (p_undo_redo) {
|
||||
ur->commit_action();
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
@@ -35,6 +35,8 @@
|
||||
class MultiNodeEdit : public RefCounted {
|
||||
GDCLASS(MultiNodeEdit, RefCounted);
|
||||
|
||||
friend class EditorQuickOpenDialog;
|
||||
|
||||
LocalVector<NodePath> 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();
|
||||
|
||||
|
||||
@@ -656,6 +656,7 @@ void EditorSettings::_load_defaults(Ref<ConfigFile> 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)
|
||||
|
||||
Reference in New Issue
Block a user