#include "pch.h" #include "app.h" #include "app_core/document_layer.h" #include "app_core/document_resize.h" #include "app_core/document_export.h" #include "app_core/document_session.h" #include "legacy_document_canvas_services.h" #include "legacy_brush_package_export_services.h" #include "legacy_document_export_services.h" #include "legacy_document_layer_services.h" #include "legacy_document_session_services.h" #include "settings.h" #include "node_dialog_open.h" #include "node_dialog_browse.h" #include "node_dialog_resize.h" #include "node_dialog_cloud.h" #include "node_about.h" #include "node_changelog.h" #include "node_usermanual.h" #include "node_dialog_export_ppbr.h" #include "node_remote_page.h" #include "node_shorcuts.h" #include #define MP4V2_NO_STDINT_DEFS #include #include #ifdef __QUEST__ #include "oculus_vr.h" #endif namespace { [[nodiscard]] bool can_start_document_export(App& app, bool requires_license) { const auto decision = pp::app::plan_document_export_start( requires_license, !requires_license || app.check_license(), app.canvas != nullptr); switch (decision) { case pp::app::DocumentExportStartDecision::start_now: return true; case pp::app::DocumentExportStartDecision::show_license_disabled: app.message_box("License", "This function is disabled in demo mode."); return false; case pp::app::DocumentExportStartDecision::unavailable_no_canvas: return false; } return false; } void start_document_export_collection( App& app, pp::app::DocumentExportCollectionKind kind, const char* message_title, const char* collection_log_message, const char* stem_log_message) { const auto plan = pp::app::plan_document_export_collection_target( kind, app.uses_work_directory_document_export_collections()); if (plan.destination == pp::app::DocumentExportCollectionDestination::work_directory_collection) { const auto target = pp::app::make_document_export_collection_target( app.work_path, app.doc_name, plan.suffix); if (!target) { app.message_box(message_title, target.status().message); return; } const auto status = pp::panopainter::execute_legacy_document_export_collection( app, plan.kind, target.value()); if (!status.ok()) LOG("%s: %s", collection_log_message, status.message); return; } app.pick_dir([ &app, kind = plan.kind, title = std::string(message_title), log_message = std::string(stem_log_message) ](std::string path) { const auto target = pp::app::make_document_export_stem_target(path, app.doc_name); if (!target) { app.message_box(title, target.status().message); return; } const auto status = pp::panopainter::execute_legacy_document_export_stem( app, kind, target.value()); if (!status.ok()) LOG("%s: %s", log_message.c_str(), status.message); }); } } std::shared_ptr App::show_progress(const std::string& title, int total /*= 0*/) { auto pb = std::make_shared(); pb->set_manager(&layout); pb->init(); pb->create(); pb->loaded(); pb->m_progress->SetWidthP(0); pb->m_title->set_text(title.c_str()); pb->m_total = total; pb->m_count = 0; layout[main_id]->add_child(pb); return pb; } std::shared_ptr App::message_box(const std::string &title, const std::string& text, bool cancel_button) { auto m = std::make_shared(); m->set_manager(&layout); m->init(); m->create(); m->loaded(); m->m_title->set_text(title.c_str()); m->m_message->set_text(text.c_str()); m->btn_ok->m_text->set_text("Ok"); if (!cancel_button) m->btn_cancel->destroy(); layout[main_id]->add_child(m); return m; } std::shared_ptr App::input_box(const std::string& title, const std::string& field_name, const std::string& ok_caption /*= "Ok"*/) { auto m = std::make_shared(); m->set_manager(&layout); m->init(); m->create(); m->loaded(); m->m_title->set_text(title.c_str()); m->m_field_name->set_text(field_name.c_str()); m->btn_ok->m_text->set_text(ok_caption.c_str()); layout[main_id]->add_child(m); return m; } void App::dialog_usermanual() { auto dialog = std::make_shared(); dialog->set_manager(&layout); dialog->init(); dialog->create(); dialog->loaded(); layout[main_id]->add_child(dialog); } void App::dialog_changelog() { auto dialog = std::make_shared(); dialog->set_manager(&layout); dialog->init(); dialog->create(); dialog->loaded(); layout[main_id]->add_child(dialog); } void App::dialog_about() { auto dialog = std::make_shared(); dialog->set_manager(&layout); dialog->init(); dialog->create(); dialog->loaded(); layout[main_id]->add_child(dialog); } void App::continue_document_workflow_after_optional_save(std::function action) { const bool has_canvas = canvas != nullptr; const bool has_unsaved_changes = has_canvas && Canvas::I->m_unsaved; const auto decision = pp::app::plan_document_workflow(has_canvas, has_unsaved_changes); const auto status = pp::panopainter::execute_legacy_document_workflow_decision( *this, decision, std::move(action)); if (!status.ok()) LOG("Document workflow action failed: %s", status.message); } void App::dialog_newdoc() { auto show_dialog = [this] { auto dialog = std::make_shared(); dialog->set_manager(&layout); dialog->init(); dialog->create(); dialog->loaded(); dialog->input->set_text("name"); layout[main_id]->add_child(dialog); App::I->showKeyboard(); dialog->btn_ok->on_click = [this, dialog](Node*) { std::string name = dialog->input->m_text; const auto plan = pp::app::plan_new_document( work_path, name, dialog->m_resolution->m_current_index, [](const std::string& path) { return Asset::exist(path); }); if (!plan) { const bool missing_name = plan.status().code == pp::foundation::StatusCode::invalid_argument; message_box( "Warning", missing_name ? "You need to specify a name to file." : plan.status().message); return; } const auto status = pp::panopainter::execute_legacy_new_document_plan(*this, plan.value(), dialog); if (!status.ok()) LOG("New document action failed: %s", status.message); }; dialog->btn_cancel->on_click = [this, dialog](Node*) { dialog->destroy(); App::I->hideKeyboard(); }; }; continue_document_workflow_after_optional_save(show_dialog); } // DEPRECATED void App::dialog_open() { auto show_dialog = [this] { // load thumbnail test auto dialog = std::make_shared(); dialog->set_manager(&layout); dialog->init(); dialog->create(); dialog->loaded(); layout[main_id]->add_child(dialog); dialog->btn_ok->on_click = [this, dialog](Node*) { // canvas->reset_camera(); // layers->clear(); // doc_name = dialog->selected_name; // canvas->m_canvas->project_open(dialog->selected_path, [this](bool success) { // // on complete // async_start(); // title_update(); // for (auto& i : canvas->m_canvas->m_order) // layers->add_layer(canvas->m_canvas->m_layers[i]->m_name.c_str()); // async_end(); // }); // dialog->destroy(); // ActionManager::clear(); }; }; continue_document_workflow_after_optional_save(show_dialog); } void App::dialog_browse() { auto show_dialog = [this] { // load thumbnail test auto dialog = std::make_shared(); dialog->set_manager(&layout); dialog->search_paths = document_browse_roots(); dialog->init(); dialog->create(); dialog->loaded(); layout[main_id]->add_child(dialog); dialog->btn_ok->on_click = [this, dialog](Node*) { if (dialog->is_selected()) { open_document(dialog->selected_path); dialog->destroy(); } }; }; continue_document_workflow_after_optional_save(show_dialog); } void App::dialog_save_ver() { if (!check_license()) { message_box("License", "This function is disabled in demo mode."); return; } const auto target = pp::app::find_next_document_version_target( doc_dir, doc_name, [](const std::string& path) { return Asset::exist(path); }); if (!target) { message_box("Saving Error", target.status().message); return; } const auto status = pp::panopainter::execute_legacy_document_version_save(*this, target.value()); if (!status.ok()) LOG("Document version save action failed: %s", status.message); } void App::save_document(pp::app::DocumentSaveIntent intent) { const auto decision = pp::app::plan_document_save( Canvas::I->m_newdoc, Canvas::I->m_unsaved, intent); const auto status = pp::panopainter::execute_legacy_document_save_decision(*this, decision); if (!status.ok()) LOG("Document save action failed: %s", status.message); } void App::dialog_save() { if (!check_license()) { message_box("License", "This function is disabled in demo mode."); return; } if (canvas) { auto dialog = std::make_shared(); dialog->set_manager(&layout); dialog->init(); dialog->create(); dialog->loaded(); dialog->input->set_text(doc_name); App::I->showKeyboard(); dialog->btn_ok->on_click = [this, dialog](Node*) { std::string name = dialog->input->m_text; const auto plan = pp::app::plan_document_file_save( work_path, name, [](const std::string& path) { return Asset::exist(path); }); if (!plan) { message_box("Warning", "You need to specify a name to file."); return; } const auto status = pp::panopainter::execute_legacy_document_file_save_plan(*this, plan.value(), dialog); if (!status.ok()) LOG("Document file save action failed: %s", status.message); }; dialog->btn_cancel->on_click = [this, dialog](Node*) { dialog->destroy(); App::I->hideKeyboard(); }; layout[main_id]->add_child(dialog); } } void App::dialog_export(std::string ext) { if (!can_start_document_export(*this, true)) return; // TODO: use picker const auto target = pp::app::make_document_export_file_target(work_path, doc_name, ext); if (!target) { message_box("Export Equirectangular", target.status().message); return; } const auto status = pp::panopainter::execute_legacy_document_export_file(*this, target.value()); if (!status.ok()) LOG("Document export file action failed: %s", status.message); } void App::dialog_export_layers() { if (!can_start_document_export(*this, true)) return; start_document_export_collection( *this, pp::app::DocumentExportCollectionKind::layers, "Export Layers", "Document layer collection export failed", "Document layer stem export failed"); } void App::dialog_export_anim_frames() { if (!can_start_document_export(*this, true)) return; start_document_export_collection( *this, pp::app::DocumentExportCollectionKind::animation_frames, "Export Layers", "Document animation frame collection export failed", "Document animation frame stem export failed"); } void App::dialog_export_depth() { if (!can_start_document_export(*this, true)) return; const auto status = pp::panopainter::execute_legacy_document_export_depth(*this, doc_name); if (!status.ok()) LOG("Document depth export failed: %s", status.message); } void App::dialog_resize() { auto dialog = std::make_shared(); dialog->set_manager(&layout); dialog->init(); dialog->create(); dialog->loaded(); layout[main_id]->add_child(dialog); dialog->btn_ok->on_click = [this,dialog](Node*) { const auto plan = pp::app::plan_document_resize( dialog->combo ? dialog->combo->m_current_index : 0); if (!plan) { dialog->destroy(); return; } const auto status = pp::panopainter::execute_legacy_document_resize_plan(*this, plan.value()); if (!status.ok()) LOG("Document resize failed: %s", status.message); dialog->destroy(); }; } void App::dialog_export_cube_faces() { if (!can_start_document_export(*this, false)) return; const auto status = pp::panopainter::execute_legacy_document_export_cube_faces(*this, doc_name); if (!status.ok()) LOG("Document cube-face export failed: %s", status.message); } void App::dialog_layer_rename() { auto dialog = std::make_shared(); dialog->set_manager(&layout); dialog->init(); dialog->create(); dialog->loaded(); dialog->input->set_text(layers->m_current_layer->m_label_text); App::I->showKeyboard(); layout[main_id]->add_child(dialog); dialog->btn_ok->on_click = [this,dialog](Node*) { const auto old_name = layers->m_current_layer->m_label_text; const auto plan = pp::app::plan_document_layer_rename(old_name, dialog->get_name()); if (!plan) return; const auto status = pp::panopainter::execute_legacy_document_layer_rename_plan(*this, plan.value(), dialog); if (!status.ok()) LOG("Layer rename failed: %s", status.message); }; dialog->btn_cancel->on_click = [this, dialog](Node*) { dialog->destroy(); App::I->hideKeyboard(); }; } void App::dialog_preset_download() { } void App::dialog_ppbr_export() { auto root = layout[main_id]; auto dialog = root->add_child_ref(); dialog->btn_ok->on_click = [this, dialog] (Node*) { const auto request = pp::panopainter::make_legacy_brush_package_export_request(*dialog); if (uses_prepared_file_writes()) { pick_file_save("ppbr", "exported-brushes", [this, dialog, request] (std::string path) { const auto status = pp::panopainter::execute_legacy_brush_package_export( *this, *dialog, request, path, pp::panopainter::LegacyBrushPackageExportMode::inline_export_only); if (!status.ok()) LOG("PPBR export failed: %s", status.message); }, [dialog] (const std::string& path, bool saved) { (void)path; pp::panopainter::complete_legacy_brush_package_export(*dialog, saved); } ); return; } pick_file_save({ "ppbr" }, [this, dialog, request] (std::string path) { const auto status = pp::panopainter::execute_legacy_brush_package_export( *this, *dialog, request, path, pp::panopainter::LegacyBrushPackageExportMode::desktop_async_close_and_message); if (!status.ok()) LOG("PPBR export failed: %s", status.message); }); }; } void App::dialog_timelapse_export() { if (!can_start_document_export(*this, false)) return; if (uses_prepared_file_writes()) { const auto target = pp::app::make_document_export_suggested_name(doc_name, "-timelapse"); if (!target) { message_box("Export Timelapse", target.status().message); return; } pick_file_save("mp4", target.value().name, [this](std::string path) { const auto status = pp::panopainter::execute_legacy_document_video_export( *this, pp::app::DocumentVideoExportKind::timelapse, path, false); if (!status.ok()) LOG("Document timelapse export failed: %s", status.message); }, [](const std::string& path, bool saved) { (void)path; (void)saved; } ); return; } pick_file_save({ "mp4" }, [this](std::string path) { const auto status = pp::panopainter::execute_legacy_document_video_export( *this, pp::app::DocumentVideoExportKind::timelapse, path, true); if (!status.ok()) LOG("Document timelapse export failed: %s", status.message); }); } void App::dialog_export_mp4() { if (!can_start_document_export(*this, false)) return; if (uses_prepared_file_writes()) { const auto target = pp::app::make_document_export_suggested_name(doc_name, "-animation"); if (!target) { message_box("Export Animation", target.status().message); return; } pick_file_save("mp4", target.value().name, [this](std::string path) { const auto status = pp::panopainter::execute_legacy_document_video_export( *this, pp::app::DocumentVideoExportKind::animation_mp4, path, false); if (!status.ok()) LOG("Document animation export failed: %s", status.message); }, [](const std::string& path, bool saved) { (void)path; (void)saved; } ); return; } pick_file_save({ "mp4" }, [this](std::string path) { const auto status = pp::panopainter::execute_legacy_document_video_export( *this, pp::app::DocumentVideoExportKind::animation_mp4, path, true); if (!status.ok()) LOG("Document animation export failed: %s", status.message); }); } void App::dialog_whatsnew(bool force_show) { auto whatsnew = std::make_shared(); whatsnew->m_manager = &layout; whatsnew->init(); std::string url = fmt::format("https://panopainter.com/app-content/whatsnew/?version={}", g_version_build); whatsnew->load_url(url, [this, whatsnew, force_show](bool success) { if (success) { int last_id = Settings::value_or("whatsnew-id", 0); if (force_show || (whatsnew->m_page_id <= g_version_build && whatsnew->m_page_id > last_id)) { whatsnew->set_title(fmt::format("What's new in version {}", g_version_number)); if (!force_show) layout[main_id]->add_child(whatsnew); } } }); whatsnew->add_button("Reload", 120, [this, whatsnew](Node*) { whatsnew->reload(); }); whatsnew->add_button("Read Later", 120, [this, whatsnew](Node*) { Settings::unset("whatsnew-id"); Settings::save(); whatsnew->destroy(); }); whatsnew->add_button("Close", 100, [this, whatsnew](Node*) { Settings::set("whatsnew-id", whatsnew->m_page_id); Settings::save(); whatsnew->destroy(); }); if (force_show) layout[main_id]->add_child(whatsnew); } void App::dialog_shortcuts() { layout[main_id]->add_child(); }