Skip to content

Commit 2f1e8da

Browse files
committed
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 <[email protected]> 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 <[email protected]> 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 <[email protected]> Co-authored-by: A Thousand Ships <[email protected]> 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`
1 parent 8d8041b commit 2f1e8da

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
@@ -795,6 +795,9 @@
795795
<member name="filesystem/quick_open_dialog/include_addons" type="bool" setter="" getter="">
796796
If [code]true[/code], results will include files located in the [code]addons[/code] folder.
797797
</member>
798+
<member name="filesystem/quick_open_dialog/instant_preview" type="bool" setter="" getter="">
799+
If [code]true[/code], highlighting a resource will preview it quickly without confirming the selection or closing the dialog.
800+
</member>
798801
<member name="filesystem/quick_open_dialog/max_fuzzy_misses" type="int" setter="" getter="">
799802
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].
800803
</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;
@@ -679,6 +782,8 @@ void QuickOpenResultContainer::_select_item(int p_index) {
679782
bool in_history = history_set.has(candidates[selection_index].file_path);
680783
file_details_path->set_text(get_selected() + (in_history ? TTR(" (recently opened)") : ""));
681784

785+
emit_signal(SNAME("selection_changed"));
786+
682787
const QuickOpenResultItem *item = result_items[selection_index];
683788

684789
// Copied from Tree.
@@ -700,7 +805,7 @@ void QuickOpenResultContainer::_item_input(const Ref<InputEvent> &p_ev, int p_in
700805
if (mb.is_valid() && mb->is_pressed()) {
701806
if (mb->get_button_index() == MouseButton::LEFT) {
702807
_select_item(p_index);
703-
emit_signal(SNAME("result_clicked"));
808+
emit_signal(SNAME("result_clicked"), mb->is_double_click());
704809
} else if (mb->get_button_index() == MouseButton::RIGHT) {
705810
_select_item(p_index);
706811
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
710815
}
711816
}
712817

818+
void QuickOpenResultContainer::_toggle_instant_preview(bool p_pressed) {
819+
EditorSettings::get_singleton()->set("filesystem/quick_open_dialog/instant_preview", p_pressed);
820+
}
821+
713822
void QuickOpenResultContainer::_toggle_fuzzy_search(bool p_pressed) {
714823
EditorSettings::get_singleton()->set("filesystem/quick_open_dialog/enable_fuzzy_matching", p_pressed);
715824
update_results();
@@ -810,6 +919,14 @@ String _get_uid_string(const String &p_filepath) {
810919
return id == ResourceUID::INVALID_ID ? p_filepath : ResourceUID::get_singleton()->id_to_text(id);
811920
}
812921

922+
bool QuickOpenResultContainer::is_instant_preview_enabled() const {
923+
return instant_preview_toggle && instant_preview_toggle->is_visible() && instant_preview_toggle->is_pressed();
924+
}
925+
926+
void QuickOpenResultContainer::set_instant_preview_toggle_visible(bool p_visible) {
927+
instant_preview_toggle->set_visible(p_visible);
928+
}
929+
813930
void QuickOpenResultContainer::save_selected_item() {
814931
if (base_types.size() > 1) {
815932
// 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) {
8851002
}
8861003

8871004
void QuickOpenResultContainer::_bind_methods() {
888-
ADD_SIGNAL(MethodInfo("result_clicked"));
1005+
ADD_SIGNAL(MethodInfo("selection_changed"));
1006+
ADD_SIGNAL(MethodInfo("result_clicked", PropertyInfo(Variant::BOOL, "double_click")));
8891007
}
8901008

8911009
//------------------------- Result Item
8921010

8931011
QuickOpenResultItem::QuickOpenResultItem() {
894-
set_focus_mode(FocusMode::FOCUS_ALL);
1012+
set_focus_mode(FocusMode::FOCUS_NONE);
8951013
_set_enabled(false);
8961014
set_default_cursor_shape(CURSOR_POINTING_HAND);
8971015

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
@@ -3441,6 +3441,7 @@ void EditorPropertyResource::setup(Object *p_object, const String &p_path, const
34413441

34423442
resource_picker->set_base_type(p_base_type);
34433443
resource_picker->set_resource_owner(p_object);
3444+
resource_picker->set_property_path(p_path);
34443445
resource_picker->set_editable(true);
34453446
resource_picker->set_h_size_flags(SIZE_EXPAND_FILL);
34463447
add_child(resource_picker);

editor/inspector/editor_resource_picker.cpp

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

364-
EditorNode::get_singleton()->get_quick_open_dialog()->popup_dialog(base_types, callable_mp(this, &EditorResourcePicker::_file_selected));
364+
EditorQuickOpenDialog *quick_open = EditorNode::get_singleton()->get_quick_open_dialog();
365+
if (resource_owner) {
366+
quick_open->popup_dialog_for_property(base_types, resource_owner, property_path, callable_mp(this, &EditorResourcePicker::_file_selected));
367+
} else {
368+
quick_open->popup_dialog(base_types, callable_mp(this, &EditorResourcePicker::_file_selected));
369+
}
370+
365371
} break;
366372

367373
case OBJ_MENU_INSPECT: {

editor/inspector/editor_resource_picker.h

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

8585
Object *resource_owner = nullptr;
86+
StringName property_path;
8687

8788
PopupMenu *edit_menu = nullptr;
8889

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

145146
void set_resource_owner(Object *p_object);
147+
void set_property_path(const StringName &p_path) { property_path = p_path; }
146148

147149
void set_editable(bool p_editable);
148150
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)