1/**************************************************************************/
2/* resource_preloader_editor_plugin.cpp */
3/**************************************************************************/
4/* This file is part of: */
5/* GODOT ENGINE */
6/* https://godotengine.org */
7/**************************************************************************/
8/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
9/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
10/* */
11/* Permission is hereby granted, free of charge, to any person obtaining */
12/* a copy of this software and associated documentation files (the */
13/* "Software"), to deal in the Software without restriction, including */
14/* without limitation the rights to use, copy, modify, merge, publish, */
15/* distribute, sublicense, and/or sell copies of the Software, and to */
16/* permit persons to whom the Software is furnished to do so, subject to */
17/* the following conditions: */
18/* */
19/* The above copyright notice and this permission notice shall be */
20/* included in all copies or substantial portions of the Software. */
21/* */
22/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
23/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
24/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */
25/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
26/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
27/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
28/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
29/**************************************************************************/
30
31#include "resource_preloader_editor_plugin.h"
32
33#include "core/config/project_settings.h"
34#include "core/io/resource_loader.h"
35#include "editor/editor_interface.h"
36#include "editor/editor_node.h"
37#include "editor/editor_scale.h"
38#include "editor/editor_settings.h"
39#include "editor/editor_undo_redo_manager.h"
40#include "editor/gui/editor_file_dialog.h"
41
42void ResourcePreloaderEditor::_notification(int p_what) {
43 switch (p_what) {
44 case NOTIFICATION_ENTER_TREE:
45 case NOTIFICATION_THEME_CHANGED: {
46 load->set_icon(get_editor_theme_icon(SNAME("Folder")));
47 } break;
48 }
49}
50
51void ResourcePreloaderEditor::_files_load_request(const Vector<String> &p_paths) {
52 for (int i = 0; i < p_paths.size(); i++) {
53 String path = p_paths[i];
54
55 Ref<Resource> resource;
56 resource = ResourceLoader::load(path);
57
58 if (resource.is_null()) {
59 dialog->set_text(TTR("ERROR: Couldn't load resource!"));
60 dialog->set_title(TTR("Error!"));
61 //dialog->get_cancel()->set_text("Close");
62 dialog->set_ok_button_text(TTR("Close"));
63 dialog->popup_centered();
64 return; ///beh should show an error i guess
65 }
66
67 String basename = path.get_file().get_basename();
68 String name = basename;
69 int counter = 1;
70 while (preloader->has_resource(name)) {
71 counter++;
72 name = basename + " " + itos(counter);
73 }
74
75 EditorUndoRedoManager *undo_redo = EditorUndoRedoManager::get_singleton();
76 undo_redo->create_action(TTR("Add Resource"));
77 undo_redo->add_do_method(preloader, "add_resource", name, resource);
78 undo_redo->add_undo_method(preloader, "remove_resource", name);
79 undo_redo->add_do_method(this, "_update_library");
80 undo_redo->add_undo_method(this, "_update_library");
81 undo_redo->commit_action();
82 }
83}
84
85void ResourcePreloaderEditor::_load_pressed() {
86 loading_scene = false;
87
88 file->clear_filters();
89 List<String> extensions;
90 ResourceLoader::get_recognized_extensions_for_type("", &extensions);
91 for (int i = 0; i < extensions.size(); i++) {
92 file->add_filter("*." + extensions[i]);
93 }
94
95 file->set_file_mode(EditorFileDialog::FILE_MODE_OPEN_FILES);
96 file->popup_file_dialog();
97}
98
99void ResourcePreloaderEditor::_item_edited() {
100 if (!tree->get_selected()) {
101 return;
102 }
103
104 TreeItem *s = tree->get_selected();
105
106 if (tree->get_selected_column() == 0) {
107 // renamed
108 String old_name = s->get_metadata(0);
109 String new_name = s->get_text(0);
110 if (old_name == new_name) {
111 return;
112 }
113
114 if (new_name.is_empty() || new_name.contains("\\") || new_name.contains("/") || preloader->has_resource(new_name)) {
115 s->set_text(0, old_name);
116 return;
117 }
118
119 Ref<Resource> samp = preloader->get_resource(old_name);
120 EditorUndoRedoManager *undo_redo = EditorUndoRedoManager::get_singleton();
121 undo_redo->create_action(TTR("Rename Resource"));
122 undo_redo->add_do_method(preloader, "remove_resource", old_name);
123 undo_redo->add_do_method(preloader, "add_resource", new_name, samp);
124 undo_redo->add_undo_method(preloader, "remove_resource", new_name);
125 undo_redo->add_undo_method(preloader, "add_resource", old_name, samp);
126 undo_redo->add_do_method(this, "_update_library");
127 undo_redo->add_undo_method(this, "_update_library");
128 undo_redo->commit_action();
129 }
130}
131
132void ResourcePreloaderEditor::_remove_resource(const String &p_to_remove) {
133 EditorUndoRedoManager *undo_redo = EditorUndoRedoManager::get_singleton();
134 undo_redo->create_action(TTR("Delete Resource"));
135 undo_redo->add_do_method(preloader, "remove_resource", p_to_remove);
136 undo_redo->add_undo_method(preloader, "add_resource", p_to_remove, preloader->get_resource(p_to_remove));
137 undo_redo->add_do_method(this, "_update_library");
138 undo_redo->add_undo_method(this, "_update_library");
139 undo_redo->commit_action();
140}
141
142void ResourcePreloaderEditor::_paste_pressed() {
143 Ref<Resource> r = EditorSettings::get_singleton()->get_resource_clipboard();
144 if (!r.is_valid()) {
145 dialog->set_text(TTR("Resource clipboard is empty!"));
146 dialog->set_title(TTR("Error!"));
147 dialog->set_ok_button_text(TTR("Close"));
148 dialog->popup_centered();
149 return; ///beh should show an error i guess
150 }
151
152 String name = r->get_name();
153 if (name.is_empty()) {
154 name = r->get_path().get_file();
155 }
156 if (name.is_empty()) {
157 name = r->get_class();
158 }
159
160 String basename = name;
161 int counter = 1;
162 while (preloader->has_resource(name)) {
163 counter++;
164 name = basename + " " + itos(counter);
165 }
166
167 EditorUndoRedoManager *undo_redo = EditorUndoRedoManager::get_singleton();
168 undo_redo->create_action(TTR("Paste Resource"));
169 undo_redo->add_do_method(preloader, "add_resource", name, r);
170 undo_redo->add_undo_method(preloader, "remove_resource", name);
171 undo_redo->add_do_method(this, "_update_library");
172 undo_redo->add_undo_method(this, "_update_library");
173 undo_redo->commit_action();
174}
175
176void ResourcePreloaderEditor::_update_library() {
177 tree->clear();
178 tree->set_hide_root(true);
179 TreeItem *root = tree->create_item(nullptr);
180
181 List<StringName> rnames;
182 preloader->get_resource_list(&rnames);
183
184 List<String> names;
185 for (const StringName &E : rnames) {
186 names.push_back(E);
187 }
188
189 names.sort();
190
191 for (const String &E : names) {
192 TreeItem *ti = tree->create_item(root);
193 ti->set_cell_mode(0, TreeItem::CELL_MODE_STRING);
194 ti->set_editable(0, true);
195 ti->set_selectable(0, true);
196 ti->set_text(0, E);
197 ti->set_metadata(0, E);
198
199 Ref<Resource> r = preloader->get_resource(E);
200
201 ERR_CONTINUE(r.is_null());
202
203 String type = r->get_class();
204 ti->set_icon(0, EditorNode::get_singleton()->get_class_icon(type, "Object"));
205 ti->set_tooltip_text(0, TTR("Instance:") + " " + r->get_path() + "\n" + TTR("Type:") + " " + type);
206
207 ti->set_text(1, r->get_path());
208 ti->set_editable(1, false);
209 ti->set_selectable(1, false);
210
211 if (type == "PackedScene") {
212 ti->add_button(1, get_editor_theme_icon(SNAME("InstanceOptions")), BUTTON_OPEN_SCENE, false, TTR("Open in Editor"));
213 } else {
214 ti->add_button(1, get_editor_theme_icon(SNAME("Load")), BUTTON_EDIT_RESOURCE, false, TTR("Open in Editor"));
215 }
216 ti->add_button(1, get_editor_theme_icon(SNAME("Remove")), BUTTON_REMOVE, false, TTR("Remove"));
217 }
218
219 //player->add_resource("default",resource);
220}
221
222void ResourcePreloaderEditor::_cell_button_pressed(Object *p_item, int p_column, int p_id, MouseButton p_button) {
223 if (p_button != MouseButton::LEFT) {
224 return;
225 }
226
227 TreeItem *item = Object::cast_to<TreeItem>(p_item);
228 ERR_FAIL_NULL(item);
229
230 if (p_id == BUTTON_OPEN_SCENE) {
231 String rpath = item->get_text(p_column);
232 EditorInterface::get_singleton()->open_scene_from_path(rpath);
233
234 } else if (p_id == BUTTON_EDIT_RESOURCE) {
235 Ref<Resource> r = preloader->get_resource(item->get_text(0));
236 EditorInterface::get_singleton()->edit_resource(r);
237
238 } else if (p_id == BUTTON_REMOVE) {
239 _remove_resource(item->get_text(0));
240 }
241}
242
243void ResourcePreloaderEditor::edit(ResourcePreloader *p_preloader) {
244 preloader = p_preloader;
245
246 if (p_preloader) {
247 _update_library();
248 } else {
249 hide();
250 set_physics_process(false);
251 }
252}
253
254Variant ResourcePreloaderEditor::get_drag_data_fw(const Point2 &p_point, Control *p_from) {
255 TreeItem *ti = tree->get_item_at_position(p_point);
256 if (!ti) {
257 return Variant();
258 }
259
260 String name = ti->get_metadata(0);
261
262 Ref<Resource> res = preloader->get_resource(name);
263 if (!res.is_valid()) {
264 return Variant();
265 }
266
267 return EditorNode::get_singleton()->drag_resource(res, p_from);
268}
269
270bool ResourcePreloaderEditor::can_drop_data_fw(const Point2 &p_point, const Variant &p_data, Control *p_from) const {
271 Dictionary d = p_data;
272
273 if (!d.has("type")) {
274 return false;
275 }
276
277 if (d.has("from") && (Object *)(d["from"]) == tree) {
278 return false;
279 }
280
281 if (String(d["type"]) == "resource" && d.has("resource")) {
282 Ref<Resource> r = d["resource"];
283
284 return r.is_valid();
285 }
286
287 if (String(d["type"]) == "files") {
288 Vector<String> files = d["files"];
289
290 return files.size() != 0;
291 }
292 return false;
293}
294
295void ResourcePreloaderEditor::drop_data_fw(const Point2 &p_point, const Variant &p_data, Control *p_from) {
296 if (!can_drop_data_fw(p_point, p_data, p_from)) {
297 return;
298 }
299
300 Dictionary d = p_data;
301
302 if (!d.has("type")) {
303 return;
304 }
305
306 if (String(d["type"]) == "resource" && d.has("resource")) {
307 Ref<Resource> r = d["resource"];
308
309 if (r.is_valid()) {
310 String basename;
311 if (!r->get_name().is_empty()) {
312 basename = r->get_name();
313 } else if (r->get_path().is_resource_file()) {
314 basename = r->get_path().get_basename();
315 } else {
316 basename = "Resource";
317 }
318
319 String name = basename;
320 int counter = 0;
321 while (preloader->has_resource(name)) {
322 counter++;
323 name = basename + "_" + itos(counter);
324 }
325
326 EditorUndoRedoManager *undo_redo = EditorUndoRedoManager::get_singleton();
327 undo_redo->create_action(TTR("Add Resource"));
328 undo_redo->add_do_method(preloader, "add_resource", name, r);
329 undo_redo->add_undo_method(preloader, "remove_resource", name);
330 undo_redo->add_do_method(this, "_update_library");
331 undo_redo->add_undo_method(this, "_update_library");
332 undo_redo->commit_action();
333 }
334 }
335
336 if (String(d["type"]) == "files") {
337 Vector<String> files = d["files"];
338
339 _files_load_request(files);
340 }
341}
342
343void ResourcePreloaderEditor::_bind_methods() {
344 ClassDB::bind_method(D_METHOD("_update_library"), &ResourcePreloaderEditor::_update_library);
345 ClassDB::bind_method(D_METHOD("_remove_resource", "to_remove"), &ResourcePreloaderEditor::_remove_resource);
346}
347
348ResourcePreloaderEditor::ResourcePreloaderEditor() {
349 //add_style_override("panel", EditorNode::get_singleton()->get_gui_base()->get_stylebox("panel","Panel"));
350
351 VBoxContainer *vbc = memnew(VBoxContainer);
352 add_child(vbc);
353
354 HBoxContainer *hbc = memnew(HBoxContainer);
355 vbc->add_child(hbc);
356
357 load = memnew(Button);
358 load->set_tooltip_text(TTR("Load Resource"));
359 hbc->add_child(load);
360
361 paste = memnew(Button);
362 paste->set_text(TTR("Paste"));
363 hbc->add_child(paste);
364
365 file = memnew(EditorFileDialog);
366 add_child(file);
367
368 tree = memnew(Tree);
369 tree->connect("button_clicked", callable_mp(this, &ResourcePreloaderEditor::_cell_button_pressed));
370 tree->set_columns(2);
371 tree->set_column_expand_ratio(0, 2);
372 tree->set_column_clip_content(0, true);
373 tree->set_column_expand_ratio(1, 3);
374 tree->set_column_clip_content(1, true);
375 tree->set_column_expand(0, true);
376 tree->set_column_expand(1, true);
377 tree->set_v_size_flags(SIZE_EXPAND_FILL);
378
379 SET_DRAG_FORWARDING_GCD(tree, ResourcePreloaderEditor);
380 vbc->add_child(tree);
381
382 dialog = memnew(AcceptDialog);
383 add_child(dialog);
384
385 load->connect("pressed", callable_mp(this, &ResourcePreloaderEditor::_load_pressed));
386 paste->connect("pressed", callable_mp(this, &ResourcePreloaderEditor::_paste_pressed));
387 file->connect("files_selected", callable_mp(this, &ResourcePreloaderEditor::_files_load_request));
388 tree->connect("item_edited", callable_mp(this, &ResourcePreloaderEditor::_item_edited));
389 loading_scene = false;
390}
391
392void ResourcePreloaderEditorPlugin::edit(Object *p_object) {
393 ResourcePreloader *s = Object::cast_to<ResourcePreloader>(p_object);
394 if (!s) {
395 return;
396 }
397
398 preloader_editor->edit(s);
399}
400
401bool ResourcePreloaderEditorPlugin::handles(Object *p_object) const {
402 return p_object->is_class("ResourcePreloader");
403}
404
405void ResourcePreloaderEditorPlugin::make_visible(bool p_visible) {
406 if (p_visible) {
407 //preloader_editor->show();
408 button->show();
409 EditorNode::get_singleton()->make_bottom_panel_item_visible(preloader_editor);
410 //preloader_editor->set_process(true);
411 } else {
412 if (preloader_editor->is_visible_in_tree()) {
413 EditorNode::get_singleton()->hide_bottom_panel();
414 }
415 button->hide();
416 //preloader_editor->hide();
417 //preloader_editor->set_process(false);
418 }
419}
420
421ResourcePreloaderEditorPlugin::ResourcePreloaderEditorPlugin() {
422 preloader_editor = memnew(ResourcePreloaderEditor);
423 preloader_editor->set_custom_minimum_size(Size2(0, 250) * EDSCALE);
424
425 button = EditorNode::get_singleton()->add_bottom_panel_item("ResourcePreloader", preloader_editor);
426 button->hide();
427}
428
429ResourcePreloaderEditorPlugin::~ResourcePreloaderEditorPlugin() {
430}
431