summaryrefslogtreecommitdiffstats
path: root/editor
diff options
context:
space:
mode:
Diffstat (limited to 'editor')
-rw-r--r--editor/SCsub1
-rw-r--r--editor/debugger/SCsub1
-rw-r--r--editor/debugger/debug_adapter/SCsub1
-rw-r--r--editor/editor_file_system.cpp307
-rw-r--r--editor/editor_file_system.h8
-rw-r--r--editor/editor_node.cpp16
-rw-r--r--editor/editor_node.h4
-rw-r--r--editor/export/SCsub1
-rw-r--r--editor/export/editor_export.cpp3
-rw-r--r--editor/export/editor_export_platform.cpp174
-rw-r--r--editor/export/editor_export_platform.h20
-rw-r--r--editor/export/editor_export_platform_extension.cpp40
-rw-r--r--editor/export/editor_export_platform_extension.h6
-rw-r--r--editor/export/editor_export_platform_pc.cpp2
-rw-r--r--editor/export/editor_export_preset.cpp37
-rw-r--r--editor/export/editor_export_preset.h9
-rw-r--r--editor/export/project_export.cpp189
-rw-r--r--editor/export/project_export.h13
-rw-r--r--editor/file_info.cpp61
-rw-r--r--editor/file_info.h74
-rw-r--r--editor/filesystem_dock.cpp159
-rw-r--r--editor/filesystem_dock.h32
-rw-r--r--editor/gui/SCsub1
-rw-r--r--editor/gui/editor_dir_dialog.cpp3
-rw-r--r--editor/gui/editor_file_dialog.cpp138
-rw-r--r--editor/gui/editor_file_dialog.h16
-rw-r--r--editor/icons/SCsub1
-rw-r--r--editor/import/SCsub1
-rw-r--r--editor/import/resource_importer_layered_texture.cpp12
-rw-r--r--editor/import/resource_importer_layered_texture.h2
-rw-r--r--editor/import/resource_importer_texture.cpp14
-rw-r--r--editor/import/resource_importer_texture.h2
-rw-r--r--editor/plugins/SCsub1
-rw-r--r--editor/plugins/gizmos/SCsub1
-rw-r--r--editor/plugins/tiles/SCsub1
-rw-r--r--editor/plugins/tiles/tile_set_atlas_source_editor.h2
-rw-r--r--editor/project_manager/SCsub1
-rw-r--r--editor/themes/SCsub1
38 files changed, 1064 insertions, 291 deletions
diff --git a/editor/SCsub b/editor/SCsub
index 029048969a..9fcaf61245 100644
--- a/editor/SCsub
+++ b/editor/SCsub
@@ -1,4 +1,5 @@
#!/usr/bin/env python
+from misc.utility.scons_hints import *
Import("env")
diff --git a/editor/debugger/SCsub b/editor/debugger/SCsub
index 99f1c888f0..e26d09d88b 100644
--- a/editor/debugger/SCsub
+++ b/editor/debugger/SCsub
@@ -1,4 +1,5 @@
#!/usr/bin/env python
+from misc.utility.scons_hints import *
Import("env")
diff --git a/editor/debugger/debug_adapter/SCsub b/editor/debugger/debug_adapter/SCsub
index 359d04e5df..b3cff5b9dc 100644
--- a/editor/debugger/debug_adapter/SCsub
+++ b/editor/debugger/debug_adapter/SCsub
@@ -1,4 +1,5 @@
#!/usr/bin/env python
+from misc.utility.scons_hints import *
Import("env")
diff --git a/editor/editor_file_system.cpp b/editor/editor_file_system.cpp
index 2b51071b15..afa0548044 100644
--- a/editor/editor_file_system.cpp
+++ b/editor/editor_file_system.cpp
@@ -282,6 +282,21 @@ void EditorFileSystem::_first_scan_process_scripts(const ScannedDirectory *p_sca
}
for (const String &scan_file : p_scan_dir->files) {
+ // Optimization to skip the ResourceLoader::get_resource_type for files
+ // that are not scripts. Some loader get_resource_type methods read the file
+ // which can be very slow on large projects.
+ String ext = scan_file.get_extension().to_lower();
+ bool is_script = false;
+ for (int i = 0; i < ScriptServer::get_language_count(); i++) {
+ if (ScriptServer::get_language(i)->get_extension() == ext) {
+ is_script = true;
+ break;
+ }
+ }
+ if (!is_script) {
+ continue; // Not a script.
+ }
+
String path = p_scan_dir->full_path.path_join(scan_file);
String type = ResourceLoader::get_resource_type(path);
@@ -371,6 +386,11 @@ void EditorFileSystem::_scan_filesystem() {
fc.script_class_name = split[7].get_slice("<>", 0);
fc.script_class_extends = split[7].get_slice("<>", 1);
fc.script_class_icon_path = split[7].get_slice("<>", 2);
+ fc.import_md5 = split[7].get_slice("<>", 3);
+ String dest_paths = split[7].get_slice("<>", 4);
+ if (!dest_paths.is_empty()) {
+ fc.import_dest_paths = dest_paths.split("<*>");
+ }
String deps = split[8].strip_edges();
if (deps.length()) {
@@ -463,12 +483,33 @@ void EditorFileSystem::_thread_func(void *_userdata) {
sd->_scan_filesystem();
}
-bool EditorFileSystem::_test_for_reimport(const String &p_path, bool p_only_imported_files) {
- if (!reimport_on_missing_imported_files && p_only_imported_files) {
- return false;
+bool EditorFileSystem::_is_test_for_reimport_needed(const String &p_path, uint64_t p_last_modification_time, uint64_t p_modification_time, uint64_t p_last_import_modification_time, uint64_t p_import_modification_time, const Vector<String> &p_import_dest_paths) {
+ // The idea here is to trust the cache. If the last modification times in the cache correspond
+ // to the last modification times of the files on disk, it means the files have not changed since
+ // the last import, and the files in .godot/imported (p_import_dest_paths) should all be valid.
+ if (p_last_modification_time != p_modification_time) {
+ return true;
+ }
+ if (p_last_import_modification_time != p_import_modification_time) {
+ return true;
+ }
+ if (reimport_on_missing_imported_files) {
+ for (const String &path : p_import_dest_paths) {
+ if (!FileAccess::exists(path)) {
+ return true;
+ }
+ }
}
+ return false;
+}
- if (!FileAccess::exists(p_path + ".import")) {
+bool EditorFileSystem::_test_for_reimport(const String &p_path, const String &p_expected_import_md5) {
+ if (p_expected_import_md5.is_empty()) {
+ // Marked as reimportation needed.
+ return true;
+ }
+ String new_md5 = FileAccess::get_md5(p_path + ".import");
+ if (p_expected_import_md5 != new_md5) {
return true;
}
@@ -489,7 +530,7 @@ bool EditorFileSystem::_test_for_reimport(const String &p_path, bool p_only_impo
int lines = 0;
String error_text;
- List<String> to_check;
+ Vector<String> to_check;
String importer_name;
String source_file = "";
@@ -498,6 +539,7 @@ bool EditorFileSystem::_test_for_reimport(const String &p_path, bool p_only_impo
String dest_md5 = "";
int version = 0;
bool found_uid = false;
+ Variant meta;
while (true) {
assign = Variant();
@@ -522,8 +564,8 @@ bool EditorFileSystem::_test_for_reimport(const String &p_path, bool p_only_impo
to_check.push_back(value);
} else if (assign == "files") {
Array fa = value;
- for (int i = 0; i < fa.size(); i++) {
- to_check.push_back(fa[i]);
+ for (const Variant &check_path : fa) {
+ to_check.push_back(check_path);
}
} else if (assign == "importer_version") {
version = value;
@@ -531,12 +573,12 @@ bool EditorFileSystem::_test_for_reimport(const String &p_path, bool p_only_impo
importer_name = value;
} else if (assign == "uid") {
found_uid = true;
- } else if (!p_only_imported_files) {
- if (assign == "source_file") {
- source_file = value;
- } else if (assign == "dest_files") {
- dest_files = value;
- }
+ } else if (assign == "source_file") {
+ source_file = value;
+ } else if (assign == "dest_files") {
+ dest_files = value;
+ } else if (assign == "metadata") {
+ meta = value;
}
} else if (next_tag.name != "remap" && next_tag.name != "deps") {
@@ -544,33 +586,40 @@ bool EditorFileSystem::_test_for_reimport(const String &p_path, bool p_only_impo
}
}
- if (!ResourceFormatImporter::get_singleton()->are_import_settings_valid(p_path)) {
- // Reimport settings are out of sync with project settings, reimport.
- return true;
- }
-
if (importer_name == "keep" || importer_name == "skip") {
- return false; //keep mode, do not reimport
+ return false; // Keep mode, do not reimport.
}
if (!found_uid) {
- return true; //UID not found, old format, reimport.
+ return true; // UID not found, old format, reimport.
+ }
+
+ // Imported files are gone, reimport.
+ for (const String &E : to_check) {
+ if (!FileAccess::exists(E)) {
+ return true;
+ }
}
Ref<ResourceImporter> importer = ResourceFormatImporter::get_singleton()->get_importer_by_name(importer_name);
if (importer.is_null()) {
- return true; // the importer has possibly changed, try to reimport.
+ return true; // The importer has possibly changed, try to reimport.
}
if (importer->get_format_version() > version) {
- return true; // version changed, reimport
+ return true; // Version changed, reimport.
+ }
+
+ if (!importer->are_import_settings_valid(p_path, meta)) {
+ // Reimport settings are out of sync with project settings, reimport.
+ return true;
}
- // Read the md5's from a separate file (so the import parameters aren't dependent on the file version
+ // Read the md5's from a separate file (so the import parameters aren't dependent on the file version).
String base_path = ResourceFormatImporter::get_singleton()->get_import_base_path(p_path);
Ref<FileAccess> md5s = FileAccess::open(base_path + ".md5", FileAccess::READ, &err);
- if (md5s.is_null()) { // No md5's stored for this resource
+ if (md5s.is_null()) { // No md5's stored for this resource.
return true;
}
@@ -588,50 +637,101 @@ bool EditorFileSystem::_test_for_reimport(const String &p_path, bool p_only_impo
break;
} else if (err != OK) {
ERR_PRINT("ResourceFormatImporter::load - '" + p_path + ".import.md5:" + itos(lines) + "' error '" + error_text + "'.");
- return false; // parse error
+ return false; // Parse error.
}
if (!assign.is_empty()) {
- if (!p_only_imported_files) {
- if (assign == "source_md5") {
- source_md5 = value;
- } else if (assign == "dest_md5") {
- dest_md5 = value;
- }
+ if (assign == "source_md5") {
+ source_md5 = value;
+ } else if (assign == "dest_md5") {
+ dest_md5 = value;
}
}
}
- //imported files are gone, reimport
- for (const String &E : to_check) {
- if (!FileAccess::exists(E)) {
+ // Check source md5 matching.
+ if (!source_file.is_empty() && source_file != p_path) {
+ return true; // File was moved, reimport.
+ }
+
+ if (source_md5.is_empty()) {
+ return true; // Lacks md5, so just reimport.
+ }
+
+ String md5 = FileAccess::get_md5(p_path);
+ if (md5 != source_md5) {
+ return true;
+ }
+
+ if (!dest_files.is_empty() && !dest_md5.is_empty()) {
+ md5 = FileAccess::get_multiple_md5(dest_files);
+ if (md5 != dest_md5) {
return true;
}
}
- //check source md5 matching
- if (!p_only_imported_files) {
- if (!source_file.is_empty() && source_file != p_path) {
- return true; //file was moved, reimport
- }
+ return false; // Nothing changed.
+}
- if (source_md5.is_empty()) {
- return true; //lacks md5, so just reimport
- }
+Vector<String> EditorFileSystem::_get_import_dest_paths(const String &p_path) {
+ Error err;
+ Ref<FileAccess> f = FileAccess::open(p_path + ".import", FileAccess::READ, &err);
- String md5 = FileAccess::get_md5(p_path);
- if (md5 != source_md5) {
- return true;
+ if (f.is_null()) { // No import file, reimport.
+ return Vector<String>();
+ }
+
+ VariantParser::StreamFile stream;
+ stream.f = f;
+
+ String assign;
+ Variant value;
+ VariantParser::Tag next_tag;
+
+ int lines = 0;
+ String error_text;
+
+ Vector<String> dest_paths;
+ String importer_name;
+
+ while (true) {
+ assign = Variant();
+ next_tag.fields.clear();
+ next_tag.name = String();
+
+ err = VariantParser::parse_tag_assign_eof(&stream, lines, error_text, next_tag, assign, value, nullptr, true);
+ if (err == ERR_FILE_EOF) {
+ break;
+ } else if (err != OK) {
+ ERR_PRINT("ResourceFormatImporter::load - '" + p_path + ".import:" + itos(lines) + "' error '" + error_text + "'.");
+ // Parse error, skip and let user attempt manual reimport to avoid reimport loop.
+ return Vector<String>();
}
- if (dest_files.size() && !dest_md5.is_empty()) {
- md5 = FileAccess::get_multiple_md5(dest_files);
- if (md5 != dest_md5) {
- return true;
+ if (!assign.is_empty()) {
+ if (assign == "valid" && value.operator bool() == false) {
+ // Invalid import (failed previous import), skip and let user attempt manual reimport to avoid reimport loop.
+ return Vector<String>();
}
+ if (assign.begins_with("path")) {
+ dest_paths.push_back(value);
+ } else if (assign == "files") {
+ Array fa = value;
+ for (const Variant &dest_path : fa) {
+ dest_paths.push_back(dest_path);
+ }
+ } else if (assign == "importer") {
+ importer_name = value;
+ }
+ } else if (next_tag.name != "remap" && next_tag.name != "deps") {
+ break;
}
}
- return false; //nothing changed
+ if (importer_name == "keep" || importer_name == "skip") {
+ return Vector<String>();
+ }
+
+ return dest_paths;
}
bool EditorFileSystem::_scan_import_support(const Vector<String> &reimports) {
@@ -774,20 +874,9 @@ bool EditorFileSystem::_update_scan_actions() {
ERR_CONTINUE(idx == -1);
String full_path = ia.dir->get_file_path(idx);
- bool need_reimport = _test_for_reimport(full_path, false);
- // Workaround GH-94416 for the Android editor for now.
- // `import_mt` seems to always be 0 and force a reimport on any fs scan.
-#ifndef ANDROID_ENABLED
- if (!need_reimport && FileAccess::exists(full_path + ".import")) {
- uint64_t import_mt = ia.dir->get_file_import_modified_time(idx);
- if (import_mt != FileAccess::get_modified_time(full_path + ".import")) {
- need_reimport = true;
- }
- }
-#endif
-
+ bool need_reimport = _test_for_reimport(full_path, ia.dir->files[idx]->import_md5);
if (need_reimport) {
- //must reimport
+ // Must reimport.
reimports.push_back(full_path);
Vector<String> dependencies = _get_dependencies(full_path);
for (const String &dep : dependencies) {
@@ -797,10 +886,14 @@ bool EditorFileSystem::_update_scan_actions() {
}
}
} else {
- //must not reimport, all was good
- //update modified times, to avoid reimport
+ // Must not reimport, all was good.
+ // Update modified times, md5 and destination paths, to avoid reimport.
ia.dir->files[idx]->modified_time = FileAccess::get_modified_time(full_path);
ia.dir->files[idx]->import_modified_time = FileAccess::get_modified_time(full_path + ".import");
+ if (ia.dir->files[idx]->import_md5.is_empty()) {
+ ia.dir->files[idx]->import_md5 = FileAccess::get_md5(full_path + ".import");
+ }
+ ia.dir->files[idx]->import_dest_paths = _get_import_dest_paths(full_path);
}
fs_changed = true;
@@ -1029,26 +1122,36 @@ void EditorFileSystem::_process_file_system(const ScannedDirectory *p_scan_dir,
if (import_extensions.has(ext)) {
//is imported
- uint64_t import_mt = 0;
- if (FileAccess::exists(path + ".import")) {
- import_mt = FileAccess::get_modified_time(path + ".import");
- }
+ uint64_t import_mt = FileAccess::get_modified_time(path + ".import");
- if (fc && fc->modification_time == mt && fc->import_modification_time == import_mt && !_test_for_reimport(path, true)) {
+ if (fc) {
fi->type = fc->type;
fi->resource_script_class = fc->resource_script_class;
fi->uid = fc->uid;
fi->deps = fc->deps;
- fi->modified_time = fc->modification_time;
- fi->import_modified_time = fc->import_modification_time;
-
+ fi->modified_time = mt;
+ fi->import_modified_time = import_mt;
+ fi->import_md5 = fc->import_md5;
+ fi->import_dest_paths = fc->import_dest_paths;
fi->import_valid = fc->import_valid;
fi->script_class_name = fc->script_class_name;
fi->import_group_file = fc->import_group_file;
fi->script_class_extends = fc->script_class_extends;
fi->script_class_icon_path = fc->script_class_icon_path;
- if (revalidate_import_files && !ResourceFormatImporter::get_singleton()->are_import_settings_valid(path)) {
+ // Ensures backward compatibility when the project is loaded for the first time with the added import_md5
+ // and import_dest_paths properties in the file cache.
+ if (fc->import_md5.is_empty()) {
+ fi->import_md5 = FileAccess::get_md5(path + ".import");
+ fi->import_dest_paths = _get_import_dest_paths(path);
+ }
+
+ // The method _is_test_for_reimport_needed checks if the files were modified and ensures that
+ // all the destination files still exist without reading the .import file.
+ // If something is different, we will queue a test for reimportation that will check
+ // the md5 of all files and import settings and, if necessary, execute a reimportation.
+ if (_is_test_for_reimport_needed(path, fc->modification_time, mt, fc->import_modification_time, import_mt, fi->import_dest_paths) ||
+ (revalidate_import_files && !ResourceFormatImporter::get_singleton()->are_import_settings_valid(path))) {
ItemAction ia;
ia.action = ItemAction::ACTION_FILE_TEST_REIMPORT;
ia.dir = p_dir;
@@ -1075,6 +1178,8 @@ void EditorFileSystem::_process_file_system(const ScannedDirectory *p_scan_dir,
fi->script_class_name = _get_global_script_class(fi->type, path, &fi->script_class_extends, &fi->script_class_icon_path);
fi->modified_time = 0;
fi->import_modified_time = 0;
+ fi->import_md5 = "";
+ fi->import_dest_paths = Vector<String>();
fi->import_valid = (fi->type == "TextFile" || fi->type == "OtherFile") ? true : ResourceLoader::is_import_valid(path);
ItemAction ia;
@@ -1089,9 +1194,11 @@ void EditorFileSystem::_process_file_system(const ScannedDirectory *p_scan_dir,
fi->type = fc->type;
fi->resource_script_class = fc->resource_script_class;
fi->uid = fc->uid;
- fi->modified_time = fc->modification_time;
+ fi->modified_time = mt;
fi->deps = fc->deps;
fi->import_modified_time = 0;
+ fi->import_md5 = "";
+ fi->import_dest_paths = Vector<String>();
fi->import_valid = true;
fi->script_class_name = fc->script_class_name;
fi->script_class_extends = fc->script_class_extends;
@@ -1126,6 +1233,8 @@ void EditorFileSystem::_process_file_system(const ScannedDirectory *p_scan_dir,
fi->deps = _get_dependencies(path);
fi->modified_time = mt;
fi->import_modified_time = 0;
+ fi->import_md5 = "";
+ fi->import_dest_paths = Vector<String>();
fi->import_valid = true;
// Files in dep_update_list are forced for rescan to update dependencies. They don't need other updates.
@@ -1269,6 +1378,8 @@ void EditorFileSystem::_scan_fs_changes(EditorFileSystemDirectory *p_dir, ScanPr
String path = cd.path_join(fi->file);
fi->modified_time = FileAccess::get_modified_time(path);
fi->import_modified_time = 0;
+ fi->import_md5 = "";
+ fi->import_dest_paths = Vector<String>();
fi->type = ResourceLoader::get_resource_type(path);
fi->resource_script_class = ResourceLoader::get_resource_script_class(path);
if (fi->type == "" && textfile_extensions.has(ext)) {
@@ -1323,26 +1434,13 @@ void EditorFileSystem::_scan_fs_changes(EditorFileSystemDirectory *p_dir, ScanPr
String path = cd.path_join(p_dir->files[i]->file);
if (import_extensions.has(p_dir->files[i]->file.get_extension().to_lower())) {
- //check here if file must be imported or not
-
+ // Check here if file must be imported or not.
+ // Same logic as in _process_file_system, the last modifications dates
+ // needs to be trusted to prevent reading all the .import files and the md5
+ // each time the user switch back to Godot.
uint64_t mt = FileAccess::get_modified_time(path);
-
- bool reimport = false;
-
- if (mt != p_dir->files[i]->modified_time) {
- reimport = true; //it was modified, must be reimported.
- } else if (!FileAccess::exists(path + ".import")) {
- reimport = true; //no .import file, obviously reimport
- } else {
- uint64_t import_mt = FileAccess::get_modified_time(path + ".import");
- if (import_mt != p_dir->files[i]->import_modified_time) {
- reimport = true;
- } else if (_test_for_reimport(path, true)) {
- reimport = true;
- }
- }
-
- if (reimport) {
+ uint64_t import_mt = FileAccess::get_modified_time(path + ".import");
+ if (_is_test_for_reimport_needed(path, p_dir->files[i]->modified_time, mt, p_dir->files[i]->import_modified_time, import_mt, p_dir->files[i]->import_dest_paths)) {
ItemAction ia;
ia.action = ItemAction::ACTION_FILE_TEST_REIMPORT;
ia.dir = p_dir;
@@ -1625,7 +1723,7 @@ void EditorFileSystem::_save_filesystem_cache(EditorFileSystemDirectory *p_dir,
cache_string.append(itos(file_info->import_modified_time));
cache_string.append(itos(file_info->import_valid));
cache_string.append(file_info->import_group_file);
- cache_string.append(String("<>").join({ file_info->script_class_name, file_info->script_class_extends, file_info->script_class_icon_path }));
+ cache_string.append(String("<>").join({ file_info->script_class_name, file_info->script_class_extends, file_info->script_class_icon_path, file_info->import_md5, String("<*>").join(file_info->import_dest_paths) }));
cache_string.append(String("<>").join(file_info->deps));
p_file->store_line(String("::").join(cache_string));
@@ -1879,6 +1977,13 @@ void EditorFileSystem::_update_file_icon_path(EditorFileSystemDirectory::FileInf
}
}
+ if (icon_path.is_empty() && !file_info->type.is_empty()) {
+ Ref<Texture2D> icon = EditorNode::get_singleton()->get_class_icon(file_info->type);
+ if (icon.is_valid()) {
+ icon_path = icon->get_path();
+ }
+ }
+
file_info->icon_path = icon_path;
}
@@ -2183,6 +2288,8 @@ void EditorFileSystem::update_files(const Vector<String> &p_script_paths) {
fi->file = file_name;
fi->import_modified_time = 0;
fi->import_valid = (type == "TextFile" || type == "OtherFile") ? true : ResourceLoader::is_import_valid(file);
+ fi->import_md5 = "";
+ fi->import_dest_paths = Vector<String>();
if (idx == fs->files.size()) {
fs->files.push_back(fi);
@@ -2227,7 +2334,7 @@ void EditorFileSystem::update_files(const Vector<String> &p_script_paths) {
_queue_update_scene_groups(file);
}
- if (fs->files[cpos]->type == SNAME("Resource")) {
+ if (ClassDB::is_parent_class(fs->files[cpos]->type, SNAME("Resource"))) {
files_to_update_icon_path.push_back(fs->files[cpos]);
} else if (old_script_class_icon_path != fs->files[cpos]->script_class_icon_path) {
update_files_icon_cache = true;
@@ -2458,6 +2565,8 @@ Error EditorFileSystem::_reimport_group(const String &p_group_file, const Vector
//update modified times, to avoid reimport
fs->files[cpos]->modified_time = FileAccess::get_modified_time(file);
fs->files[cpos]->import_modified_time = FileAccess::get_modified_time(file + ".import");
+ fs->files[cpos]->import_md5 = FileAccess::get_md5(file + ".import");
+ fs->files[cpos]->import_dest_paths = dest_paths;
fs->files[cpos]->deps = _get_dependencies(file);
fs->files[cpos]->uid = uid;
fs->files[cpos]->type = importer->get_resource_type();
@@ -2559,11 +2668,13 @@ Error EditorFileSystem::_reimport_file(const String &p_file, const HashMap<Strin
if (p_update_file_system) {
fs->files[cpos]->modified_time = FileAccess::get_modified_time(p_file);
fs->files[cpos]->import_modified_time = FileAccess::get_modified_time(p_file + ".import");
+ fs->files[cpos]->import_md5 = FileAccess::get_md5(p_file + ".import");
+ fs->files[cpos]->import_dest_paths = Vector<String>();
fs->files[cpos]->deps.clear();
fs->files[cpos]->type = "";
fs->files[cpos]->import_valid = false;
+ EditorResourcePreview::get_singleton()->check_for_invalidation(p_file);
}
- EditorResourcePreview::get_singleton()->check_for_invalidation(p_file);
return OK;
}
Ref<ResourceImporter> importer;
@@ -2729,6 +2840,8 @@ Error EditorFileSystem::_reimport_file(const String &p_file, const HashMap<Strin
// Update modified times, to avoid reimport.
fs->files[cpos]->modified_time = FileAccess::get_modified_time(p_file);
fs->files[cpos]->import_modified_time = FileAccess::get_modified_time(p_file + ".import");
+ fs->files[cpos]->import_md5 = FileAccess::get_md5(p_file + ".import");
+ fs->files[cpos]->import_dest_paths = dest_paths;
fs->files[cpos]->deps = _get_dependencies(p_file);
fs->files[cpos]->type = importer->get_resource_type();
fs->files[cpos]->uid = uid;
diff --git a/editor/editor_file_system.h b/editor/editor_file_system.h
index e53187c1d7..255eaff10d 100644
--- a/editor/editor_file_system.h
+++ b/editor/editor_file_system.h
@@ -60,6 +60,8 @@ class EditorFileSystemDirectory : public Object {
ResourceUID::ID uid = ResourceUID::INVALID_ID;
uint64_t modified_time = 0;
uint64_t import_modified_time = 0;
+ String import_md5;
+ Vector<String> import_dest_paths;
bool import_valid = false;
String import_group_file;
Vector<String> deps;
@@ -206,6 +208,8 @@ class EditorFileSystem : public Node {
ResourceUID::ID uid = ResourceUID::INVALID_ID;
uint64_t modification_time = 0;
uint64_t import_modification_time = 0;
+ String import_md5;
+ Vector<String> import_dest_paths;
Vector<String> deps;
bool import_valid = false;
String import_group_file;
@@ -264,7 +268,9 @@ class EditorFileSystem : public Node {
Error _reimport_file(const String &p_file, const HashMap<StringName, Variant> &p_custom_options = HashMap<StringName, Variant>(), const String &p_custom_importer = String(), Variant *generator_parameters = nullptr, bool p_update_file_system = true);
Error _reimport_group(const String &p_group_file, const Vector<String> &p_files);
- bool _test_for_reimport(const String &p_path, bool p_only_imported_files);
+ bool _test_for_reimport(const String &p_path, const String &p_expected_import_md5);
+ bool _is_test_for_reimport_needed(const String &p_path, uint64_t p_last_modification_time, uint64_t p_modification_time, uint64_t p_last_import_modification_time, uint64_t p_import_modification_time, const Vector<String> &p_import_dest_paths);
+ Vector<String> _get_import_dest_paths(const String &p_path);
bool reimport_on_missing_imported_files;
diff --git a/editor/editor_node.cpp b/editor/editor_node.cpp
index f248d03140..665255b9b2 100644
--- a/editor/editor_node.cpp
+++ b/editor/editor_node.cpp
@@ -1007,9 +1007,17 @@ void EditorNode::_fs_changed() {
export_preset->update_value_overrides();
if (export_defer.pack_only) { // Only export .pck or .zip data pack.
if (export_path.ends_with(".zip")) {
- err = platform->export_zip(export_preset, export_defer.debug, export_path);
+ if (export_defer.patch) {
+ err = platform->export_zip_patch(export_preset, export_defer.debug, export_path, export_defer.patches);
+ } else {
+ err = platform->export_zip(export_preset, export_defer.debug, export_path);
+ }
} else if (export_path.ends_with(".pck")) {
- err = platform->export_pack(export_preset, export_defer.debug, export_path);
+ if (export_defer.patch) {
+ err = platform->export_pack_patch(export_preset, export_defer.debug, export_path, export_defer.patches);
+ } else {
+ err = platform->export_pack(export_preset, export_defer.debug, export_path);
+ }
} else {
ERR_PRINT(vformat("Export path \"%s\" doesn't end with a supported extension.", export_path));
err = FAILED;
@@ -5149,12 +5157,14 @@ void EditorNode::_begin_first_scan() {
requested_first_scan = true;
}
-Error EditorNode::export_preset(const String &p_preset, const String &p_path, bool p_debug, bool p_pack_only, bool p_android_build_template) {
+Error EditorNode::export_preset(const String &p_preset, const String &p_path, bool p_debug, bool p_pack_only, bool p_android_build_template, bool p_patch, const Vector<String> &p_patches) {
export_defer.preset = p_preset;
export_defer.path = p_path;
export_defer.debug = p_debug;
export_defer.pack_only = p_pack_only;
export_defer.android_build_template = p_android_build_template;
+ export_defer.patch = p_patch;
+ export_defer.patches = p_patches;
cmdline_export_mode = true;
return OK;
}
diff --git a/editor/editor_node.h b/editor/editor_node.h
index 7ef38b4edb..36332e3d78 100644
--- a/editor/editor_node.h
+++ b/editor/editor_node.h
@@ -246,6 +246,8 @@ private:
bool debug = false;
bool pack_only = false;
bool android_build_template = false;
+ bool patch = false;
+ Vector<String> patches;
} export_defer;
static EditorNode *singleton;
@@ -880,7 +882,7 @@ public:
void _copy_warning(const String &p_str);
- Error export_preset(const String &p_preset, const String &p_path, bool p_debug, bool p_pack_only, bool p_android_build_template);
+ Error export_preset(const String &p_preset, const String &p_path, bool p_debug, bool p_pack_only, bool p_android_build_template, bool p_patch, const Vector<String> &p_patches);
bool is_project_exporting() const;
Control *get_gui_base() { return gui_base; }
diff --git a/editor/export/SCsub b/editor/export/SCsub
index 359d04e5df..b3cff5b9dc 100644
--- a/editor/export/SCsub
+++ b/editor/export/SCsub
@@ -1,4 +1,5 @@
#!/usr/bin/env python
+from misc.utility.scons_hints import *
Import("env")
diff --git a/editor/export/editor_export.cpp b/editor/export/editor_export.cpp
index 975a601ae1..6ca83c5e25 100644
--- a/editor/export/editor_export.cpp
+++ b/editor/export/editor_export.cpp
@@ -83,6 +83,8 @@ void EditorExport::_save() {
config->set_value(section, "include_filter", preset->get_include_filter());
config->set_value(section, "exclude_filter", preset->get_exclude_filter());
config->set_value(section, "export_path", preset->get_export_path());
+ config->set_value(section, "patches", preset->get_patches());
+
config->set_value(section, "encryption_include_filters", preset->get_enc_in_filter());
config->set_value(section, "encryption_exclude_filters", preset->get_enc_ex_filter());
config->set_value(section, "encrypt_pck", preset->get_enc_pck());
@@ -303,6 +305,7 @@ void EditorExport::load_config() {
preset->set_exclude_filter(config->get_value(section, "exclude_filter"));
preset->set_export_path(config->get_value(section, "export_path", ""));
preset->set_script_export_mode(config->get_value(section, "script_export_mode", EditorExportPreset::MODE_SCRIPT_BINARY_TOKENS_COMPRESSED));
+ preset->set_patches(config->get_value(section, "patches", Vector<String>()));
if (config->has_section_key(section, "encrypt_pck")) {
preset->set_enc_pck(config->get_value(section, "encrypt_pck"));
diff --git a/editor/export/editor_export_platform.cpp b/editor/export/editor_export_platform.cpp
index 983f4ee36a..58737c53ed 100644
--- a/editor/export/editor_export_platform.cpp
+++ b/editor/export/editor_export_platform.cpp
@@ -167,6 +167,44 @@ bool EditorExportPlatform::fill_log_messages(RichTextLabel *p_log, Error p_err)
return has_messages;
}
+bool EditorExportPlatform::_check_hash(const uint8_t *p_hash, const Vector<uint8_t> &p_data) {
+ if (p_hash == nullptr) {
+ return false;
+ }
+
+ unsigned char hash[16];
+ Error err = CryptoCore::md5(p_data.ptr(), p_data.size(), hash);
+ if (err != OK) {
+ return false;
+ }
+
+ for (int i = 0; i < 16; i++) {
+ if (p_hash[i] != hash[i]) {
+ return false;
+ }
+ }
+
+ return true;
+}
+
+Error EditorExportPlatform::_load_patches(const Vector<String> &p_patches) {
+ Error err = OK;
+ if (!p_patches.is_empty()) {
+ for (const String &path : p_patches) {
+ err = PackedData::get_singleton()->add_pack(path, true, 0);
+ if (err != OK) {
+ add_message(EXPORT_MESSAGE_ERROR, TTR("Patch Creation"), vformat(TTR("Could not load patch pack with path \"%s\"."), path));
+ return err;
+ }
+ }
+ }
+ return err;
+}
+
+void EditorExportPlatform::_unload_patches() {
+ PackedData::get_singleton()->clear();
+}
+
Error EditorExportPlatform::_save_pack_file(void *p_userdata, const String &p_path, const Vector<uint8_t> &p_data, int p_file, int p_total, const Vector<String> &p_enc_in_filters, const Vector<String> &p_enc_ex_filters, const Vector<uint8_t> &p_key) {
ERR_FAIL_COND_V_MSG(p_total < 1, ERR_PARAMETER_RANGE_ERROR, "Must select at least one file to export.");
@@ -237,6 +275,14 @@ Error EditorExportPlatform::_save_pack_file(void *p_userdata, const String &p_pa
return OK;
}
+Error EditorExportPlatform::_save_pack_patch_file(void *p_userdata, const String &p_path, const Vector<uint8_t> &p_data, int p_file, int p_total, const Vector<String> &p_enc_in_filters, const Vector<String> &p_enc_ex_filters, const Vector<uint8_t> &p_key) {
+ if (_check_hash(PackedData::get_singleton()->get_file_hash(p_path), p_data)) {
+ return OK;
+ }
+
+ return _save_pack_file(p_userdata, p_path, p_data, p_file, p_total, p_enc_in_filters, p_enc_ex_filters, p_key);
+}
+
Error EditorExportPlatform::_save_zip_file(void *p_userdata, const String &p_path, const Vector<uint8_t> &p_data, int p_file, int p_total, const Vector<String> &p_enc_in_filters, const Vector<String> &p_enc_ex_filters, const Vector<uint8_t> &p_key) {
ERR_FAIL_COND_V_MSG(p_total < 1, ERR_PARAMETER_RANGE_ERROR, "Must select at least one file to export.");
@@ -260,6 +306,8 @@ Error EditorExportPlatform::_save_zip_file(void *p_userdata, const String &p_pat
zipWriteInFileInZip(zip, p_data.ptr(), p_data.size());
zipCloseFileInZip(zip);
+ zd->file_count += 1;
+
if (zd->ep->step(TTR("Storing File:") + " " + p_path, 2 + p_file * 100 / p_total, false)) {
return ERR_SKIP;
}
@@ -267,6 +315,14 @@ Error EditorExportPlatform::_save_zip_file(void *p_userdata, const String &p_pat
return OK;
}
+Error EditorExportPlatform::_save_zip_patch_file(void *p_userdata, const String &p_path, const Vector<uint8_t> &p_data, int p_file, int p_total, const Vector<String> &p_enc_in_filters, const Vector<String> &p_enc_ex_filters, const Vector<uint8_t> &p_key) {
+ if (_check_hash(PackedData::get_singleton()->get_file_hash(p_path), p_data)) {
+ return OK;
+ }
+
+ return _save_zip_file(p_userdata, p_path, p_data, p_file, p_total, p_enc_in_filters, p_enc_ex_filters, p_key);
+}
+
Ref<ImageTexture> EditorExportPlatform::get_option_icon(int p_index) const {
Ref<Theme> theme = EditorNode::get_singleton()->get_editor_theme();
ERR_FAIL_COND_V(theme.is_null(), Ref<ImageTexture>());
@@ -1561,7 +1617,7 @@ Dictionary EditorExportPlatform::_save_pack(const Ref<EditorExportPreset> &p_pre
Vector<SharedObject> so_files;
int64_t embedded_start = 0;
int64_t embedded_size = 0;
- Error err_code = save_pack(p_preset, p_debug, p_path, &so_files, p_embed, &embedded_start, &embedded_size);
+ Error err_code = save_pack(p_preset, p_debug, p_path, &so_files, nullptr, p_embed, &embedded_start, &embedded_size);
Dictionary ret;
ret["result"] = err_code;
@@ -1605,9 +1661,55 @@ Dictionary EditorExportPlatform::_save_zip(const Ref<EditorExportPreset> &p_pres
return ret;
}
-Error EditorExportPlatform::save_pack(const Ref<EditorExportPreset> &p_preset, bool p_debug, const String &p_path, Vector<SharedObject> *p_so_files, bool p_embed, int64_t *r_embedded_start, int64_t *r_embedded_size) {
+Dictionary EditorExportPlatform::_save_pack_patch(const Ref<EditorExportPreset> &p_preset, bool p_debug, const String &p_path) {
+ Vector<SharedObject> so_files;
+ Error err_code = save_pack_patch(p_preset, p_debug, p_path, &so_files);
+
+ Dictionary ret;
+ ret["result"] = err_code;
+ if (err_code == OK) {
+ Array arr;
+ for (const SharedObject &E : so_files) {
+ Dictionary so;
+ so["path"] = E.path;
+ so["tags"] = E.tags;
+ so["target_folder"] = E.target;
+ arr.push_back(so);
+ }
+ ret["so_files"] = arr;
+ }
+
+ return ret;
+}
+
+Dictionary EditorExportPlatform::_save_zip_patch(const Ref<EditorExportPreset> &p_preset, bool p_debug, const String &p_path) {
+ Vector<SharedObject> so_files;
+ Error err_code = save_zip_patch(p_preset, p_debug, p_path, &so_files);
+
+ Dictionary ret;
+ ret["result"] = err_code;
+ if (err_code == OK) {
+ Array arr;
+ for (const SharedObject &E : so_files) {
+ Dictionary so;
+ so["path"] = E.path;
+ so["tags"] = E.tags;
+ so["target_folder"] = E.target;
+ arr.push_back(so);
+ }
+ ret["so_files"] = arr;
+ }
+
+ return ret;
+}
+
+Error EditorExportPlatform::save_pack(const Ref<EditorExportPreset> &p_preset, bool p_debug, const String &p_path, Vector<SharedObject> *p_so_files, EditorExportSaveFunction p_save_func, bool p_embed, int64_t *r_embedded_start, int64_t *r_embedded_size) {
EditorProgress ep("savepack", TTR("Packing"), 102, true);
+ if (p_save_func == nullptr) {
+ p_save_func = _save_pack_file;
+ }
+
// Create the temporary export directory if it doesn't exist.
Ref<DirAccess> da = DirAccess::create(DirAccess::ACCESS_FILESYSTEM);
da->make_dir_recursive(EditorPaths::get_singleton()->get_cache_dir());
@@ -1624,7 +1726,7 @@ Error EditorExportPlatform::save_pack(const Ref<EditorExportPreset> &p_preset, b
pd.f = ftmp;
pd.so_files = p_so_files;
- Error err = export_project_files(p_preset, p_debug, _save_pack_file, &pd, _pack_add_shared_object);
+ Error err = export_project_files(p_preset, p_debug, p_save_func, &pd, _pack_add_shared_object);
// Close temp file.
pd.f.unref();
@@ -1636,6 +1738,12 @@ Error EditorExportPlatform::save_pack(const Ref<EditorExportPreset> &p_preset, b
return err;
}
+ if (pd.file_ofs.is_empty()) {
+ DirAccess::remove_file_or_error(tmppath);
+ add_message(EXPORT_MESSAGE_ERROR, TTR("Save PCK"), TTR("No files or changes to export."));
+ return FAILED;
+ }
+
pd.file_ofs.sort(); //do sort, so we can do binary search later
Ref<FileAccess> f;
@@ -1831,28 +1939,56 @@ Error EditorExportPlatform::save_pack(const Ref<EditorExportPreset> &p_preset, b
return OK;
}
-Error EditorExportPlatform::save_zip(const Ref<EditorExportPreset> &p_preset, bool p_debug, const String &p_path, Vector<SharedObject> *p_so_files) {
+Error EditorExportPlatform::save_pack_patch(const Ref<EditorExportPreset> &p_preset, bool p_debug, const String &p_path, Vector<SharedObject> *p_so_files, bool p_embed, int64_t *r_embedded_start, int64_t *r_embedded_size) {
+ return save_pack(p_preset, p_debug, p_path, p_so_files, _save_pack_patch_file, p_embed, r_embedded_start, r_embedded_size);
+}
+
+Error EditorExportPlatform::save_zip(const Ref<EditorExportPreset> &p_preset, bool p_debug, const String &p_path, Vector<SharedObject> *p_so_files, EditorExportSaveFunction p_save_func) {
EditorProgress ep("savezip", TTR("Packing"), 102, true);
+ if (p_save_func == nullptr) {
+ p_save_func = _save_zip_file;
+ }
+
+ String tmppath = EditorPaths::get_singleton()->get_cache_dir().path_join("packtmp");
+
Ref<FileAccess> io_fa;
zlib_filefunc_def io = zipio_create_io(&io_fa);
- zipFile zip = zipOpen2(p_path.utf8().get_data(), APPEND_STATUS_CREATE, nullptr, &io);
+ zipFile zip = zipOpen2(tmppath.utf8().get_data(), APPEND_STATUS_CREATE, nullptr, &io);
ZipData zd;
zd.ep = &ep;
zd.zip = zip;
zd.so_files = p_so_files;
- Error err = export_project_files(p_preset, p_debug, _save_zip_file, &zd, _zip_add_shared_object);
+ Error err = export_project_files(p_preset, p_debug, p_save_func, &zd, _zip_add_shared_object);
if (err != OK && err != ERR_SKIP) {
add_message(EXPORT_MESSAGE_ERROR, TTR("Save ZIP"), TTR("Failed to export project files."));
}
zipClose(zip, nullptr);
+ Ref<DirAccess> da = DirAccess::create(DirAccess::ACCESS_FILESYSTEM);
+
+ if (zd.file_count == 0) {
+ da->remove(tmppath);
+ add_message(EXPORT_MESSAGE_ERROR, TTR("Save PCK"), TTR("No files or changes to export."));
+ return FAILED;
+ }
+
+ err = da->rename(tmppath, p_path);
+ if (err != OK) {
+ da->remove(tmppath);
+ add_message(EXPORT_MESSAGE_ERROR, TTR("Save ZIP"), vformat(TTR("Failed to move temporary file \"%s\" to \"%s\"."), tmppath, p_path));
+ }
+
return OK;
}
+Error EditorExportPlatform::save_zip_patch(const Ref<EditorExportPreset> &p_preset, bool p_debug, const String &p_path, Vector<SharedObject> *p_so_files) {
+ return save_zip(p_preset, p_debug, p_path, p_so_files, _save_zip_patch_file);
+}
+
Error EditorExportPlatform::export_pack(const Ref<EditorExportPreset> &p_preset, bool p_debug, const String &p_path, BitField<EditorExportPlatform::DebugFlags> p_flags) {
ExportNotifier notifier(*this, p_preset, p_debug, p_path, p_flags);
return save_pack(p_preset, p_debug, p_path);
@@ -1863,6 +1999,28 @@ Error EditorExportPlatform::export_zip(const Ref<EditorExportPreset> &p_preset,
return save_zip(p_preset, p_debug, p_path);
}
+Error EditorExportPlatform::export_pack_patch(const Ref<EditorExportPreset> &p_preset, bool p_debug, const String &p_path, const Vector<String> &p_patches, BitField<EditorExportPlatform::DebugFlags> p_flags) {
+ ExportNotifier notifier(*this, p_preset, p_debug, p_path, p_flags);
+ Error err = _load_patches(p_patches.is_empty() ? p_preset->get_patches() : p_patches);
+ if (err != OK) {
+ return err;
+ }
+ err = save_pack_patch(p_preset, p_debug, p_path);
+ _unload_patches();
+ return err;
+}
+
+Error EditorExportPlatform::export_zip_patch(const Ref<EditorExportPreset> &p_preset, bool p_debug, const String &p_path, const Vector<String> &p_patches, BitField<EditorExportPlatform::DebugFlags> p_flags) {
+ ExportNotifier notifier(*this, p_preset, p_debug, p_path, p_flags);
+ Error err = _load_patches(p_patches.is_empty() ? p_preset->get_patches() : p_patches);
+ if (err != OK) {
+ return err;
+ }
+ err = save_zip_patch(p_preset, p_debug, p_path);
+ _unload_patches();
+ return err;
+}
+
Vector<String> EditorExportPlatform::gen_export_flags(BitField<EditorExportPlatform::DebugFlags> p_flags) {
Vector<String> ret;
String host = EDITOR_GET("network/debug/remote_host");
@@ -2115,6 +2273,8 @@ void EditorExportPlatform::_bind_methods() {
ClassDB::bind_method(D_METHOD("save_pack", "preset", "debug", "path", "embed"), &EditorExportPlatform::_save_pack, DEFVAL(false));
ClassDB::bind_method(D_METHOD("save_zip", "preset", "debug", "path"), &EditorExportPlatform::_save_zip);
+ ClassDB::bind_method(D_METHOD("save_pack_patch", "preset", "debug", "path"), &EditorExportPlatform::_save_pack_patch);
+ ClassDB::bind_method(D_METHOD("save_zip_patch", "preset", "debug", "path"), &EditorExportPlatform::_save_zip_patch);
ClassDB::bind_method(D_METHOD("gen_export_flags", "flags"), &EditorExportPlatform::gen_export_flags);
@@ -2123,6 +2283,8 @@ void EditorExportPlatform::_bind_methods() {
ClassDB::bind_method(D_METHOD("export_project", "preset", "debug", "path", "flags"), &EditorExportPlatform::export_project, DEFVAL(0));
ClassDB::bind_method(D_METHOD("export_pack", "preset", "debug", "path", "flags"), &EditorExportPlatform::export_pack, DEFVAL(0));
ClassDB::bind_method(D_METHOD("export_zip", "preset", "debug", "path", "flags"), &EditorExportPlatform::export_zip, DEFVAL(0));
+ ClassDB::bind_method(D_METHOD("export_pack_patch", "preset", "debug", "path", "patches", "flags"), &EditorExportPlatform::export_pack_patch, DEFVAL(PackedStringArray()), DEFVAL(0));
+ ClassDB::bind_method(D_METHOD("export_zip_patch", "preset", "debug", "path", "patches", "flags"), &EditorExportPlatform::export_zip_patch, DEFVAL(PackedStringArray()), DEFVAL(0));
ClassDB::bind_method(D_METHOD("clear_messages"), &EditorExportPlatform::clear_messages);
ClassDB::bind_method(D_METHOD("add_message", "type", "category", "message"), &EditorExportPlatform::add_message);
diff --git a/editor/export/editor_export_platform.h b/editor/export/editor_export_platform.h
index 95f21ceafb..ef3274c5e4 100644
--- a/editor/export/editor_export_platform.h
+++ b/editor/export/editor_export_platform.h
@@ -101,6 +101,7 @@ private:
void *zip = nullptr;
EditorProgress *ep = nullptr;
Vector<SharedObject> *so_files = nullptr;
+ int file_count = 0;
};
Vector<ExportMessage> messages;
@@ -109,10 +110,14 @@ private:
void _export_find_customized_resources(const Ref<EditorExportPreset> &p_preset, EditorFileSystemDirectory *p_dir, EditorExportPreset::FileExportMode p_mode, HashSet<String> &p_paths);
void _export_find_dependencies(const String &p_path, HashSet<String> &p_paths);
+ static bool _check_hash(const uint8_t *p_hash, const Vector<uint8_t> &p_data);
+
static Error _save_pack_file(void *p_userdata, const String &p_path, const Vector<uint8_t> &p_data, int p_file, int p_total, const Vector<String> &p_enc_in_filters, const Vector<String> &p_enc_ex_filters, const Vector<uint8_t> &p_key);
+ static Error _save_pack_patch_file(void *p_userdata, const String &p_path, const Vector<uint8_t> &p_data, int p_file, int p_total, const Vector<String> &p_enc_in_filters, const Vector<String> &p_enc_ex_filters, const Vector<uint8_t> &p_key);
static Error _pack_add_shared_object(void *p_userdata, const SharedObject &p_so);
static Error _save_zip_file(void *p_userdata, const String &p_path, const Vector<uint8_t> &p_data, int p_file, int p_total, const Vector<String> &p_enc_in_filters, const Vector<String> &p_enc_ex_filters, const Vector<uint8_t> &p_key);
+ static Error _save_zip_patch_file(void *p_userdata, const String &p_path, const Vector<uint8_t> &p_data, int p_file, int p_total, const Vector<String> &p_enc_in_filters, const Vector<String> &p_enc_ex_filters, const Vector<uint8_t> &p_key);
static Error _zip_add_shared_object(void *p_userdata, const SharedObject &p_so);
struct ScriptCallbackData {
@@ -188,6 +193,9 @@ protected:
Error ssh_run_on_remote_no_wait(const String &p_host, const String &p_port, const Vector<String> &p_ssh_args, const String &p_cmd_args, OS::ProcessID *r_pid = nullptr, int p_port_fwd = -1) const;
Error ssh_push_to_remote(const String &p_host, const String &p_port, const Vector<String> &p_scp_args, const String &p_src_file, const String &p_dst_file) const;
+ Error _load_patches(const Vector<String> &p_patches);
+ void _unload_patches();
+
public:
virtual void get_preset_features(const Ref<EditorExportPreset> &p_preset, List<String> *r_features) const = 0;
@@ -284,8 +292,14 @@ public:
Dictionary _save_pack(const Ref<EditorExportPreset> &p_preset, bool p_debug, const String &p_path, bool p_embed = false);
Dictionary _save_zip(const Ref<EditorExportPreset> &p_preset, bool p_debug, const String &p_path);
- Error save_pack(const Ref<EditorExportPreset> &p_preset, bool p_debug, const String &p_path, Vector<SharedObject> *p_so_files = nullptr, bool p_embed = false, int64_t *r_embedded_start = nullptr, int64_t *r_embedded_size = nullptr);
- Error save_zip(const Ref<EditorExportPreset> &p_preset, bool p_debug, const String &p_path, Vector<SharedObject> *p_so_files = nullptr);
+ Dictionary _save_pack_patch(const Ref<EditorExportPreset> &p_preset, bool p_debug, const String &p_path);
+ Dictionary _save_zip_patch(const Ref<EditorExportPreset> &p_preset, bool p_debug, const String &p_path);
+
+ Error save_pack(const Ref<EditorExportPreset> &p_preset, bool p_debug, const String &p_path, Vector<SharedObject> *p_so_files = nullptr, EditorExportSaveFunction p_save_func = nullptr, bool p_embed = false, int64_t *r_embedded_start = nullptr, int64_t *r_embedded_size = nullptr);
+ Error save_zip(const Ref<EditorExportPreset> &p_preset, bool p_debug, const String &p_path, Vector<SharedObject> *p_so_files = nullptr, EditorExportSaveFunction p_save_func = nullptr);
+
+ Error save_pack_patch(const Ref<EditorExportPreset> &p_preset, bool p_debug, const String &p_path, Vector<SharedObject> *p_so_files = nullptr, bool p_embed = false, int64_t *r_embedded_start = nullptr, int64_t *r_embedded_size = nullptr);
+ Error save_zip_patch(const Ref<EditorExportPreset> &p_preset, bool p_debug, const String &p_path, Vector<SharedObject> *p_so_files = nullptr);
virtual bool poll_export() { return false; }
virtual int get_options_count() const { return 0; }
@@ -307,6 +321,8 @@ public:
virtual Error export_project(const Ref<EditorExportPreset> &p_preset, bool p_debug, const String &p_path, BitField<EditorExportPlatform::DebugFlags> p_flags = 0) = 0;
virtual Error export_pack(const Ref<EditorExportPreset> &p_preset, bool p_debug, const String &p_path, BitField<EditorExportPlatform::DebugFlags> p_flags = 0);
virtual Error export_zip(const Ref<EditorExportPreset> &p_preset, bool p_debug, const String &p_path, BitField<EditorExportPlatform::DebugFlags> p_flags = 0);
+ virtual Error export_pack_patch(const Ref<EditorExportPreset> &p_preset, bool p_debug, const String &p_path, const Vector<String> &p_patches = Vector<String>(), BitField<EditorExportPlatform::DebugFlags> p_flags = 0);
+ virtual Error export_zip_patch(const Ref<EditorExportPreset> &p_preset, bool p_debug, const String &p_path, const Vector<String> &p_patches = Vector<String>(), BitField<EditorExportPlatform::DebugFlags> p_flags = 0);
virtual void get_platform_features(List<String> *r_features) const = 0;
virtual void resolve_platform_feature_priorities(const Ref<EditorExportPreset> &p_preset, HashSet<String> &p_features) {}
virtual String get_debug_protocol() const { return "tcp://"; }
diff --git a/editor/export/editor_export_platform_extension.cpp b/editor/export/editor_export_platform_extension.cpp
index 808a2076e2..5b75ff19a4 100644
--- a/editor/export/editor_export_platform_extension.cpp
+++ b/editor/export/editor_export_platform_extension.cpp
@@ -71,6 +71,8 @@ void EditorExportPlatformExtension::_bind_methods() {
GDVIRTUAL_BIND(_export_project, "preset", "debug", "path", "flags");
GDVIRTUAL_BIND(_export_pack, "preset", "debug", "path", "flags");
GDVIRTUAL_BIND(_export_zip, "preset", "debug", "path", "flags");
+ GDVIRTUAL_BIND(_export_pack_patch, "preset", "debug", "path", "patches", "flags");
+ GDVIRTUAL_BIND(_export_zip_patch, "preset", "debug", "path", "patches", "flags");
GDVIRTUAL_BIND(_get_platform_features);
@@ -291,6 +293,44 @@ Error EditorExportPlatformExtension::export_zip(const Ref<EditorExportPreset> &p
return save_zip(p_preset, p_debug, p_path);
}
+Error EditorExportPlatformExtension::export_pack_patch(const Ref<EditorExportPreset> &p_preset, bool p_debug, const String &p_path, const Vector<String> &p_patches, BitField<EditorExportPlatform::DebugFlags> p_flags) {
+ ExportNotifier notifier(*this, p_preset, p_debug, p_path, p_flags);
+
+ Error err = _load_patches(p_patches.is_empty() ? p_preset->get_patches() : p_patches);
+ if (err != OK) {
+ return err;
+ }
+
+ Error ret = FAILED;
+ if (GDVIRTUAL_CALL(_export_pack_patch, p_preset, p_debug, p_path, p_patches, p_flags, ret)) {
+ _unload_patches();
+ return ret;
+ }
+
+ err = save_pack_patch(p_preset, p_debug, p_path);
+ _unload_patches();
+ return err;
+}
+
+Error EditorExportPlatformExtension::export_zip_patch(const Ref<EditorExportPreset> &p_preset, bool p_debug, const String &p_path, const Vector<String> &p_patches, BitField<EditorExportPlatform::DebugFlags> p_flags) {
+ ExportNotifier notifier(*this, p_preset, p_debug, p_path, p_flags);
+
+ Error err = _load_patches(p_patches.is_empty() ? p_preset->get_patches() : p_patches);
+ if (err != OK) {
+ return err;
+ }
+
+ Error ret = FAILED;
+ if (GDVIRTUAL_CALL(_export_zip_patch, p_preset, p_debug, p_path, p_patches, p_flags, ret)) {
+ _unload_patches();
+ return ret;
+ }
+
+ err = save_zip_patch(p_preset, p_debug, p_path);
+ _unload_patches();
+ return err;
+}
+
void EditorExportPlatformExtension::get_platform_features(List<String> *r_features) const {
Vector<String> ret;
if (GDVIRTUAL_REQUIRED_CALL(_get_platform_features, ret) && r_features) {
diff --git a/editor/export/editor_export_platform_extension.h b/editor/export/editor_export_platform_extension.h
index 6391e65ac1..d1db922698 100644
--- a/editor/export/editor_export_platform_extension.h
+++ b/editor/export/editor_export_platform_extension.h
@@ -136,6 +136,12 @@ public:
virtual Error export_zip(const Ref<EditorExportPreset> &p_preset, bool p_debug, const String &p_path, BitField<EditorExportPlatform::DebugFlags> p_flags = 0) override;
GDVIRTUAL4R(Error, _export_zip, Ref<EditorExportPreset>, bool, const String &, BitField<EditorExportPlatform::DebugFlags>);
+ virtual Error export_pack_patch(const Ref<EditorExportPreset> &p_preset, bool p_debug, const String &p_path, const Vector<String> &p_patches = Vector<String>(), BitField<EditorExportPlatform::DebugFlags> p_flags = 0) override;
+ GDVIRTUAL5R(Error, _export_pack_patch, Ref<EditorExportPreset>, bool, const String &, const Vector<String> &, BitField<EditorExportPlatform::DebugFlags>);
+
+ virtual Error export_zip_patch(const Ref<EditorExportPreset> &p_preset, bool p_debug, const String &p_path, const Vector<String> &p_patches = Vector<String>(), BitField<EditorExportPlatform::DebugFlags> p_flags = 0) override;
+ GDVIRTUAL5R(Error, _export_zip_patch, Ref<EditorExportPreset>, bool, const String &, const Vector<String> &, BitField<EditorExportPlatform::DebugFlags>);
+
virtual void get_platform_features(List<String> *r_features) const override;
GDVIRTUAL0RC(Vector<String>, _get_platform_features);
diff --git a/editor/export/editor_export_platform_pc.cpp b/editor/export/editor_export_platform_pc.cpp
index 24d89b7f34..31a5b60d77 100644
--- a/editor/export/editor_export_platform_pc.cpp
+++ b/editor/export/editor_export_platform_pc.cpp
@@ -194,7 +194,7 @@ Error EditorExportPlatformPC::export_project_data(const Ref<EditorExportPreset>
int64_t embedded_pos;
int64_t embedded_size;
- Error err = save_pack(p_preset, p_debug, pck_path, &so_files, p_preset->get("binary_format/embed_pck"), &embedded_pos, &embedded_size);
+ Error err = save_pack(p_preset, p_debug, pck_path, &so_files, nullptr, p_preset->get("binary_format/embed_pck"), &embedded_pos, &embedded_size);
if (err == OK && p_preset->get("binary_format/embed_pck")) {
if (embedded_size >= 0x100000000 && String(p_preset->get("binary_format/architecture")).contains("32")) {
add_message(EXPORT_MESSAGE_ERROR, TTR("PCK Embedding"), TTR("On 32-bit exports the embedded PCK cannot be bigger than 4 GiB."));
diff --git a/editor/export/editor_export_preset.cpp b/editor/export/editor_export_preset.cpp
index 9f805666d0..1ca72348e2 100644
--- a/editor/export/editor_export_preset.cpp
+++ b/editor/export/editor_export_preset.cpp
@@ -79,6 +79,7 @@ void EditorExportPreset::_bind_methods() {
ClassDB::bind_method(D_METHOD("get_include_filter"), &EditorExportPreset::get_include_filter);
ClassDB::bind_method(D_METHOD("get_exclude_filter"), &EditorExportPreset::get_exclude_filter);
ClassDB::bind_method(D_METHOD("get_custom_features"), &EditorExportPreset::get_custom_features);
+ ClassDB::bind_method(D_METHOD("get_patches"), &EditorExportPreset::get_patches);
ClassDB::bind_method(D_METHOD("get_export_path"), &EditorExportPreset::get_export_path);
ClassDB::bind_method(D_METHOD("get_encryption_in_filter"), &EditorExportPreset::get_enc_in_filter);
ClassDB::bind_method(D_METHOD("get_encryption_ex_filter"), &EditorExportPreset::get_enc_ex_filter);
@@ -366,6 +367,42 @@ EditorExportPreset::FileExportMode EditorExportPreset::get_file_export_mode(cons
return p_default;
}
+void EditorExportPreset::add_patch(const String &p_path, int p_at_pos) {
+ ERR_FAIL_COND_EDMSG(patches.has(p_path), vformat("Failed to add patch \"%s\". Patches must be unique.", p_path));
+
+ if (p_at_pos < 0) {
+ patches.push_back(p_path);
+ } else {
+ patches.insert(p_at_pos, p_path);
+ }
+
+ EditorExport::singleton->save_presets();
+}
+
+void EditorExportPreset::set_patch(int p_index, const String &p_path) {
+ remove_patch(p_index);
+ add_patch(p_path, p_index);
+}
+
+String EditorExportPreset::get_patch(int p_index) {
+ ERR_FAIL_INDEX_V(p_index, patches.size(), String());
+ return patches[p_index];
+}
+
+void EditorExportPreset::remove_patch(int p_index) {
+ ERR_FAIL_INDEX(p_index, patches.size());
+ patches.remove_at(p_index);
+ EditorExport::singleton->save_presets();
+}
+
+void EditorExportPreset::set_patches(const Vector<String> &p_patches) {
+ patches = p_patches;
+}
+
+Vector<String> EditorExportPreset::get_patches() const {
+ return patches;
+}
+
void EditorExportPreset::set_custom_features(const String &p_custom_features) {
custom_features = p_custom_features;
EditorExport::singleton->save_presets();
diff --git a/editor/export/editor_export_preset.h b/editor/export/editor_export_preset.h
index f220477461..af3a23fc50 100644
--- a/editor/export/editor_export_preset.h
+++ b/editor/export/editor_export_preset.h
@@ -74,6 +74,8 @@ private:
bool advanced_options_enabled = false;
bool dedicated_server = false;
+ Vector<String> patches;
+
friend class EditorExport;
friend class EditorExportPlatform;
@@ -144,6 +146,13 @@ public:
void set_exclude_filter(const String &p_exclude);
String get_exclude_filter() const;
+ void add_patch(const String &p_path, int p_at_pos = -1);
+ void set_patch(int p_index, const String &p_path);
+ String get_patch(int p_index);
+ void remove_patch(int p_index);
+ void set_patches(const Vector<String> &p_patches);
+ Vector<String> get_patches() const;
+
void set_custom_features(const String &p_custom_features);
String get_custom_features() const;
diff --git a/editor/export/project_export.cpp b/editor/export/project_export.cpp
index be9e0f78ec..f9137082d7 100644
--- a/editor/export/project_export.cpp
+++ b/editor/export/project_export.cpp
@@ -102,11 +102,13 @@ void ProjectExportDialog::_notification(int p_what) {
case NOTIFICATION_THEME_CHANGED: {
duplicate_preset->set_icon(presets->get_editor_theme_icon(SNAME("Duplicate")));
delete_preset->set_icon(presets->get_editor_theme_icon(SNAME("Remove")));
+ patch_add_btn->set_icon(get_editor_theme_icon(SNAME("Add")));
} break;
case NOTIFICATION_READY: {
duplicate_preset->set_icon(presets->get_editor_theme_icon(SNAME("Duplicate")));
delete_preset->set_icon(presets->get_editor_theme_icon(SNAME("Remove")));
+ patch_add_btn->set_icon(get_editor_theme_icon(SNAME("Add")));
connect(SceneStringName(confirmed), callable_mp(this, &ProjectExportDialog::_export_pck_zip));
_update_export_all();
} break;
@@ -248,6 +250,7 @@ void ProjectExportDialog::_edit_preset(int p_index) {
duplicate_preset->set_disabled(true);
delete_preset->set_disabled(true);
sections->hide();
+ patches->clear();
export_error->hide();
export_templates_error->hide();
return;
@@ -292,6 +295,21 @@ void ProjectExportDialog::_edit_preset(int p_index) {
exclude_filters->set_text(current->get_exclude_filter());
server_strip_message->set_visible(current->get_export_filter() == EditorExportPreset::EXPORT_CUSTOMIZED);
+ patches->clear();
+ TreeItem *patch_root = patches->create_item();
+ Vector<String> patch_list = current->get_patches();
+ for (int i = 0; i < patch_list.size(); i++) {
+ TreeItem *patch = patches->create_item(patch_root);
+ const String &patch_path = patch_list[i];
+ patch->set_cell_mode(0, TreeItem::CELL_MODE_STRING);
+ patch->set_editable(0, true);
+ patch->set_text(0, patch_path.get_file());
+ patch->set_tooltip_text(0, patch_path);
+ patch->set_metadata(0, i);
+ patch->add_button(0, get_editor_theme_icon(SNAME("Remove")), 0);
+ patch->add_button(0, get_editor_theme_icon(SNAME("FileBrowse")), 1);
+ }
+
_fill_resource_tree();
bool needs_templates;
@@ -664,6 +682,7 @@ void ProjectExportDialog::_duplicate_preset() {
preset->set_export_filter(current->get_export_filter());
preset->set_include_filter(current->get_include_filter());
preset->set_exclude_filter(current->get_exclude_filter());
+ preset->set_patches(current->get_patches());
preset->set_custom_features(current->get_custom_features());
for (const KeyValue<StringName, Variant> &E : current->get_values()) {
@@ -720,8 +739,22 @@ Variant ProjectExportDialog::get_drag_data_fw(const Point2 &p_point, Control *p_
return d;
}
- }
+ } else if (p_from == patches) {
+ TreeItem *item = patches->get_item_at_position(p_point);
+
+ if (item) {
+ int item_metadata = item->get_metadata(0);
+ Dictionary d;
+ d["type"] = "export_patch";
+ d["patch"] = item_metadata;
+ Label *label = memnew(Label);
+ label->set_text(item->get_text(0));
+ patches->set_drag_preview(label);
+
+ return d;
+ }
+ }
return Variant();
}
@@ -735,6 +768,18 @@ bool ProjectExportDialog::can_drop_data_fw(const Point2 &p_point, const Variant
if (presets->get_item_at_position(p_point, true) < 0 && !presets->is_pos_at_end_of_items(p_point)) {
return false;
}
+ } else if (p_from == patches) {
+ Dictionary d = p_data;
+ if (d.get("type", "") != "export_patch") {
+ return false;
+ }
+
+ TreeItem *item = patches->get_item_at_position(p_point);
+ if (!item) {
+ return false;
+ }
+
+ patches->set_drop_mode_flags(Tree::DROP_MODE_INBETWEEN);
}
return true;
@@ -771,6 +816,31 @@ void ProjectExportDialog::drop_data_fw(const Point2 &p_point, const Variant &p_d
} else {
_edit_preset(presets->get_item_count() - 1);
}
+ } else if (p_from == patches) {
+ Dictionary d = p_data;
+ int from_pos = d["patch"];
+
+ TreeItem *item = patches->get_item_at_position(p_point);
+ if (!item) {
+ return;
+ }
+
+ int to_pos = item->get_metadata(0);
+
+ if (patches->get_drop_section_at_position(p_point) > 0) {
+ to_pos++;
+ }
+
+ if (to_pos > from_pos) {
+ to_pos--;
+ }
+
+ Ref<EditorExportPreset> preset = get_current_preset();
+ String patch = preset->get_patch(from_pos);
+ preset->remove_patch(from_pos);
+ preset->add_patch(patch, to_pos);
+
+ _update_current_preset();
}
}
@@ -1026,6 +1096,75 @@ void ProjectExportDialog::_set_file_export_mode(int p_id) {
_propagate_file_export_mode(include_files->get_root(), EditorExportPreset::MODE_FILE_NOT_CUSTOMIZED);
}
+void ProjectExportDialog::_patch_tree_button_clicked(Object *p_item, int p_column, int p_id, int p_mouse_button_index) {
+ TreeItem *ti = Object::cast_to<TreeItem>(p_item);
+
+ patch_index = ti->get_metadata(0);
+
+ Ref<EditorExportPreset> current = get_current_preset();
+ ERR_FAIL_COND(current.is_null());
+
+ if (p_id == 0) {
+ Vector<String> preset_patches = current->get_patches();
+ ERR_FAIL_INDEX(patch_index, preset_patches.size());
+ patch_erase->set_text(vformat(TTR("Delete patch '%s' from list?"), preset_patches[patch_index].get_file()));
+ patch_erase->popup_centered();
+ } else {
+ patch_dialog->popup_file_dialog();
+ }
+}
+
+void ProjectExportDialog::_patch_tree_item_edited() {
+ TreeItem *item = patches->get_edited();
+ if (!item) {
+ return;
+ }
+
+ Ref<EditorExportPreset> current = get_current_preset();
+ ERR_FAIL_COND(current.is_null());
+
+ int index = item->get_metadata(0);
+ String patch_path = item->get_text(0);
+
+ current->set_patch(index, patch_path);
+ item->set_tooltip_text(0, patch_path);
+}
+
+void ProjectExportDialog::_patch_file_selected(const String &p_path) {
+ Ref<EditorExportPreset> current = get_current_preset();
+ ERR_FAIL_COND(current.is_null());
+
+ String relative_path = ProjectSettings::get_singleton()->get_resource_path().path_to_file(p_path);
+
+ Vector<String> preset_patches = current->get_patches();
+ if (patch_index >= preset_patches.size()) {
+ current->add_patch(relative_path);
+ } else {
+ current->set_patch(patch_index, relative_path);
+ }
+
+ _update_current_preset();
+}
+
+void ProjectExportDialog::_patch_delete_confirmed() {
+ Ref<EditorExportPreset> current = get_current_preset();
+ ERR_FAIL_COND(current.is_null());
+
+ Vector<String> preset_patches = current->get_patches();
+ if (patch_index < preset_patches.size()) {
+ current->remove_patch(patch_index);
+ _update_current_preset();
+ }
+}
+
+void ProjectExportDialog::_patch_add_pack_pressed() {
+ Ref<EditorExportPreset> current = get_current_preset();
+ ERR_FAIL_COND(current.is_null());
+
+ patch_index = current->get_patches().size();
+ patch_dialog->popup_file_dialog();
+}
+
void ProjectExportDialog::_export_pck_zip() {
Ref<EditorExportPreset> current = get_current_preset();
ERR_FAIL_COND(current.is_null());
@@ -1044,11 +1183,20 @@ void ProjectExportDialog::_export_pck_zip_selected(const String &p_path) {
const Dictionary &fd_option = export_pck_zip->get_selected_options();
bool export_debug = fd_option.get(TTR("Export With Debug"), true);
+ bool export_as_patch = fd_option.get(TTR("Export As Patch"), true);
if (p_path.ends_with(".zip")) {
- platform->export_zip(current, export_debug, p_path);
+ if (export_as_patch) {
+ platform->export_zip_patch(current, export_debug, p_path);
+ } else {
+ platform->export_zip(current, export_debug, p_path);
+ }
} else if (p_path.ends_with(".pck")) {
- platform->export_pack(current, export_debug, p_path);
+ if (export_as_patch) {
+ platform->export_pack_patch(current, export_debug, p_path);
+ } else {
+ platform->export_pack(current, export_debug, p_path);
+ }
} else {
ERR_FAIL_MSG("Path must end with .pck or .zip");
}
@@ -1386,6 +1534,40 @@ ProjectExportDialog::ProjectExportDialog() {
exclude_filters);
exclude_filters->connect(SceneStringName(text_changed), callable_mp(this, &ProjectExportDialog::_filter_changed));
+ // Patch packages.
+
+ VBoxContainer *patch_vb = memnew(VBoxContainer);
+ sections->add_child(patch_vb);
+ patch_vb->set_name(TTR("Patches"));
+
+ patches = memnew(Tree);
+ patches->set_v_size_flags(Control::SIZE_EXPAND_FILL);
+ patches->set_hide_root(true);
+ patches->set_auto_translate_mode(AUTO_TRANSLATE_MODE_DISABLED);
+ patches->connect("button_clicked", callable_mp(this, &ProjectExportDialog::_patch_tree_button_clicked));
+ patches->connect("item_edited", callable_mp(this, &ProjectExportDialog::_patch_tree_item_edited));
+ SET_DRAG_FORWARDING_GCD(patches, ProjectExportDialog);
+ patches->set_edit_checkbox_cell_only_when_checkbox_is_pressed(true);
+ patch_vb->add_margin_child(TTR("Base Packs:"), patches, true);
+
+ patch_dialog = memnew(EditorFileDialog);
+ patch_dialog->add_filter("*.pck", TTR("Godot Project Pack"));
+ patch_dialog->set_access(EditorFileDialog::ACCESS_FILESYSTEM);
+ patch_dialog->set_file_mode(EditorFileDialog::FILE_MODE_OPEN_FILE);
+ patch_dialog->connect("file_selected", callable_mp(this, &ProjectExportDialog::_patch_file_selected));
+ add_child(patch_dialog);
+
+ patch_erase = memnew(ConfirmationDialog);
+ patch_erase->set_ok_button_text(TTR("Delete"));
+ patch_erase->connect(SceneStringName(confirmed), callable_mp(this, &ProjectExportDialog::_patch_delete_confirmed));
+ add_child(patch_erase);
+
+ patch_add_btn = memnew(Button);
+ patch_add_btn->set_text(TTR("Add Pack"));
+ patch_add_btn->set_h_size_flags(Control::SIZE_SHRINK_CENTER);
+ patch_add_btn->connect(SceneStringName(pressed), callable_mp(this, &ProjectExportDialog::_patch_add_pack_pressed));
+ patch_vb->add_child(patch_add_btn);
+
// Feature tags.
VBoxContainer *feature_vb = memnew(VBoxContainer);
@@ -1569,6 +1751,7 @@ ProjectExportDialog::ProjectExportDialog() {
export_project->add_option(TTR("Export With Debug"), Vector<String>(), true);
export_pck_zip->add_option(TTR("Export With Debug"), Vector<String>(), true);
+ export_pck_zip->add_option(TTR("Export As Patch"), Vector<String>(), true);
set_hide_on_ok(false);
diff --git a/editor/export/project_export.h b/editor/export/project_export.h
index c3499177f3..e360596be6 100644
--- a/editor/export/project_export.h
+++ b/editor/export/project_export.h
@@ -105,6 +105,13 @@ class ProjectExportDialog : public ConfirmationDialog {
AcceptDialog *export_all_dialog = nullptr;
RBSet<String> feature_set;
+
+ Tree *patches = nullptr;
+ int patch_index = -1;
+ EditorFileDialog *patch_dialog = nullptr;
+ ConfirmationDialog *patch_erase = nullptr;
+ Button *patch_add_btn = nullptr;
+
LineEdit *custom_features = nullptr;
RichTextLabel *custom_feature_display = nullptr;
@@ -148,6 +155,12 @@ class ProjectExportDialog : public ConfirmationDialog {
void _tree_popup_edited(bool p_arrow_clicked);
void _set_file_export_mode(int p_id);
+ void _patch_tree_button_clicked(Object *p_item, int p_column, int p_id, int p_mouse_button_index);
+ void _patch_tree_item_edited();
+ void _patch_file_selected(const String &p_path);
+ void _patch_delete_confirmed();
+ void _patch_add_pack_pressed();
+
Variant get_drag_data_fw(const Point2 &p_point, Control *p_from);
bool can_drop_data_fw(const Point2 &p_point, const Variant &p_data, Control *p_from) const;
void drop_data_fw(const Point2 &p_point, const Variant &p_data, Control *p_from);
diff --git a/editor/file_info.cpp b/editor/file_info.cpp
new file mode 100644
index 0000000000..04d82547ab
--- /dev/null
+++ b/editor/file_info.cpp
@@ -0,0 +1,61 @@
+/**************************************************************************/
+/* file_info.cpp */
+/**************************************************************************/
+/* This file is part of: */
+/* GODOT ENGINE */
+/* https://godotengine.org */
+/**************************************************************************/
+/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
+/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
+/* */
+/* Permission is hereby granted, free of charge, to any person obtaining */
+/* a copy of this software and associated documentation files (the */
+/* "Software"), to deal in the Software without restriction, including */
+/* without limitation the rights to use, copy, modify, merge, publish, */
+/* distribute, sublicense, and/or sell copies of the Software, and to */
+/* permit persons to whom the Software is furnished to do so, subject to */
+/* the following conditions: */
+/* */
+/* The above copyright notice and this permission notice shall be */
+/* included in all copies or substantial portions of the Software. */
+/* */
+/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
+/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
+/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */
+/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
+/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
+/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
+/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
+/**************************************************************************/
+
+#include "editor/file_info.h"
+
+void sort_file_info_list(List<FileInfo> &r_file_list, FileSortOption p_file_sort_option) {
+ // Sort the file list if needed.
+ switch (p_file_sort_option) {
+ case FileSortOption::FILE_SORT_TYPE:
+ r_file_list.sort_custom<FileInfoTypeComparator>();
+ break;
+ case FileSortOption::FILE_SORT_TYPE_REVERSE:
+ r_file_list.sort_custom<FileInfoTypeComparator>();
+ r_file_list.reverse();
+ break;
+ case FileSortOption::FILE_SORT_MODIFIED_TIME:
+ r_file_list.sort_custom<FileInfoModifiedTimeComparator>();
+ break;
+ case FileSortOption::FILE_SORT_MODIFIED_TIME_REVERSE:
+ r_file_list.sort_custom<FileInfoModifiedTimeComparator>();
+ r_file_list.reverse();
+ break;
+ case FileSortOption::FILE_SORT_NAME_REVERSE:
+ r_file_list.sort();
+ r_file_list.reverse();
+ break;
+ case FileSortOption::FILE_SORT_NAME:
+ r_file_list.sort();
+ break;
+ default:
+ ERR_FAIL_MSG("Invalid file sort option.");
+ break;
+ }
+}
diff --git a/editor/file_info.h b/editor/file_info.h
new file mode 100644
index 0000000000..2ce521992c
--- /dev/null
+++ b/editor/file_info.h
@@ -0,0 +1,74 @@
+/**************************************************************************/
+/* file_info.h */
+/**************************************************************************/
+/* This file is part of: */
+/* GODOT ENGINE */
+/* https://godotengine.org */
+/**************************************************************************/
+/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
+/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
+/* */
+/* Permission is hereby granted, free of charge, to any person obtaining */
+/* a copy of this software and associated documentation files (the */
+/* "Software"), to deal in the Software without restriction, including */
+/* without limitation the rights to use, copy, modify, merge, publish, */
+/* distribute, sublicense, and/or sell copies of the Software, and to */
+/* permit persons to whom the Software is furnished to do so, subject to */
+/* the following conditions: */
+/* */
+/* The above copyright notice and this permission notice shall be */
+/* included in all copies or substantial portions of the Software. */
+/* */
+/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
+/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
+/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */
+/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
+/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
+/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
+/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
+/**************************************************************************/
+
+#ifndef FILE_INFO_H
+#define FILE_INFO_H
+
+#include "core/variant/variant.h"
+
+enum class FileSortOption {
+ FILE_SORT_NAME = 0,
+ FILE_SORT_NAME_REVERSE = 1,
+ FILE_SORT_TYPE = 2,
+ FILE_SORT_TYPE_REVERSE = 3,
+ FILE_SORT_MODIFIED_TIME = 4,
+ FILE_SORT_MODIFIED_TIME_REVERSE = 5,
+ FILE_SORT_MAX = 6,
+};
+
+struct FileInfo {
+ String name;
+ String path;
+ String icon_path;
+ StringName type;
+ Vector<String> sources;
+ bool import_broken = false;
+ uint64_t modified_time = 0;
+
+ bool operator<(const FileInfo &p_fi) const {
+ return FileNoCaseComparator()(name, p_fi.name);
+ }
+};
+
+struct FileInfoTypeComparator {
+ bool operator()(const FileInfo &p_a, const FileInfo &p_b) const {
+ return FileNoCaseComparator()(p_a.name.get_extension() + p_a.type + p_a.name.get_basename(), p_b.name.get_extension() + p_b.type + p_b.name.get_basename());
+ }
+};
+
+struct FileInfoModifiedTimeComparator {
+ bool operator()(const FileInfo &p_a, const FileInfo &p_b) const {
+ return p_a.modified_time > p_b.modified_time;
+ }
+};
+
+void sort_file_info_list(List<FileInfo> &r_file_list, FileSortOption p_file_sort_option);
+
+#endif // FILE_INFO_H
diff --git a/editor/filesystem_dock.cpp b/editor/filesystem_dock.cpp
index dd2eec6893..c9cc42d68d 100644
--- a/editor/filesystem_dock.cpp
+++ b/editor/filesystem_dock.cpp
@@ -263,7 +263,7 @@ bool FileSystemDock::_create_tree(TreeItem *p_parent, EditorFileSystemDirectory
}
// Create items for all subdirectories.
- bool reversed = file_sort == FILE_SORT_NAME_REVERSE;
+ bool reversed = file_sort == FileSortOption::FILE_SORT_NAME_REVERSE;
for (int i = reversed ? p_dir->get_subdir_count() - 1 : 0;
reversed ? i >= 0 : i < p_dir->get_subdir_count();
reversed ? i-- : i++) {
@@ -294,28 +294,28 @@ bool FileSystemDock::_create_tree(TreeItem *p_parent, EditorFileSystemDirectory
}
}
- FileInfo fi;
- fi.name = p_dir->get_file(i);
- fi.type = p_dir->get_file_type(i);
- fi.icon_path = p_dir->get_file_icon_path(i);
- fi.import_broken = !p_dir->get_file_import_is_valid(i);
- fi.modified_time = p_dir->get_file_modified_time(i);
+ FileInfo file_info;
+ file_info.name = p_dir->get_file(i);
+ file_info.type = p_dir->get_file_type(i);
+ file_info.icon_path = p_dir->get_file_icon_path(i);
+ file_info.import_broken = !p_dir->get_file_import_is_valid(i);
+ file_info.modified_time = p_dir->get_file_modified_time(i);
- file_list.push_back(fi);
+ file_list.push_back(file_info);
}
// Sort the file list if needed.
- _sort_file_info_list(file_list);
+ sort_file_info_list(file_list, file_sort);
// Build the tree.
const int icon_size = get_theme_constant(SNAME("class_icon_size"), EditorStringName(Editor));
- for (const FileInfo &fi : file_list) {
+ for (const FileInfo &file_info : file_list) {
TreeItem *file_item = tree->create_item(subdirectory_item);
- const String file_metadata = lpath.path_join(fi.name);
- file_item->set_text(0, fi.name);
+ const String file_metadata = lpath.path_join(file_info.name);
+ file_item->set_text(0, file_info.name);
file_item->set_structured_text_bidi_override(0, TextServer::STRUCTURED_TEXT_FILE);
- file_item->set_icon(0, _get_tree_item_icon(!fi.import_broken, fi.type, fi.icon_path));
+ file_item->set_icon(0, _get_tree_item_icon(!file_info.import_broken, file_info.type, file_info.icon_path));
if (da->is_link(file_metadata)) {
file_item->set_icon_overlay(0, get_editor_theme_icon(SNAME("LinkOverlay")));
file_item->set_tooltip_text(0, vformat(TTR("Link to: %s"), da->read_link(file_metadata)));
@@ -860,19 +860,19 @@ void FileSystemDock::_search(EditorFileSystemDirectory *p_path, List<FileInfo> *
String file = p_path->get_file(i);
if (_matches_all_search_tokens(file)) {
- FileInfo fi;
- fi.name = file;
- fi.type = p_path->get_file_type(i);
- fi.path = p_path->get_file_path(i);
- fi.import_broken = !p_path->get_file_import_is_valid(i);
- fi.modified_time = p_path->get_file_modified_time(i);
-
- if (_is_file_type_disabled_by_feature_profile(fi.type)) {
+ FileInfo file_info;
+ file_info.name = file;
+ file_info.type = p_path->get_file_type(i);
+ file_info.path = p_path->get_file_path(i);
+ file_info.import_broken = !p_path->get_file_import_is_valid(i);
+ file_info.modified_time = p_path->get_file_modified_time(i);
+
+ if (_is_file_type_disabled_by_feature_profile(file_info.type)) {
// This type is disabled, will not appear here.
continue;
}
- matches->push_back(fi);
+ matches->push_back(file_info);
if (matches->size() > p_max_items) {
return;
}
@@ -880,45 +880,6 @@ void FileSystemDock::_search(EditorFileSystemDirectory *p_path, List<FileInfo> *
}
}
-struct FileSystemDock::FileInfoTypeComparator {
- bool operator()(const FileInfo &p_a, const FileInfo &p_b) const {
- return FileNoCaseComparator()(p_a.name.get_extension() + p_a.type + p_a.name.get_basename(), p_b.name.get_extension() + p_b.type + p_b.name.get_basename());
- }
-};
-
-struct FileSystemDock::FileInfoModifiedTimeComparator {
- bool operator()(const FileInfo &p_a, const FileInfo &p_b) const {
- return p_a.modified_time > p_b.modified_time;
- }
-};
-
-void FileSystemDock::_sort_file_info_list(List<FileSystemDock::FileInfo> &r_file_list) {
- // Sort the file list if needed.
- switch (file_sort) {
- case FILE_SORT_TYPE:
- r_file_list.sort_custom<FileInfoTypeComparator>();
- break;
- case FILE_SORT_TYPE_REVERSE:
- r_file_list.sort_custom<FileInfoTypeComparator>();
- r_file_list.reverse();
- break;
- case FILE_SORT_MODIFIED_TIME:
- r_file_list.sort_custom<FileInfoModifiedTimeComparator>();
- break;
- case FILE_SORT_MODIFIED_TIME_REVERSE:
- r_file_list.sort_custom<FileInfoModifiedTimeComparator>();
- r_file_list.reverse();
- break;
- case FILE_SORT_NAME_REVERSE:
- r_file_list.sort();
- r_file_list.reverse();
- break;
- default: // FILE_SORT_NAME
- r_file_list.sort();
- break;
- }
-}
-
void FileSystemDock::_update_file_list(bool p_keep_selection) {
// Register the previously current and selected items.
HashSet<String> previous_selection;
@@ -1005,22 +966,22 @@ void FileSystemDock::_update_file_list(bool p_keep_selection) {
int index;
EditorFileSystemDirectory *efd = EditorFileSystem::get_singleton()->find_file(favorite, &index);
- FileInfo fi;
- fi.name = favorite.get_file();
- fi.path = favorite;
+ FileInfo file_info;
+ file_info.name = favorite.get_file();
+ file_info.path = favorite;
if (efd) {
- fi.type = efd->get_file_type(index);
- fi.icon_path = efd->get_file_icon_path(index);
- fi.import_broken = !efd->get_file_import_is_valid(index);
- fi.modified_time = efd->get_file_modified_time(index);
+ file_info.type = efd->get_file_type(index);
+ file_info.icon_path = efd->get_file_icon_path(index);
+ file_info.import_broken = !efd->get_file_import_is_valid(index);
+ file_info.modified_time = efd->get_file_modified_time(index);
} else {
- fi.type = "";
- fi.import_broken = true;
- fi.modified_time = 0;
+ file_info.type = "";
+ file_info.import_broken = true;
+ file_info.modified_time = 0;
}
- if (searched_tokens.is_empty() || _matches_all_search_tokens(fi.name)) {
- file_list.push_back(fi);
+ if (searched_tokens.is_empty() || _matches_all_search_tokens(file_info.name)) {
+ file_list.push_back(file_info);
}
}
}
@@ -1077,7 +1038,7 @@ void FileSystemDock::_update_file_list(bool p_keep_selection) {
files->set_item_icon_modulate(-1, editor_is_dark_theme ? inherited_folder_color : inherited_folder_color * ITEM_COLOR_SCALE);
}
- bool reversed = file_sort == FILE_SORT_NAME_REVERSE;
+ bool reversed = file_sort == FileSortOption::FILE_SORT_NAME_REVERSE;
for (int i = reversed ? efd->get_subdir_count() - 1 : 0;
reversed ? i >= 0 : i < efd->get_subdir_count();
reversed ? i-- : i++) {
@@ -1099,21 +1060,21 @@ void FileSystemDock::_update_file_list(bool p_keep_selection) {
// Display the folder content.
for (int i = 0; i < efd->get_file_count(); i++) {
- FileInfo fi;
- fi.name = efd->get_file(i);
- fi.path = directory.path_join(fi.name);
- fi.type = efd->get_file_type(i);
- fi.icon_path = efd->get_file_icon_path(i);
- fi.import_broken = !efd->get_file_import_is_valid(i);
- fi.modified_time = efd->get_file_modified_time(i);
+ FileInfo file_info;
+ file_info.name = efd->get_file(i);
+ file_info.path = directory.path_join(file_info.name);
+ file_info.type = efd->get_file_type(i);
+ file_info.icon_path = efd->get_file_icon_path(i);
+ file_info.import_broken = !efd->get_file_import_is_valid(i);
+ file_info.modified_time = efd->get_file_modified_time(i);
- file_list.push_back(fi);
+ file_list.push_back(file_info);
}
}
}
// Sort the file list if needed.
- _sort_file_info_list(file_list);
+ sort_file_info_list(file_list, file_sort);
// Fills the ItemList control node from the FileInfos.
String main_scene = GLOBAL_GET("application/run/main_scene");
@@ -3935,7 +3896,7 @@ void FileSystemDock::_project_settings_changed() {
}
void FileSystemDock::set_file_sort(FileSortOption p_file_sort) {
- for (int i = 0; i != FILE_SORT_MAX; i++) {
+ for (int i = 0; i != (int)FileSortOption::FILE_SORT_MAX; i++) {
tree_button_sort->get_popup()->set_item_checked(i, (i == (int)p_file_sort));
file_list_button_sort->get_popup()->set_item_checked(i, (i == (int)p_file_sort));
}
@@ -3965,13 +3926,13 @@ MenuButton *FileSystemDock::_create_file_menu_button() {
PopupMenu *p = button->get_popup();
p->connect(SceneStringName(id_pressed), callable_mp(this, &FileSystemDock::_file_sort_popup));
- p->add_radio_check_item(TTR("Sort by Name (Ascending)"), FILE_SORT_NAME);
- p->add_radio_check_item(TTR("Sort by Name (Descending)"), FILE_SORT_NAME_REVERSE);
- p->add_radio_check_item(TTR("Sort by Type (Ascending)"), FILE_SORT_TYPE);
- p->add_radio_check_item(TTR("Sort by Type (Descending)"), FILE_SORT_TYPE_REVERSE);
- p->add_radio_check_item(TTR("Sort by Last Modified"), FILE_SORT_MODIFIED_TIME);
- p->add_radio_check_item(TTR("Sort by First Modified"), FILE_SORT_MODIFIED_TIME_REVERSE);
- p->set_item_checked(file_sort, true);
+ p->add_radio_check_item(TTR("Sort by Name (Ascending)"), (int)FileSortOption::FILE_SORT_NAME);
+ p->add_radio_check_item(TTR("Sort by Name (Descending)"), (int)FileSortOption::FILE_SORT_NAME_REVERSE);
+ p->add_radio_check_item(TTR("Sort by Type (Ascending)"), (int)FileSortOption::FILE_SORT_TYPE);
+ p->add_radio_check_item(TTR("Sort by Type (Descending)"), (int)FileSortOption::FILE_SORT_TYPE_REVERSE);
+ p->add_radio_check_item(TTR("Sort by Last Modified"), (int)FileSortOption::FILE_SORT_MODIFIED_TIME);
+ p->add_radio_check_item(TTR("Sort by First Modified"), (int)FileSortOption::FILE_SORT_MODIFIED_TIME_REVERSE);
+ p->set_item_checked((int)file_sort, true);
return button;
}
@@ -4041,7 +4002,7 @@ void FileSystemDock::save_layout_to_config(Ref<ConfigFile> p_layout, const Strin
p_layout->set_value(p_section, "dock_filesystem_h_split_offset", get_h_split_offset());
p_layout->set_value(p_section, "dock_filesystem_v_split_offset", get_v_split_offset());
p_layout->set_value(p_section, "dock_filesystem_display_mode", get_display_mode());
- p_layout->set_value(p_section, "dock_filesystem_file_sort", get_file_sort());
+ p_layout->set_value(p_section, "dock_filesystem_file_sort", (int)get_file_sort());
p_layout->set_value(p_section, "dock_filesystem_file_list_display_mode", get_file_list_display_mode());
PackedStringArray selected_files = get_selected_paths();
p_layout->set_value(p_section, "dock_filesystem_selected_paths", selected_files);
@@ -4142,22 +4103,25 @@ FileSystemDock::FileSystemDock() {
add_child(top_vbc);
HBoxContainer *toolbar_hbc = memnew(HBoxContainer);
- toolbar_hbc->add_theme_constant_override("separation", 0);
top_vbc->add_child(toolbar_hbc);
+ HBoxContainer *nav_hbc = memnew(HBoxContainer);
+ nav_hbc->add_theme_constant_override("separation", 0);
+ toolbar_hbc->add_child(nav_hbc);
+
button_hist_prev = memnew(Button);
button_hist_prev->set_flat(true);
button_hist_prev->set_disabled(true);
button_hist_prev->set_focus_mode(FOCUS_NONE);
button_hist_prev->set_tooltip_text(TTR("Go to previous selected folder/file."));
- toolbar_hbc->add_child(button_hist_prev);
+ nav_hbc->add_child(button_hist_prev);
button_hist_next = memnew(Button);
button_hist_next->set_flat(true);
button_hist_next->set_disabled(true);
button_hist_next->set_focus_mode(FOCUS_NONE);
button_hist_next->set_tooltip_text(TTR("Go to next selected folder/file."));
- toolbar_hbc->add_child(button_hist_next);
+ nav_hbc->add_child(button_hist_next);
current_path_line_edit = memnew(LineEdit);
current_path_line_edit->set_structured_text_bidi_override(TextServer::STRUCTURED_TEXT_FILE);
@@ -4180,13 +4144,12 @@ FileSystemDock::FileSystemDock() {
toolbar_hbc->add_child(button_toggle_display_mode);
button_dock_placement = memnew(Button);
- button_dock_placement->set_flat(true);
+ button_dock_placement->set_theme_type_variation("FlatMenuButton");
button_dock_placement->connect(SceneStringName(pressed), callable_mp(this, &FileSystemDock::_change_bottom_dock_placement));
button_dock_placement->hide();
toolbar_hbc->add_child(button_dock_placement);
toolbar2_hbc = memnew(HBoxContainer);
- toolbar2_hbc->add_theme_constant_override("separation", 0);
top_vbc->add_child(toolbar2_hbc);
tree_search_box = memnew(LineEdit);
@@ -4252,7 +4215,7 @@ FileSystemDock::FileSystemDock() {
path_hb->add_child(file_list_button_sort);
button_file_list_display_mode = memnew(Button);
- button_file_list_display_mode->set_flat(true);
+ button_file_list_display_mode->set_theme_type_variation("FlatMenuButton");
path_hb->add_child(button_file_list_display_mode);
files = memnew(FileSystemList);
diff --git a/editor/filesystem_dock.h b/editor/filesystem_dock.h
index 108b646029..ee68f44d4c 100644
--- a/editor/filesystem_dock.h
+++ b/editor/filesystem_dock.h
@@ -33,6 +33,7 @@
#include "editor/dependency_editor.h"
#include "editor/editor_file_system.h"
+#include "editor/file_info.h"
#include "editor/plugins/script_editor_plugin.h"
#include "editor/script_create_dialog.h"
#include "scene/gui/box_container.h"
@@ -93,16 +94,6 @@ public:
DISPLAY_MODE_HSPLIT,
};
- enum FileSortOption {
- FILE_SORT_NAME = 0,
- FILE_SORT_NAME_REVERSE,
- FILE_SORT_TYPE,
- FILE_SORT_TYPE_REVERSE,
- FILE_SORT_MODIFIED_TIME,
- FILE_SORT_MODIFIED_TIME_REVERSE,
- FILE_SORT_MAX,
- };
-
enum Overwrite {
OVERWRITE_UNDECIDED,
OVERWRITE_REPLACE,
@@ -146,7 +137,7 @@ private:
HashMap<String, Color> folder_colors;
Dictionary assigned_folder_colors;
- FileSortOption file_sort = FILE_SORT_NAME;
+ FileSortOption file_sort = FileSortOption::FILE_SORT_NAME;
VBoxContainer *scanning_vb = nullptr;
ProgressBar *scanning_progress = nullptr;
@@ -336,25 +327,6 @@ private:
void _tree_empty_click(const Vector2 &p_pos, MouseButton p_button);
void _tree_empty_selected();
- struct FileInfo {
- String name;
- String path;
- String icon_path;
- StringName type;
- Vector<String> sources;
- bool import_broken = false;
- uint64_t modified_time = 0;
-
- bool operator<(const FileInfo &fi) const {
- return FileNoCaseComparator()(name, fi.name);
- }
- };
-
- struct FileInfoTypeComparator;
- struct FileInfoModifiedTimeComparator;
-
- void _sort_file_info_list(List<FileSystemDock::FileInfo> &r_file_list);
-
void _search(EditorFileSystemDirectory *p_path, List<FileInfo> *matches, int p_max_items);
void _set_current_path_line_edit_text(const String &p_path);
diff --git a/editor/gui/SCsub b/editor/gui/SCsub
index 359d04e5df..b3cff5b9dc 100644
--- a/editor/gui/SCsub
+++ b/editor/gui/SCsub
@@ -1,4 +1,5 @@
#!/usr/bin/env python
+from misc.utility.scons_hints import *
Import("env")
diff --git a/editor/gui/editor_dir_dialog.cpp b/editor/gui/editor_dir_dialog.cpp
index b677ba1098..23568a3c2a 100644
--- a/editor/gui/editor_dir_dialog.cpp
+++ b/editor/gui/editor_dir_dialog.cpp
@@ -215,8 +215,9 @@ EditorDirDialog::EditorDirDialog() {
makedir->connect(SceneStringName(pressed), callable_mp(this, &EditorDirDialog::_make_dir));
tree = memnew(Tree);
- vb->add_child(tree);
+ tree->set_auto_translate_mode(AUTO_TRANSLATE_MODE_DISABLED);
tree->set_v_size_flags(Control::SIZE_EXPAND_FILL);
+ vb->add_child(tree);
tree->connect("item_activated", callable_mp(this, &EditorDirDialog::_item_activated));
tree->connect("item_collapsed", callable_mp(this, &EditorDirDialog::_item_collapsed), CONNECT_DEFERRED);
diff --git a/editor/gui/editor_file_dialog.cpp b/editor/gui/editor_file_dialog.cpp
index 18f1f6da0c..a63c3f7848 100644
--- a/editor/gui/editor_file_dialog.cpp
+++ b/editor/gui/editor_file_dialog.cpp
@@ -31,7 +31,6 @@
#include "editor_file_dialog.h"
#include "core/config/project_settings.h"
-#include "core/io/file_access.h"
#include "core/os/keyboard.h"
#include "core/os/os.h"
#include "editor/dependency_editor.h"
@@ -183,6 +182,9 @@ void EditorFileDialog::_update_theme_item_cache() {
theme_cache.favorites_down = get_editor_theme_icon(SNAME("MoveDown"));
theme_cache.create_folder = get_editor_theme_icon(SNAME("FolderCreate"));
+ theme_cache.filter_box = get_editor_theme_icon(SNAME("Search"));
+ theme_cache.file_sort_button = get_editor_theme_icon(SNAME("Sort"));
+
theme_cache.folder = get_editor_theme_icon(SNAME("Folder"));
theme_cache.folder_icon_color = get_theme_color(SNAME("folder_icon_color"), SNAME("FileDialog"));
@@ -326,6 +328,10 @@ void EditorFileDialog::shortcut_input(const Ref<InputEvent> &p_event) {
dir->select_all();
handled = true;
}
+ if (ED_IS_SHORTCUT("file_dialog/focus_filter", p_event)) {
+ _focus_filter_box();
+ handled = true;
+ }
if (ED_IS_SHORTCUT("file_dialog/move_favorite_up", p_event)) {
_favorite_move_up();
handled = true;
@@ -369,6 +375,8 @@ void EditorFileDialog::update_dir() {
}
dir->set_text(dir_access->get_current_dir(false));
+ filter_box->clear();
+
// Disable "Open" button only when selecting file(s) mode.
get_ok_button()->set_disabled(_is_open_should_be_disabled());
switch (mode) {
@@ -422,7 +430,13 @@ void EditorFileDialog::_post_popup() {
item_list->grab_focus();
}
- if (mode == FILE_MODE_OPEN_DIR) {
+ bool is_open_directory_mode = mode == FILE_MODE_OPEN_DIR;
+ PopupMenu *p = file_sort_button->get_popup();
+ p->set_item_disabled(2, is_open_directory_mode);
+ p->set_item_disabled(3, is_open_directory_mode);
+ p->set_item_disabled(4, is_open_directory_mode);
+ p->set_item_disabled(5, is_open_directory_mode);
+ if (is_open_directory_mode) {
file_box->set_visible(false);
} else {
file_box->set_visible(true);
@@ -956,7 +970,7 @@ void EditorFileDialog::update_file_list() {
dir_access->list_dir_begin();
- List<String> files;
+ List<FileInfo> file_infos;
List<String> dirs;
String item = dir_access->get_next();
@@ -967,27 +981,44 @@ void EditorFileDialog::update_file_list() {
continue;
}
- if (show_hidden_files) {
- if (!dir_access->current_is_dir()) {
- files.push_back(item);
- } else {
- dirs.push_back(item);
- }
- } else if (!dir_access->current_is_hidden()) {
- String full_path = cdir == "res://" ? item : dir_access->get_current_dir() + "/" + item;
- if (dir_access->current_is_dir()) {
- if (Engine::get_singleton()->is_project_manager_hint() || !EditorFileSystem::_should_skip_directory(full_path)) {
- dirs.push_back(item);
+ bool matches_search = true;
+ if (search_string.length() > 0) {
+ matches_search = item.find(search_string) != -1;
+ }
+
+ FileInfo file_info;
+ file_info.name = item;
+ file_info.path = cdir.path_join(file_info.name);
+ file_info.type = item.get_extension();
+ file_info.modified_time = FileAccess::get_modified_time(file_info.path);
+
+ if (matches_search) {
+ if (show_hidden_files) {
+ if (!dir_access->current_is_dir()) {
+ file_infos.push_back(file_info);
+ } else {
+ dirs.push_back(file_info.name);
+ }
+ } else if (!dir_access->current_is_hidden()) {
+ String full_path = cdir == "res://" ? file_info.name : dir_access->get_current_dir() + "/" + file_info.name;
+ if (dir_access->current_is_dir()) {
+ if (Engine::get_singleton()->is_project_manager_hint() || !EditorFileSystem::_should_skip_directory(full_path)) {
+ dirs.push_back(file_info.name);
+ }
+ } else {
+ file_infos.push_back(file_info);
}
- } else {
- files.push_back(item);
}
}
item = dir_access->get_next();
}
dirs.sort_custom<FileNoCaseComparator>();
- files.sort_custom<FileNoCaseComparator>();
+ bool reverse_directories = file_sort == FileSortOption::FILE_SORT_NAME_REVERSE;
+ if (reverse_directories) {
+ dirs.reverse();
+ }
+ sort_file_info_list(file_infos, file_sort);
while (!dirs.is_empty()) {
const String &dir_name = dirs.front()->get();
@@ -1037,25 +1068,26 @@ void EditorFileDialog::update_file_list() {
}
}
- while (!files.is_empty()) {
+ while (!file_infos.is_empty()) {
bool match = patterns.is_empty();
+ FileInfo file_info = file_infos.front()->get();
for (const String &E : patterns) {
- if (files.front()->get().matchn(E)) {
+ if (file_info.name.matchn(E)) {
match = true;
break;
}
}
if (match) {
- item_list->add_item(files.front()->get());
+ item_list->add_item(file_info.name);
if (get_icon_func) {
- Ref<Texture2D> icon = get_icon_func(cdir.path_join(files.front()->get()));
+ Ref<Texture2D> icon = get_icon_func(file_info.path);
if (display_mode == DISPLAY_THUMBNAILS) {
Ref<Texture2D> thumbnail;
if (get_thumbnail_func) {
- thumbnail = get_thumbnail_func(cdir.path_join(files.front()->get()));
+ thumbnail = get_thumbnail_func(file_info.path);
}
if (thumbnail.is_null()) {
thumbnail = file_thumbnail;
@@ -1069,22 +1101,21 @@ void EditorFileDialog::update_file_list() {
}
Dictionary d;
- d["name"] = files.front()->get();
+ d["name"] = file_info.name;
d["dir"] = false;
- String fullpath = cdir.path_join(files.front()->get());
- d["path"] = fullpath;
+ d["path"] = file_info.path;
item_list->set_item_metadata(-1, d);
if (display_mode == DISPLAY_THUMBNAILS && previews_enabled) {
- EditorResourcePreview::get_singleton()->queue_resource_preview(fullpath, this, "_thumbnail_result", fullpath);
+ EditorResourcePreview::get_singleton()->queue_resource_preview(file_info.path, this, "_thumbnail_result", file_info.path);
}
- if (file->get_text() == files.front()->get()) {
+ if (file->get_text() == file_info.name) {
item_list->set_current(item_list->get_item_count() - 1);
}
}
- files.pop_front();
+ file_infos.pop_front();
}
if (favorites->get_current() >= 0) {
@@ -1353,6 +1384,24 @@ void EditorFileDialog::_make_dir() {
makedirname->grab_focus();
}
+void EditorFileDialog::_focus_filter_box() {
+ filter_box->grab_focus();
+ filter_box->select_all();
+}
+
+void EditorFileDialog::_filter_changed(const String &p_text) {
+ search_string = p_text;
+ invalidate();
+}
+
+void EditorFileDialog::_file_sort_popup(int p_id) {
+ for (int i = 0; i != static_cast<int>(FileSortOption::FILE_SORT_MAX); i++) {
+ file_sort_button->get_popup()->set_item_checked(i, (i == p_id));
+ }
+ file_sort = static_cast<FileSortOption>(p_id);
+ invalidate();
+}
+
void EditorFileDialog::_delete_items() {
// Collect the selected folders and files to delete and check them in the deletion dependency dialog.
Vector<String> folders;
@@ -1444,6 +1493,10 @@ void EditorFileDialog::_update_icons() {
show_hidden->set_icon(theme_cache.toggle_hidden);
makedir->set_icon(theme_cache.create_folder);
+ filter_box->set_right_icon(theme_cache.filter_box);
+ file_sort_button->set_icon(theme_cache.file_sort_button);
+ filter_box->set_clear_button_enabled(true);
+
fav_up->set_icon(theme_cache.favorites_up);
fav_down->set_icon(theme_cache.favorites_down);
}
@@ -2093,6 +2146,7 @@ EditorFileDialog::EditorFileDialog() {
// Allow both Cmd + L and Cmd + Shift + G to match Safari's and Finder's shortcuts respectively.
ED_SHORTCUT_OVERRIDE_ARRAY("file_dialog/focus_path", "macos",
{ int32_t(KeyModifierMask::META | Key::L), int32_t(KeyModifierMask::META | KeyModifierMask::SHIFT | Key::G) });
+ ED_SHORTCUT("file_dialog/focus_filter", TTR("Focus Filter"), KeyModifierMask::CMD_OR_CTRL | Key::F);
ED_SHORTCUT("file_dialog/move_favorite_up", TTR("Move Favorite Up"), KeyModifierMask::CMD_OR_CTRL | Key::UP);
ED_SHORTCUT("file_dialog/move_favorite_down", TTR("Move Favorite Down"), KeyModifierMask::CMD_OR_CTRL | Key::DOWN);
@@ -2256,11 +2310,37 @@ EditorFileDialog::EditorFileDialog() {
VBoxContainer *list_vb = memnew(VBoxContainer);
list_vb->set_h_size_flags(Control::SIZE_EXPAND_FILL);
+ HBoxContainer *lower_hb = memnew(HBoxContainer);
+ lower_hb->set_h_size_flags(Control::SIZE_EXPAND_FILL);
+
l = memnew(Label(TTR("Directories & Files:")));
l->set_theme_type_variation("HeaderSmall");
- list_vb->add_child(l);
+ lower_hb->add_child(l);
+
+ list_vb->add_child(lower_hb);
preview_hb->add_child(list_vb);
+ filter_box = memnew(LineEdit);
+ filter_box->set_h_size_flags(Control::SIZE_EXPAND_FILL);
+ filter_box->set_placeholder(TTR("Filter"));
+ filter_box->connect(SceneStringName(text_changed), callable_mp(this, &EditorFileDialog::_filter_changed));
+ lower_hb->add_child(filter_box);
+
+ file_sort_button = memnew(MenuButton);
+ file_sort_button->set_flat(true);
+ file_sort_button->set_tooltip_text(TTR("Sort files"));
+
+ PopupMenu *p = file_sort_button->get_popup();
+ p->connect(SceneStringName(id_pressed), callable_mp(this, &EditorFileDialog::_file_sort_popup));
+ p->add_radio_check_item(TTR("Sort by Name (Ascending)"), static_cast<int>(FileSortOption::FILE_SORT_NAME));
+ p->add_radio_check_item(TTR("Sort by Name (Descending)"), static_cast<int>(FileSortOption::FILE_SORT_NAME_REVERSE));
+ p->add_radio_check_item(TTR("Sort by Type (Ascending)"), static_cast<int>(FileSortOption::FILE_SORT_TYPE));
+ p->add_radio_check_item(TTR("Sort by Type (Descending)"), static_cast<int>(FileSortOption::FILE_SORT_TYPE_REVERSE));
+ p->add_radio_check_item(TTR("Sort by Last Modified"), static_cast<int>(FileSortOption::FILE_SORT_MODIFIED_TIME));
+ p->add_radio_check_item(TTR("Sort by First Modified"), static_cast<int>(FileSortOption::FILE_SORT_MODIFIED_TIME_REVERSE));
+ p->set_item_checked(0, true);
+ lower_hb->add_child(file_sort_button);
+
// Item (files and folders) list with context menu.
item_list = memnew(ItemList);
diff --git a/editor/gui/editor_file_dialog.h b/editor/gui/editor_file_dialog.h
index 6272e27f82..1922155133 100644
--- a/editor/gui/editor_file_dialog.h
+++ b/editor/gui/editor_file_dialog.h
@@ -32,13 +32,15 @@
#define EDITOR_FILE_DIALOG_H
#include "core/io/dir_access.h"
+#include "editor/file_info.h"
#include "scene/gui/dialogs.h"
#include "scene/property_list_helper.h"
-class GridContainer;
class DependencyRemoveDialog;
+class GridContainer;
class HSplitContainer;
class ItemList;
+class MenuButton;
class OptionButton;
class PopupMenu;
class TextureRect;
@@ -127,6 +129,11 @@ private:
Button *favorite = nullptr;
Button *show_hidden = nullptr;
+ String search_string;
+ LineEdit *filter_box = nullptr;
+ FileSortOption file_sort = FileSortOption::FILE_SORT_NAME;
+ MenuButton *file_sort_button = nullptr;
+
Button *fav_up = nullptr;
Button *fav_down = nullptr;
@@ -165,6 +172,9 @@ private:
Ref<Texture2D> favorites_up;
Ref<Texture2D> favorites_down;
+ Ref<Texture2D> filter_box;
+ Ref<Texture2D> file_sort_button;
+
Ref<Texture2D> folder;
Color folder_icon_color;
@@ -227,6 +237,10 @@ private:
void _make_dir();
void _make_dir_confirm();
+ void _focus_filter_box();
+ void _filter_changed(const String &p_text);
+ void _file_sort_popup(int p_id);
+
void _delete_items();
void _delete_files_global();
diff --git a/editor/icons/SCsub b/editor/icons/SCsub
index 0d9ac43c46..a66ef56699 100644
--- a/editor/icons/SCsub
+++ b/editor/icons/SCsub
@@ -1,4 +1,5 @@
#!/usr/bin/env python
+from misc.utility.scons_hints import *
Import("env")
diff --git a/editor/import/SCsub b/editor/import/SCsub
index a8c06cc406..3d3b7780ba 100644
--- a/editor/import/SCsub
+++ b/editor/import/SCsub
@@ -1,4 +1,5 @@
#!/usr/bin/env python
+from misc.utility.scons_hints import *
Import("env")
diff --git a/editor/import/resource_importer_layered_texture.cpp b/editor/import/resource_importer_layered_texture.cpp
index 552815a6af..72d715ac2d 100644
--- a/editor/import/resource_importer_layered_texture.cpp
+++ b/editor/import/resource_importer_layered_texture.cpp
@@ -431,22 +431,20 @@ String ResourceImporterLayeredTexture::get_import_settings_string() const {
return s;
}
-bool ResourceImporterLayeredTexture::are_import_settings_valid(const String &p_path) const {
+bool ResourceImporterLayeredTexture::are_import_settings_valid(const String &p_path, const Dictionary &p_meta) const {
//will become invalid if formats are missing to import
- Dictionary meta = ResourceFormatImporter::get_singleton()->get_resource_metadata(p_path);
-
- if (!meta.has("vram_texture")) {
+ if (!p_meta.has("vram_texture")) {
return false;
}
- bool vram = meta["vram_texture"];
+ bool vram = p_meta["vram_texture"];
if (!vram) {
return true; //do not care about non vram
}
Vector<String> formats_imported;
- if (meta.has("imported_formats")) {
- formats_imported = meta["imported_formats"];
+ if (p_meta.has("imported_formats")) {
+ formats_imported = p_meta["imported_formats"];
}
int index = 0;
diff --git a/editor/import/resource_importer_layered_texture.h b/editor/import/resource_importer_layered_texture.h
index 5a21651de3..26495eed8d 100644
--- a/editor/import/resource_importer_layered_texture.h
+++ b/editor/import/resource_importer_layered_texture.h
@@ -114,7 +114,7 @@ public:
virtual Error import(const String &p_source_file, const String &p_save_path, const HashMap<StringName, Variant> &p_options, List<String> *r_platform_variants, List<String> *r_gen_files = nullptr, Variant *r_metadata = nullptr) override;
- virtual bool are_import_settings_valid(const String &p_path) const override;
+ virtual bool are_import_settings_valid(const String &p_path, const Dictionary &p_meta) const override;
virtual String get_import_settings_string() const override;
void set_mode(Mode p_mode) { mode = p_mode; }
diff --git a/editor/import/resource_importer_texture.cpp b/editor/import/resource_importer_texture.cpp
index 487b8fc175..a205123df1 100644
--- a/editor/import/resource_importer_texture.cpp
+++ b/editor/import/resource_importer_texture.cpp
@@ -740,10 +740,8 @@ String ResourceImporterTexture::get_import_settings_string() const {
return s;
}
-bool ResourceImporterTexture::are_import_settings_valid(const String &p_path) const {
- Dictionary meta = ResourceFormatImporter::get_singleton()->get_resource_metadata(p_path);
-
- if (meta.has("has_editor_variant")) {
+bool ResourceImporterTexture::are_import_settings_valid(const String &p_path, const Dictionary &p_meta) const {
+ if (p_meta.has("has_editor_variant")) {
String imported_path = ResourceFormatImporter::get_singleton()->get_internal_resource_path(p_path);
if (!FileAccess::exists(imported_path)) {
return false;
@@ -760,19 +758,19 @@ bool ResourceImporterTexture::are_import_settings_valid(const String &p_path) co
}
}
- if (!meta.has("vram_texture")) {
+ if (!p_meta.has("vram_texture")) {
return false;
}
- bool vram = meta["vram_texture"];
+ bool vram = p_meta["vram_texture"];
if (!vram) {
return true; // Do not care about non-VRAM.
}
// Will become invalid if formats are missing to import.
Vector<String> formats_imported;
- if (meta.has("imported_formats")) {
- formats_imported = meta["imported_formats"];
+ if (p_meta.has("imported_formats")) {
+ formats_imported = p_meta["imported_formats"];
}
int index = 0;
diff --git a/editor/import/resource_importer_texture.h b/editor/import/resource_importer_texture.h
index f2539a8f52..6d74c4e2f9 100644
--- a/editor/import/resource_importer_texture.h
+++ b/editor/import/resource_importer_texture.h
@@ -104,7 +104,7 @@ public:
void update_imports();
- virtual bool are_import_settings_valid(const String &p_path) const override;
+ virtual bool are_import_settings_valid(const String &p_path, const Dictionary &p_meta) const override;
virtual String get_import_settings_string() const override;
ResourceImporterTexture(bool p_singleton = false);
diff --git a/editor/plugins/SCsub b/editor/plugins/SCsub
index 4b6abf18f9..2d3066c7c9 100644
--- a/editor/plugins/SCsub
+++ b/editor/plugins/SCsub
@@ -1,4 +1,5 @@
#!/usr/bin/env python
+from misc.utility.scons_hints import *
Import("env")
diff --git a/editor/plugins/gizmos/SCsub b/editor/plugins/gizmos/SCsub
index 359d04e5df..b3cff5b9dc 100644
--- a/editor/plugins/gizmos/SCsub
+++ b/editor/plugins/gizmos/SCsub
@@ -1,4 +1,5 @@
#!/usr/bin/env python
+from misc.utility.scons_hints import *
Import("env")
diff --git a/editor/plugins/tiles/SCsub b/editor/plugins/tiles/SCsub
index 359d04e5df..b3cff5b9dc 100644
--- a/editor/plugins/tiles/SCsub
+++ b/editor/plugins/tiles/SCsub
@@ -1,4 +1,5 @@
#!/usr/bin/env python
+from misc.utility.scons_hints import *
Import("env")
diff --git a/editor/plugins/tiles/tile_set_atlas_source_editor.h b/editor/plugins/tiles/tile_set_atlas_source_editor.h
index 957f768429..c1a8338f81 100644
--- a/editor/plugins/tiles/tile_set_atlas_source_editor.h
+++ b/editor/plugins/tiles/tile_set_atlas_source_editor.h
@@ -177,7 +177,7 @@ private:
DRAG_TYPE_MAY_POPUP_MENU,
- // Warning: keep in this order.
+ // WARNING: Keep in this order.
DRAG_TYPE_RESIZE_TOP_LEFT,
DRAG_TYPE_RESIZE_TOP,
DRAG_TYPE_RESIZE_TOP_RIGHT,
diff --git a/editor/project_manager/SCsub b/editor/project_manager/SCsub
index 359d04e5df..b3cff5b9dc 100644
--- a/editor/project_manager/SCsub
+++ b/editor/project_manager/SCsub
@@ -1,4 +1,5 @@
#!/usr/bin/env python
+from misc.utility.scons_hints import *
Import("env")
diff --git a/editor/themes/SCsub b/editor/themes/SCsub
index e8f96e4299..5a9949dfa7 100644
--- a/editor/themes/SCsub
+++ b/editor/themes/SCsub
@@ -1,4 +1,5 @@
#!/usr/bin/env python
+from misc.utility.scons_hints import *
Import("env")