#pragma once #include "foundation/result.h" #include #include #include namespace pp::app { struct DocumentExportFileTarget { std::string path; std::string suggested_name; }; struct DocumentExportCollectionTarget { std::string directory; std::string stem_path; }; struct DocumentExportStemTarget { std::string stem_path; }; struct DocumentExportSuggestedName { std::string name; }; enum class DocumentExportCollectionDestination { work_directory_collection, picked_directory_stem, }; enum class DocumentExportStartDecision { start_now, show_license_disabled, unavailable_no_canvas, }; enum class DocumentExportMenuKind { jpeg, png, layers, cube_faces, depth, animation_frames, animation_mp4, timelapse, }; enum class DocumentExportMenuAction { show_jpeg_dialog, show_png_dialog, show_layers_dialog, show_cube_faces_dialog, show_depth_dialog, show_animation_frames_dialog, show_animation_mp4_dialog, show_timelapse_dialog, show_license_disabled, unavailable_no_canvas, }; enum class DocumentExportCollectionKind { layers, animation_frames, }; enum class DocumentVideoExportKind { animation_mp4, timelapse, }; struct DocumentExportMenuPlan { DocumentExportMenuKind kind = DocumentExportMenuKind::jpeg; DocumentExportMenuAction action = DocumentExportMenuAction::show_jpeg_dialog; bool opens_dialog = true; }; struct DocumentExportCollectionTargetPlan { DocumentExportCollectionKind kind = DocumentExportCollectionKind::layers; DocumentExportCollectionDestination destination = DocumentExportCollectionDestination::picked_directory_stem; std::string_view suffix; }; class DocumentExportMenuServices { public: virtual ~DocumentExportMenuServices() = default; virtual void show_jpeg_dialog() = 0; virtual void show_png_dialog() = 0; virtual void show_layers_dialog() = 0; virtual void show_cube_faces_dialog() = 0; virtual void show_depth_dialog() = 0; virtual void show_animation_frames_dialog() = 0; virtual void show_animation_mp4_dialog() = 0; virtual void show_timelapse_dialog() = 0; virtual void show_license_disabled() = 0; }; class DocumentExportServices { public: virtual ~DocumentExportServices() = default; virtual bool create_directory(std::string_view directory) = 0; virtual void export_equirectangular(const DocumentExportFileTarget& target) = 0; virtual void export_layers_to_stem(const DocumentExportStemTarget& target) = 0; virtual void export_layers_to_collection(const DocumentExportCollectionTarget& target) = 0; virtual void export_animation_frames_to_stem(const DocumentExportStemTarget& target) = 0; virtual void export_animation_frames_to_collection(const DocumentExportCollectionTarget& target) = 0; virtual void export_depth(std::string_view document_name) = 0; virtual void export_cube_faces(std::string_view document_name) = 0; }; class DocumentVideoExportServices { public: virtual ~DocumentVideoExportServices() = default; virtual void export_animation_mp4(std::string_view path) = 0; virtual void export_timelapse_mp4(std::string_view path) = 0; virtual void show_animation_export_success(std::string_view path) = 0; virtual void show_timelapse_export_success(std::string_view path) = 0; }; [[nodiscard]] constexpr DocumentExportStartDecision plan_document_export_start( bool requires_license, bool license_valid, bool has_canvas) noexcept { if (requires_license && !license_valid) { return DocumentExportStartDecision::show_license_disabled; } return has_canvas ? DocumentExportStartDecision::start_now : DocumentExportStartDecision::unavailable_no_canvas; } [[nodiscard]] constexpr bool document_export_menu_requires_license( DocumentExportMenuKind kind) noexcept { switch (kind) { case DocumentExportMenuKind::animation_mp4: case DocumentExportMenuKind::timelapse: return true; case DocumentExportMenuKind::jpeg: case DocumentExportMenuKind::png: case DocumentExportMenuKind::layers: case DocumentExportMenuKind::cube_faces: case DocumentExportMenuKind::depth: case DocumentExportMenuKind::animation_frames: return false; } return false; } [[nodiscard]] constexpr DocumentExportMenuAction document_export_menu_dialog_action( DocumentExportMenuKind kind) noexcept { switch (kind) { case DocumentExportMenuKind::jpeg: return DocumentExportMenuAction::show_jpeg_dialog; case DocumentExportMenuKind::png: return DocumentExportMenuAction::show_png_dialog; case DocumentExportMenuKind::layers: return DocumentExportMenuAction::show_layers_dialog; case DocumentExportMenuKind::cube_faces: return DocumentExportMenuAction::show_cube_faces_dialog; case DocumentExportMenuKind::depth: return DocumentExportMenuAction::show_depth_dialog; case DocumentExportMenuKind::animation_frames: return DocumentExportMenuAction::show_animation_frames_dialog; case DocumentExportMenuKind::animation_mp4: return DocumentExportMenuAction::show_animation_mp4_dialog; case DocumentExportMenuKind::timelapse: return DocumentExportMenuAction::show_timelapse_dialog; } return DocumentExportMenuAction::show_jpeg_dialog; } [[nodiscard]] constexpr DocumentExportMenuPlan plan_document_export_menu_action( DocumentExportMenuKind kind, bool has_canvas, bool license_valid) noexcept { DocumentExportMenuPlan plan; plan.kind = kind; plan.action = document_export_menu_dialog_action(kind); const auto start = plan_document_export_start( document_export_menu_requires_license(kind), license_valid, has_canvas); if (start == DocumentExportStartDecision::show_license_disabled) { plan.action = DocumentExportMenuAction::show_license_disabled; plan.opens_dialog = false; } else if (start == DocumentExportStartDecision::unavailable_no_canvas) { plan.action = DocumentExportMenuAction::unavailable_no_canvas; plan.opens_dialog = false; } return plan; } [[nodiscard]] constexpr std::string_view document_export_collection_suffix( DocumentExportCollectionKind kind) noexcept { switch (kind) { case DocumentExportCollectionKind::layers: return "_layers"; case DocumentExportCollectionKind::animation_frames: return "_frames"; } return {}; } [[nodiscard]] constexpr DocumentExportCollectionTargetPlan plan_document_export_collection_target( DocumentExportCollectionKind kind, bool use_work_directory_collection) noexcept { return { kind, use_work_directory_collection ? DocumentExportCollectionDestination::work_directory_collection : DocumentExportCollectionDestination::picked_directory_stem, document_export_collection_suffix(kind), }; } [[nodiscard]] inline pp::foundation::Result make_document_export_file_target( std::string_view work_directory, std::string_view document_name, std::string_view extension) { if (document_name.empty()) { return pp::foundation::Result::failure( pp::foundation::Status::invalid_argument("document name must not be empty")); } if (extension.empty()) { return pp::foundation::Result::failure( pp::foundation::Status::invalid_argument("extension must not be empty")); } DocumentExportFileTarget target; target.suggested_name.reserve(document_name.size() + extension.size()); target.suggested_name += document_name; target.suggested_name += extension; target.path.reserve(work_directory.size() + target.suggested_name.size() + 1); target.path += work_directory; target.path += "/"; target.path += target.suggested_name; return pp::foundation::Result::success(std::move(target)); } [[nodiscard]] inline pp::foundation::Result make_document_export_collection_target( std::string_view work_directory, std::string_view document_name, std::string_view suffix) { if (document_name.empty()) { return pp::foundation::Result::failure( pp::foundation::Status::invalid_argument("document name must not be empty")); } DocumentExportCollectionTarget target; target.directory.reserve(work_directory.size() + document_name.size() + suffix.size() + 1); target.directory += work_directory; target.directory += "/"; target.directory += document_name; target.directory += suffix; target.stem_path.reserve(target.directory.size() + document_name.size() + 1); target.stem_path += target.directory; target.stem_path += "/"; target.stem_path += document_name; return pp::foundation::Result::success(std::move(target)); } [[nodiscard]] inline pp::foundation::Result make_document_export_stem_target( std::string_view directory, std::string_view document_name) { if (document_name.empty()) { return pp::foundation::Result::failure( pp::foundation::Status::invalid_argument("document name must not be empty")); } DocumentExportStemTarget target; target.stem_path.reserve(directory.size() + document_name.size() + 1); target.stem_path += directory; target.stem_path += "/"; target.stem_path += document_name; return pp::foundation::Result::success(std::move(target)); } [[nodiscard]] inline pp::foundation::Result make_document_export_suggested_name( std::string_view document_name, std::string_view suffix) { if (document_name.empty()) { return pp::foundation::Result::failure( pp::foundation::Status::invalid_argument("document name must not be empty")); } DocumentExportSuggestedName target; target.name.reserve(document_name.size() + suffix.size()); target.name += document_name; target.name += suffix; return pp::foundation::Result::success(std::move(target)); } [[nodiscard]] inline pp::foundation::Status execute_document_export_file( const DocumentExportFileTarget& target, DocumentExportServices& services) { if (target.path.empty() || target.suggested_name.empty()) { return pp::foundation::Status::invalid_argument("export file target requires a path and suggested name"); } services.export_equirectangular(target); return pp::foundation::Status::success(); } [[nodiscard]] inline pp::foundation::Status execute_document_export_stem( DocumentExportCollectionKind kind, const DocumentExportStemTarget& target, DocumentExportServices& services) { if (target.stem_path.empty()) { return pp::foundation::Status::invalid_argument("export stem target requires a stem path"); } switch (kind) { case DocumentExportCollectionKind::layers: services.export_layers_to_stem(target); return pp::foundation::Status::success(); case DocumentExportCollectionKind::animation_frames: services.export_animation_frames_to_stem(target); return pp::foundation::Status::success(); } return pp::foundation::Status::invalid_argument("unknown document export collection kind"); } [[nodiscard]] inline pp::foundation::Status execute_document_export_collection( DocumentExportCollectionKind kind, const DocumentExportCollectionTarget& target, DocumentExportServices& services) { if (target.directory.empty() || target.stem_path.empty()) { return pp::foundation::Status::invalid_argument("export collection target requires a directory and stem path"); } if (!services.create_directory(target.directory)) { return pp::foundation::Status::success(); } switch (kind) { case DocumentExportCollectionKind::layers: services.export_layers_to_collection(target); return pp::foundation::Status::success(); case DocumentExportCollectionKind::animation_frames: services.export_animation_frames_to_collection(target); return pp::foundation::Status::success(); } return pp::foundation::Status::invalid_argument("unknown document export collection kind"); } [[nodiscard]] inline pp::foundation::Status execute_document_export_depth( std::string_view document_name, DocumentExportServices& services) { if (document_name.empty()) { return pp::foundation::Status::invalid_argument("document name must not be empty"); } services.export_depth(document_name); return pp::foundation::Status::success(); } [[nodiscard]] inline pp::foundation::Status execute_document_export_cube_faces( std::string_view document_name, DocumentExportServices& services) { if (document_name.empty()) { return pp::foundation::Status::invalid_argument("document name must not be empty"); } services.export_cube_faces(document_name); return pp::foundation::Status::success(); } [[nodiscard]] inline pp::foundation::Status execute_document_video_export( DocumentVideoExportKind kind, std::string_view path, DocumentVideoExportServices& services) { if (path.empty()) { return pp::foundation::Status::invalid_argument("video export path must not be empty"); } switch (kind) { case DocumentVideoExportKind::animation_mp4: services.export_animation_mp4(path); services.show_animation_export_success(path); return pp::foundation::Status::success(); case DocumentVideoExportKind::timelapse: services.export_timelapse_mp4(path); services.show_timelapse_export_success(path); return pp::foundation::Status::success(); } return pp::foundation::Status::invalid_argument("unknown document video export kind"); } [[nodiscard]] inline pp::foundation::Status execute_document_export_menu_plan( const DocumentExportMenuPlan& plan, DocumentExportMenuServices& services) { switch (plan.action) { case DocumentExportMenuAction::show_jpeg_dialog: services.show_jpeg_dialog(); return pp::foundation::Status::success(); case DocumentExportMenuAction::show_png_dialog: services.show_png_dialog(); return pp::foundation::Status::success(); case DocumentExportMenuAction::show_layers_dialog: services.show_layers_dialog(); return pp::foundation::Status::success(); case DocumentExportMenuAction::show_cube_faces_dialog: services.show_cube_faces_dialog(); return pp::foundation::Status::success(); case DocumentExportMenuAction::show_depth_dialog: services.show_depth_dialog(); return pp::foundation::Status::success(); case DocumentExportMenuAction::show_animation_frames_dialog: services.show_animation_frames_dialog(); return pp::foundation::Status::success(); case DocumentExportMenuAction::show_animation_mp4_dialog: services.show_animation_mp4_dialog(); return pp::foundation::Status::success(); case DocumentExportMenuAction::show_timelapse_dialog: services.show_timelapse_dialog(); return pp::foundation::Status::success(); case DocumentExportMenuAction::show_license_disabled: services.show_license_disabled(); return pp::foundation::Status::success(); case DocumentExportMenuAction::unavailable_no_canvas: return pp::foundation::Status::success(); } return pp::foundation::Status::invalid_argument("unknown document export menu action"); } }