diff options
Diffstat (limited to 'editor')
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") |