Skip to content

Commit fe9cdea

Browse files
committed
Merge pull request godotengine#106947 from Meorge/feat/quick-load-preview
Allow Quick Open dialog to preview change in scene
2 parents 0f897e7 + 2f1e8da commit fe9cdea

File tree

9 files changed

+183
-16
lines changed

9 files changed

+183
-16
lines changed

doc/classes/EditorSettings.xml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -798,6 +798,9 @@
798798
<member name="filesystem/quick_open_dialog/include_addons" type="bool" setter="" getter="">
799799
If [code]true[/code], results will include files located in the [code]addons[/code] folder.
800800
</member>
801+
<member name="filesystem/quick_open_dialog/instant_preview" type="bool" setter="" getter="">
802+
If [code]true[/code], highlighting a resource will preview it quickly without confirming the selection or closing the dialog.
803+
</member>
801804
<member name="filesystem/quick_open_dialog/max_fuzzy_misses" type="int" setter="" getter="">
802805
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].
803806
</member>

editor/gui/editor_quick_open_dialog.cpp

Lines changed: 126 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -35,9 +35,11 @@
3535
#include "editor/docks/filesystem_dock.h"
3636
#include "editor/editor_node.h"
3737
#include "editor/editor_string_names.h"
38+
#include "editor/editor_undo_redo_manager.h"
3839
#include "editor/file_system/editor_file_system.h"
3940
#include "editor/file_system/editor_paths.h"
4041
#include "editor/inspector/editor_resource_preview.h"
42+
#include "editor/inspector/multi_node_edit.h"
4143
#include "editor/settings/editor_settings.h"
4244
#include "editor/themes/editor_scale.h"
4345
#include "scene/gui/center_container.h"
@@ -123,7 +125,8 @@ EditorQuickOpenDialog::EditorQuickOpenDialog() {
123125

124126
{
125127
container = memnew(QuickOpenResultContainer);
126-
container->connect("result_clicked", callable_mp(this, &EditorQuickOpenDialog::ok_pressed));
128+
container->connect("selection_changed", callable_mp(this, &EditorQuickOpenDialog::selection_changed));
129+
container->connect("result_clicked", callable_mp(this, &EditorQuickOpenDialog::item_pressed));
127130
vbc->add_child(container);
128131
}
129132

@@ -149,26 +152,117 @@ void EditorQuickOpenDialog::popup_dialog(const Vector<StringName> &p_base_types,
149152
ERR_FAIL_COND(p_base_types.is_empty());
150153
ERR_FAIL_COND(!p_item_selected_callback.is_valid());
151154

155+
property_object = nullptr;
156+
property_path = "";
152157
item_selected_callback = p_item_selected_callback;
153158

154159
container->init(p_base_types);
155-
get_ok_button()->set_disabled(container->has_nothing_selected());
160+
container->set_instant_preview_toggle_visible(false);
161+
_finish_dialog_setup(p_base_types);
162+
}
163+
164+
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) {
165+
ERR_FAIL_NULL(p_obj);
166+
ERR_FAIL_COND(p_base_types.is_empty());
167+
ERR_FAIL_COND(!p_item_selected_callback.is_valid());
168+
169+
property_object = p_obj;
170+
property_path = p_path;
171+
item_selected_callback = p_item_selected_callback;
172+
initial_property_value = property_object->get(property_path);
156173

174+
// Reset this, so that the property isn't updated immediately upon opening
175+
// the window.
176+
initial_selection_performed = false;
177+
178+
container->init(p_base_types);
179+
container->set_instant_preview_toggle_visible(true);
180+
_finish_dialog_setup(p_base_types);
181+
}
182+
183+
void EditorQuickOpenDialog::_finish_dialog_setup(const Vector<StringName> &p_base_types) {
184+
get_ok_button()->set_disabled(container->has_nothing_selected());
157185
set_title(get_dialog_title(p_base_types));
158186
popup_centered_clamped(Size2(780, 650) * EDSCALE, 0.8f);
159187
search_box->grab_focus();
160188
}
161189

