690 lines
21 KiB
C++
690 lines
21 KiB
C++
#include "pch.h"
|
|
#include "app.h"
|
|
#include "app_core/app_dialog.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 <codec_api.h>
|
|
#define MP4V2_NO_STDINT_DEFS
|
|
#include <mp4v2/mp4v2.h>
|
|
|
|
#include <utility>
|
|
|
|
#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<NodeProgressBar> App::show_progress(const std::string& title, int total /*= 0*/)
|
|
{
|
|
const auto plan = pp::app::plan_app_progress_dialog(title, total);
|
|
auto pb = std::make_shared<NodeProgressBar>();
|
|
pb->set_manager(&layout);
|
|
pb->init();
|
|
pb->create();
|
|
pb->loaded();
|
|
pb->m_progress->SetWidthP(plan.progress_fraction);
|
|
pb->m_title->set_text(plan.title.c_str());
|
|
pb->m_total = plan.total;
|
|
pb->m_count = plan.count;
|
|
layout[main_id]->add_child(pb);
|
|
return pb;
|
|
}
|
|
|
|
std::shared_ptr<NodeMessageBox> App::message_box(const std::string &title, const std::string& text, bool cancel_button)
|
|
{
|
|
const auto plan = pp::app::plan_app_message_dialog(title, text, cancel_button);
|
|
auto m = std::make_shared<NodeMessageBox>();
|
|
m->set_manager(&layout);
|
|
m->init();
|
|
m->create();
|
|
m->loaded();
|
|
m->m_title->set_text(plan.title.c_str());
|
|
m->m_message->set_text(plan.message.c_str());
|
|
m->btn_ok->m_text->set_text(plan.ok_caption.c_str());
|
|
if (!plan.show_cancel)
|
|
m->btn_cancel->destroy();
|
|
layout[main_id]->add_child(m);
|
|
return m;
|
|
}
|
|
|
|
std::shared_ptr<NodeInputBox> App::input_box(const std::string& title,
|
|
const std::string& field_name, const std::string& ok_caption /*= "Ok"*/)
|
|
{
|
|
const auto plan_result = pp::app::plan_app_input_dialog(title, field_name, ok_caption);
|
|
if (!plan_result) {
|
|
LOG("input dialog skipped: %s", plan_result.status().message);
|
|
return nullptr;
|
|
}
|
|
const auto& plan = plan_result.value();
|
|
auto m = std::make_shared<NodeInputBox>();
|
|
m->set_manager(&layout);
|
|
m->init();
|
|
m->create();
|
|
m->loaded();
|
|
m->m_title->set_text(plan.title.c_str());
|
|
m->m_field_name->set_text(plan.field_name.c_str());
|
|
m->btn_ok->m_text->set_text(plan.ok_caption.c_str());
|
|
layout[main_id]->add_child(m);
|
|
return m;
|
|
}
|
|
|
|
void App::dialog_usermanual()
|
|
{
|
|
auto dialog = std::make_shared<NodeUserManual>();
|
|
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<NodeChangelog>();
|
|
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<NodeAbout>();
|
|
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<void()> 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<NodeDialogNewDoc>();
|
|
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<NodeDialogOpen>();
|
|
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<NodeDialogBrowse>();
|
|
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<NodeDialogSave>();
|
|
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<NodeDialogResize>();
|
|
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<NodeDialogLayerRename>();
|
|
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<NodeDialogExportPPBR>();
|
|
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<NodeRemotePage>();
|
|
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<Serializer::Integer>("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<Serializer::Integer>("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<NodeShortcuts>();
|
|
}
|