#pragma once #include "app_core/app_dialog.h" #include "foundation/result.h" #include #include #include namespace pp::app { enum class RecordingStartAction { start_thread, no_op_already_running, }; enum class RecordingStopAction { stop_thread, no_op_not_running, }; struct RecordingClearPlan { bool stop_running_recording = false; bool delete_recorded_files = false; int frame_count_after_clear = 0; }; struct RecordingExportPlan { std::size_t frame_count = 0; int progress_total = 0; }; struct RecordingWorkerIterationPlan { bool continue_running = true; bool encode_frame = false; bool clear_dirty_stroke = false; bool update_frame_label = false; }; class RecordingServices { public: virtual ~RecordingServices() = default; virtual void start_thread() = 0; virtual void stop_thread() = 0; virtual void delete_recorded_files() = 0; virtual void set_frame_count(int frame_count) = 0; virtual void update_frame_label() = 0; virtual void begin_export(int progress_total) = 0; virtual void write_mp4(std::string_view path) = 0; virtual void end_export() = 0; }; [[nodiscard]] constexpr RecordingStartAction plan_recording_start(bool is_running) noexcept { return is_running ? RecordingStartAction::no_op_already_running : RecordingStartAction::start_thread; } [[nodiscard]] constexpr RecordingStopAction plan_recording_stop(bool is_running) noexcept { return is_running ? RecordingStopAction::stop_thread : RecordingStopAction::no_op_not_running; } [[nodiscard]] constexpr RecordingClearPlan plan_recording_clear( bool is_running, bool platform_deletes_recorded_files) noexcept { return { is_running, platform_deletes_recorded_files, 0, }; } [[nodiscard]] constexpr RecordingExportPlan plan_recording_export(std::size_t frame_count) noexcept { const auto max_progress_total = static_cast(std::numeric_limits::max()); return { frame_count, frame_count > max_progress_total ? std::numeric_limits::max() : static_cast(frame_count), }; } [[nodiscard]] inline AppProgressDialogPlan plan_recording_export_progress_dialog( const RecordingExportPlan& plan) { return plan_app_progress_dialog("Exporting MP4 movie", plan.progress_total); } [[nodiscard]] constexpr RecordingWorkerIterationPlan plan_recording_worker_iteration( bool is_running_after_wake, bool has_encoder, bool has_canvas_document) noexcept { const bool encode = is_running_after_wake && has_encoder && has_canvas_document; return { .continue_running = is_running_after_wake, .encode_frame = encode, .clear_dirty_stroke = encode, .update_frame_label = encode, }; } [[nodiscard]] inline pp::foundation::Status execute_recording_start_action( RecordingStartAction action, RecordingServices& services) { switch (action) { case RecordingStartAction::start_thread: services.start_thread(); return pp::foundation::Status::success(); case RecordingStartAction::no_op_already_running: return pp::foundation::Status::success(); } return pp::foundation::Status::invalid_argument("unknown recording start action"); } [[nodiscard]] inline pp::foundation::Status execute_recording_stop_action( RecordingStopAction action, RecordingServices& services) { switch (action) { case RecordingStopAction::stop_thread: services.stop_thread(); return pp::foundation::Status::success(); case RecordingStopAction::no_op_not_running: return pp::foundation::Status::success(); } return pp::foundation::Status::invalid_argument("unknown recording stop action"); } [[nodiscard]] inline pp::foundation::Status execute_recording_clear_plan( const RecordingClearPlan& plan, RecordingServices& services) { if (plan.stop_running_recording) { services.stop_thread(); } if (plan.delete_recorded_files) { services.delete_recorded_files(); } services.set_frame_count(plan.frame_count_after_clear); services.update_frame_label(); return pp::foundation::Status::success(); } [[nodiscard]] inline pp::foundation::Status execute_recording_export_plan( const RecordingExportPlan& plan, RecordingServices& services, std::string_view path) { services.begin_export(plan.progress_total); services.write_mp4(path); services.end_export(); return pp::foundation::Status::success(); } }