Path: blob/master/editor/asset_library/editor_asset_installer.cpp
21100 views
/**************************************************************************/1/* editor_asset_installer.cpp */2/**************************************************************************/3/* This file is part of: */4/* GODOT ENGINE */5/* https://godotengine.org */6/**************************************************************************/7/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */8/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */9/* */10/* Permission is hereby granted, free of charge, to any person obtaining */11/* a copy of this software and associated documentation files (the */12/* "Software"), to deal in the Software without restriction, including */13/* without limitation the rights to use, copy, modify, merge, publish, */14/* distribute, sublicense, and/or sell copies of the Software, and to */15/* permit persons to whom the Software is furnished to do so, subject to */16/* the following conditions: */17/* */18/* The above copyright notice and this permission notice shall be */19/* included in all copies or substantial portions of the Software. */20/* */21/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */22/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */23/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */24/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */25/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */26/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */27/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */28/**************************************************************************/2930#include "editor_asset_installer.h"3132#include "core/io/dir_access.h"33#include "core/io/file_access.h"34#include "core/io/zip_io.h"35#include "editor/editor_node.h"36#include "editor/editor_string_names.h"37#include "editor/file_system/editor_file_system.h"38#include "editor/gui/editor_file_dialog.h"39#include "editor/gui/editor_toaster.h"40#include "editor/gui/progress_dialog.h"41#include "editor/themes/editor_scale.h"42#include "scene/gui/check_box.h"43#include "scene/gui/label.h"44#include "scene/gui/link_button.h"45#include "scene/gui/margin_container.h"46#include "scene/gui/separator.h"47#include "scene/gui/split_container.h"4849void EditorAssetInstaller::_item_checked_cbk() {50if (updating_source || !source_tree->get_edited()) {51return;52}5354updating_source = true;55TreeItem *item = source_tree->get_edited();56item->propagate_check(0);57_fix_conflicted_indeterminate_state(source_tree->get_root(), 0);58_update_confirm_button();59_rebuild_destination_tree();60updating_source = false;61}6263// Determine parent state based on non-conflict children, to avoid indeterminate state, and allow toggle dir with conflicts.64bool EditorAssetInstaller::_fix_conflicted_indeterminate_state(TreeItem *p_item, int p_column) {65if (p_item->get_child_count() == 0) {66return false;67}68bool all_non_conflict_checked = true;69bool all_non_conflict_unchecked = true;70bool has_conflict_child = false;71bool has_indeterminate_child = false;72TreeItem *child_item = p_item->get_first_child();73while (child_item) {74has_conflict_child |= _fix_conflicted_indeterminate_state(child_item, p_column);75Dictionary child_meta = child_item->get_metadata(p_column);76bool child_conflict = child_meta.get("is_conflict", false);77if (child_conflict) {78child_item->set_checked(p_column, false);79has_conflict_child = true;80} else {81bool child_checked = child_item->is_checked(p_column);82bool child_indeterminate = child_item->is_indeterminate(p_column);83all_non_conflict_checked &= (child_checked || child_indeterminate);84all_non_conflict_unchecked &= !child_checked;85has_indeterminate_child |= child_indeterminate;86}87child_item = child_item->get_next();88}89if (has_indeterminate_child) {90p_item->set_indeterminate(p_column, true);91} else if (all_non_conflict_checked) {92p_item->set_checked(p_column, true);93} else if (all_non_conflict_unchecked) {94p_item->set_checked(p_column, false);95}96if (has_conflict_child) {97p_item->set_custom_color(p_column, get_theme_color(SNAME("error_color"), EditorStringName(Editor)));98} else {99p_item->clear_custom_color(p_column);100}101return has_conflict_child;102}103104bool EditorAssetInstaller::_is_item_checked(const String &p_source_path) const {105return file_item_map.has(p_source_path) && (file_item_map[p_source_path]->is_checked(0) || file_item_map[p_source_path]->is_indeterminate(0));106}107108void EditorAssetInstaller::open_asset(const String &p_path, bool p_autoskip_toplevel) {109package_path = p_path;110asset_files.clear();111112Ref<FileAccess> io_fa;113zlib_filefunc_def io = zipio_create_io(&io_fa);114115unzFile pkg = unzOpen2(p_path.utf8().get_data(), &io);116if (!pkg) {117EditorToaster::get_singleton()->popup_str(vformat(TTR("Error opening asset file for \"%s\" (not in ZIP format)."), asset_name), EditorToaster::SEVERITY_ERROR);118return;119}120121int ret = unzGoToFirstFile(pkg);122123while (ret == UNZ_OK) {124//get filename125unz_file_info info;126char fname[16384];127unzGetCurrentFileInfo(pkg, &info, fname, 16384, nullptr, 0, nullptr, 0);128129String source_name = String::utf8(fname);130131// Create intermediate directories if they aren't reported by unzip.132// We are only interested in subfolders, so skip the root slash.133int separator = source_name.find_char('/', 1);134while (separator != -1) {135String dir_name = source_name.substr(0, separator + 1);136if (!dir_name.is_empty() && !asset_files.has(dir_name)) {137asset_files.insert(dir_name);138}139140separator = source_name.find_char('/', separator + 1);141}142143if (!source_name.is_empty() && !asset_files.has(source_name)) {144asset_files.insert(source_name);145}146147ret = unzGoToNextFile(pkg);148}149150unzClose(pkg);151152asset_title_label->set_text(asset_name);153154_check_has_toplevel();155// Default to false, unless forced.156skip_toplevel = p_autoskip_toplevel;157skip_toplevel_check->set_block_signals(true);158skip_toplevel_check->set_pressed(!skip_toplevel_check->is_disabled() && skip_toplevel);159skip_toplevel_check->set_block_signals(false);160161_update_file_mappings();162_rebuild_source_tree();163_rebuild_destination_tree();164165popup_centered_clamped(Size2(620, 640) * EDSCALE);166}167168void EditorAssetInstaller::_update_file_mappings() {169mapped_files.clear();170171bool first = true;172for (const String &E : asset_files) {173if (first) {174first = false;175176if (!toplevel_prefix.is_empty() && skip_toplevel) {177continue;178}179}180181String path = E; // We're going to mutate it.182if (!toplevel_prefix.is_empty() && skip_toplevel) {183path = path.trim_prefix(toplevel_prefix);184}185186mapped_files[E] = path;187}188}189190void EditorAssetInstaller::_rebuild_source_tree() {191updating_source = true;192source_tree->clear();193194TreeItem *root = source_tree->create_item();195root->set_cell_mode(0, TreeItem::CELL_MODE_CHECK);196root->set_checked(0, true);197root->set_icon(0, get_theme_icon(SNAME("folder"), SNAME("FileDialog")));198root->set_text(0, "/");199root->set_editable(0, true);200201file_item_map.clear();202HashMap<String, TreeItem *> directory_item_map;203int num_file_conflicts = 0;204first_file_conflict = nullptr;205206for (const String &E : asset_files) {207String path = E; // We're going to mutate it.208209bool is_directory = false;210if (path.ends_with("/")) {211path = path.trim_suffix("/");212is_directory = true;213}214215TreeItem *parent_item;216217int separator = path.rfind_char('/');218if (separator == -1) {219parent_item = root;220} else {221String parent_path = path.substr(0, separator);222HashMap<String, TreeItem *>::Iterator I = directory_item_map.find(parent_path);223ERR_CONTINUE(!I);224parent_item = I->value;225}226227TreeItem *ti;228if (is_directory) {229ti = _create_dir_item(source_tree, parent_item, path, directory_item_map);230} else {231ti = _create_file_item(source_tree, parent_item, path, &num_file_conflicts);232}233file_item_map[E] = ti;234}235236_update_conflict_status(num_file_conflicts);237_update_confirm_button();238239updating_source = false;240}241242void EditorAssetInstaller::_update_source_tree() {243int num_file_conflicts = 0;244first_file_conflict = nullptr;245246for (const KeyValue<String, TreeItem *> &E : file_item_map) {247TreeItem *ti = E.value;248Dictionary item_meta = ti->get_metadata(0);249if ((bool)item_meta.get("is_dir", false)) {250continue;251}252253String asset_path = item_meta.get("asset_path", "");254ERR_CONTINUE(asset_path.is_empty());255256bool target_exists = _update_source_item_status(ti, asset_path);257if (target_exists) {258if (first_file_conflict == nullptr) {259first_file_conflict = ti;260}261num_file_conflicts += 1;262}263264item_meta["is_conflict"] = target_exists;265ti->set_metadata(0, item_meta);266}267268_update_conflict_status(num_file_conflicts);269_update_confirm_button();270}271272bool EditorAssetInstaller::_update_source_item_status(TreeItem *p_item, const String &p_path) {273ERR_FAIL_COND_V(!mapped_files.has(p_path), false);274String target_path = target_dir_path.path_join(mapped_files[p_path]);275276bool target_exists = FileAccess::exists(target_path);277if (target_exists) {278p_item->set_custom_color(0, get_theme_color(SNAME("error_color"), EditorStringName(Editor)));279p_item->set_tooltip_text(0, vformat(TTR("%s (already exists)"), target_path));280p_item->set_checked(0, false);281} else {282p_item->clear_custom_color(0);283p_item->set_tooltip_text(0, target_path);284p_item->set_checked(0, true);285}286287p_item->propagate_check(0);288_fix_conflicted_indeterminate_state(p_item->get_tree()->get_root(), 0);289return target_exists;290}291292void EditorAssetInstaller::_rebuild_destination_tree() {293destination_tree->clear();294295TreeItem *root = destination_tree->create_item();296root->set_icon(0, get_theme_icon(SNAME("folder"), SNAME("FileDialog")));297root->set_text(0, target_dir_path + (target_dir_path == "res://" ? "" : "/"));298299HashMap<String, TreeItem *> directory_item_map;300301for (const KeyValue<String, String> &E : mapped_files) {302if (!_is_item_checked(E.key)) {303continue;304}305306String path = E.value; // We're going to mutate it.307308bool is_directory = false;309if (path.ends_with("/")) {310path = path.trim_suffix("/");311is_directory = true;312}313314TreeItem *parent_item;315316int separator = path.rfind_char('/');317if (separator == -1) {318parent_item = root;319} else {320String parent_path = path.substr(0, separator);321HashMap<String, TreeItem *>::Iterator I = directory_item_map.find(parent_path);322ERR_CONTINUE(!I);323parent_item = I->value;324}325326if (is_directory) {327_create_dir_item(destination_tree, parent_item, path, directory_item_map);328} else {329int num_file_conflicts = 0; // Don't need it, but need to pass something.330_create_file_item(destination_tree, parent_item, path, &num_file_conflicts);331}332}333}334335TreeItem *EditorAssetInstaller::_create_dir_item(Tree *p_tree, TreeItem *p_parent, const String &p_path, HashMap<String, TreeItem *> &p_item_map) {336TreeItem *ti = p_tree->create_item(p_parent);337338if (p_tree == source_tree) {339ti->set_cell_mode(0, TreeItem::CELL_MODE_CHECK);340ti->set_editable(0, true);341ti->set_checked(0, true);342ti->propagate_check(0);343_fix_conflicted_indeterminate_state(ti->get_tree()->get_root(), 0);344345Dictionary meta;346meta["asset_path"] = p_path + "/";347meta["is_dir"] = true;348meta["is_conflict"] = false;349ti->set_metadata(0, meta);350}351352ti->set_text(0, p_path.get_file() + "/");353ti->set_icon(0, get_theme_icon(SNAME("folder"), SNAME("FileDialog")));354355p_item_map[p_path] = ti;356return ti;357}358359TreeItem *EditorAssetInstaller::_create_file_item(Tree *p_tree, TreeItem *p_parent, const String &p_path, int *r_conflicts) {360TreeItem *ti = p_tree->create_item(p_parent);361362if (p_tree == source_tree) {363ti->set_cell_mode(0, TreeItem::CELL_MODE_CHECK);364ti->set_editable(0, true);365366bool target_exists = _update_source_item_status(ti, p_path);367if (target_exists) {368if (first_file_conflict == nullptr) {369first_file_conflict = ti;370}371*r_conflicts += 1;372}373374Dictionary meta;375meta["asset_path"] = p_path;376meta["is_dir"] = false;377meta["is_conflict"] = target_exists;378ti->set_metadata(0, meta);379}380381String file = p_path.get_file();382String extension = file.get_extension().to_lower();383if (extension_icon_map.has(extension)) {384ti->set_icon(0, extension_icon_map[extension]);385} else {386ti->set_icon(0, generic_extension_icon);387}388ti->set_text(0, file);389390return ti;391}392393void EditorAssetInstaller::_update_conflict_status(int p_conflicts) {394if (p_conflicts >= 1) {395asset_conflicts_link->set_text(vformat(TTRN("%d file conflicts with your project and won't be installed", "%d files conflict with your project and won't be installed", p_conflicts), p_conflicts));396asset_conflicts_link->show();397asset_conflicts_label->hide();398} else {399asset_conflicts_link->hide();400asset_conflicts_label->show();401}402}403404void EditorAssetInstaller::_update_confirm_button() {405TreeItem *root = source_tree->get_root();406get_ok_button()->set_disabled(!root || (!root->is_checked(0) && !root->is_indeterminate(0)));407}408409void EditorAssetInstaller::_toggle_source_tree(bool p_visible, bool p_scroll_to_error) {410source_tree_vb->set_visible(p_visible);411show_source_files_button->set_pressed_no_signal(p_visible); // To keep in sync if triggered by something else.412413if (p_visible) {414show_source_files_button->set_button_icon(get_editor_theme_icon(SNAME("Back")));415destination_tree_mc->set_theme_type_variation("");416destination_tree->set_theme_type_variation("TreeSecondary");417destination_tree->set_scroll_hint_mode(Tree::SCROLL_HINT_MODE_DISABLED);418} else {419show_source_files_button->set_button_icon(get_editor_theme_icon(SNAME("Forward")));420destination_tree_mc->set_theme_type_variation("NoBorderHorizontalWindow");421destination_tree->set_theme_type_variation("");422destination_tree->set_scroll_hint_mode(Tree::SCROLL_HINT_MODE_BOTH);423}424425if (p_visible && p_scroll_to_error && first_file_conflict) {426source_tree->scroll_to_item(first_file_conflict, true);427}428}429430void EditorAssetInstaller::_check_has_toplevel() {431// Check if the file structure has a distinct top-level directory. This is typical432// for archives generated by GitHub, etc, but not for manually created ZIPs.433434toplevel_prefix = "";435skip_toplevel_check->set_pressed(false);436skip_toplevel_check->set_disabled(true);437skip_toplevel_check->set_tooltip_text(TTRC("This asset doesn't have a root directory, so it can't be ignored."));438439if (asset_files.is_empty()) {440return;441}442443String first_asset;444for (const String &E : asset_files) {445if (first_asset.is_empty()) { // Checking the first file/directory.446if (!E.ends_with("/")) {447return; // No directories in this asset.448}449450// We will match everything else against this directory.451first_asset = E;452continue;453}454455if (!E.begins_with(first_asset)) {456return; // Found a file or a directory that doesn't share the same base path.457}458}459460toplevel_prefix = first_asset;461skip_toplevel_check->set_disabled(false);462skip_toplevel_check->set_tooltip_text(TTRC("Ignore the root directory when extracting files."));463}464465void EditorAssetInstaller::_set_skip_toplevel(bool p_checked) {466if (skip_toplevel == p_checked) {467return;468}469470skip_toplevel = p_checked;471_update_file_mappings();472_update_source_tree();473_rebuild_destination_tree();474}475476void EditorAssetInstaller::_open_target_dir_dialog() {477if (!target_dir_dialog) {478target_dir_dialog = memnew(EditorFileDialog);479target_dir_dialog->set_file_mode(EditorFileDialog::FILE_MODE_OPEN_DIR);480target_dir_dialog->set_title(TTRC("Select Install Folder"));481target_dir_dialog->set_current_dir(target_dir_path);482target_dir_dialog->connect("dir_selected", callable_mp(this, &EditorAssetInstaller::_target_dir_selected));483add_child(target_dir_dialog);484}485486target_dir_dialog->popup_file_dialog();487}488489void EditorAssetInstaller::_target_dir_selected(const String &p_target_path) {490if (target_dir_path == p_target_path) {491return;492}493494target_dir_path = p_target_path;495_update_file_mappings();496_update_source_tree();497_rebuild_destination_tree();498}499500void EditorAssetInstaller::ok_pressed() {501_install_asset();502}503504void EditorAssetInstaller::_install_asset() {505Ref<FileAccess> io_fa;506zlib_filefunc_def io = zipio_create_io(&io_fa);507508unzFile pkg = unzOpen2(package_path.utf8().get_data(), &io);509if (!pkg) {510EditorToaster::get_singleton()->popup_str(vformat(TTR("Error opening asset file for \"%s\" (not in ZIP format)."), asset_name), EditorToaster::SEVERITY_ERROR);511return;512}513514Vector<String> failed_files;515int ret = unzGoToFirstFile(pkg);516517ProgressDialog::get_singleton()->add_task("uncompress", TTR("Uncompressing Assets"), file_item_map.size());518519Ref<DirAccess> da = DirAccess::create(DirAccess::ACCESS_RESOURCES);520for (int idx = 0; ret == UNZ_OK; ret = unzGoToNextFile(pkg), idx++) {521unz_file_info info;522char fname[16384];523ret = unzGetCurrentFileInfo(pkg, &info, fname, 16384, nullptr, 0, nullptr, 0);524if (ret != UNZ_OK) {525break;526}527528String source_name = String::utf8(fname);529if (!_is_item_checked(source_name)) {530continue;531}532533HashMap<String, String>::Iterator E = mapped_files.find(source_name);534if (!E) {535continue; // No remapped path means we don't want it; most likely the root.536}537538String target_path = target_dir_path.path_join(E->value);539540Dictionary asset_meta = file_item_map[source_name]->get_metadata(0);541bool is_dir = asset_meta.get("is_dir", false);542if (is_dir) {543if (target_path.ends_with("/")) {544target_path = target_path.substr(0, target_path.length() - 1);545}546547da->make_dir_recursive(target_path);548} else {549Vector<uint8_t> uncomp_data;550uncomp_data.resize(info.uncompressed_size);551552unzOpenCurrentFile(pkg);553unzReadCurrentFile(pkg, uncomp_data.ptrw(), uncomp_data.size());554unzCloseCurrentFile(pkg);555556// Ensure that the target folder exists.557da->make_dir_recursive(target_path.get_base_dir());558559Ref<FileAccess> f = FileAccess::open(target_path, FileAccess::WRITE);560if (f.is_valid()) {561f->store_buffer(uncomp_data.ptr(), uncomp_data.size());562} else {563failed_files.push_back(target_path);564}565566ProgressDialog::get_singleton()->task_step("uncompress", target_path, idx);567}568}569570ProgressDialog::get_singleton()->end_task("uncompress");571unzClose(pkg);572573if (failed_files.size()) {574String msg = vformat(TTR("The following files failed extraction from asset \"%s\":"), asset_name) + "\n\n";575for (int i = 0; i < failed_files.size(); i++) {576if (i > 10) {577msg += "\n" + vformat(TTR("(and %s more files)"), itos(failed_files.size() - i));578break;579}580msg += "\n" + failed_files[i];581}582if (EditorNode::get_singleton() != nullptr) {583EditorNode::get_singleton()->show_warning(msg);584}585} else {586if (EditorNode::get_singleton() != nullptr) {587EditorNode::get_singleton()->show_warning(vformat(TTR("Asset \"%s\" installed successfully!"), asset_name), TTRC("Success!"));588}589}590591EditorFileSystem::get_singleton()->scan_changes();592}593594void EditorAssetInstaller::set_asset_name(const String &p_asset_name) {595asset_name = p_asset_name;596}597598String EditorAssetInstaller::get_asset_name() const {599return asset_name;600}601602void EditorAssetInstaller::_notification(int p_what) {603switch (p_what) {604case NOTIFICATION_THEME_CHANGED: {605if (show_source_files_button->is_pressed()) {606show_source_files_button->set_button_icon(get_editor_theme_icon(SNAME("Back")));607} else {608show_source_files_button->set_button_icon(get_editor_theme_icon(SNAME("Forward")));609}610asset_conflicts_link->add_theme_color_override(SceneStringName(font_color), get_theme_color(SNAME("error_color"), EditorStringName(Editor)));611612generic_extension_icon = get_editor_theme_icon(SNAME("Object"));613614extension_icon_map.clear();615{616extension_icon_map["bmp"] = get_editor_theme_icon(SNAME("ImageTexture"));617extension_icon_map["dds"] = get_editor_theme_icon(SNAME("ImageTexture"));618extension_icon_map["exr"] = get_editor_theme_icon(SNAME("ImageTexture"));619extension_icon_map["hdr"] = get_editor_theme_icon(SNAME("ImageTexture"));620extension_icon_map["jpg"] = get_editor_theme_icon(SNAME("ImageTexture"));621extension_icon_map["jpeg"] = get_editor_theme_icon(SNAME("ImageTexture"));622extension_icon_map["png"] = get_editor_theme_icon(SNAME("ImageTexture"));623extension_icon_map["svg"] = get_editor_theme_icon(SNAME("ImageTexture"));624extension_icon_map["tga"] = get_editor_theme_icon(SNAME("ImageTexture"));625extension_icon_map["webp"] = get_editor_theme_icon(SNAME("ImageTexture"));626627extension_icon_map["wav"] = get_editor_theme_icon(SNAME("AudioStreamWAV"));628extension_icon_map["ogg"] = get_editor_theme_icon(SNAME("AudioStreamOggVorbis"));629extension_icon_map["mp3"] = get_editor_theme_icon(SNAME("AudioStreamMP3"));630631extension_icon_map["scn"] = get_editor_theme_icon(SNAME("PackedScene"));632extension_icon_map["tscn"] = get_editor_theme_icon(SNAME("PackedScene"));633extension_icon_map["escn"] = get_editor_theme_icon(SNAME("PackedScene"));634extension_icon_map["dae"] = get_editor_theme_icon(SNAME("PackedScene"));635extension_icon_map["gltf"] = get_editor_theme_icon(SNAME("PackedScene"));636extension_icon_map["glb"] = get_editor_theme_icon(SNAME("PackedScene"));637638extension_icon_map["gdshader"] = get_editor_theme_icon(SNAME("Shader"));639extension_icon_map["gdshaderinc"] = get_editor_theme_icon(SNAME("TextFile"));640extension_icon_map["gd"] = get_editor_theme_icon(SNAME("GDScript"));641if (ClassDB::class_exists("CSharpScript")) {642extension_icon_map["cs"] = get_editor_theme_icon(SNAME("CSharpScript"));643} else {644// Mark C# support as unavailable.645extension_icon_map["cs"] = get_editor_theme_icon(SNAME("ImportFail"));646}647648extension_icon_map["res"] = get_editor_theme_icon(SNAME("Resource"));649extension_icon_map["tres"] = get_editor_theme_icon(SNAME("Resource"));650extension_icon_map["atlastex"] = get_editor_theme_icon(SNAME("AtlasTexture"));651// By default, OBJ files are imported as Mesh resources rather than PackedScenes.652extension_icon_map["obj"] = get_editor_theme_icon(SNAME("MeshItem"));653654extension_icon_map["txt"] = get_editor_theme_icon(SNAME("TextFile"));655extension_icon_map["md"] = get_editor_theme_icon(SNAME("TextFile"));656extension_icon_map["rst"] = get_editor_theme_icon(SNAME("TextFile"));657extension_icon_map["json"] = get_editor_theme_icon(SNAME("TextFile"));658extension_icon_map["yml"] = get_editor_theme_icon(SNAME("TextFile"));659extension_icon_map["yaml"] = get_editor_theme_icon(SNAME("TextFile"));660extension_icon_map["toml"] = get_editor_theme_icon(SNAME("TextFile"));661extension_icon_map["cfg"] = get_editor_theme_icon(SNAME("TextFile"));662extension_icon_map["ini"] = get_editor_theme_icon(SNAME("TextFile"));663}664} break;665}666}667668EditorAssetInstaller::EditorAssetInstaller() {669VBoxContainer *vb = memnew(VBoxContainer);670add_child(vb);671672// Status bar.673674HBoxContainer *asset_status = memnew(HBoxContainer);675vb->add_child(asset_status);676677Label *asset_label = memnew(Label);678asset_label->set_text(TTRC("Asset:"));679asset_label->set_theme_type_variation("HeaderSmall");680asset_status->add_child(asset_label);681682asset_title_label = memnew(Label);683asset_title_label->set_focus_mode(Control::FOCUS_ACCESSIBILITY);684asset_status->add_child(asset_title_label);685686// File remapping controls.687688HBoxContainer *remapping_tools = memnew(HBoxContainer);689vb->add_child(remapping_tools);690691show_source_files_button = memnew(Button);692show_source_files_button->set_toggle_mode(true);693show_source_files_button->set_tooltip_text(TTRC("Open the list of the asset contents and select which files to install."));694remapping_tools->add_child(show_source_files_button);695show_source_files_button->connect(SceneStringName(toggled), callable_mp(this, &EditorAssetInstaller::_toggle_source_tree).bind(false));696697Button *target_dir_button = memnew(Button);698target_dir_button->set_text(TTRC("Change Install Folder"));699target_dir_button->set_tooltip_text(TTRC("Change the folder where the contents of the asset are going to be installed."));700remapping_tools->add_child(target_dir_button);701target_dir_button->connect(SceneStringName(pressed), callable_mp(this, &EditorAssetInstaller::_open_target_dir_dialog));702703remapping_tools->add_child(memnew(VSeparator));704705skip_toplevel_check = memnew(CheckBox);706skip_toplevel_check->set_text(TTRC("Ignore asset root"));707skip_toplevel_check->set_tooltip_text(TTRC("Ignore the root directory when extracting files."));708skip_toplevel_check->connect(SceneStringName(toggled), callable_mp(this, &EditorAssetInstaller::_set_skip_toplevel));709remapping_tools->add_child(skip_toplevel_check);710711remapping_tools->add_spacer();712713asset_conflicts_label = memnew(Label);714asset_conflicts_label->set_focus_mode(Control::FOCUS_ACCESSIBILITY);715asset_conflicts_label->set_theme_type_variation("HeaderSmall");716asset_conflicts_label->set_text(TTRC("No files conflict with your project"));717remapping_tools->add_child(asset_conflicts_label);718asset_conflicts_link = memnew(LinkButton);719asset_conflicts_link->set_theme_type_variation("HeaderSmallLink");720asset_conflicts_link->set_v_size_flags(Control::SIZE_SHRINK_CENTER);721asset_conflicts_link->set_tooltip_text(TTRC("Show contents of the asset and conflicting files."));722asset_conflicts_link->set_visible(false);723remapping_tools->add_child(asset_conflicts_link);724asset_conflicts_link->connect(SceneStringName(pressed), callable_mp(this, &EditorAssetInstaller::_toggle_source_tree).bind(true, true));725726// File hierarchy trees.727728HSplitContainer *tree_split = memnew(HSplitContainer);729tree_split->set_v_size_flags(Control::SIZE_EXPAND_FILL);730vb->add_child(tree_split);731732source_tree_vb = memnew(VBoxContainer);733source_tree_vb->set_h_size_flags(Control::SIZE_EXPAND_FILL);734source_tree_vb->set_visible(show_source_files_button->is_pressed());735tree_split->add_child(source_tree_vb);736737Label *source_tree_label = memnew(Label);738source_tree_label->set_text(TTRC("Contents of the asset:"));739source_tree_label->set_theme_type_variation("HeaderSmall");740source_tree_vb->add_child(source_tree_label);741742source_tree = memnew(Tree);743source_tree->set_accessibility_name(TTRC("Source Files"));744source_tree->set_auto_translate_mode(AUTO_TRANSLATE_MODE_DISABLED);745source_tree->set_v_size_flags(Control::SIZE_EXPAND_FILL);746source_tree->connect("item_edited", callable_mp(this, &EditorAssetInstaller::_item_checked_cbk));747source_tree->set_theme_type_variation("TreeSecondary");748source_tree_vb->add_child(source_tree);749750VBoxContainer *destination_tree_vb = memnew(VBoxContainer);751destination_tree_vb->set_h_size_flags(Control::SIZE_EXPAND_FILL);752tree_split->add_child(destination_tree_vb);753754Label *destination_tree_label = memnew(Label);755destination_tree_label->set_text(TTRC("Installation preview:"));756destination_tree_label->set_theme_type_variation("HeaderSmall");757destination_tree_vb->add_child(destination_tree_label);758759destination_tree_mc = memnew(MarginContainer);760destination_tree_mc->set_v_size_flags(Control::SIZE_EXPAND_FILL);761destination_tree_mc->set_theme_type_variation("NoBorderHorizontalWindow");762destination_tree_vb->add_child(destination_tree_mc);763764destination_tree = memnew(Tree);765destination_tree->set_accessibility_name(TTRC("Destination Files"));766destination_tree->set_auto_translate_mode(AUTO_TRANSLATE_MODE_DISABLED);767destination_tree->set_scroll_hint_mode(Tree::SCROLL_HINT_MODE_BOTH);768destination_tree->connect("item_edited", callable_mp(this, &EditorAssetInstaller::_item_checked_cbk));769destination_tree_mc->add_child(destination_tree);770771// Dialog configuration.772773set_title(TTRC("Configure Asset Before Installing"));774set_ok_button_text(TTRC("Install"));775set_hide_on_ok(true);776}777778779