diff --git a/cmake/PanoPainterSources.cmake b/cmake/PanoPainterSources.cmake index e266dbb4..22402ed0 100644 --- a/cmake/PanoPainterSources.cmake +++ b/cmake/PanoPainterSources.cmake @@ -92,6 +92,7 @@ set(PP_PANOPAINTER_APP_SOURCES src/app_cloud.cpp src/app_commands.cpp src/app_dialogs.cpp + src/app_dialogs_export.cpp src/app_dialogs_info_openers.cpp src/app_events.cpp src/app_layout.cpp @@ -100,6 +101,7 @@ set(PP_PANOPAINTER_APP_SOURCES src/app_layout_tools_menu.cpp src/app_shaders.cpp src/app_vr.cpp + src/legacy_app_frame_services.cpp src/legacy_app_dialog_services.cpp src/legacy_app_dialog_services.h src/legacy_app_preference_services.cpp diff --git a/docs/modernization/roadmap.md b/docs/modernization/roadmap.md index 69076a2e..4f7afbc5 100644 --- a/docs/modernization/roadmap.md +++ b/docs/modernization/roadmap.md @@ -86,9 +86,9 @@ Current hotspot files: - `src/main.cpp`: 1117 lines - `src/node_panel_brush.cpp`: 1197 lines - `src/node_stroke_preview.cpp`: 933 lines -- `src/node_canvas.cpp`: 953 lines -- `src/app.cpp`: 950 lines -- `src/app_dialogs.cpp`: 789 lines +- `src/node_canvas.cpp`: 897 lines +- `src/app.cpp`: 502 lines +- `src/app_dialogs.cpp`: 441 lines Current architecture mismatches that must be treated as real blockers: @@ -139,7 +139,10 @@ Current architecture mismatches that must be treated as real blockers: `src/app_layout_about_layer_menu.cpp` and `App::init_menu_about()` plus `App::init_menu_layer()` are now thin call-throughs, while the informational overlay opener family now also lives in `src/app_dialogs_info_openers.cpp` - and the corresponding `App::dialog_*` entrypoints are thinner. + and the corresponding `App::dialog_*` entrypoints are thinner, while the + export/video/PPBR dialog family now also lives in + `src/app_dialogs_export.cpp` and those `App::dialog_*` entrypoints are + thinner too. - `App`, `Canvas`, `Node`, retained workers, and platform entrypoints still use global singleton reach, raw observer pointers, retained static worker ownership in several app families, and ad hoc mutex/condition-variable @@ -172,7 +175,12 @@ Current architecture mismatches that must be treated as real blockers: while `App::rec_loop()` now delegates worker-iteration orchestration into the retained recording bridge, `App::update_rec_frames()` now delegates recording label refresh through that same retained recording path, and the - canvas state-management cluster for picking, clear/clear-all, layer + UI observer math, repeated UI child traversal, and canvas toolbar refresh + now live in `src/legacy_app_frame_services.cpp` instead of staying inline in + `src/app.cpp`, while the larger document/export/save/open/thumbnail + document-IO cluster now lives in `src/legacy_canvas_document_io_services.cpp` + and `src/app.cpp` is materially thinner, + while the canvas state-management cluster for picking, clear/clear-all, layer add/remove/order/lookups, animation frame control, resize, and snapshot save/restore now lives in `src/legacy_canvas_state_services.cpp` instead of `src/canvas.cpp`, while the larger import/export/save/open/thumbnail diff --git a/docs/modernization/tasks.md b/docs/modernization/tasks.md index 397263d6..241bb26c 100644 --- a/docs/modernization/tasks.md +++ b/docs/modernization/tasks.md @@ -199,7 +199,12 @@ Current slice: - `NodeCanvas` non-`draw_merged` per-layer temporary erase/paint, layer-texture, and blend setup now also route through `make_legacy_canvas_draw_merge_layer_path_gl_execution(...)`, but the node - still owns the remaining draw lambdas and broader renderer-state shell. + still owned the remaining draw lambdas and broader renderer-state shell. +- `NodeCanvas` now routes that remaining non-`draw_merged` per-layer blend, + composite, debug-outline, and frame callback assembly through the local + `make_node_canvas_layer_path_execution(...)` helper, which materially thins + `NodeCanvas::draw()` even though the broader draw loop still lives in + `src/node_canvas.cpp`. Write scope: - `src/node_stroke_preview.cpp` @@ -322,7 +327,7 @@ Mini-model packet: #### ARC-APP-002 - Split `app_dialogs.cpp` Into Workflow Adapters And Widget Openers -Status: Ready +Status: In Progress Why now: `src/app_dialogs.cpp` still mixes document workflow decisions, export routing, @@ -334,6 +339,10 @@ Current slice: and the corresponding `App::dialog_*` entrypoints are now thin call-throughs, but document/export workflow and retained dialog execution are still inline in `src/app_dialogs.cpp`. +- Export, video-export, and PPBR export entrypoints now also live in + `src/app_dialogs_export.cpp`, and the corresponding `App::dialog_*` + entrypoints are now thin call-throughs, but new/open/save/browse/resize and + retained dialog execution are still inline in `src/app_dialogs.cpp`. Write scope: - `src/app_dialogs.cpp` @@ -364,11 +373,19 @@ Mini-model packet: #### ARC-APP-003 - Reduce `app.cpp` To Frame, Queue, And Composition Shell -Status: Ready +Status: In Progress Why now: `src/app.cpp` still carries startup, frame flow, queue draining, recording, -observer math, and composition logic in one 950-line file. +and composition logic in one 502-line file. + +Current slice: +- UI observer math now routes through `src/legacy_app_frame_services.cpp` + instead of staying inline in `src/app.cpp`. +- The repeated UI child traversal in `App::draw()` now routes through the same + retained helper. +- Canvas toolbar refresh in `App::update()` now also routes through that helper + file, materially shrinking `src/app.cpp`. Write scope: - `src/app.cpp` diff --git a/src/app.cpp b/src/app.cpp index 51042f8c..28ac9fbb 100644 --- a/src/app.cpp +++ b/src/app.cpp @@ -33,37 +33,6 @@ App* App::I = nullptr; // singleton namespace { - -pp::app::CanvasToolMode canvas_tool_mode_from_canvas_mode(kCanvasMode mode) noexcept -{ - switch (mode) { - case kCanvasMode::Draw: - return pp::app::CanvasToolMode::draw; - case kCanvasMode::Erase: - return pp::app::CanvasToolMode::erase; - case kCanvasMode::Line: - return pp::app::CanvasToolMode::line; - case kCanvasMode::Camera: - return pp::app::CanvasToolMode::camera; - case kCanvasMode::Grid: - return pp::app::CanvasToolMode::grid; - case kCanvasMode::Copy: - return pp::app::CanvasToolMode::copy; - case kCanvasMode::Cut: - return pp::app::CanvasToolMode::cut; - case kCanvasMode::Fill: - return pp::app::CanvasToolMode::fill; - case kCanvasMode::MaskFree: - return pp::app::CanvasToolMode::mask_free; - case kCanvasMode::MaskLine: - return pp::app::CanvasToolMode::mask_line; - case kCanvasMode::FloodFill: - return pp::app::CanvasToolMode::flood_fill; - default: - return pp::app::CanvasToolMode::draw; - } -} - void apply_app_viewport(pp::renderer::gl::OpenGlViewportRect viewport) { const auto status = pp::renderer::gl::apply_opengl_viewport( @@ -215,6 +184,9 @@ namespace pp::panopainter { bool process_legacy_recording_worker_iteration(App& app); void update_legacy_recording_frame_label(App& app); + bool update_legacy_app_ui_observer(App& app, Node* n); + void watch_legacy_app_ui_children(App& app, const std::function& observer, bool skip_first_main_child); + void update_legacy_canvas_toolbar(App& app); } bool App::check_license() @@ -359,67 +331,7 @@ void App::async_swap() bool App::update_ui_observer(Node *n) { - std::vector parent_clips; - if (n) { - for (Node* p = n->m_parent; p; p = p->m_parent) { - parent_clips.push_back(pp::app::AppUiObserverParentClip { - .clip = pp::app::AppUiObserverRect { - .x = p->m_clip_uncut.x, - .y = p->m_clip_uncut.y, - .width = p->m_clip_uncut.z, - .height = p->m_clip_uncut.w, - }, - .padding_top = YGNodeLayoutGetPadding(p->y_node, YGEdgeTop), - .padding_right = YGNodeLayoutGetPadding(p->y_node, YGEdgeRight), - .padding_bottom = YGNodeLayoutGetPadding(p->y_node, YGEdgeBottom), - .padding_left = YGNodeLayoutGetPadding(p->y_node, YGEdgeLeft), - }); - } - } - - const auto plan = pp::app::plan_app_ui_observer( - n != nullptr, - n && n->m_display, - n && n->m_on_screen, - n - ? pp::app::AppUiObserverRect { - .x = n->m_clip_uncut.x, - .y = n->m_clip_uncut.y, - .width = n->m_clip_uncut.z, - .height = n->m_clip_uncut.w, - } - : pp::app::AppUiObserverRect {}, - parent_clips, - height, - zoom, - off_x, - off_y); - if (!plan) { - LOG("UI observer plan failed: %s", plan.status().message); - return false; - } - - if (!n) - return false; - - if (plan.value().notify_leave_screen) - n->handle_on_screen(true, false); - if (plan.value().notify_enter_screen) - n->handle_on_screen(false, true); - n->m_on_screen = plan.value().next_on_screen; - - if (!plan.value().draw_node) - return false; - - apply_app_scissor(pp::renderer::gl::OpenGlScissorRect { - .enabled = 1U, - .x = plan.value().scissor_x, - .y = plan.value().scissor_y, - .width = plan.value().scissor_width, - .height = plan.value().scissor_height, - }); - n->draw(); - return true; + return pp::panopainter::update_legacy_app_ui_observer(*this, n); } void App::draw(float dt) @@ -446,11 +358,7 @@ void App::draw(float dt) .height = static_cast(uirtt.getHeight()), }); apply_app_scissor_test(true); - for (int i = 1; i < layout[main_id]->m_children.size(); i++) - layout[main_id]->m_children[i]->watch(observer); - for (int i = 0; layout_designer.get(main_id) && i < layout_designer[main_id]->m_children.size(); i++) - layout_designer[main_id]->m_children[i]->watch(observer); - //msgbox->watch(observer); + pp::panopainter::watch_legacy_app_ui_children(*this, observer, true); apply_app_scissor_test(false); uirtt.unbindFramebuffer(); } @@ -465,11 +373,7 @@ void App::draw(float dt) .height = static_cast(height), }); apply_app_scissor_test(true); - for (int i = 0; i < layout[main_id]->m_children.size(); i++) - layout[main_id]->m_children[i]->watch(observer); - for (int i = 0; layout_designer.get(main_id) && i < layout_designer[main_id]->m_children.size(); i++) - layout_designer[main_id]->m_children[i]->watch(observer); - //msgbox->watch(observer); + pp::panopainter::watch_legacy_app_ui_children(*this, observer, false); apply_app_scissor_test(false); } @@ -492,28 +396,7 @@ void App::update(float dt) if (!update_plan.refresh_canvas_toolbar) return; - { - auto mode = Canvas::I->m_current_mode; - - CanvasModePen* pm = (CanvasModePen*)canvas->m_canvas->modes[(int)kCanvasMode::Draw][0]; - const auto toolbar = pp::app::plan_canvas_tool_button_state( - canvas_tool_mode_from_canvas_mode(mode), - pm && pm->m_picking, - canvas->m_canvas->m_touch_lock); - layout[main_id]->find("btn-pick")->set_active(toolbar.pick_active); - layout[main_id]->find("btn-touchlock")->set_active(toolbar.touch_lock_active); - - layout[main_id]->find("btn-pen")->set_active(toolbar.pen_active); - layout[main_id]->find("btn-erase")->set_active(toolbar.erase_active); - layout[main_id]->find("btn-cam")->set_active(toolbar.camera_active); - layout[main_id]->find("btn-line")->set_active(toolbar.line_active); - layout[main_id]->find("btn-grid")->set_active(toolbar.grid_active); - layout[main_id]->find("btn-copy")->set_active(toolbar.copy_active); - layout[main_id]->find("btn-cut")->set_active(toolbar.cut_active); - layout[main_id]->find("btn-mask-free")->set_active(toolbar.mask_free_active); - layout[main_id]->find("btn-mask-line")->set_active(toolbar.mask_line_active); - layout[main_id]->find("btn-bucket")->set_active(toolbar.flood_fill_active); - } + pp::panopainter::update_legacy_canvas_toolbar(*this); } void App::terminate() diff --git a/src/app_dialogs.cpp b/src/app_dialogs.cpp index a19badbf..65e923d4 100644 --- a/src/app_dialogs.cpp +++ b/src/app_dialogs.cpp @@ -16,13 +16,6 @@ #include "node_dialog_browse.h" #include "node_dialog_resize.h" #include "node_dialog_cloud.h" -#include "node_dialog_export_ppbr.h" - -#include -#define MP4V2_NO_STDINT_DEFS -#include - -#include #ifdef __QUEST__ #include "oculus_vr.h" @@ -34,95 +27,18 @@ void open_changelog_dialog(App& app); void open_about_dialog(App& app); void open_whatsnew_dialog(App& app, bool force_show); void open_shortcuts_dialog(App& app); +void open_document_export_dialog(App& app, std::string ext); +void open_document_export_layers_dialog(App& app); +void open_document_export_anim_frames_dialog(App& app); +void open_document_export_depth_dialog(App& app); +void open_document_export_cube_faces_dialog(App& app); +void open_document_timelapse_export_dialog(App& app); +void open_document_export_mp4_dialog(App& app); +void open_ppbr_export_dialog(App& app); } 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: - { - const auto plan = pp::app::plan_document_export_license_disabled_dialog(); - app.message_box(plan.title, plan.message, plan.show_cancel); - 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 auto plan = pp::app::plan_document_export_collection_target( - kind, - app.uses_work_directory_document_export_collections()); - const auto success_kind = pp::app::document_export_collection_success_kind(kind); - - 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) { - const auto dialog = pp::app::plan_document_export_failure_dialog(success_kind, target.status().message); - app.message_box(dialog.title, dialog.message, dialog.show_cancel); - return; - } - - const auto status = pp::panopainter::execute_legacy_document_export_collection( - app, - plan.kind, - target.value()); - if (!status.ok()) - LOG( - "%s: %s", - pp::app::document_export_execution_log_message( - kind == pp::app::DocumentExportCollectionKind::layers - ? pp::app::DocumentExportExecutionKind::layers_collection - : pp::app::DocumentExportExecutionKind::animation_frames_collection), - status.message); - return; - } - - app.pick_dir([ - &app, - kind = plan.kind, - success_kind - ](std::string path) { - const auto target = pp::app::make_document_export_stem_target(path, app.doc_name); - if (!target) { - const auto dialog = pp::app::plan_document_export_failure_dialog(success_kind, target.status().message); - app.message_box(dialog.title, dialog.message, dialog.show_cancel); - return; - } - - const auto status = pp::panopainter::execute_legacy_document_export_stem( - app, - kind, - target.value()); - if (!status.ok()) - LOG( - "%s: %s", - pp::app::document_export_execution_log_message( - kind == pp::app::DocumentExportCollectionKind::layers - ? pp::app::DocumentExportExecutionKind::layers_stem - : pp::app::DocumentExportExecutionKind::animation_frames_stem), - status.message); - }); -} - void wire_document_browse_dialog_actions( App& app, const std::shared_ptr& dialog, @@ -453,59 +369,22 @@ void App::dialog_save() 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) { - const auto dialog = pp::app::plan_document_export_failure_dialog( - pp::app::DocumentExportSuccessKind::equirectangular, - target.status().message); - message_box(dialog.title, dialog.message, dialog.show_cancel); - return; - } - - const auto status = pp::panopainter::execute_legacy_document_export_file(*this, target.value()); - if (!status.ok()) - LOG( - "%s: %s", - pp::app::document_export_execution_log_message( - pp::app::DocumentExportExecutionKind::equirectangular_file), - status.message); + pp::panopainter::open_document_export_dialog(*this, ext); } void App::dialog_export_layers() { - if (!can_start_document_export(*this, true)) - return; - - start_document_export_collection( - *this, - pp::app::DocumentExportCollectionKind::layers); + pp::panopainter::open_document_export_layers_dialog(*this); } void App::dialog_export_anim_frames() { - if (!can_start_document_export(*this, true)) - return; - - start_document_export_collection( - *this, - pp::app::DocumentExportCollectionKind::animation_frames); + pp::panopainter::open_document_export_anim_frames_dialog(*this); } 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( - "%s: %s", - pp::app::document_export_execution_log_message(pp::app::DocumentExportExecutionKind::depth), - status.message); + pp::panopainter::open_document_export_depth_dialog(*this); } void App::dialog_resize() @@ -551,15 +430,7 @@ void App::dialog_resize() 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( - "%s: %s", - pp::app::document_export_execution_log_message(pp::app::DocumentExportExecutionKind::cube_faces), - status.message); + pp::panopainter::open_document_export_cube_faces_dialog(*this); } void App::dialog_layer_rename() @@ -613,169 +484,17 @@ void App::dialog_preset_download() void App::dialog_ppbr_export() { - auto* overlay_anchor = layout[main_id]; - if (!overlay_anchor) { - LOG("PPBR export dialog open failed: main layout anchor is missing"); - return; - } - - auto dialog = pp::panopainter::make_legacy_overlay_node(*this); - - const auto overlay = pp::panopainter::open_legacy_overlay_node_with_handle(*overlay_anchor, dialog); - if (!overlay) { - LOG("PPBR export dialog open failed: %s", overlay.status().message); - return; - } - const auto overlay_handle = overlay.value(); - - const auto close_dialog = [overlay_anchor, overlay_handle]() { - const auto close_status = pp::panopainter::close_legacy_overlay_node(*overlay_anchor, overlay_handle); - (void)close_status; - }; - - 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); - }); - }; - dialog->btn_cancel->on_click = [close_dialog](Node*) - { - close_dialog(); - }; + pp::panopainter::open_ppbr_export_dialog(*this); } 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) { - const auto dialog = pp::app::plan_document_export_failure_dialog( - pp::app::DocumentExportSuccessKind::timelapse, - target.status().message); - message_box(dialog.title, dialog.message, dialog.show_cancel); - 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( - "%s: %s", - pp::app::document_export_execution_log_message( - pp::app::DocumentExportExecutionKind::timelapse), - 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( - "%s: %s", - pp::app::document_export_execution_log_message(pp::app::DocumentExportExecutionKind::timelapse), - status.message); - }); + pp::panopainter::open_document_timelapse_export_dialog(*this); } 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) { - const auto dialog = pp::app::plan_document_export_failure_dialog( - pp::app::DocumentExportSuccessKind::animation_mp4, - target.status().message); - message_box(dialog.title, dialog.message, dialog.show_cancel); - 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( - "%s: %s", - pp::app::document_export_execution_log_message( - pp::app::DocumentExportExecutionKind::animation_mp4), - 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( - "%s: %s", - pp::app::document_export_execution_log_message(pp::app::DocumentExportExecutionKind::animation_mp4), - status.message); - }); + pp::panopainter::open_document_export_mp4_dialog(*this); } void App::dialog_whatsnew(bool force_show) diff --git a/src/app_dialogs_export.cpp b/src/app_dialogs_export.cpp new file mode 100644 index 00000000..d3fa09c4 --- /dev/null +++ b/src/app_dialogs_export.cpp @@ -0,0 +1,316 @@ +#include "pch.h" +#include "app.h" +#include "app_core/app_dialog.h" +#include "app_core/document_export.h" +#include "legacy_brush_package_export_services.h" +#include "legacy_document_export_services.h" +#include "legacy_ui_overlay_services.h" +#include "node_dialog_export_ppbr.h" + +#include +#define MP4V2_NO_STDINT_DEFS +#include + +#include + +namespace pp::panopainter { + +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: + { + const auto plan = pp::app::plan_document_export_license_disabled_dialog(); + app.message_box(plan.title, plan.message, plan.show_cancel); + 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 auto plan = pp::app::plan_document_export_collection_target( + kind, + app.uses_work_directory_document_export_collections()); + const auto success_kind = pp::app::document_export_collection_success_kind(kind); + + 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) { + const auto dialog = pp::app::plan_document_export_failure_dialog(success_kind, target.status().message); + app.message_box(dialog.title, dialog.message, dialog.show_cancel); + return; + } + + const auto status = pp::panopainter::execute_legacy_document_export_collection( + app, + plan.kind, + target.value()); + if (!status.ok()) + LOG( + "%s: %s", + pp::app::document_export_execution_log_message( + kind == pp::app::DocumentExportCollectionKind::layers + ? pp::app::DocumentExportExecutionKind::layers_collection + : pp::app::DocumentExportExecutionKind::animation_frames_collection), + status.message); + return; + } + + app.pick_dir([ + &app, + kind = plan.kind, + success_kind + ](std::string path) { + const auto target = pp::app::make_document_export_stem_target(path, app.doc_name); + if (!target) { + const auto dialog = pp::app::plan_document_export_failure_dialog(success_kind, target.status().message); + app.message_box(dialog.title, dialog.message, dialog.show_cancel); + return; + } + + const auto status = pp::panopainter::execute_legacy_document_export_stem( + app, + kind, + target.value()); + if (!status.ok()) + LOG( + "%s: %s", + pp::app::document_export_execution_log_message( + kind == pp::app::DocumentExportCollectionKind::layers + ? pp::app::DocumentExportExecutionKind::layers_stem + : pp::app::DocumentExportExecutionKind::animation_frames_stem), + status.message); + }); +} + +void start_document_video_export( + App& app, + pp::app::DocumentVideoExportKind kind, + pp::app::DocumentExportSuccessKind success_kind, + const char* suffix, + pp::app::DocumentExportExecutionKind execution_kind) +{ + if (!can_start_document_export(app, false)) + return; + + if (app.uses_prepared_file_writes()) + { + const auto target = pp::app::make_document_export_suggested_name(app.doc_name, suffix); + if (!target) { + const auto dialog = pp::app::plan_document_export_failure_dialog( + success_kind, + target.status().message); + app.message_box(dialog.title, dialog.message, dialog.show_cancel); + return; + } + + app.pick_file_save("mp4", target.value().name, + [&app, kind, execution_kind](std::string path) { + const auto status = pp::panopainter::execute_legacy_document_video_export( + app, + kind, + path, + false); + if (!status.ok()) + LOG( + "%s: %s", + pp::app::document_export_execution_log_message(execution_kind), + status.message); + }, + [](const std::string& path, bool saved) { + (void)path; + (void)saved; + } + ); + return; + } + + app.pick_file_save({ "mp4" }, [&app, kind, execution_kind](std::string path) { + const auto status = pp::panopainter::execute_legacy_document_video_export( + app, + kind, + path, + true); + if (!status.ok()) + LOG( + "%s: %s", + pp::app::document_export_execution_log_message(execution_kind), + status.message); + }); +} + +} // namespace + +void open_document_export_dialog(App& app, std::string ext) +{ + if (!can_start_document_export(app, true)) + return; + + // TODO: use picker + const auto target = pp::app::make_document_export_file_target(app.work_path, app.doc_name, ext); + if (!target) { + const auto dialog = pp::app::plan_document_export_failure_dialog( + pp::app::DocumentExportSuccessKind::equirectangular, + target.status().message); + app.message_box(dialog.title, dialog.message, dialog.show_cancel); + return; + } + + const auto status = pp::panopainter::execute_legacy_document_export_file(app, target.value()); + if (!status.ok()) + LOG( + "%s: %s", + pp::app::document_export_execution_log_message( + pp::app::DocumentExportExecutionKind::equirectangular_file), + status.message); +} + +void open_document_export_layers_dialog(App& app) +{ + if (!can_start_document_export(app, true)) + return; + + start_document_export_collection( + app, + pp::app::DocumentExportCollectionKind::layers); +} + +void open_document_export_anim_frames_dialog(App& app) +{ + if (!can_start_document_export(app, true)) + return; + + start_document_export_collection( + app, + pp::app::DocumentExportCollectionKind::animation_frames); +} + +void open_document_export_depth_dialog(App& app) +{ + if (!can_start_document_export(app, true)) + return; + + const auto status = pp::panopainter::execute_legacy_document_export_depth(app, app.doc_name); + if (!status.ok()) + LOG( + "%s: %s", + pp::app::document_export_execution_log_message(pp::app::DocumentExportExecutionKind::depth), + status.message); +} + +void open_document_export_cube_faces_dialog(App& app) +{ + if (!can_start_document_export(app, false)) + return; + + const auto status = pp::panopainter::execute_legacy_document_export_cube_faces(app, app.doc_name); + if (!status.ok()) + LOG( + "%s: %s", + pp::app::document_export_execution_log_message(pp::app::DocumentExportExecutionKind::cube_faces), + status.message); +} + +void open_document_timelapse_export_dialog(App& app) +{ + start_document_video_export( + app, + pp::app::DocumentVideoExportKind::timelapse, + pp::app::DocumentExportSuccessKind::timelapse, + "-timelapse", + pp::app::DocumentExportExecutionKind::timelapse); +} + +void open_document_export_mp4_dialog(App& app) +{ + start_document_video_export( + app, + pp::app::DocumentVideoExportKind::animation_mp4, + pp::app::DocumentExportSuccessKind::animation_mp4, + "-animation", + pp::app::DocumentExportExecutionKind::animation_mp4); +} + +void open_ppbr_export_dialog(App& app) +{ + auto* overlay_anchor = app.layout[app.main_id]; + if (!overlay_anchor) { + LOG("PPBR export dialog open failed: main layout anchor is missing"); + return; + } + + auto dialog = pp::panopainter::make_legacy_overlay_node(app); + + const auto overlay = pp::panopainter::open_legacy_overlay_node_with_handle(*overlay_anchor, dialog); + if (!overlay) { + LOG("PPBR export dialog open failed: %s", overlay.status().message); + return; + } + const auto overlay_handle = overlay.value(); + + const auto close_dialog = [overlay_anchor, overlay_handle]() { + const auto close_status = pp::panopainter::close_legacy_overlay_node(*overlay_anchor, overlay_handle); + (void)close_status; + }; + + dialog->btn_ok->on_click = [&app, dialog] (Node*) { + const auto request = pp::panopainter::make_legacy_brush_package_export_request(*dialog); + + if (app.uses_prepared_file_writes()) + { + app.pick_file_save("ppbr", "exported-brushes", + [&app, dialog, request] (std::string path) { + const auto status = pp::panopainter::execute_legacy_brush_package_export( + app, + *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; + } + + app.pick_file_save({ "ppbr" }, [&app, dialog, request] (std::string path) { + const auto status = pp::panopainter::execute_legacy_brush_package_export( + app, + *dialog, + request, + path, + pp::panopainter::LegacyBrushPackageExportMode::desktop_async_close_and_message); + if (!status.ok()) + LOG("PPBR export failed: %s", status.message); + }); + }; + dialog->btn_cancel->on_click = [close_dialog](Node*) + { + close_dialog(); + }; +} + +} // namespace pp::panopainter diff --git a/src/legacy_app_frame_services.cpp b/src/legacy_app_frame_services.cpp new file mode 100644 index 00000000..3ee01a25 --- /dev/null +++ b/src/legacy_app_frame_services.cpp @@ -0,0 +1,164 @@ +#include "pch.h" + +#include "app.h" +#include "app_core/app_frame.h" +#include "app_core/canvas_tool_ui.h" +#include "legacy_ui_gl_dispatch.h" +#include "renderer_gl/opengl_capabilities.h" + +namespace { + +pp::app::CanvasToolMode canvas_tool_mode_from_canvas_mode(kCanvasMode mode) noexcept +{ + switch (mode) { + case kCanvasMode::Draw: + return pp::app::CanvasToolMode::draw; + case kCanvasMode::Erase: + return pp::app::CanvasToolMode::erase; + case kCanvasMode::Line: + return pp::app::CanvasToolMode::line; + case kCanvasMode::Camera: + return pp::app::CanvasToolMode::camera; + case kCanvasMode::Grid: + return pp::app::CanvasToolMode::grid; + case kCanvasMode::Copy: + return pp::app::CanvasToolMode::copy; + case kCanvasMode::Cut: + return pp::app::CanvasToolMode::cut; + case kCanvasMode::Fill: + return pp::app::CanvasToolMode::fill; + case kCanvasMode::MaskFree: + return pp::app::CanvasToolMode::mask_free; + case kCanvasMode::MaskLine: + return pp::app::CanvasToolMode::mask_line; + case kCanvasMode::FloodFill: + return pp::app::CanvasToolMode::flood_fill; + default: + return pp::app::CanvasToolMode::draw; + } +} + +void apply_legacy_app_scissor(pp::renderer::gl::OpenGlScissorRect scissor) +{ + const auto status = pp::renderer::gl::apply_opengl_scissor_rect( + scissor, + pp::renderer::gl::OpenGlScissorDispatch { + .enable = pp::legacy::ui_gl::enable_opengl_state, + .disable = pp::legacy::ui_gl::disable_opengl_state, + .scissor = pp::legacy::ui_gl::set_opengl_scissor, + }); + if (!status.ok()) + LOG("OpenGL scissor failed: %s", status.message); +} + +void watch_layout_children(LayoutManager* layout, uint16_t main_id, int start_index, const std::function& observer) +{ + if (!layout) + return; + + if (auto* root = layout->get(main_id)) { + for (int i = start_index; i < static_cast(root->m_children.size()); ++i) + root->m_children[i]->watch(observer); + } +} + +} + +namespace pp::panopainter +{ + +bool update_legacy_app_ui_observer(App& app, Node* n) +{ + std::vector parent_clips; + if (n) { + for (Node* p = n->m_parent; p; p = p->m_parent) { + parent_clips.push_back(pp::app::AppUiObserverParentClip { + .clip = pp::app::AppUiObserverRect { + .x = p->m_clip_uncut.x, + .y = p->m_clip_uncut.y, + .width = p->m_clip_uncut.z, + .height = p->m_clip_uncut.w, + }, + .padding_top = YGNodeLayoutGetPadding(p->y_node, YGEdgeTop), + .padding_right = YGNodeLayoutGetPadding(p->y_node, YGEdgeRight), + .padding_bottom = YGNodeLayoutGetPadding(p->y_node, YGEdgeBottom), + .padding_left = YGNodeLayoutGetPadding(p->y_node, YGEdgeLeft), + }); + } + } + + const auto plan = pp::app::plan_app_ui_observer( + n != nullptr, + n && n->m_display, + n && n->m_on_screen, + n + ? pp::app::AppUiObserverRect { + .x = n->m_clip_uncut.x, + .y = n->m_clip_uncut.y, + .width = n->m_clip_uncut.z, + .height = n->m_clip_uncut.w, + } + : pp::app::AppUiObserverRect {}, + parent_clips, + app.height, + app.zoom, + app.off_x, + app.off_y); + if (!plan) { + LOG("UI observer plan failed: %s", plan.status().message); + return false; + } + + if (!n) + return false; + + if (plan.value().notify_leave_screen) + n->handle_on_screen(true, false); + if (plan.value().notify_enter_screen) + n->handle_on_screen(false, true); + n->m_on_screen = plan.value().next_on_screen; + + if (!plan.value().draw_node) + return false; + + apply_legacy_app_scissor(pp::renderer::gl::OpenGlScissorRect { + .enabled = 1U, + .x = plan.value().scissor_x, + .y = plan.value().scissor_y, + .width = plan.value().scissor_width, + .height = plan.value().scissor_height, + }); + n->draw(); + return true; +} + +void watch_legacy_app_ui_children(App& app, const std::function& observer, bool skip_first_main_child) +{ + watch_layout_children(&app.layout, app.main_id, skip_first_main_child ? 1 : 0, observer); + watch_layout_children(&app.layout_designer, app.main_id, 0, observer); +} + +void update_legacy_canvas_toolbar(App& app) +{ + const auto mode = Canvas::I->m_current_mode; + auto* pm = app.canvas->m_canvas->get_mode(); + const auto toolbar = pp::app::plan_canvas_tool_button_state( + canvas_tool_mode_from_canvas_mode(mode), + pm && pm->m_picking, + app.canvas->m_canvas->m_touch_lock); + app.layout[app.main_id]->find("btn-pick")->set_active(toolbar.pick_active); + app.layout[app.main_id]->find("btn-touchlock")->set_active(toolbar.touch_lock_active); + + app.layout[app.main_id]->find("btn-pen")->set_active(toolbar.pen_active); + app.layout[app.main_id]->find("btn-erase")->set_active(toolbar.erase_active); + app.layout[app.main_id]->find("btn-cam")->set_active(toolbar.camera_active); + app.layout[app.main_id]->find("btn-line")->set_active(toolbar.line_active); + app.layout[app.main_id]->find("btn-grid")->set_active(toolbar.grid_active); + app.layout[app.main_id]->find("btn-copy")->set_active(toolbar.copy_active); + app.layout[app.main_id]->find("btn-cut")->set_active(toolbar.cut_active); + app.layout[app.main_id]->find("btn-mask-free")->set_active(toolbar.mask_free_active); + app.layout[app.main_id]->find("btn-mask-line")->set_active(toolbar.mask_line_active); + app.layout[app.main_id]->find("btn-bucket")->set_active(toolbar.flood_fill_active); +} + +} diff --git a/src/legacy_canvas_draw_merge_services.h b/src/legacy_canvas_draw_merge_services.h index 67be87d8..820aafd7 100644 --- a/src/legacy_canvas_draw_merge_services.h +++ b/src/legacy_canvas_draw_merge_services.h @@ -811,6 +811,82 @@ inline void execute_legacy_canvas_draw_merge_layer_path( }; } +template < + typename BindBlenderFramebuffer, + typename ClearBlenderFramebuffer, + typename UnbindBlenderFramebuffer, + typename BindSampler, + typename BindNearestSampler, + typename BindStencilSampler, + typename SetActiveTextureUnit, + typename BindTemporaryTexture, + typename UnbindTemporaryTexture, + typename BindSmaskTexture, + typename UnbindSmaskTexture, + typename BindTemporaryDualTexture, + typename UnbindTemporaryDualTexture, + typename BindPatternTexture, + typename DrawFace, + typename BindBlenderTexture, + typename UnbindBlenderTexture, + typename BindDestinationTexture, + typename UnbindDestinationTexture, + typename CopyDestinationFramebuffer, + typename DrawDebugOutline, + typename DrawFrame> +[[nodiscard]] inline LegacyCanvasDrawMergeLayerPathExecution make_legacy_canvas_draw_merge_layer_path_gl_execution( + const LegacyCanvasDrawMergeLayerPathGlUniforms& uniforms, + BindBlenderFramebuffer&& bind_blender_framebuffer, + ClearBlenderFramebuffer&& clear_blender_framebuffer, + UnbindBlenderFramebuffer&& unbind_blender_framebuffer, + BindSampler&& bind_sampler, + BindNearestSampler&& bind_nearest_sampler, + BindStencilSampler&& bind_stencil_sampler, + SetActiveTextureUnit&& set_active_texture_unit, + BindTemporaryTexture&& bind_temporary_texture, + UnbindTemporaryTexture&& unbind_temporary_texture, + BindSmaskTexture&& bind_smask_texture, + UnbindSmaskTexture&& unbind_smask_texture, + BindTemporaryDualTexture&& bind_temporary_dual_texture, + UnbindTemporaryDualTexture&& unbind_temporary_dual_texture, + BindPatternTexture&& bind_pattern_texture, + DrawFace&& draw_face, + BindBlenderTexture&& bind_blender_texture, + UnbindBlenderTexture&& unbind_blender_texture, + BindDestinationTexture&& bind_destination_texture, + UnbindDestinationTexture&& unbind_destination_texture, + CopyDestinationFramebuffer&& copy_destination_framebuffer, + DrawDebugOutline&& draw_debug_outline, + DrawFrame&& draw_frame) +{ + return make_legacy_canvas_draw_merge_layer_path_gl_execution( + uniforms, + LegacyCanvasDrawMergeLayerPathGlExecution { + .bind_blender_framebuffer = std::forward(bind_blender_framebuffer), + .clear_blender_framebuffer = std::forward(clear_blender_framebuffer), + .unbind_blender_framebuffer = std::forward(unbind_blender_framebuffer), + .bind_sampler = std::forward(bind_sampler), + .bind_nearest_sampler = std::forward(bind_nearest_sampler), + .bind_stencil_sampler = std::forward(bind_stencil_sampler), + .set_active_texture_unit = std::forward(set_active_texture_unit), + .bind_temporary_texture = std::forward(bind_temporary_texture), + .unbind_temporary_texture = std::forward(unbind_temporary_texture), + .bind_smask_texture = std::forward(bind_smask_texture), + .unbind_smask_texture = std::forward(unbind_smask_texture), + .bind_temporary_dual_texture = std::forward(bind_temporary_dual_texture), + .unbind_temporary_dual_texture = std::forward(unbind_temporary_dual_texture), + .bind_pattern_texture = std::forward(bind_pattern_texture), + .draw_face = std::forward(draw_face), + .bind_blender_texture = std::forward(bind_blender_texture), + .unbind_blender_texture = std::forward(unbind_blender_texture), + .bind_destination_texture = std::forward(bind_destination_texture), + .unbind_destination_texture = std::forward(unbind_destination_texture), + .copy_destination_framebuffer = std::forward(copy_destination_framebuffer), + .draw_debug_outline = std::forward(draw_debug_outline), + .draw_frame = std::forward(draw_frame), + }); +} + template inline void execute_legacy_canvas_draw_layer_traversal( size_t layer_count, diff --git a/src/node_canvas.cpp b/src/node_canvas.cpp index 2e6bff3f..bf7e0608 100644 --- a/src/node_canvas.cpp +++ b/src/node_canvas.cpp @@ -231,6 +231,170 @@ void run_canvas_tool_mode(pp::app::CanvasToolMode mode) LOG("Canvas input tool action failed: %s", status.message); } +pp::panopainter::LegacyCanvasDrawMergeLayerPathExecution make_node_canvas_layer_path_execution( + NodeCanvas& node_canvas, + size_t layer_index, + int plane_index, + const glm::mat4& plane_mvp_z, + const Brush* brush, + bool copy_blend_destination, + bool use_nearest_sampler) +{ + auto* layer = node_canvas.m_canvas->m_layers[layer_index].get(); + const auto plane_mvp = plane_mvp_z; + const auto brush_ptr = brush; + const auto smask_active = node_canvas.m_canvas->m_smask_active; + auto draw_layer_frame = pp::panopainter::make_legacy_canvas_draw_merge_layer_frame_draw( + layer, + &node_canvas.m_face_plane, + set_active_texture_unit, + plane_index, + layer->m_opacity); + + glm::vec2 patt_scale = glm::vec2(brush_ptr->m_pattern_scale); + if (brush_ptr->m_pattern_flipx) + patt_scale.x *= -1.f; + if (brush_ptr->m_pattern_flipy) + patt_scale.y *= -1.f; + + return pp::panopainter::make_legacy_canvas_draw_merge_layer_path_gl_execution( + { + .temporary_erase = { + .mvp = plane_mvp, + .texture_slot = 0, + .stroke_texture_slot = 1, + .mask_texture_slot = 2, + .mask_enabled = smask_active, + }, + .temporary_paint = { + .resolution = Canvas::I->m_size, + .pattern = { + .scale = patt_scale, + .invert = static_cast(brush_ptr->m_pattern_invert), + .brightness = brush_ptr->m_pattern_brightness, + .contrast = brush_ptr->m_pattern_contrast, + .depth = brush_ptr->m_pattern_depth, + .blend_mode = brush_ptr->m_pattern_blend_mode, + .offset = Canvas::I->m_pattern_offset, + }, + .mvp = plane_mvp, + .layer_alpha = 1.0f, + .alpha_lock = layer->m_alpha_locked, + .mask_enabled = smask_active, + .use_fragcoord = false, + .blend_mode = brush_ptr->m_blend_mode, + .use_dual = brush_ptr->m_dual_enabled, + .dual_blend_mode = brush_ptr->m_dual_blend_mode, + .dual_alpha = brush_ptr->m_dual_opacity, + .use_pattern = brush_ptr->m_pattern_enabled && !brush_ptr->m_pattern_eachsample, + }, + .layer_texture = { + .mvp = plane_mvp, + .texture_slot = 0, + .alpha = 1.f, + .highlight = layer->m_hightlight, + }, + .blend = { + .shader = { + .mvp = glm::ortho(-1, 1, -1, 1), + .texture_slot = 0, + .destination_texture_slot = 2, + .use_destination_texture = copy_blend_destination, + .blend_mode = layer->m_blend_mode, + .alpha = 1.f, + }, + .copy_destination = copy_blend_destination, + }, + .use_nearest_sampler = use_nearest_sampler, + .use_dual_texture = brush_ptr->m_dual_enabled, + }, + [&node_canvas] { + node_canvas.m_blender_rtt.bindFramebuffer(); + }, + [&node_canvas] { + node_canvas.m_blender_rtt.clear(); + }, + [&node_canvas] { + node_canvas.m_blender_rtt.unbindFramebuffer(); + }, + [&node_canvas](int unit) { + node_canvas.m_sampler.bind(unit); + }, + [&node_canvas](int unit) { + node_canvas.m_sampler_nearest.bind(unit); + }, + [&node_canvas](int unit) { + node_canvas.m_sampler_stencil.bind(unit); + }, + [](int unit) { + set_active_texture_unit(unit); + }, + [&node_canvas, plane_index] { + node_canvas.m_canvas->m_tmp[plane_index].bindTexture(); + }, + [&node_canvas, plane_index] { + node_canvas.m_canvas->m_tmp[plane_index].unbindTexture(); + }, + [&node_canvas, plane_index] { + node_canvas.m_canvas->m_smask.rtt(plane_index).bindTexture(); + }, + [&node_canvas, plane_index] { + node_canvas.m_canvas->m_smask.rtt(plane_index).unbindTexture(); + }, + [&node_canvas, plane_index] { + node_canvas.m_canvas->m_tmp_dual[plane_index].bindTexture(); + }, + [&node_canvas, plane_index] { + node_canvas.m_canvas->m_tmp_dual[plane_index].unbindTexture(); + }, + [brush_ptr] { + brush_ptr->m_pattern_texture ? brush_ptr->m_pattern_texture->bind() : unbind_texture_2d(); + }, + [&node_canvas] { + node_canvas.m_face_plane.draw_fill(); + }, + [&node_canvas] { + node_canvas.m_blender_rtt.bindTexture(); + }, + [&node_canvas] { + node_canvas.m_blender_rtt.unbindTexture(); + }, + [&node_canvas] { + node_canvas.m_blender_bg.bind(); + }, + [&node_canvas] { + node_canvas.m_blender_bg.unbind(); + }, + [&node_canvas] { + copy_framebuffer_to_texture_2d( + 0, + 0, + 0, + 0, + node_canvas.m_blender_bg.size().x, + node_canvas.m_blender_bg.size().y); + }, +#ifdef _DEBUG + [&node_canvas, layer_index, plane_index, plane_mvp] { + auto bb = node_canvas.m_canvas->m_layers[layer_index]->box(plane_index) / (float)node_canvas.m_canvas->m_layers[layer_index]->w; + glm::vec2 bbmin = xy(bb); + glm::vec2 bbsz = zw(bb) - xy(bb); + pp::panopainter::configure_legacy_ui_color_shader( + plane_mvp + * glm::translate(glm::vec3(bbmin * 2.f, 0)) + * glm::translate(glm::vec3(-1, -1, 0)) + * glm::scale(glm::vec3(bbsz, 1)) + * glm::translate(glm::vec3(1, 1, 0)), + { 1, 0, 0, 1 }); + node_canvas.m_face_plane.draw_stroke(); + }, +#else + [] { + }, +#endif + draw_layer_frame); +} + } Node* NodeCanvas::clone_instantiate() const @@ -467,161 +631,14 @@ void NodeCanvas::draw() glm::eulerAngleYXZ(yaw, pitch, roll) * m_canvas->m_plane_transform[plane_index] * glm::translate(glm::vec3(0, 0, -1)); - - const auto draw_layer_frame = pp::panopainter::make_legacy_canvas_draw_merge_layer_frame_draw( - m_canvas->m_layers[layer_index].get(), - &m_face_plane, - set_active_texture_unit, + const auto layer_path_execution = make_node_canvas_layer_path_execution( + *this, + layer_index, plane_index, - m_canvas->m_layers[layer_index]->m_opacity); - - glm::vec2 patt_scale = glm::vec2(b->m_pattern_scale); - if (b->m_pattern_flipx) - patt_scale.x *= -1.f; - if (b->m_pattern_flipy) - patt_scale.y *= -1.f; - - const auto layer_path_execution = pp::panopainter::make_legacy_canvas_draw_merge_layer_path_gl_execution( - { - .temporary_erase = { - .mvp = plane_mvp_z, - .texture_slot = 0, - .stroke_texture_slot = 1, - .mask_texture_slot = 2, - .mask_enabled = m_canvas->m_smask_active, - }, - .temporary_paint = { - .resolution = Canvas::I->m_size, - .pattern = { - .scale = patt_scale, - .invert = static_cast(b->m_pattern_invert), - .brightness = b->m_pattern_brightness, - .contrast = b->m_pattern_contrast, - .depth = b->m_pattern_depth, - .blend_mode = b->m_pattern_blend_mode, - .offset = Canvas::I->m_pattern_offset, - }, - .mvp = plane_mvp_z, - .layer_alpha = 1.0f, - .alpha_lock = m_canvas->m_layers[layer_index]->m_alpha_locked, - .mask_enabled = m_canvas->m_smask_active, - .use_fragcoord = false, - .blend_mode = b->m_blend_mode, - .use_dual = b->m_dual_enabled, - .dual_blend_mode = b->m_dual_blend_mode, - .dual_alpha = b->m_dual_opacity, - .use_pattern = b->m_pattern_enabled && !b->m_pattern_eachsample, - }, - .layer_texture = { - .mvp = plane_mvp_z, - .texture_slot = 0, - .alpha = 1.f, - .highlight = m_canvas->m_layers[layer_index]->m_hightlight, - }, - .blend = { - .shader = { - .mvp = glm::ortho(-1, 1, -1, 1), - .texture_slot = 0, - .destination_texture_slot = 2, - .use_destination_texture = copy_blend_destination, - .blend_mode = m_canvas->m_layers[layer_index]->m_blend_mode, - .alpha = 1.f, - }, - .copy_destination = copy_blend_destination, - }, - .use_nearest_sampler = m_canvas->m_cam_fov < 20.f, - .use_dual_texture = b->m_dual_enabled, - }, - { - .bind_blender_framebuffer = [&] { - m_blender_rtt.bindFramebuffer(); - }, - .clear_blender_framebuffer = [&] { - m_blender_rtt.clear(); - }, - .unbind_blender_framebuffer = [&] { - m_blender_rtt.unbindFramebuffer(); - }, - .bind_sampler = [&](int unit) { - m_sampler.bind(unit); - }, - .bind_nearest_sampler = [&](int unit) { - m_sampler_nearest.bind(unit); - }, - .bind_stencil_sampler = [&](int unit) { - m_sampler_stencil.bind(unit); - }, - .set_active_texture_unit = [&](int unit) { - set_active_texture_unit(unit); - }, - .bind_temporary_texture = [&] { - m_canvas->m_tmp[plane_index].bindTexture(); - }, - .unbind_temporary_texture = [&] { - m_canvas->m_tmp[plane_index].unbindTexture(); - }, - .bind_smask_texture = [&] { - m_canvas->m_smask.rtt(plane_index).bindTexture(); - }, - .unbind_smask_texture = [&] { - m_canvas->m_smask.rtt(plane_index).unbindTexture(); - }, - .bind_temporary_dual_texture = [&] { - m_canvas->m_tmp_dual[plane_index].bindTexture(); - }, - .unbind_temporary_dual_texture = [&] { - m_canvas->m_tmp_dual[plane_index].unbindTexture(); - }, - .bind_pattern_texture = [&] { - b->m_pattern_texture ? - b->m_pattern_texture->bind() : - unbind_texture_2d(); - }, - .draw_face = [&] { - m_face_plane.draw_fill(); - }, - .bind_blender_texture = [&] { - m_blender_rtt.bindTexture(); - }, - .unbind_blender_texture = [&] { - m_blender_rtt.unbindTexture(); - }, - .bind_destination_texture = [&] { - m_blender_bg.bind(); - }, - .unbind_destination_texture = [&] { - m_blender_bg.unbind(); - }, - .copy_destination_framebuffer = [&] { - copy_framebuffer_to_texture_2d( - 0, - 0, - 0, - 0, - m_blender_bg.size().x, - m_blender_bg.size().y); - }, - .draw_debug_outline = -#ifdef _DEBUG - [&] { - auto bb = m_canvas->m_layers[layer_index]->box(plane_index) / (float)m_canvas->m_layers[layer_index]->w; - glm::vec2 bbmin = xy(bb); - glm::vec2 bbsz = zw(bb) - xy(bb); - pp::panopainter::configure_legacy_ui_color_shader( - plane_mvp_z - * glm::translate(glm::vec3(bbmin * 2.f, 0)) - * glm::translate(glm::vec3(-1, -1, 0)) - * glm::scale(glm::vec3(bbsz, 1)) - * glm::translate(glm::vec3(1, 1, 0)), - { 1, 0, 0, 1 }); - m_face_plane.draw_stroke(); - }, -#else - [] { - }, -#endif - .draw_frame = draw_layer_frame, - }); + plane_mvp_z, + b.get(), + copy_blend_destination, + m_canvas->m_cam_fov < 20.f); pp::panopainter::execute_legacy_canvas_draw_merge_layer_path( m_canvas->m_current_stroke && m_canvas->m_current_mode == kCanvasMode::Erase && m_canvas->m_show_tmp && m_canvas->m_current_layer_idx == layer_index,