162190
void EditorQuickOpenDialog::ok_pressed() {
163-
item_selected_callback.call(container->get_selected());
164-
165-
container->save_selected_item();
191+
update_property();
166192
container->cleanup();
167193
search_box->clear();
168194
hide();
169195
}
170196

197+
bool EditorQuickOpenDialog::_is_instant_preview_active() const {
198+
return property_object != nullptr && container->is_instant_preview_enabled();
199+
}
200+
201+
void EditorQuickOpenDialog::selection_changed() {
202+
if (!_is_instant_preview_active()) {
203+
return;
204+
}
205+
206+
// This prevents the property from being changed the first time the Quick Open
207+
// window is opened.
208+
if (!initial_selection_performed) {
209+
initial_selection_performed = true;
210+
} else {
211+
preview_property();
212+
}
213+
}
214+
215+
void EditorQuickOpenDialog::item_pressed(bool p_double_click) {
216+
// A double-click should always be taken as a "confirm" action.
217+
if (p_double_click) {
218+
container->save_selected_item();
219+
ok_pressed();
220+
return;
221+
}
222+
223+
// Single-clicks should be taken as a "confirm" action only if Instant Preview
224+
// isn't currently enabled, or the property object is null for some reason.
225+
if (!_is_instant_preview_active()) {
226+
container->save_selected_item();
227+
ok_pressed();
228+
}
229+
}
230+
231+
void EditorQuickOpenDialog::preview_property() {
232+
Ref<Resource> loaded_resource = ResourceLoader::load(container->get_selected());
233+
ERR_FAIL_COND_MSG(loaded_resource.is_null(), "Cannot load resource from path '" + container->get_selected() + "'.");
234+
235+
// MultiNodeEdit has adding to the undo/redo stack baked into its set function.
236+
// As such, we have to specifically call a version of its setter that doesn't
237+
// create undo/redo actions.
238+
if (Object::cast_to<MultiNodeEdit>(property_object)) {
239+
Object::cast_to<MultiNodeEdit>(property_object)->_set_impl(property_path, loaded_resource, "", false);
240+
} else {
241+
property_object->set(property_path, loaded_resource);
242+
}
243+
}
244+
245+
void EditorQuickOpenDialog::update_property() {
246+
// Set the property back to the initial value first, so that the undo action
247+
// has the correct object.
248+
if (property_object) {
249+
if (Object::cast_to<MultiNodeEdit>(property_object)) {
250+
Object::cast_to<MultiNodeEdit>(property_object)->_set_impl(property_path, initial_property_value, "", false);
251+
} else {
252+
property_object->set(property_path, initial_property_value);
253+
}
254+
}
255+
item_selected_callback.call(container->get_selected());
256+
}
257+
171258
void EditorQuickOpenDialog::cancel_pressed() {
259+
if (property_object) {
260+
if (Object::cast_to<MultiNodeEdit>(property_object)) {
261+
Object::cast_to<MultiNodeEdit>(property_object)->_set_impl(property_path, initial_property_value, "", false);
262+
} else {
263+
property_object->set(property_path, initial_property_value);
264+
}
265+
}
172266
container->cleanup();
173267
search_box->clear();
174268
}
@@ -262,6 +356,13 @@ QuickOpenResultContainer::QuickOpenResultContainer() {
262356
bottom_bar->add_theme_constant_override("separation", 3);
263357
add_child(bottom_bar);
264358

359+
instant_preview_toggle = memnew(CheckButton);
360+
style_button(instant_preview_toggle);
361+
instant_preview_toggle->set_text(TTRC("Instant Preview"));
362+
instant_preview_toggle->set_tooltip_text(TTRC("Selected resource will be previewed in the editor before accepting."));
363+
instant_preview_toggle->connect(SceneStringName(toggled), callable_mp(this, &QuickOpenResultContainer::_toggle_instant_preview));
364+
bottom_bar->add_child(instant_preview_toggle);
365+
265366
fuzzy_search_toggle = memnew(CheckButton);
266367
style_button(fuzzy_search_toggle);
267368
fuzzy_search_toggle->set_text(TTR("Fuzzy Search"));
@@ -333,8 +434,10 @@ void QuickOpenResultContainer::init(const Vector<StringName> &p_base_types) {
333434
_set_display_mode((QuickOpenDisplayMode)last);
334435
}
335436

437+
const bool do_instant_preview = EDITOR_GET("filesystem/quick_open_dialog/instant_preview");
336438
const bool fuzzy_matching = EDITOR_GET("filesystem/quick_open_dialog/enable_fuzzy_matching");
337439
const bool include_addons = EDITOR_GET("filesystem/quick_open_dialog/include_addons");
440+
instant_preview_toggle->set_pressed_no_signal(do_instant_preview);
338441
fuzzy_search_toggle->set_pressed_no_signal(fuzzy_matching);
339442
include_addons_toggle->set_pressed_no_signal(include_addons);
340443
never_opened = false;
@@ -689,6 +792,8 @@ void QuickOpenResultContainer::_select_item(int p_index) {
689792
bool in_history = history_set.has(candidates[selection_index].file_path);
690793
file_details_path->set_text(get_selected() + (in_history ? TTR(" (recently opened)") : ""));
691794

795+
emit_signal(SNAME("selection_changed"));
796+
692797
const QuickOpenResultItem *item = result_items[selection_index];
693798

694799
// Copied from Tree.
@@ -710,7 +815,7 @@ void QuickOpenResultContainer::_item_input(const Ref<InputEvent> &p_ev, int p_in
710815
if (mb.is_valid() && mb->is_pressed()) {
711816
if (mb->get_button_index() == MouseButton::LEFT) {
712817
_select_item(p_index);
713-
emit_signal(SNAME("result_clicked"));
818+
emit_signal(SNAME("result_clicked"), mb->is_double_click());
714819
} else if (mb->get_button_index() == MouseButton::RIGHT) {
715820
_select_item(p_index);
716821
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<InputEvent> &p_ev, int p_in
720825
}
721826
}
722827

828+
void QuickOpenResultContainer::_toggle_instant_preview(bool p_pressed) {
829+
EditorSettings::get_singleton()->set("filesystem/quick_open_dialog/instant_preview", p_pressed);
830+
}
831+
723832
void QuickOpenResultContainer::_toggle_fuzzy_search(bool p_pressed) {
724833
EditorSettings::get_singleton()->set("filesystem/quick_open_dialog/enable_fuzzy_matching", p_pressed);
725834
update_results();
@@ -820,6 +929,14 @@ String _get_uid_string(const String &p_filepath) {
820929
return id == ResourceUID::INVALID_ID ? p_filepath : ResourceUID::get_singleton()->id_to_text(id);
821930
}
822931

932+
bool QuickOpenResultContainer::is_instant_preview_enabled() const {
933+
return instant_preview_toggle && instant_preview_toggle->is_visible() && instant_preview_toggle->is_pressed();
934+
}
935+
936+
void QuickOpenResultContainer::set_instant_preview_toggle_visible(bool p_visible) {
937+
instant_preview_toggle->set_visible(p_visible);
938+
}
939+
823940
void QuickOpenResultContainer::save_selected_item() {
824941
if (base_types.size() > 1) {
825942
// 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) {
8951012
}
8961013

8971014
void QuickOpenResultContainer::_bind_methods() {
898-
ADD_SIGNAL(MethodInfo("result_clicked"));
1015+
ADD_SIGNAL(MethodInfo("selection_changed"));
1016+
ADD_SIGNAL(MethodInfo("result_clicked", PropertyInfo(Variant::BOOL, "double_click")));
8991017
}
9001018

9011019
//------------------------- Result Item
9021020

9031021
QuickOpenResultItem::QuickOpenResultItem() {
904-
set_focus_mode(FocusMode::FOCUS_ALL);
1022+
set_focus_mode(FocusMode::FOCUS_NONE);
9051023
_set_enabled(false);
9061024
set_default_cursor_shape(CURSOR_POINTING_HAND);
9071025

editor/gui/editor_quick_open_dialog.h

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -97,6 +97,9 @@ class QuickOpenResultContainer : public VBoxContainer {
9797
bool has_nothing_selected() const;
9898
String get_selected() const;
9999

100+
bool is_instant_preview_enabled() const;
101+
void set_instant_preview_toggle_visible(bool p_visible);
102+
100103
void save_selected_item();
101104
void cleanup();
102105

@@ -139,6 +142,7 @@ class QuickOpenResultContainer : public VBoxContainer {
139142

140143
Label *file_details_path = nullptr;
141144
Button *display_mode_toggle = nullptr;
145+
CheckButton *instant_preview_toggle = nullptr;
142146
CheckButton *include_addons_toggle = nullptr;
143147
CheckButton *fuzzy_search_toggle = nullptr;
144148

@@ -168,6 +172,7 @@ class QuickOpenResultContainer : public VBoxContainer {
168172
void _layout_result_item(QuickOpenResultItem *p_item);
169173
void _set_display_mode(QuickOpenDisplayMode p_display_mode);
170174
void _toggle_display_mode();
175+
void _toggle_instant_preview(bool p_pressed);
171176
void _toggle_include_addons(bool p_pressed);
172177
void _toggle_fuzzy_search(bool p_pressed);
173178
void _menu_option(int p_option);
@@ -252,11 +257,14 @@ class EditorQuickOpenDialog : public AcceptDialog {
252257

253258
public:
254259
void popup_dialog(const Vector<StringName> &p_base_types, const Callable &p_item_selected_callback);
260+
void popup_dialog_for_property(const Vector<StringName> &p_base_types, Object *p_obj, const StringName &p_path, const Callable &p_item_selected_callback);
255261
EditorQuickOpenDialog();
256262

257263
protected:
258264
virtual void cancel_pressed() override;
259265
virtual void ok_pressed() override;
266+
void item_pressed(bool p_double_click);
267+
void selection_changed();
260268

261269
private:
262270
static String get_dialog_title(const Vector<StringName> &p_base_types);
@@ -266,5 +274,14 @@ class EditorQuickOpenDialog : public AcceptDialog {
266274

267275
Callable item_selected_callback;
268276

277+
Object *property_object = nullptr;
278+
StringName property_path;
279+
Variant initial_property_value;
280+
bool initial_selection_performed = false;
281+
bool _is_instant_preview_active() const;
269282
void _search_box_text_changed(const String &p_query);
283+
void _finish_dialog_setup(const Vector<StringName> &p_base_types);
284+
285+
void preview_property();
286+
void update_property();
270287
};

editor/inspector/editor_properties.cpp

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3472,6 +3472,7 @@ void EditorPropertyResource::setup(Object *p_object, const String &p_path, const
34723472

34733473
resource_picker->set_base_type(p_base_type);
34743474
resource_picker->set_resource_owner(p_object);
3475+
resource_picker->set_property_path(p_path);
34753476
resource_picker->set_editable(true);
34763477
resource_picker->set_h_size_flags(SIZE_EXPAND_FILL);
34773478
add_child(resource_picker);

editor/inspector/editor_resource_picker.cpp

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -379,7 +379,13 @@ void EditorResourcePicker::_edit_menu_cbk(int p_which) {
379379
base_types.push_back(type);
380380
}
381381

382-
EditorNode::get_singleton()->get_quick_open_dialog()->popup_dialog(base_types, callable_mp(this, &EditorResourcePicker::_file_selected));
382+
EditorQuickOpenDialog *quick_open = EditorNode::get_singleton()->get_quick_open_dialog();
383+
if (resource_owner) {
384+
quick_open->popup_dialog_for_property(base_types, resource_owner, property_path, callable_mp(this, &EditorResourcePicker::_file_selected));
385+
} else {
386+
quick_open->popup_dialog(base_types, callable_mp(this, &EditorResourcePicker::_file_selected));
387+
}
388+
383389
} break;
384390

385391
case OBJ_MENU_INSPECT: {

editor/inspector/editor_resource_picker.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -84,6 +84,7 @@ class EditorResourcePicker : public HBoxContainer {
8484
};
8585

8686
Object *resource_owner = nullptr;
87+
StringName property_path;
8788

8889
PopupMenu *edit_menu = nullptr;
8990

@@ -144,6 +145,7 @@ class EditorResourcePicker : public HBoxContainer {
144145
bool is_toggle_pressed() const;
145146

146147
void set_resource_owner(Object *p_object);
148+
void set_property_path(const StringName &p_path) { property_path = p_path; }
147149

148150
void set_editable(bool p_editable);
149151
bool is_editable() const;

editor/inspector/multi_node_edit.cpp

Lines changed: 23 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,7 @@ bool MultiNodeEdit::_set(const StringName &p_name, const Variant &p_value) {
3838
return _set_impl(p_name, p_value, "");
3939
}
4040

41-
bool MultiNodeEdit::_set_impl(const StringName &p_name, const Variant &p_value, const String &p_field) {
41+
bool MultiNodeEdit::_set_impl(const StringName &p_name, const Variant &p_value, const String &p_field, bool p_undo_redo) {
4242
Node *es = EditorNode::get_singleton()->get_edited_scene();
4343
if (!es) {
4444
return false;
@@ -59,7 +59,10 @@ bool MultiNodeEdit::_set_impl(const StringName &p_name, const Variant &p_value,
5959

6060
EditorUndoRedoManager *ur = EditorUndoRedoManager::get_singleton();
6161

62-
ur->create_action(vformat(TTR("Set %s on %d nodes"), name, get_node_count()), UndoRedo::MERGE_ENDS);
62+
if (p_undo_redo) {
63+
ur->create_action(vformat(TTR("Set %s on %d nodes"), name, get_node_count()), UndoRedo::MERGE_ENDS);
64+
}
65+
6366
for (const NodePath &E : nodes) {
6467
Node *n = es->get_node_or_null(E);
6568
if (!n) {
@@ -71,7 +74,12 @@ bool MultiNodeEdit::_set_impl(const StringName &p_name, const Variant &p_value,
7174
if (node_path_target) {
7275
path = n->get_path_to(node_path_target);
7376
}
74-
ur->add_do_property(n, name, path);
77+
78+
if (p_undo_redo) {
79+
ur->add_do_property(n, name, path);
80+
} else {
81+
n->set(name, path);
82+
}
7583
} else {
7684
Variant new_value;
7785
if (p_field.is_empty()) {
@@ -81,13 +89,22 @@ bool MultiNodeEdit::_set_impl(const StringName &p_name, const Variant &p_value,
8189
// only one field
8290
new_value = fieldwise_assign(n->get(name), p_value, p_field);
8391
}
84-
ur->add_do_property(n, name, new_value);
92+
93+
if (p_undo_redo) {
94+
ur->add_do_property(n, name, new_value);
95+
} else {
96+
n->set(name, new_value);
97+
}
8598
}
8699

87-
ur->add_undo_property(n, name, n->get(name));
100+
if (p_undo_redo) {
101+
ur->add_undo_property(n, name, n->get(name));
102+
}
88103
}
89104

90-
ur->commit_action();
105+
if (p_undo_redo) {
106+
ur->commit_action();
107+
}
91108
return true;
92109
}
93110

0 commit comments

Comments
 (0)