diff --git a/doc/classes/RichTextLabel.xml b/doc/classes/RichTextLabel.xml index 0cbb317055f..feff2cd8da7 100644 --- a/doc/classes/RichTextLabel.xml +++ b/doc/classes/RichTextLabel.xml @@ -22,6 +22,7 @@ + Adds an image's opening and closing tags to the tag stack, optionally providing a [code]width[/code] and [code]height[/code] to resize the image. If [code]width[/code] or [code]height[/code] is set to 0, the image size will be adjusted in order to keep the original aspect ratio. @@ -335,6 +336,18 @@ Makes text fill width. + + Aligns top of the inline image to the top of the text. + + + Aligns center of the inline image to the center of the text. + + + Aligns bottom of the inline image to the baseline of the text. + + + Aligns bottom of the inline image to the bottom of the text. + Each list item has a number marker. diff --git a/editor/editor_export.cpp b/editor/editor_export.cpp index 241ffbd3aa4..61c99982fd3 100644 --- a/editor/editor_export.cpp +++ b/editor/editor_export.cpp @@ -44,6 +44,7 @@ #include "editor/editor_file_system.h" #include "editor/plugins/script_editor_plugin.h" #include "editor_node.h" +#include "editor_scale.h" #include "editor_settings.h" #include "scene/resources/resource_format_text.h" @@ -220,6 +221,82 @@ EditorExportPreset::EditorExportPreset() : /////////////////////////////////// +bool EditorExportPlatform::fill_log_messages(RichTextLabel *p_log, Error p_err) { + bool has_messages = false; + + int msg_count = get_message_count(); + + p_log->add_text(TTR("Project export for platform:") + " "); + p_log->add_image(get_logo(), 16 * EDSCALE, 16 * EDSCALE, RichTextLabel::INLINE_ALIGN_CENTER); + p_log->add_text(" "); + p_log->add_text(get_name()); + p_log->add_text(" - "); + if (p_err == OK) { + if (get_worst_message_type() >= EditorExportPlatform::EXPORT_MESSAGE_WARNING) { + p_log->add_image(EditorNode::get_singleton()->get_gui_base()->get_icon("StatusWarning", "EditorIcons"), 16 * EDSCALE, 16 * EDSCALE, RichTextLabel::INLINE_ALIGN_CENTER); + p_log->add_text(" "); + p_log->add_text(TTR("Completed with errors.")); + has_messages = true; + } else { + p_log->add_image(EditorNode::get_singleton()->get_gui_base()->get_icon("StatusSuccess", "EditorIcons"), 16 * EDSCALE, 16 * EDSCALE, RichTextLabel::INLINE_ALIGN_CENTER); + p_log->add_text(" "); + p_log->add_text(TTR("Completed sucessfully.")); + if (msg_count > 0) { + has_messages = true; + } + } + } else { + p_log->add_image(EditorNode::get_singleton()->get_gui_base()->get_icon("StatusError", "EditorIcons"), 16 * EDSCALE, 16 * EDSCALE, RichTextLabel::INLINE_ALIGN_CENTER); + p_log->add_text(" "); + p_log->add_text(TTR("Failed.")); + has_messages = true; + } + + if (msg_count) { + p_log->push_table(2); + p_log->set_table_column_expand(0, false); + p_log->set_table_column_expand(1, true); + for (int m = 0; m < msg_count; m++) { + EditorExportPlatform::ExportMessage msg = get_message(m); + Color color = EditorNode::get_singleton()->get_gui_base()->get_color("font_color", "Label"); + Ref icon; + + switch (msg.msg_type) { + case EditorExportPlatform::EXPORT_MESSAGE_INFO: { + color = EditorNode::get_singleton()->get_gui_base()->get_color("font_color", "Editor") * Color(1, 1, 1, 0.6); + } break; + case EditorExportPlatform::EXPORT_MESSAGE_WARNING: { + icon = EditorNode::get_singleton()->get_gui_base()->get_icon("Warning", "EditorIcons"); + color = EditorNode::get_singleton()->get_gui_base()->get_color("warning_color", "Editor"); + } break; + case EditorExportPlatform::EXPORT_MESSAGE_ERROR: { + icon = EditorNode::get_singleton()->get_gui_base()->get_icon("Error", "EditorIcons"); + color = EditorNode::get_singleton()->get_gui_base()->get_color("error_color", "Editor"); + } break; + default: + break; + } + + p_log->push_cell(); + p_log->add_text("\t"); + if (icon.is_valid()) { + p_log->add_image(icon); + } + p_log->pop(); + + p_log->push_cell(); + p_log->push_color(color); + p_log->add_text(vformat("[%s]: %s", msg.category, msg.text)); + p_log->pop(); + p_log->pop(); + } + p_log->pop(); + p_log->add_newline(); + } + p_log->add_newline(); + return has_messages; +} + void EditorExportPlatform::gen_debug_flags(Vector &r_flags, int p_flags) { String host = EditorSettings::get_singleton()->get("network/debug/remote_host"); int remote_port = (int)EditorSettings::get_singleton()->get("network/debug/remote_port"); @@ -961,7 +1038,10 @@ Error EditorExportPlatform::save_pack(const Ref &p_preset, c String tmppath = EditorSettings::get_singleton()->get_cache_dir().plus_file("packtmp"); FileAccess *ftmp = FileAccess::open(tmppath, FileAccess::WRITE); - ERR_FAIL_COND_V_MSG(!ftmp, ERR_CANT_CREATE, "Cannot create file '" + tmppath + "'."); + if (!ftmp) { + add_message(EXPORT_MESSAGE_ERROR, TTR("Save PCK"), vformat(TTR("Cannot create file \"%s\"."), tmppath)); + return ERR_CANT_CREATE; + } PackData pd; pd.ep = &ep; @@ -974,7 +1054,7 @@ Error EditorExportPlatform::save_pack(const Ref &p_preset, c if (err != OK) { DirAccess::remove_file_or_error(tmppath); - ERR_PRINT("Failed to export project files"); + add_message(EXPORT_MESSAGE_ERROR, TTR("Save PCK"), TTR("Failed to export project files.")); return err; } @@ -1066,7 +1146,8 @@ Error EditorExportPlatform::save_pack(const Ref &p_preset, c if (!ftmp) { memdelete(f); DirAccess::remove_file_or_error(tmppath); - ERR_FAIL_V_MSG(ERR_CANT_CREATE, "Can't open file to read from path '" + String(tmppath) + "'."); + add_message(EXPORT_MESSAGE_ERROR, TTR("Save PCK"), vformat(TTR("Can't open file to read from path \"%s\"."), tmppath)); + return ERR_CANT_CREATE; } const int bufsize = 16384; @@ -1117,8 +1198,9 @@ Error EditorExportPlatform::save_zip(const Ref &p_preset, co zd.zip = zip; Error err = export_project_files(p_preset, _save_zip_file, &zd); - if (err != OK && err != ERR_SKIP) - ERR_PRINT("Failed to export project files"); + if (err != OK && err != ERR_SKIP) { + add_message(EXPORT_MESSAGE_ERROR, TTR("Save ZIP"), TTR("Failed to export project files.")); + } zipClose(zip, nullptr); @@ -1184,6 +1266,7 @@ void EditorExportPlatform::gen_export_flags(Vector &r_flags, int p_flags r_flags.push_back("--debug-navigation"); } } + EditorExportPlatform::EditorExportPlatform() { } @@ -1638,6 +1721,7 @@ Error EditorExportPlatformPC::export_project(const Ref &p_pr Error EditorExportPlatformPC::prepare_template(const Ref &p_preset, bool p_debug, const String &p_path, int p_flags) { if (!DirAccess::exists(p_path.get_base_dir())) { + add_message(EXPORT_MESSAGE_ERROR, TTR("Prepare Template"), TTR("The given export path doesn't exist.")); return ERR_FILE_BAD_PATH; } @@ -1665,13 +1749,16 @@ Error EditorExportPlatformPC::prepare_template(const Ref &p_ } if (template_path != String() && !FileAccess::exists(template_path)) { - EditorNode::get_singleton()->show_warning(TTR("Template file not found:") + "\n" + template_path); + add_message(EXPORT_MESSAGE_ERROR, TTR("Prepare Template"), vformat(TTR("Template file not found: \"%s\"."), template_path)); return ERR_FILE_NOT_FOUND; } DirAccessRef da = DirAccess::create(DirAccess::ACCESS_FILESYSTEM); da->make_dir_recursive(p_path.get_base_dir()); Error err = da->copy(template_path, p_path, get_chmod_flags()); + if (err != OK) { + add_message(EXPORT_MESSAGE_ERROR, TTR("Prepare Template"), TTR("Failed to copy export template.")); + } return err; } @@ -1691,14 +1778,11 @@ Error EditorExportPlatformPC::export_project_data(const Ref Error err = save_pack(p_preset, pck_path, &so_files, 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 && !p_preset->get("binary_format/64_bits")) { - EditorNode::get_singleton()->show_warning(TTR("On 32-bit exports the embedded PCK cannot be bigger than 4 GiB.")); + add_message(EXPORT_MESSAGE_ERROR, TTR("PCK Embedding"), TTR("On 32-bit exports the embedded PCK cannot be bigger than 4 GiB.")); return ERR_INVALID_PARAMETER; } - FixUpEmbeddedPckFunc fixup_func = get_fixup_embedded_pck_func(); - if (fixup_func) { - err = fixup_func(p_path, embedded_pos, embedded_size); - } + err = fixup_embedded_pck(p_path, embedded_pos, embedded_size); } if (err == OK && !so_files.empty()) { @@ -1778,17 +1862,8 @@ void EditorExportPlatformPC::set_chmod_flags(int p_flags) { chmod_flags = p_flags; } -EditorExportPlatformPC::FixUpEmbeddedPckFunc EditorExportPlatformPC::get_fixup_embedded_pck_func() const { - return fixup_embedded_pck_func; -} - -void EditorExportPlatformPC::set_fixup_embedded_pck_func(FixUpEmbeddedPckFunc p_fixup_embedded_pck_func) { - fixup_embedded_pck_func = p_fixup_embedded_pck_func; -} - EditorExportPlatformPC::EditorExportPlatformPC() { chmod_flags = -1; - fixup_embedded_pck_func = nullptr; } /////////////////////// diff --git a/editor/editor_export.h b/editor/editor_export.h index 2affd40ad9e..3c6c15ad297 100644 --- a/editor/editor_export.h +++ b/editor/editor_export.h @@ -33,6 +33,7 @@ #include "core/os/dir_access.h" #include "core/resource.h" +#include "scene/gui/rich_text_label.h" #include "scene/main/node.h" #include "scene/main/timer.h" #include "scene/resources/texture.h" @@ -151,6 +152,19 @@ public: typedef Error (*EditorExportSaveFunction)(void *p_userdata, const String &p_path, const Vector &p_data, int p_file, int p_total); typedef Error (*EditorExportSaveSharedObject)(void *p_userdata, const SharedObject &p_so); + enum ExportMessageType { + EXPORT_MESSAGE_NONE, + EXPORT_MESSAGE_INFO, + EXPORT_MESSAGE_WARNING, + EXPORT_MESSAGE_ERROR, + }; + + struct ExportMessage { + ExportMessageType msg_type; + String category; + String text; + }; + private: struct SavedData { uint64_t ofs; @@ -180,6 +194,8 @@ private: PoolVector features_pv; }; + Vector messages; + void _export_find_resources(EditorFileSystemDirectory *p_dir, Set &p_paths); void _export_find_dependencies(const String &p_path, Set &p_paths); @@ -220,6 +236,47 @@ public: virtual Ref create_preset(); + virtual void clear_messages() { messages.clear(); } + virtual void add_message(ExportMessageType p_type, const String &p_category, const String &p_message) { + ExportMessage msg; + msg.category = p_category; + msg.text = p_message; + msg.msg_type = p_type; + messages.push_back(msg); + switch (p_type) { + case EXPORT_MESSAGE_INFO: { + print_line(vformat("%s: %s\n", msg.category, msg.text)); + } break; + case EXPORT_MESSAGE_WARNING: { + WARN_PRINT(vformat("%s: %s\n", msg.category, msg.text)); + } break; + case EXPORT_MESSAGE_ERROR: { + ERR_PRINT(vformat("%s: %s\n", msg.category, msg.text)); + } break; + default: + break; + } + } + + virtual int get_message_count() const { + return messages.size(); + } + + virtual ExportMessage get_message(int p_index) const { + ERR_FAIL_INDEX_V(p_index, messages.size(), ExportMessage()); + return messages[p_index]; + } + + virtual ExportMessageType get_worst_message_type() const { + ExportMessageType worst_type = EXPORT_MESSAGE_NONE; + for (int i = 0; i < messages.size(); i++) { + worst_type = MAX(worst_type, messages[i].msg_type); + } + return worst_type; + } + + virtual bool fill_log_messages(RichTextLabel *p_log, Error p_err); + virtual void get_export_options(List *r_options) = 0; virtual bool should_update_export_options() { return false; } virtual bool get_option_visibility(const String &p_option, const Map &p_options) const { return true; } @@ -400,9 +457,6 @@ public: class EditorExportPlatformPC : public EditorExportPlatform { GDCLASS(EditorExportPlatformPC, EditorExportPlatform); -public: - typedef Error (*FixUpEmbeddedPckFunc)(const String &p_path, int64_t p_embedded_start, int64_t p_embedded_size); - private: Ref logo; String name; @@ -418,8 +472,6 @@ private: int chmod_flags; - FixUpEmbeddedPckFunc fixup_embedded_pck_func; - public: virtual void get_preset_features(const Ref &p_preset, List *r_features); @@ -437,6 +489,7 @@ public: virtual Error prepare_template(const Ref &p_preset, bool p_debug, const String &p_path, int p_flags); virtual Error modify_template(const Ref &p_preset, bool p_debug, const String &p_path, int p_flags) { return OK; } virtual Error export_project_data(const Ref &p_preset, bool p_debug, const String &p_path, int p_flags); + virtual Error fixup_embedded_pck(const String &p_path, int64_t p_embedded_start, int64_t p_embedded_size) { return OK; } void set_extension(const String &p_extension, const String &p_feature_key = "default"); void set_name(const String &p_name); @@ -456,9 +509,6 @@ public: int get_chmod_flags() const; void set_chmod_flags(int p_flags); - FixUpEmbeddedPckFunc get_fixup_embedded_pck_func() const; - void set_fixup_embedded_pck_func(FixUpEmbeddedPckFunc p_fixup_embedded_pck_func); - EditorExportPlatformPC(); }; diff --git a/editor/editor_node.cpp b/editor/editor_node.cpp index eedfca1cf8a..bb28d5d740d 100644 --- a/editor/editor_node.cpp +++ b/editor/editor_node.cpp @@ -798,21 +798,14 @@ void EditorNode::_fs_changed() { ERR_PRINT(vformat("Cannot export project with preset \"%s\" due to configuration errors:\n%s", preset_name, config_error)); err = missing_templates ? ERR_FILE_NOT_FOUND : ERR_UNCONFIGURED; } else { + platform->clear_messages(); err = platform->export_project(preset, export_defer.debug, export_path); } } - switch (err) { - case OK: - break; - case ERR_FILE_NOT_FOUND: - export_error = vformat("Project export failed for preset \"%s\". The export template appears to be missing.", preset_name); - break; - case ERR_FILE_BAD_PATH: - export_error = vformat("Project export failed for preset \"%s\". The target path \"%s\" appears to be invalid.", preset_name, export_path); - break; - default: - export_error = vformat("Project export failed with error code %d for preset \"%s\".", (int)err, preset_name); - break; + if (err != OK) { + export_error = vformat("Project export for preset \"%s\" failed.", preset_name); + } else if (platform->get_worst_message_type() >= EditorExportPlatform::EXPORT_MESSAGE_WARNING) { + export_error = vformat("Project export for preset \"%s\" completed with errors.", preset_name); } } } diff --git a/editor/editor_run_native.cpp b/editor/editor_run_native.cpp index 2c29c61b979..a783bba6d5a 100644 --- a/editor/editor_run_native.cpp +++ b/editor/editor_run_native.cpp @@ -146,7 +146,12 @@ void EditorRunNative::_run_native(int p_idx, int p_platform) { flags |= EditorExportPlatform::DEBUG_FLAG_SHADER_FALLBACKS; } - eep->run(preset, p_idx, flags); + eep->clear_messages(); + Error err = eep->run(preset, p_idx, flags); + result_dialog_log->clear(); + if (eep->fill_log_messages(result_dialog_log, err)) { + result_dialog->popup_centered_ratio(0.5); + } } void EditorRunNative::resume_run_native() { @@ -200,6 +205,15 @@ bool EditorRunNative::get_debug_shader_fallbacks() const { } EditorRunNative::EditorRunNative() { + result_dialog = memnew(AcceptDialog); + result_dialog->set_title(TTR("Project Run")); + result_dialog_log = memnew(RichTextLabel); + result_dialog_log->set_custom_minimum_size(Size2(300, 80) * EDSCALE); + result_dialog->add_child(result_dialog_log); + + add_child(result_dialog); + result_dialog->hide(); + set_process(true); first = true; deploy_dumb = false; diff --git a/editor/editor_run_native.h b/editor/editor_run_native.h index d04c6d2e343..1dbb002cea6 100644 --- a/editor/editor_run_native.h +++ b/editor/editor_run_native.h @@ -32,11 +32,16 @@ #define EDITOR_RUN_NATIVE_H #include "scene/gui/box_container.h" +#include "scene/gui/dialogs.h" #include "scene/gui/menu_button.h" +#include "scene/gui/rich_text_label.h" class EditorRunNative : public HBoxContainer { GDCLASS(EditorRunNative, HBoxContainer); + RichTextLabel *result_dialog_log; + AcceptDialog *result_dialog; + Map menus; bool first; bool deploy_dumb; diff --git a/editor/project_export.cpp b/editor/project_export.cpp index 3ba0cf3d404..c2d541c87a6 100644 --- a/editor/project_export.cpp +++ b/editor/project_export.cpp @@ -883,17 +883,14 @@ void ProjectExportDialog::_export_project_to_path(const String &p_path) { ERR_FAIL_COND(platform.is_null()); current->set_export_path(p_path); + platform->clear_messages(); Error err = platform->export_project(current, export_debug->is_pressed(), p_path, 0); - if (err != OK && err != ERR_SKIP) { - if (err == ERR_FILE_NOT_FOUND) { - error_dialog->set_text(vformat(TTR("Failed to export the project for platform '%s'.\nExport templates seem to be missing or invalid."), platform->get_name())); - } else { // Assume misconfiguration. FIXME: Improve error handling and preset config validation. - error_dialog->set_text(vformat(TTR("Failed to export the project for platform '%s'.\nThis might be due to a configuration issue in the export preset or your export settings."), platform->get_name())); - } + result_dialog_log->clear(); - ERR_PRINT(vformat("Failed to export the project for platform '%s'.", platform->get_name())); - error_dialog->show(); - error_dialog->popup_centered_minsize(Size2(300, 80)); + if (err != ERR_SKIP) { + if (platform->fill_log_messages(result_dialog_log, err)) { + result_dialog->popup_centered_ratio(0.5); + } } } @@ -912,6 +909,8 @@ void ProjectExportDialog::_export_all(bool p_debug) { String mode = p_debug ? TTR("Debug") : TTR("Release"); EditorProgress ep("exportall", TTR("Exporting All") + " " + mode, EditorExport::get_singleton()->get_export_preset_count(), true); + bool show_dialog = false; + result_dialog_log->clear(); for (int i = 0; i < EditorExport::get_singleton()->get_export_preset_count(); i++) { Ref preset = EditorExport::get_singleton()->get_export_preset(i); ERR_FAIL_COND(preset.is_null()); @@ -920,17 +919,16 @@ void ProjectExportDialog::_export_all(bool p_debug) { ep.step(preset->get_name(), i); + platform->clear_messages(); Error err = platform->export_project(preset, p_debug, preset->get_export_path(), 0); - if (err != OK && err != ERR_SKIP) { - if (err == ERR_FILE_BAD_PATH) { - error_dialog->set_text(TTR("The given export path doesn't exist:") + "\n" + preset->get_export_path().get_base_dir()); - } else { - error_dialog->set_text(TTR("Export templates for this platform are missing/corrupted:") + " " + platform->get_name()); - } - error_dialog->show(); - error_dialog->popup_centered_minsize(Size2(300, 80)); - ERR_PRINT("Failed to export project"); + if (err == ERR_SKIP) { + return; } + bool has_messages = platform->fill_log_messages(result_dialog_log, err); + show_dialog = show_dialog || has_messages; + } + if (show_dialog) { + result_dialog->popup_centered_ratio(0.5); } } @@ -1200,11 +1198,14 @@ ProjectExportDialog::ProjectExportDialog() { export_error2->add_color_override("font_color", EditorNode::get_singleton()->get_gui_base()->get_color("error_color", "Editor")); export_error2->set_text(" - " + TTR("Export templates for this platform are missing:") + " "); - error_dialog = memnew(AcceptDialog); - error_dialog->set_title("Error"); - error_dialog->set_text(TTR("Export templates for this platform are missing/corrupted:") + " "); - main_vb->add_child(error_dialog); - error_dialog->hide(); + result_dialog = memnew(AcceptDialog); + result_dialog->set_title(TTR("Project Export")); + result_dialog_log = memnew(RichTextLabel); + result_dialog_log->set_custom_minimum_size(Size2(300, 80) * EDSCALE); + result_dialog->add_child(result_dialog_log); + + main_vb->add_child(result_dialog); + result_dialog->hide(); LinkButton *download_templates = memnew(LinkButton); download_templates->set_text(TTR("Manage Export Templates")); diff --git a/editor/project_export.h b/editor/project_export.h index c3d1edfedb8..49df5dfdea0 100644 --- a/editor/project_export.h +++ b/editor/project_export.h @@ -74,7 +74,8 @@ private: Button *button_export; bool updating; - AcceptDialog *error_dialog; + RichTextLabel *result_dialog_log; + AcceptDialog *result_dialog; ConfirmationDialog *delete_confirm; OptionButton *export_filter; diff --git a/platform/android/export/export_plugin.cpp b/platform/android/export/export_plugin.cpp index 7f3246961c9..d837129412e 100644 --- a/platform/android/export/export_plugin.cpp +++ b/platform/android/export/export_plugin.cpp @@ -1828,7 +1828,7 @@ Error EditorExportPlatformAndroid::run(const Ref &p_preset, String can_export_error; bool can_export_missing_templates; if (!can_export(p_preset, can_export_error, can_export_missing_templates)) { - EditorNode::add_io_error(can_export_error); + add_message(EXPORT_MESSAGE_ERROR, TTR("Run"), can_export_error); return ERR_UNCONFIGURED; } @@ -1909,7 +1909,7 @@ Error EditorExportPlatformAndroid::run(const Ref &p_preset, err = OS::get_singleton()->execute(adb, args, true, nullptr, &output, &rv, true); print_verbose(output); if (err || rv != 0) { - EditorNode::add_io_error(vformat(TTR("Could not install to device: %s"), output)); + add_message(EXPORT_MESSAGE_ERROR, TTR("Run"), vformat(TTR("Could not install to device: %s"), output)); CLEANUP_AND_RETURN(ERR_CANT_CREATE); } @@ -1987,7 +1987,7 @@ Error EditorExportPlatformAndroid::run(const Ref &p_preset, err = OS::get_singleton()->execute(adb, args, true, nullptr, &output, &rv, true); print_verbose(output); if (err || rv != 0) { - EditorNode::add_io_error(TTR("Could not execute on device.")); + add_message(EXPORT_MESSAGE_ERROR, TTR("Run"), TTR("Could not execute on device.")); CLEANUP_AND_RETURN(ERR_CANT_CREATE); } @@ -2042,7 +2042,7 @@ String EditorExportPlatformAndroid::get_apksigner_path() { da->list_dir_end(); if (apksigner_path.empty()) { - EditorNode::get_singleton()->show_warning(TTR("Unable to find the 'apksigner' tool.")); + print_error("Unable to find the 'apksigner' tool."); } return apksigner_path; @@ -2643,7 +2643,7 @@ Error EditorExportPlatformAndroid::sign_apk(const Ref &p_pre String apksigner = get_apksigner_path(); print_verbose("Starting signing of the " + export_label + " binary using " + apksigner); if (!FileAccess::exists(apksigner)) { - EditorNode::add_io_error(vformat(TTR("'apksigner' could not be found.\nPlease check the command is available in the Android SDK build-tools directory.\nThe resulting %s is unsigned."), export_label)); + add_message(EXPORT_MESSAGE_WARNING, TTR("Code Signing"), vformat(TTR("'apksigner' could not be found. Please check that the command is available in the Android SDK build-tools directory. The resulting %s is unsigned."), export_label)); return OK; } @@ -2676,7 +2676,7 @@ Error EditorExportPlatformAndroid::sign_apk(const Ref &p_pre } if (!FileAccess::exists(keystore)) { - EditorNode::add_io_error(TTR("Could not find keystore, unable to export.")); + add_message(EXPORT_MESSAGE_WARNING, TTR("Code Signing"), TTR("Could not find keystore, unable to export.")); return ERR_FILE_CANT_OPEN; } @@ -2697,10 +2697,14 @@ Error EditorExportPlatformAndroid::sign_apk(const Ref &p_pre } int retval; output.clear(); - OS::get_singleton()->execute(apksigner, args, true, nullptr, &output, &retval, true); + Error err = OS::get_singleton()->execute(apksigner, args, true, nullptr, &output, &retval, true); + if (err != OK) { + add_message(EXPORT_MESSAGE_WARNING, TTR("Code Signing"), TTR("Could not start apksigner executable.")); + return err; + } print_verbose(output); if (retval) { - EditorNode::add_io_error(vformat(TTR("'apksigner' returned with error #%d"), retval)); + add_message(EXPORT_MESSAGE_WARNING, TTR("Code Signing"), vformat(TTR("'apksigner' returned with error #%d"), retval)); return ERR_CANT_CREATE; } @@ -2717,10 +2721,14 @@ Error EditorExportPlatformAndroid::sign_apk(const Ref &p_pre } output.clear(); - OS::get_singleton()->execute(apksigner, args, true, nullptr, &output, &retval, true); + err = OS::get_singleton()->execute(apksigner, args, true, nullptr, &output, &retval, true); + if (err != OK) { + add_message(EXPORT_MESSAGE_WARNING, TTR("Code Signing"), TTR("Could not start apksigner executable.")); + return err; + } print_verbose(output); if (retval) { - EditorNode::add_io_error(vformat(TTR("'apksigner' verification of %s failed."), export_label)); + add_message(EXPORT_MESSAGE_WARNING, TTR("Code Signing"), vformat(TTR("'apksigner' verification of %s failed."), export_label)); return ERR_CANT_CREATE; } @@ -2830,22 +2838,21 @@ Error EditorExportPlatformAndroid::export_project_helper(const Refshow_warning(TTR("Invalid filename! Android App Bundle requires the *.aab extension.")); + add_message(EXPORT_MESSAGE_ERROR, TTR("Export"), TTR("Invalid filename! Android App Bundle requires the *.aab extension.")); return ERR_UNCONFIGURED; } if (apk_expansion) { - EditorNode::get_singleton()->show_warning(TTR("APK Expansion not compatible with Android App Bundle.")); + add_message(EXPORT_MESSAGE_ERROR, TTR("Export"), TTR("APK Expansion not compatible with Android App Bundle.")); return ERR_UNCONFIGURED; } } if (export_format == EXPORT_FORMAT_APK && !p_path.ends_with(".apk")) { - EditorNode::get_singleton()->show_warning( - TTR("Invalid filename! Android APK requires the *.apk extension.")); + add_message(EXPORT_MESSAGE_ERROR, TTR("Export"), TTR("Invalid filename! Android APK requires the *.apk extension.")); return ERR_UNCONFIGURED; } if (export_format > EXPORT_FORMAT_AAB || export_format < EXPORT_FORMAT_APK) { - EditorNode::add_io_error(TTR("Unsupported export format!\n")); - return ERR_UNCONFIGURED; //TODO: is this the right error? + add_message(EXPORT_MESSAGE_ERROR, TTR("Export"), TTR("Unsupported export format!")); + return ERR_UNCONFIGURED; } if (use_custom_build) { @@ -2855,14 +2862,14 @@ Error EditorExportPlatformAndroid::export_project_helper(const Refshow_warning(TTR("Trying to build from a custom built template, but no version info for it exists. Please reinstall from the 'Project' menu.")); + add_message(EXPORT_MESSAGE_ERROR, TTR("Export"), TTR("Trying to build from a custom built template, but no version info for it exists. Please reinstall from the 'Project' menu.")); return ERR_UNCONFIGURED; } String version = f->get_line().strip_edges(); print_verbose("- build version: " + version); f->close(); if (version != VERSION_FULL_CONFIG) { - EditorNode::get_singleton()->show_warning(vformat(TTR("Android build version mismatch:\n Template installed: %s\n Godot Version: %s\nPlease reinstall Android build template from 'Project' menu."), version, VERSION_FULL_CONFIG)); + add_message(EXPORT_MESSAGE_ERROR, TTR("Export"), vformat(TTR("Android build version mismatch: Template installed: %s, Godot version: %s. Please reinstall Android build template from 'Project' menu."), version, VERSION_FULL_CONFIG)); return ERR_UNCONFIGURED; } } @@ -2875,7 +2882,7 @@ Error EditorExportPlatformAndroid::export_project_helper(const Refget("package/name")); err = _create_project_name_strings_files(p_preset, project_name); //project name localization. if (err != OK) { - EditorNode::add_io_error(TTR("Unable to overwrite res://android/build/res/*.xml files with project name")); + add_message(EXPORT_MESSAGE_ERROR, TTR("Export"), TTR("Unable to overwrite res://android/build/res/*.xml files with project name.")); } // Copies the project icon files into the appropriate Gradle project directory. _copy_icons_to_gradle_project(p_preset, processed_splash_config_xml, splash_image, splash_bg_color_image, main_image, foreground, background); @@ -2892,7 +2899,7 @@ Error EditorExportPlatformAndroid::export_project_helper(const Ref 0) { @@ -2904,7 +2911,7 @@ Error EditorExportPlatformAndroid::export_project_helper(const Refget_resource_dir().plus_file(debug_keystore).simplify_path(); } if (!FileAccess::exists(debug_keystore)) { - EditorNode::add_io_error(TTR("Could not find keystore, unable to export.")); + add_message(EXPORT_MESSAGE_ERROR, TTR("Code Signing"), TTR("Could not find keystore, unable to export.")); return ERR_FILE_CANT_OPEN; } @@ -3005,7 +3012,7 @@ Error EditorExportPlatformAndroid::export_project_helper(const Refget_resource_dir().plus_file(release_keystore).simplify_path(); } if (!FileAccess::exists(release_keystore)) { - EditorNode::add_io_error(TTR("Could not find keystore, unable to export.")); + add_message(EXPORT_MESSAGE_ERROR, TTR("Code Signing"), TTR("Could not find keystore, unable to export.")); return ERR_FILE_CANT_OPEN; } @@ -3017,7 +3024,7 @@ Error EditorExportPlatformAndroid::export_project_helper(const Refexecute_and_show_output(TTR("Building Android Project (gradle)"), build_command, cmdline); if (result != 0) { - EditorNode::get_singleton()->show_warning(TTR("Building of Android project failed, check output for the error.\nAlternatively visit docs.godotengine.org for Android build documentation.")); + add_message(EXPORT_MESSAGE_ERROR, TTR("Export"), TTR("Building of Android project failed, check output for the error. Alternatively visit docs.godotengine.org for Android build documentation.")); return ERR_CANT_CREATE; } @@ -3047,7 +3054,7 @@ Error EditorExportPlatformAndroid::export_project_helper(const Refexecute_and_show_output(TTR("Moving output"), build_command, copy_args); if (copy_result != 0) { - EditorNode::get_singleton()->show_warning(TTR("Unable to copy and rename export file, check gradle project directory for outputs.")); + add_message(EXPORT_MESSAGE_ERROR, TTR("Export"), TTR("Unable to copy and rename export file, check gradle project directory for outputs.")); return ERR_CANT_CREATE; } @@ -3070,7 +3077,7 @@ Error EditorExportPlatformAndroid::export_project_helper(const Refexecute("codesign", codesign_args, true); + + String str; + Error err = OS::get_singleton()->execute("codesign", codesign_args, true, NULL, &str, NULL, true); + print_verbose("codesign (" + p_file + "):\n" + str); + + return err; } return OK; } @@ -1686,7 +1691,7 @@ Error EditorExportPlatformIOS::export_project(const Ref &p_p String err; src_pkg_name = find_export_template("iphone.zip", &err); if (src_pkg_name == "") { - EditorNode::add_io_error(err); + add_message(EXPORT_MESSAGE_ERROR, TTR("Prepare Templates"), TTR("Export template not found.")); return ERR_FILE_NOT_FOUND; } } @@ -1783,7 +1788,7 @@ Error EditorExportPlatformIOS::export_project(const Ref &p_p zlib_filefunc_def io = zipio_create_io_from_file(&src_f); unzFile src_pkg_zip = unzOpen2(src_pkg_name.utf8().get_data(), &io); if (!src_pkg_zip) { - EditorNode::add_io_error("Could not open export template (not a zip file?):\n" + src_pkg_name); + add_message(EXPORT_MESSAGE_ERROR, TTR("Prepare Templates"), TTR("Could not open export template (not a zip file?): \"%s\".", src_pkg_name)); return ERR_CANT_OPEN; } diff --git a/platform/javascript/export/export.cpp b/platform/javascript/export/export.cpp index 37c228df9d0..4328f4c4f6d 100644 --- a/platform/javascript/export/export.cpp +++ b/platform/javascript/export/export.cpp @@ -361,12 +361,12 @@ Error EditorExportPlatformJavaScript::_extract_template(const String &p_template unzFile pkg = unzOpen2(p_template.utf8().get_data(), &io); if (!pkg) { - EditorNode::get_singleton()->show_warning(TTR("Could not open template for export:") + "\n" + p_template); + add_message(EXPORT_MESSAGE_ERROR, TTR("Prepare Templates"), vformat(TTR("Could not open template for export: \"%s\"."), p_template)); return ERR_FILE_NOT_FOUND; } if (unzGoToFirstFile(pkg) != UNZ_OK) { - EditorNode::get_singleton()->show_warning(TTR("Invalid export template:") + "\n" + p_template); + add_message(EXPORT_MESSAGE_ERROR, TTR("Prepare Templates"), vformat(TTR("Invalid export template: \"%s\"."), p_template)); unzClose(pkg); return ERR_FILE_CORRUPT; } @@ -379,6 +379,11 @@ Error EditorExportPlatformJavaScript::_extract_template(const String &p_template String file = String::utf8(fname); + // Skip folders. + if (file.ends_with("/")) { + continue; + } + // Skip service worker and offline page if not exporting pwa. if (!pwa && (file == "godot.service.worker.js" || file == "godot.offline.html")) { continue; @@ -395,7 +400,7 @@ Error EditorExportPlatformJavaScript::_extract_template(const String &p_template String dst = p_dir.plus_file(file.replace("godot", p_name)); FileAccess *f = FileAccess::open(dst, FileAccess::WRITE); if (!f) { - EditorNode::get_singleton()->show_warning(TTR("Could not write file:") + "\n" + dst); + add_message(EXPORT_MESSAGE_ERROR, TTR("Prepare Templates"), vformat(TTR("Could not write file: \"%s\"."), dst)); unzClose(pkg); return ERR_FILE_CANT_WRITE; } @@ -410,7 +415,7 @@ Error EditorExportPlatformJavaScript::_extract_template(const String &p_template Error EditorExportPlatformJavaScript::_write_or_error(const uint8_t *p_content, int p_size, String p_path) { FileAccess *f = FileAccess::open(p_path, FileAccess::WRITE); if (!f) { - EditorNode::get_singleton()->show_warning(TTR("Could not write file:") + "\n" + p_path); + add_message(EXPORT_MESSAGE_ERROR, TTR("Export"), vformat(TTR("Could not write file: \"%s\"."), p_path)); return ERR_FILE_CANT_WRITE; } f->store_buffer(p_content, p_size); @@ -488,7 +493,7 @@ Error EditorExportPlatformJavaScript::_add_manifest_icon(const String &p_path, c icon.instance(); const Error err = ImageLoader::load_image(p_icon, icon); if (err != OK) { - EditorNode::get_singleton()->show_warning(TTR("Could not read file:") + "\n" + p_icon); + add_message(EXPORT_MESSAGE_ERROR, TTR("Icon Creation"), vformat(TTR("Could not read file: \"%s\"."), p_icon)); return err; } if (icon->get_width() != p_size || icon->get_height() != p_size) { @@ -500,7 +505,7 @@ Error EditorExportPlatformJavaScript::_add_manifest_icon(const String &p_path, c } const Error err = icon->save_png(icon_dest); if (err != OK) { - EditorNode::get_singleton()->show_warning(TTR("Could not write file:") + "\n" + icon_dest); + add_message(EXPORT_MESSAGE_ERROR, TTR("Icon Creation"), vformat(TTR("Could not write file: \"%s\"."), icon_dest)); return err; } Dictionary icon_dict; @@ -558,7 +563,7 @@ Error EditorExportPlatformJavaScript::_build_pwa(const Ref & { FileAccess *f = FileAccess::open(sw_path, FileAccess::READ); if (!f) { - EditorNode::get_singleton()->show_warning(TTR("Could not read file:") + "\n" + sw_path); + add_message(EXPORT_MESSAGE_ERROR, TTR("PWA"), vformat(TTR("Could not read file: \"%s\"."), sw_path)); return ERR_FILE_CANT_READ; } sw.resize(f->get_len()); @@ -579,7 +584,7 @@ Error EditorExportPlatformJavaScript::_build_pwa(const Ref & const String offline_dest = dir.plus_file(name + ".offline.html"); err = da->copy(ProjectSettings::get_singleton()->globalize_path(offline_page), offline_dest); if (err != OK) { - EditorNode::get_singleton()->show_warning(TTR("Could not read file:") + "\n" + offline_dest); + add_message(EXPORT_MESSAGE_ERROR, TTR("PWA"), vformat(TTR("Could not read file: \"%s\"."), offline_dest)); return err; } } @@ -758,7 +763,7 @@ Error EditorExportPlatformJavaScript::export_project(const Refshow_warning(TTR("Template file not found:") + "\n" + template_path); + add_message(EXPORT_MESSAGE_ERROR, TTR("Prepare Templates"), vformat(TTR("Template file not found: \"%s\"."), template_path)); return ERR_FILE_NOT_FOUND; } @@ -767,7 +772,7 @@ Error EditorExportPlatformJavaScript::export_project(const Refshow_warning(TTR("Could not write file:") + "\n" + pck_path); + add_message(EXPORT_MESSAGE_ERROR, TTR("Export"), vformat(TTR("Could not write file: \"%s\"."), pck_path)); return error; } DirAccess *da = DirAccess::create(DirAccess::ACCESS_FILESYSTEM); @@ -775,7 +780,7 @@ Error EditorExportPlatformJavaScript::export_project(const Refcopy(shared_objects[i].path, dst); if (error != OK) { - EditorNode::get_singleton()->show_warning(TTR("Could not write file:") + "\n" + shared_objects[i].path.get_file()); + add_message(EXPORT_MESSAGE_ERROR, TTR("Export"), vformat(TTR("Could not write file: \"%s\"."), shared_objects[i].path.get_file())); memdelete(da); return error; } @@ -810,7 +815,7 @@ Error EditorExportPlatformJavaScript::export_project(const Ref html; f = FileAccess::open(html_path, FileAccess::READ); if (!f) { - EditorNode::get_singleton()->show_warning(TTR("Could not read HTML shell:") + "\n" + html_path); + add_message(EXPORT_MESSAGE_ERROR, TTR("Export"), vformat(TTR("Could not read HTML shell: \"%s\"."), html_path)); return ERR_FILE_CANT_READ; } html.resize(f->get_len()); @@ -830,7 +835,7 @@ Error EditorExportPlatformJavaScript::export_project(const Ref splash = _get_project_splash(); const String splash_png_path = base_path + ".png"; if (splash->save_png(splash_png_path) != OK) { - EditorNode::get_singleton()->show_warning(TTR("Could not write file:") + "\n" + splash_png_path); + add_message(EXPORT_MESSAGE_ERROR, TTR("Export"), vformat(TTR("Could not write file: \"%s\"."), splash_png_path)); return ERR_FILE_CANT_WRITE; } @@ -840,13 +845,13 @@ Error EditorExportPlatformJavaScript::export_project(const Ref favicon = _get_project_icon(); const String favicon_png_path = base_path + ".icon.png"; if (favicon->save_png(favicon_png_path) != OK) { - EditorNode::get_singleton()->show_warning(TTR("Could not write file:") + "\n" + favicon_png_path); + add_message(EXPORT_MESSAGE_ERROR, TTR("Export"), vformat(TTR("Could not write file: \"%s\"."), favicon_png_path)); return ERR_FILE_CANT_WRITE; } favicon->resize(180, 180); const String apple_icon_png_path = base_path + ".apple-touch-icon.png"; if (favicon->save_png(apple_icon_png_path) != OK) { - EditorNode::get_singleton()->show_warning(TTR("Could not write file:") + "\n" + apple_icon_png_path); + add_message(EXPORT_MESSAGE_ERROR, TTR("Export"), vformat(TTR("Could not write file: \"%s\"."), apple_icon_png_path)); return ERR_FILE_CANT_WRITE; } } @@ -906,10 +911,11 @@ Error EditorExportPlatformJavaScript::run(const Ref &p_prese if (!da->dir_exists(dest)) { Error err = da->make_dir_recursive(dest); if (err != OK) { - EditorNode::get_singleton()->show_warning(TTR("Could not create HTTP server directory:") + "\n" + dest); + add_message(EXPORT_MESSAGE_ERROR, TTR("Run"), vformat(TTR("Could not create HTTP server directory: %s."), dest)); return err; } } + const String basepath = dest.plus_file("tmp_js_export"); Error err = export_project(p_preset, true, basepath + ".html", p_debug_flags); if (err != OK) { @@ -952,7 +958,7 @@ Error EditorExportPlatformJavaScript::run(const Ref &p_prese err = server->listen(bind_port, bind_ip, use_ssl, ssl_key, ssl_cert); } if (err != OK) { - EditorNode::get_singleton()->show_warning(TTR("Error starting HTTP server:") + "\n" + itos(err)); + add_message(EXPORT_MESSAGE_ERROR, TTR("Run"), vformat(TTR("Error starting HTTP server: %d."), err)); return err; } diff --git a/platform/osx/export/export.cpp b/platform/osx/export/export.cpp index 94191fc92ae..9dd28d3125a 100644 --- a/platform/osx/export/export.cpp +++ b/platform/osx/export/export.cpp @@ -346,7 +346,8 @@ void EditorExportPlatformOSX::_make_icon(const Ref &p_icon, Vector &p_preset String str; Error err = OS::get_singleton()->execute("xcrun", args, true, NULL, &str, NULL, true); - ERR_FAIL_COND_V(err != OK, err); + if (err != OK || (str.find("not found") != -1) || (str.find("not recognized") != -1)) { + add_message(EXPORT_MESSAGE_WARNING, TTR("Notarization"), TTR("Could not start xcrun executable.")); + return err; + } print_verbose("altool (" + p_path + "):\n" + str); - if (str.find("RequestUUID") == -1) { - EditorNode::add_io_error("altool: " + str); + int rq_offset = str.find("RequestUUID"); + if (rq_offset == -1) { + add_message(EXPORT_MESSAGE_WARNING, TTR("Notarization"), TTR("Notarization failed.")); return FAILED; } else { - print_line(TTR("Note: The notarization process generally takes less than an hour. When the process is completed, you'll receive an email.")); - print_line(" " + TTR("You can check progress manually by opening a Terminal and running the following command:")); - print_line(" \"xcrun altool --notarization-history 0 -u -p \""); - print_line(" " + TTR("Run the following command to staple the notarization ticket to the exported application (optional):")); - print_line(" \"xcrun stapler staple \""); + int next_nl = str.find("\n", rq_offset); + String request_uuid = (next_nl == -1) ? str.substr(rq_offset + 14, -1) : str.substr(rq_offset + 14, next_nl - rq_offset - 14); + add_message(EXPORT_MESSAGE_INFO, TTR("Notarization"), vformat(TTR("Notarization request UUID: \"%s\""), request_uuid)); + add_message(EXPORT_MESSAGE_INFO, TTR("Notarization"), TTR("The notarization process generally takes less than an hour. When the process is completed, you'll receive an email.")); + add_message(EXPORT_MESSAGE_INFO, TTR("Notarization"), "\t" + TTR("You can check progress manually by opening a Terminal and running the following command:")); + add_message(EXPORT_MESSAGE_INFO, TTR("Notarization"), "\t\t\"xcrun altool --notarization-history 0 -u -p \""); + add_message(EXPORT_MESSAGE_INFO, TTR("Notarization"), "\t" + TTR("Run the following command to staple the notarization ticket to the exported application (optional):")); + add_message(EXPORT_MESSAGE_INFO, TTR("Notarization"), "\t\t\"xcrun stapler staple \""); } #endif @@ -556,21 +564,21 @@ Error EditorExportPlatformOSX::_code_sign(const Ref &p_prese #ifdef MODULE_REGEX_ENABLED #ifdef OSX_ENABLED if (p_preset->get("codesign/timestamp")) { - WARN_PRINT("Timestamping is not compatible with ad-hoc signature, and was disabled!"); + add_message(EXPORT_MESSAGE_INFO, TTR("Code Signing"), TTR("Timestamping is not compatible with ad-hoc signature, and was disabled!")); } if (p_preset->get("codesign/hardened_runtime")) { - WARN_PRINT("Hardened Runtime is not compatible with ad-hoc signature, and was disabled!"); + add_message(EXPORT_MESSAGE_INFO, TTR("Code Signing"), TTR("Hardened Runtime is not compatible with ad-hoc signature, and was disabled!")); } #endif String error_msg; Error err = CodeSign::codesign(false, p_preset->get("codesign/replace_existing_signature"), p_path, p_ent_path, error_msg); if (err != OK) { - EditorNode::add_io_error("Built-in CodeSign: " + error_msg); + add_message(EXPORT_MESSAGE_WARNING, TTR("Code Signing"), vformat(TTR("Built-in CodeSign failed with error \"%s\"."), error_msg)); return FAILED; } #else - ERR_FAIL_V_MSG(FAILED, "Built-in CodeSign require regex module"); + add_message(EXPORT_MESSAGE_WARNING, TTR("Code Signing"), TTR("Built-in CodeSign require regex module.")); #endif return OK; } else { @@ -578,14 +586,14 @@ Error EditorExportPlatformOSX::_code_sign(const Ref &p_prese List args; if (p_preset->get("codesign/timestamp")) { if (ad_hoc) { - WARN_PRINT("Timestamping is not compatible with ad-hoc signature, and was disabled!"); + add_message(EXPORT_MESSAGE_INFO, TTR("Code Signing"), TTR("Timestamping is not compatible with ad-hoc signature, and was disabled!")); } else { args.push_back("--timestamp"); } } if (p_preset->get("codesign/hardened_runtime")) { if (ad_hoc) { - WARN_PRINT("Hardened Runtime is not compatible with ad-hoc signature, and was disabled!"); + add_message(EXPORT_MESSAGE_INFO, TTR("Code Signing"), TTR("Hardened Runtime is not compatible with ad-hoc signature, and was disabled!")); } else { args.push_back("--options"); args.push_back("runtime"); @@ -622,15 +630,18 @@ Error EditorExportPlatformOSX::_code_sign(const Ref &p_prese String str; Error err = OS::get_singleton()->execute("codesign", args, true, NULL, &str, NULL, true); - ERR_FAIL_COND_V(err != OK, err); + if (err != OK || (str.find("not found") != -1) || (str.find("not recognized") != -1)) { + add_message(EXPORT_MESSAGE_WARNING, TTR("Code Signing"), TTR("Could not start codesign executable, make sure Xcode command line tools are installed.")); + return err; + } print_verbose("codesign (" + p_path + "):\n" + str); if (str.find("no identity found") != -1) { - EditorNode::add_io_error("CodeSign: " + TTR("No identity found.")); + add_message(EXPORT_MESSAGE_WARNING, TTR("Code Signing"), TTR("No identity found.")); return FAILED; } if ((str.find("unrecognized blob type") != -1) || (str.find("cannot read entitlement data") != -1)) { - EditorNode::add_io_error("CodeSign: " + TTR("Invalid entitlements file.")); + add_message(EXPORT_MESSAGE_WARNING, TTR("Code Signing"), TTR("Invalid entitlements file.")); return FAILED; } return OK; @@ -675,7 +686,7 @@ Error EditorExportPlatformOSX::_code_sign_directory(const Refdir_exists(p_src_path)) { #ifndef UNIX_ENABLED - WARN_PRINT("Relative symlinks are not supported, exported " + p_src_path.get_file() + " might be broken!"); + add_message(EXPORT_MESSAGE_INFO, TTR("Export"), vformat(TTR("Relative symlinks are not supported, exported \"%s\" might be broken!"), p_src_path.get_file())); #endif print_verbose("export framework: " + p_src_path + " -> " + p_in_app_path); err = dir_access->make_dir_recursive(p_in_app_path); @@ -750,14 +761,17 @@ Error EditorExportPlatformOSX::_create_dmg(const String &p_dmg_path, const Strin String str; Error err = OS::get_singleton()->execute("hdiutil", args, true, nullptr, &str, nullptr, true); - ERR_FAIL_COND_V(err != OK, err); + if (err != OK) { + add_message(EXPORT_MESSAGE_ERROR, TTR("DMG Creation"), TTR("Could not start hdiutil executable.")); + return err; + } print_line("hdiutil returned: " + str); if (str.find("create failed") != -1) { if (str.find("File exists") != -1) { - EditorNode::add_io_error("hdiutil: create failed - file exists"); + add_message(EXPORT_MESSAGE_ERROR, TTR("DMG Creation"), TTR("`hdiutil create` failed - file exists.")); } else { - EditorNode::add_io_error("hdiutil: create failed"); + add_message(EXPORT_MESSAGE_ERROR, TTR("DMG Creation"), TTR("`hdiutil create` failed.")); } return FAILED; } @@ -782,12 +796,13 @@ Error EditorExportPlatformOSX::export_project(const Ref &p_p String err; src_pkg_name = find_export_template("osx.zip", &err); if (src_pkg_name == "") { - EditorNode::add_io_error(err); + add_message(EXPORT_MESSAGE_ERROR, TTR("Prepare Templates"), TTR("Export template not found.")); return ERR_FILE_NOT_FOUND; } } if (!DirAccess::exists(p_path.get_base_dir())) { + add_message(EXPORT_MESSAGE_ERROR, TTR("Prepare Templates"), TTR("The given export path doesn't exist.")); return ERR_FILE_BAD_PATH; } @@ -800,7 +815,7 @@ Error EditorExportPlatformOSX::export_project(const Ref &p_p unzFile src_pkg_zip = unzOpen2(src_pkg_name.utf8().get_data(), &io); if (!src_pkg_zip) { - EditorNode::add_io_error(TTR("Could not find template app to export:") + "\n" + src_pkg_name); + add_message(EXPORT_MESSAGE_ERROR, TTR("Prepare Templates"), vformat(TTR("Could not find template app to export: \"%s\"."), src_pkg_name)); return ERR_FILE_NOT_FOUND; } @@ -827,7 +842,7 @@ Error EditorExportPlatformOSX::export_project(const Ref &p_p } else if (p_path.ends_with("app")) { export_format = "app"; } else { - EditorNode::add_io_error("Invalid export format"); + add_message(EXPORT_MESSAGE_ERROR, TTR("Export"), TTR("Invalid export format.")); return ERR_CANT_CREATE; } @@ -849,8 +864,10 @@ Error EditorExportPlatformOSX::export_project(const Ref &p_p } if (DirAccess::exists(tmp_app_dir_name)) { + String old_dir = tmp_app_dir->get_current_dir(); if (tmp_app_dir->change_dir(tmp_app_path_name) == OK) { tmp_app_dir->erase_contents_recursive(); + tmp_app_dir->change_dir(old_dir); } } @@ -920,7 +937,7 @@ Error EditorExportPlatformOSX::export_project(const Ref &p_p if (((info.external_fa >> 16L) & 0120000) == 0120000) { #ifndef UNIX_ENABLED - WARN_PRINT(vformat(TTR("Relative symlinks are not supported on this OS, the exported project might be broken!"))); + add_message(EXPORT_MESSAGE_INFO, TTR("Export"), TTR("Relative symlinks are not supported on this OS, the exported project might be broken!")); #endif // Handle symlinks in the archive. file = tmp_app_path_name.plus_file(file); @@ -1030,7 +1047,7 @@ Error EditorExportPlatformOSX::export_project(const Ref &p_p unzClose(src_pkg_zip); if (!found_binary) { - ERR_PRINT(vformat(TTR("Requested template binary '%s' not found. It might be missing from your template archive."), binary_to_use)); + add_message(EXPORT_MESSAGE_ERROR, TTR("Export"), vformat(TTR("Requested template binary \"%s\" not found. It might be missing from your template archive."), binary_to_use)); err = ERR_FILE_NOT_FOUND; } @@ -1190,7 +1207,7 @@ Error EditorExportPlatformOSX::export_project(const Ref &p_p ad_hoc = (sign_identity == "" || sign_identity == "-"); bool lib_validation = p_preset->get("codesign/entitlements/disable_library_validation"); if ((!dylibs_found.empty() || !shared_objects.empty()) && sign_enabled && ad_hoc && !lib_validation) { - ERR_PRINT(TTR("Ad-hoc signed applications require the 'Disable Library Validation' entitlement to load dynamic libraries.")); + add_message(EXPORT_MESSAGE_ERROR, TTR("Code Signing"), TTR("Ad-hoc signed applications require the 'Disable Library Validation' entitlement to load dynamic libraries.")); err = ERR_CANT_CREATE; } } @@ -1269,7 +1286,7 @@ Error EditorExportPlatformOSX::export_project(const Ref &p_p bool noto_enabled = p_preset->get("notarization/enable"); if (err == OK && noto_enabled) { if (export_format == "app") { - WARN_PRINT(TTR("Notarization requires the app to be archived first, select the DMG or ZIP export format instead.")); + add_message(EXPORT_MESSAGE_INFO, TTR("Notarization"), TTR("Notarization requires the app to be archived first, select the DMG or ZIP export format instead.")); } else { if (ep.step(TTR("Sending archive for notarization"), 4)) { return ERR_SKIP; @@ -1390,7 +1407,8 @@ void EditorExportPlatformOSX::_zip_folder_recursive(zipFile &p_zip, const String FileAccessRef fa = FileAccess::open(dir.plus_file(f), FileAccess::READ); if (!fa) { - ERR_FAIL_MSG("Can't open file to read from path '" + String(dir.plus_file(f)) + "'."); + add_message(EXPORT_MESSAGE_ERROR, TTR("ZIP Creation"), vformat(TTR("Could not open file to read from path \"%s\"."), dir.plus_file(f))); + return; } const int bufsize = 16384; uint8_t buf[bufsize]; diff --git a/platform/windows/export/export.cpp b/platform/windows/export/export.cpp index 01d16b19ac4..b18aed62927 100644 --- a/platform/windows/export/export.cpp +++ b/platform/windows/export/export.cpp @@ -37,8 +37,6 @@ #include "editor/editor_settings.h" #include "platform/windows/logo.gen.h" -static Error fixup_embedded_pck(const String &p_path, int64_t p_embedded_start, int64_t p_embedded_size); - class EditorExportPlatformWindows : public EditorExportPlatformPC { Error _rcedit_add_data(const Ref &p_preset, const String &p_path); Error _code_sign(const Ref &p_preset, const String &p_path); @@ -47,6 +45,7 @@ public: virtual Error export_project(const Ref &p_preset, bool p_debug, const String &p_path, int p_flags = 0); virtual Error sign_shared_object(const Ref &p_preset, bool p_debug, const String &p_path); virtual Error modify_template(const Ref &p_preset, bool p_debug, const String &p_path, int p_flags); + virtual Error fixup_embedded_pck(const String &p_path, int64_t p_embedded_start, int64_t p_embedded_size); virtual void get_export_options(List *r_options); virtual bool get_option_visibility(const String &p_option, const Map &p_options) const; virtual bool can_export(const Ref &p_preset, String &r_error, bool &r_missing_templates) const; @@ -62,10 +61,9 @@ Error EditorExportPlatformWindows::sign_shared_object(const Ref &p_preset, bool p_debug, const String &p_path, int p_flags) { if (p_preset->get("application/modify_resources")) { - return _rcedit_add_data(p_preset, p_path); - } else { - return OK; + _rcedit_add_data(p_preset, p_path); } + return OK; } Error EditorExportPlatformWindows::export_project(const Ref &p_preset, bool p_debug, const String &p_path, int p_flags) { @@ -76,12 +74,15 @@ Error EditorExportPlatformWindows::export_project(const Ref Error err = EditorExportPlatformPC::export_project(p_preset, p_debug, pck_path, p_flags); if (p_preset->get("codesign/enable") && err == OK) { - err = _code_sign(p_preset, pck_path); + _code_sign(p_preset, pck_path); } if (p_preset->get("binary_format/embed_pck") && err == OK) { DirAccessRef tmp_dir = DirAccess::create_for_path(p_path.get_base_dir()); err = tmp_dir->rename(pck_path, p_path); + if (err != OK) { + add_message(EXPORT_MESSAGE_ERROR, TTR("PCK Embedding"), vformat(TTR("Failed to rename temporary file \"%s\"."), pck_path)); + } } return err; @@ -123,7 +124,7 @@ Error EditorExportPlatformWindows::_rcedit_add_data(const Refget("export/windows/rcedit"); if (rcedit_path != String() && !FileAccess::exists(rcedit_path)) { - ERR_PRINT("Could not find rcedit executable at " + rcedit_path + ", aborting."); + add_message(EXPORT_MESSAGE_WARNING, TTR("Resources Modification"), vformat(TTR("Could not find rcedit executable at \"%s\"."), rcedit_path)); return ERR_FILE_NOT_FOUND; } @@ -136,7 +137,7 @@ Error EditorExportPlatformWindows::_rcedit_add_data(const Refget("export/windows/wine"); if (wine_path != String() && !FileAccess::exists(wine_path)) { - ERR_PRINT("Could not find wine executable at " + wine_path + ", aborting."); + add_message(EXPORT_MESSAGE_WARNING, TTR("Resources Modification"), vformat(TTR("Could not find wine executable at \"%s\"."), wine_path)); return ERR_FILE_NOT_FOUND; } @@ -203,10 +204,14 @@ Error EditorExportPlatformWindows::_rcedit_add_data(const Refexecute(rcedit_path, args, true, nullptr, &str, nullptr, true); - ERR_FAIL_COND_V_MSG(err != OK, err, "Could not start rcedit executable, configure rcedit path in the Editor Settings (Export > Windows > Rcedit)."); + if (err != OK || (str.find("not found") != -1) || (str.find("not recognized") != -1)) { + add_message(EXPORT_MESSAGE_WARNING, TTR("Resources Modification"), TTR("Could not start rcedit executable, configure rcedit path in the Editor Settings (Export > Windows > Rcedit).")); + return err; + } print_line("rcedit (" + p_path + "): " + str); if (str.find("Fatal error") != -1) { + add_message(EXPORT_MESSAGE_WARNING, TTR("Resources Modification"), vformat(TTR("rcedit failed to modify executable:\n%s"), str)); return FAILED; } @@ -219,7 +224,7 @@ Error EditorExportPlatformWindows::_code_sign(const Ref &p_p #ifdef WINDOWS_ENABLED String signtool_path = EditorSettings::get_singleton()->get("export/windows/signtool"); if (signtool_path != String() && !FileAccess::exists(signtool_path)) { - ERR_PRINT("Could not find signtool executable at " + signtool_path + ", aborting."); + add_message(EXPORT_MESSAGE_WARNING, TTR("Code Signing"), vformat(TTR("Could not find signtool executable at \"%s\"."), signtool_path)); return ERR_FILE_NOT_FOUND; } if (signtool_path == String()) { @@ -228,7 +233,7 @@ Error EditorExportPlatformWindows::_code_sign(const Ref &p_p #else String signtool_path = EditorSettings::get_singleton()->get("export/windows/osslsigncode"); if (signtool_path != String() && !FileAccess::exists(signtool_path)) { - ERR_PRINT("Could not find osslsigncode executable at " + signtool_path + ", aborting."); + add_message(EXPORT_MESSAGE_WARNING, TTR("Code Signing"), vformat(TTR("Could not find osslsigncode executable at \"%s\"."), signtool_path)); return ERR_FILE_NOT_FOUND; } if (signtool_path == String()) { @@ -248,7 +253,7 @@ Error EditorExportPlatformWindows::_code_sign(const Ref &p_p args.push_back("/f"); args.push_back(p_preset->get("codesign/identity")); } else { - EditorNode::add_io_error("codesign: no identity found"); + add_message(EXPORT_MESSAGE_WARNING, TTR("Code Signing"), TTR("No identity found.")); return FAILED; } } else if (id_type == 2) { //Windows certificate store @@ -256,11 +261,11 @@ Error EditorExportPlatformWindows::_code_sign(const Ref &p_p args.push_back("/sha1"); args.push_back(p_preset->get("codesign/identity")); } else { - EditorNode::add_io_error("codesign: no identity found"); + add_message(EXPORT_MESSAGE_WARNING, TTR("Code Signing"), TTR("No identity found.")); return FAILED; } } else { - EditorNode::add_io_error("codesign: invalid identity type"); + add_message(EXPORT_MESSAGE_WARNING, TTR("Code Signing"), TTR("Invalid identity type.")); return FAILED; } #else @@ -268,7 +273,7 @@ Error EditorExportPlatformWindows::_code_sign(const Ref &p_p args.push_back("-pkcs12"); args.push_back(p_preset->get("codesign/identity")); } else { - EditorNode::add_io_error("codesign: no identity found"); + add_message(EXPORT_MESSAGE_WARNING, TTR("Code Signing"), TTR("No identity found.")); return FAILED; } #endif @@ -300,7 +305,7 @@ Error EditorExportPlatformWindows::_code_sign(const Ref &p_p args.push_back(p_preset->get("codesign/timestamp_server_url")); #endif } else { - EditorNode::add_io_error("codesign: invalid timestamp server"); + add_message(EXPORT_MESSAGE_WARNING, TTR("Code Signing"), TTR("Invalid timestamp server.")); return FAILED; } } @@ -347,7 +352,10 @@ Error EditorExportPlatformWindows::_code_sign(const Ref &p_p String str; Error err = OS::get_singleton()->execute(signtool_path, args, true, nullptr, &str, nullptr, true); - ERR_FAIL_COND_V_MSG(err != OK, err, "Could not start signtool executable, configure signtool path in the Editor Settings (Export > Windows > Signtool)."); + if (err != OK || (str.find("not found") != -1) || (str.find("not recognized") != -1)) { + add_message(EXPORT_MESSAGE_WARNING, TTR("Code Signing"), TTR("Could not start signtool executable, configure signtool path in the Editor Settings (Export > Windows > Signtool).")); + return err; + } print_line("codesign (" + p_path + "): " + str); #ifndef WINDOWS_ENABLED @@ -355,6 +363,7 @@ Error EditorExportPlatformWindows::_code_sign(const Ref &p_p #else if (str.find("Failed") != -1) { #endif + add_message(EXPORT_MESSAGE_WARNING, TTR("Code Signing"), vformat(TTR("Signtool failed to sign executable:\n%s"), str)); return FAILED; } @@ -362,10 +371,16 @@ Error EditorExportPlatformWindows::_code_sign(const Ref &p_p DirAccessRef tmp_dir = DirAccess::create_for_path(p_path.get_base_dir()); err = tmp_dir->remove(p_path); - ERR_FAIL_COND_V(err != OK, err); + if (err != OK) { + add_message(EXPORT_MESSAGE_WARNING, TTR("Code Signing"), vformat(TTR("Failed to remove temporary file \"%s\"."), p_path)); + return err; + } err = tmp_dir->rename(p_path + "_signed", p_path); - ERR_FAIL_COND_V(err != OK, err); + if (err != OK) { + add_message(EXPORT_MESSAGE_WARNING, TTR("Code Signing"), vformat(TTR("Failed to rename temporary file \"%s\"."), p_path + "_signed")); + return err; + } #endif return OK; @@ -414,49 +429,17 @@ bool EditorExportPlatformWindows::can_export(const Ref &p_pr return valid; } -void register_windows_exporter() { - EDITOR_DEF("export/windows/rcedit", ""); - EditorSettings::get_singleton()->add_property_hint(PropertyInfo(Variant::STRING, "export/windows/rcedit", PROPERTY_HINT_GLOBAL_FILE, "*.exe")); -#ifdef WINDOWS_ENABLED - EDITOR_DEF("export/windows/signtool", ""); - EditorSettings::get_singleton()->add_property_hint(PropertyInfo(Variant::STRING, "export/windows/signtool", PROPERTY_HINT_GLOBAL_FILE, "*.exe")); -#else - EDITOR_DEF("export/windows/osslsigncode", ""); - EditorSettings::get_singleton()->add_property_hint(PropertyInfo(Variant::STRING, "export/windows/osslsigncode", PROPERTY_HINT_GLOBAL_FILE)); - // On non-Windows we need WINE to run rcedit - EDITOR_DEF("export/windows/wine", ""); - EditorSettings::get_singleton()->add_property_hint(PropertyInfo(Variant::STRING, "export/windows/wine", PROPERTY_HINT_GLOBAL_FILE)); -#endif - - Ref platform; - platform.instance(); - - Ref img = memnew(Image(_windows_logo)); - Ref logo; - logo.instance(); - logo->create_from_image(img); - platform->set_logo(logo); - platform->set_name("Windows Desktop"); - platform->set_extension("exe"); - platform->set_release_32("windows_32_release.exe"); - platform->set_debug_32("windows_32_debug.exe"); - platform->set_release_64("windows_64_release.exe"); - platform->set_debug_64("windows_64_debug.exe"); - platform->set_os_name("Windows"); - platform->set_fixup_embedded_pck_func(&fixup_embedded_pck); - - EditorExport::get_singleton()->add_export_platform(platform); -} - -static Error fixup_embedded_pck(const String &p_path, int64_t p_embedded_start, int64_t p_embedded_size) { +Error EditorExportPlatformWindows::fixup_embedded_pck(const String &p_path, int64_t p_embedded_start, int64_t p_embedded_size) { // Patch the header of the "pck" section in the PE file so that it corresponds to the embedded data if (p_embedded_size + p_embedded_start >= 0x100000000) { // Check for total executable size - ERR_FAIL_V_MSG(ERR_INVALID_DATA, "Windows executables cannot be >= 4 GiB."); + add_message(EXPORT_MESSAGE_ERROR, TTR("PCK Embedding"), TTR("Windows executables cannot be >= 4 GiB.")); + return ERR_INVALID_DATA; } FileAccess *f = FileAccess::open(p_path, FileAccess::READ_WRITE); if (!f) { + add_message(EXPORT_MESSAGE_ERROR, TTR("PCK Embedding"), vformat(TTR("Failed to open executable file \"%s\"."), p_path)); return ERR_CANT_OPEN; } @@ -469,6 +452,7 @@ static Error fixup_embedded_pck(const String &p_path, int64_t p_embedded_start, uint32_t magic = f->get_32(); if (magic != 0x00004550) { f->close(); + add_message(EXPORT_MESSAGE_ERROR, TTR("PCK Embedding"), TTR("Executable file header corrupted.")); return ERR_FILE_CORRUPT; } } @@ -520,5 +504,42 @@ static Error fixup_embedded_pck(const String &p_path, int64_t p_embedded_start, f->close(); - return found ? OK : ERR_FILE_CORRUPT; + if (!found) { + add_message(EXPORT_MESSAGE_ERROR, TTR("PCK Embedding"), TTR("Executable \"pck\" section not found.")); + return ERR_FILE_CORRUPT; + } + return OK; +} + +void register_windows_exporter() { + EDITOR_DEF("export/windows/rcedit", ""); + EditorSettings::get_singleton()->add_property_hint(PropertyInfo(Variant::STRING, "export/windows/rcedit", PROPERTY_HINT_GLOBAL_FILE, "*.exe")); +#ifdef WINDOWS_ENABLED + EDITOR_DEF("export/windows/signtool", ""); + EditorSettings::get_singleton()->add_property_hint(PropertyInfo(Variant::STRING, "export/windows/signtool", PROPERTY_HINT_GLOBAL_FILE, "*.exe")); +#else + EDITOR_DEF("export/windows/osslsigncode", ""); + EditorSettings::get_singleton()->add_property_hint(PropertyInfo(Variant::STRING, "export/windows/osslsigncode", PROPERTY_HINT_GLOBAL_FILE)); + // On non-Windows we need WINE to run rcedit + EDITOR_DEF("export/windows/wine", ""); + EditorSettings::get_singleton()->add_property_hint(PropertyInfo(Variant::STRING, "export/windows/wine", PROPERTY_HINT_GLOBAL_FILE)); +#endif + + Ref platform; + platform.instance(); + + Ref img = memnew(Image(_windows_logo)); + Ref logo; + logo.instance(); + logo->create_from_image(img); + platform->set_logo(logo); + platform->set_name("Windows Desktop"); + platform->set_extension("exe"); + platform->set_release_32("windows_32_release.exe"); + platform->set_debug_32("windows_32_debug.exe"); + platform->set_release_64("windows_64_release.exe"); + platform->set_debug_64("windows_64_debug.exe"); + platform->set_os_name("Windows"); + + EditorExport::get_singleton()->add_export_platform(platform); } diff --git a/platform/x11/export/export.cpp b/platform/x11/export/export.cpp index 8c8db612386..44225ac7bd4 100644 --- a/platform/x11/export/export.cpp +++ b/platform/x11/export/export.cpp @@ -35,36 +35,17 @@ #include "platform/x11/logo.gen.h" #include "scene/resources/texture.h" -static Error fixup_embedded_pck(const String &p_path, int64_t p_embedded_start, int64_t p_embedded_size); +class EditorExportPlatformX11 : public EditorExportPlatformPC { +public: + virtual Error fixup_embedded_pck(const String &p_path, int64_t p_embedded_start, int64_t p_embedded_size); +}; -void register_x11_exporter() { - Ref platform; - platform.instance(); - - Ref img = memnew(Image(_x11_logo)); - Ref logo; - logo.instance(); - logo->create_from_image(img); - platform->set_logo(logo); - platform->set_name("Linux/X11"); - platform->set_extension("x86"); - platform->set_extension("x86_64", "binary_format/64_bits"); - platform->set_release_32("linux_x11_32_release"); - platform->set_debug_32("linux_x11_32_debug"); - platform->set_release_64("linux_x11_64_release"); - platform->set_debug_64("linux_x11_64_debug"); - platform->set_os_name("X11"); - platform->set_chmod_flags(0755); - platform->set_fixup_embedded_pck_func(&fixup_embedded_pck); - - EditorExport::get_singleton()->add_export_platform(platform); -} - -static Error fixup_embedded_pck(const String &p_path, int64_t p_embedded_start, int64_t p_embedded_size) { +Error EditorExportPlatformX11::fixup_embedded_pck(const String &p_path, int64_t p_embedded_start, int64_t p_embedded_size) { // Patch the header of the "pck" section in the ELF file so that it corresponds to the embedded data FileAccess *f = FileAccess::open(p_path, FileAccess::READ_WRITE); if (!f) { + add_message(EXPORT_MESSAGE_ERROR, TTR("PCK Embedding"), vformat(TTR("Failed to open executable file \"%s\"."), p_path)); return ERR_CANT_OPEN; } @@ -73,6 +54,7 @@ static Error fixup_embedded_pck(const String &p_path, int64_t p_embedded_start, uint32_t magic = f->get_32(); if (magic != 0x464c457f) { // 0x7F + "ELF" f->close(); + add_message(EXPORT_MESSAGE_ERROR, TTR("PCK Embedding"), TTR("Executable file header corrupted.")); return ERR_FILE_CORRUPT; } } @@ -83,7 +65,8 @@ static Error fixup_embedded_pck(const String &p_path, int64_t p_embedded_start, if (bits == 32 && p_embedded_size >= 0x100000000) { f->close(); - ERR_FAIL_V_MSG(ERR_INVALID_DATA, "32-bit executables cannot have embedded data >= 4 GiB."); + add_message(EXPORT_MESSAGE_ERROR, TTR("PCK Embedding"), TTR("32-bit executables cannot have embedded data >= 4 GiB.")); + return ERR_INVALID_DATA; } // Get info about the section header table @@ -162,5 +145,31 @@ static Error fixup_embedded_pck(const String &p_path, int64_t p_embedded_start, memfree(strings); f->close(); - return found ? OK : ERR_FILE_CORRUPT; + if (!found) { + add_message(EXPORT_MESSAGE_ERROR, TTR("PCK Embedding"), TTR("Executable \"pck\" section not found.")); + return ERR_FILE_CORRUPT; + } + return OK; +} + +void register_x11_exporter() { + Ref platform; + platform.instance(); + + Ref img = memnew(Image(_x11_logo)); + Ref logo; + logo.instance(); + logo->create_from_image(img); + platform->set_logo(logo); + platform->set_name("Linux/X11"); + platform->set_extension("x86"); + platform->set_extension("x86_64", "binary_format/64_bits"); + platform->set_release_32("linux_x11_32_release"); + platform->set_debug_32("linux_x11_32_debug"); + platform->set_release_64("linux_x11_64_release"); + platform->set_debug_64("linux_x11_64_debug"); + platform->set_os_name("X11"); + platform->set_chmod_flags(0755); + + EditorExport::get_singleton()->add_export_platform(platform); } diff --git a/scene/gui/rich_text_label.cpp b/scene/gui/rich_text_label.cpp index 6dc8f7aa5dd..2bc314627a1 100644 --- a/scene/gui/rich_text_label.cpp +++ b/scene/gui/rich_text_label.cpp @@ -686,7 +686,20 @@ int RichTextLabel::_process_line(ItemFrame *p_frame, const Vector2 &p_ofs, int & } if (p_mode == PROCESS_DRAW && visible) { - img->image->draw_rect(ci, Rect2(p_ofs + Point2(align_ofs + wofs, y + lh - font->get_descent() - img->size.height), img->size)); + switch (img->align) { + case INLINE_ALIGN_TOP: { + img->image->draw_rect(ci, Rect2(p_ofs + Point2(align_ofs + wofs, y + lh - (font->get_descent() + font->get_ascent())), img->size)); + } break; + case INLINE_ALIGN_CENTER: { + img->image->draw_rect(ci, Rect2(p_ofs + Point2(align_ofs + wofs, y + lh - (font->get_descent() + font->get_ascent() + img->size.height) / 2), img->size)); + } break; + case INLINE_ALIGN_BASELINE: { + img->image->draw_rect(ci, Rect2(p_ofs + Point2(align_ofs + wofs, y + lh - (font->get_descent() + img->size.height)), img->size)); + } break; + case INLINE_ALIGN_BOTTOM: { + img->image->draw_rect(ci, Rect2(p_ofs + Point2(align_ofs + wofs, y + lh - img->size.height), img->size)); + } break; + } } p_char_count++; @@ -1720,7 +1733,7 @@ void RichTextLabel::_remove_item(Item *p_item, const int p_line, const int p_sub memdelete(p_item); } -void RichTextLabel::add_image(const Ref &p_image, const int p_width, const int p_height) { +void RichTextLabel::add_image(const Ref &p_image, const int p_width, const int p_height, RichTextLabel::InlineAlign p_align) { if (current->type == ITEM_TABLE) { return; } @@ -1731,6 +1744,7 @@ void RichTextLabel::add_image(const Ref &p_image, const int p_width, co ItemImage *item = memnew(ItemImage); item->image = p_image; + item->align = p_align; if (p_width > 0) { // custom width @@ -2801,7 +2815,7 @@ void RichTextLabel::_bind_methods() { ClassDB::bind_method(D_METHOD("get_text"), &RichTextLabel::get_text); ClassDB::bind_method(D_METHOD("add_text", "text"), &RichTextLabel::add_text); ClassDB::bind_method(D_METHOD("set_text", "text"), &RichTextLabel::set_text); - ClassDB::bind_method(D_METHOD("add_image", "image", "width", "height"), &RichTextLabel::add_image, DEFVAL(0), DEFVAL(0)); + ClassDB::bind_method(D_METHOD("add_image", "image", "width", "height", "align"), &RichTextLabel::add_image, DEFVAL(0), DEFVAL(0), DEFVAL(INLINE_ALIGN_BASELINE)); ClassDB::bind_method(D_METHOD("newline"), &RichTextLabel::add_newline); ClassDB::bind_method(D_METHOD("remove_line", "line"), &RichTextLabel::remove_line); ClassDB::bind_method(D_METHOD("push_font", "font"), &RichTextLabel::push_font); @@ -2914,6 +2928,11 @@ void RichTextLabel::_bind_methods() { BIND_ENUM_CONSTANT(ALIGN_RIGHT); BIND_ENUM_CONSTANT(ALIGN_FILL); + BIND_ENUM_CONSTANT(INLINE_ALIGN_TOP); + BIND_ENUM_CONSTANT(INLINE_ALIGN_CENTER); + BIND_ENUM_CONSTANT(INLINE_ALIGN_BASELINE); + BIND_ENUM_CONSTANT(INLINE_ALIGN_BOTTOM); + BIND_ENUM_CONSTANT(LIST_NUMBERS); BIND_ENUM_CONSTANT(LIST_LETTERS); BIND_ENUM_CONSTANT(LIST_DOTS); diff --git a/scene/gui/rich_text_label.h b/scene/gui/rich_text_label.h index d1de4b33d6f..b1d077c9c76 100644 --- a/scene/gui/rich_text_label.h +++ b/scene/gui/rich_text_label.h @@ -46,6 +46,14 @@ public: ALIGN_FILL }; + enum InlineAlign { + + INLINE_ALIGN_TOP, + INLINE_ALIGN_CENTER, + INLINE_ALIGN_BASELINE, + INLINE_ALIGN_BOTTOM + }; + enum ListType { LIST_NUMBERS, @@ -147,7 +155,11 @@ private: struct ItemImage : public Item { Ref image; Size2 size; - ItemImage() { type = ITEM_IMAGE; } + InlineAlign align; + ItemImage() { + type = ITEM_IMAGE; + align = INLINE_ALIGN_BASELINE; + } }; struct ItemFont : public Item { @@ -407,7 +419,7 @@ protected: public: String get_text(); void add_text(const String &p_text); - void add_image(const Ref &p_image, const int p_width = 0, const int p_height = 0); + void add_image(const Ref &p_image, const int p_width = 0, const int p_height = 0, RichTextLabel::InlineAlign p_align = INLINE_ALIGN_BASELINE); void add_newline(); bool remove_line(const int p_line); void push_font(const Ref &p_font); @@ -509,6 +521,7 @@ public: }; VARIANT_ENUM_CAST(RichTextLabel::Align); +VARIANT_ENUM_CAST(RichTextLabel::InlineAlign); VARIANT_ENUM_CAST(RichTextLabel::ListType); VARIANT_ENUM_CAST(RichTextLabel::ItemType);