Extract app frame and export dialog helpers

This commit is contained in:
2026-06-16 11:58:22 +02:00
parent d2a841f348
commit 01854f9b10
9 changed files with 786 additions and 584 deletions

View File

@@ -92,6 +92,7 @@ set(PP_PANOPAINTER_APP_SOURCES
src/app_cloud.cpp src/app_cloud.cpp
src/app_commands.cpp src/app_commands.cpp
src/app_dialogs.cpp src/app_dialogs.cpp
src/app_dialogs_export.cpp
src/app_dialogs_info_openers.cpp src/app_dialogs_info_openers.cpp
src/app_events.cpp src/app_events.cpp
src/app_layout.cpp src/app_layout.cpp
@@ -100,6 +101,7 @@ set(PP_PANOPAINTER_APP_SOURCES
src/app_layout_tools_menu.cpp src/app_layout_tools_menu.cpp
src/app_shaders.cpp src/app_shaders.cpp
src/app_vr.cpp src/app_vr.cpp
src/legacy_app_frame_services.cpp
src/legacy_app_dialog_services.cpp src/legacy_app_dialog_services.cpp
src/legacy_app_dialog_services.h src/legacy_app_dialog_services.h
src/legacy_app_preference_services.cpp src/legacy_app_preference_services.cpp

View File

@@ -86,9 +86,9 @@ Current hotspot files:
- `src/main.cpp`: 1117 lines - `src/main.cpp`: 1117 lines
- `src/node_panel_brush.cpp`: 1197 lines - `src/node_panel_brush.cpp`: 1197 lines
- `src/node_stroke_preview.cpp`: 933 lines - `src/node_stroke_preview.cpp`: 933 lines
- `src/node_canvas.cpp`: 953 lines - `src/node_canvas.cpp`: 897 lines
- `src/app.cpp`: 950 lines - `src/app.cpp`: 502 lines
- `src/app_dialogs.cpp`: 789 lines - `src/app_dialogs.cpp`: 441 lines
Current architecture mismatches that must be treated as real blockers: 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 `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 `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` 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 - `App`, `Canvas`, `Node`, retained workers, and platform entrypoints still use
global singleton reach, raw observer pointers, retained static worker global singleton reach, raw observer pointers, retained static worker
ownership in several app families, and ad hoc mutex/condition-variable 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 while `App::rec_loop()` now delegates worker-iteration orchestration into
the retained recording bridge, `App::update_rec_frames()` now delegates the retained recording bridge, `App::update_rec_frames()` now delegates
recording label refresh through that same retained recording path, and the 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 add/remove/order/lookups, animation frame control, resize, and snapshot
save/restore now lives in `src/legacy_canvas_state_services.cpp` instead of save/restore now lives in `src/legacy_canvas_state_services.cpp` instead of
`src/canvas.cpp`, while the larger import/export/save/open/thumbnail `src/canvas.cpp`, while the larger import/export/save/open/thumbnail

View File

@@ -199,7 +199,12 @@ Current slice:
- `NodeCanvas` non-`draw_merged` per-layer temporary erase/paint, layer-texture, - `NodeCanvas` non-`draw_merged` per-layer temporary erase/paint, layer-texture,
and blend setup now also route through and blend setup now also route through
`make_legacy_canvas_draw_merge_layer_path_gl_execution(...)`, but the node `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: Write scope:
- `src/node_stroke_preview.cpp` - `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 #### ARC-APP-002 - Split `app_dialogs.cpp` Into Workflow Adapters And Widget Openers
Status: Ready Status: In Progress
Why now: Why now:
`src/app_dialogs.cpp` still mixes document workflow decisions, export routing, `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, and the corresponding `App::dialog_*` entrypoints are now thin call-throughs,
but document/export workflow and retained dialog execution are still inline in but document/export workflow and retained dialog execution are still inline in
`src/app_dialogs.cpp`. `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: Write scope:
- `src/app_dialogs.cpp` - `src/app_dialogs.cpp`
@@ -364,11 +373,19 @@ Mini-model packet:
#### ARC-APP-003 - Reduce `app.cpp` To Frame, Queue, And Composition Shell #### ARC-APP-003 - Reduce `app.cpp` To Frame, Queue, And Composition Shell
Status: Ready Status: In Progress
Why now: Why now:
`src/app.cpp` still carries startup, frame flow, queue draining, recording, `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: Write scope:
- `src/app.cpp` - `src/app.cpp`

View File

@@ -33,37 +33,6 @@
App* App::I = nullptr; // singleton App* App::I = nullptr; // singleton
namespace { 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) void apply_app_viewport(pp::renderer::gl::OpenGlViewportRect viewport)
{ {
const auto status = pp::renderer::gl::apply_opengl_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); bool process_legacy_recording_worker_iteration(App& app);
void update_legacy_recording_frame_label(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<bool(Node*)>& observer, bool skip_first_main_child);
void update_legacy_canvas_toolbar(App& app);
} }
bool App::check_license() bool App::check_license()
@@ -359,67 +331,7 @@ void App::async_swap()
bool App::update_ui_observer(Node *n) bool App::update_ui_observer(Node *n)
{ {
std::vector<pp::app::AppUiObserverParentClip> parent_clips; return pp::panopainter::update_legacy_app_ui_observer(*this, n);
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;
} }
void App::draw(float dt) void App::draw(float dt)
@@ -446,11 +358,7 @@ void App::draw(float dt)
.height = static_cast<std::int32_t>(uirtt.getHeight()), .height = static_cast<std::int32_t>(uirtt.getHeight()),
}); });
apply_app_scissor_test(true); apply_app_scissor_test(true);
for (int i = 1; i < layout[main_id]->m_children.size(); i++) pp::panopainter::watch_legacy_app_ui_children(*this, observer, true);
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);
apply_app_scissor_test(false); apply_app_scissor_test(false);
uirtt.unbindFramebuffer(); uirtt.unbindFramebuffer();
} }
@@ -465,11 +373,7 @@ void App::draw(float dt)
.height = static_cast<std::int32_t>(height), .height = static_cast<std::int32_t>(height),
}); });
apply_app_scissor_test(true); apply_app_scissor_test(true);
for (int i = 0; i < layout[main_id]->m_children.size(); i++) pp::panopainter::watch_legacy_app_ui_children(*this, observer, false);
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);
apply_app_scissor_test(false); apply_app_scissor_test(false);
} }
@@ -492,28 +396,7 @@ void App::update(float dt)
if (!update_plan.refresh_canvas_toolbar) if (!update_plan.refresh_canvas_toolbar)
return; return;
{ pp::panopainter::update_legacy_canvas_toolbar(*this);
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<NodeButtonCustom>("btn-pick")->set_active(toolbar.pick_active);
layout[main_id]->find<NodeButtonCustom>("btn-touchlock")->set_active(toolbar.touch_lock_active);
layout[main_id]->find<NodeButtonCustom>("btn-pen")->set_active(toolbar.pen_active);
layout[main_id]->find<NodeButtonCustom>("btn-erase")->set_active(toolbar.erase_active);
layout[main_id]->find<NodeButton>("btn-cam")->set_active(toolbar.camera_active);
layout[main_id]->find<NodeButtonCustom>("btn-line")->set_active(toolbar.line_active);
layout[main_id]->find<NodeButton>("btn-grid")->set_active(toolbar.grid_active);
layout[main_id]->find<NodeButton>("btn-copy")->set_active(toolbar.copy_active);
layout[main_id]->find<NodeButton>("btn-cut")->set_active(toolbar.cut_active);
layout[main_id]->find<NodeButtonCustom>("btn-mask-free")->set_active(toolbar.mask_free_active);
layout[main_id]->find<NodeButtonCustom>("btn-mask-line")->set_active(toolbar.mask_line_active);
layout[main_id]->find<NodeButtonCustom>("btn-bucket")->set_active(toolbar.flood_fill_active);
}
} }
void App::terminate() void App::terminate()

View File

@@ -16,13 +16,6 @@
#include "node_dialog_browse.h" #include "node_dialog_browse.h"
#include "node_dialog_resize.h" #include "node_dialog_resize.h"
#include "node_dialog_cloud.h" #include "node_dialog_cloud.h"
#include "node_dialog_export_ppbr.h"
#include <codec_api.h>
#define MP4V2_NO_STDINT_DEFS
#include <mp4v2/mp4v2.h>
#include <utility>
#ifdef __QUEST__ #ifdef __QUEST__
#include "oculus_vr.h" #include "oculus_vr.h"
@@ -34,95 +27,18 @@ void open_changelog_dialog(App& app);
void open_about_dialog(App& app); void open_about_dialog(App& app);
void open_whatsnew_dialog(App& app, bool force_show); void open_whatsnew_dialog(App& app, bool force_show);
void open_shortcuts_dialog(App& app); 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 { 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( void wire_document_browse_dialog_actions(
App& app, App& app,
const std::shared_ptr<NodeDialogBrowse>& dialog, const std::shared_ptr<NodeDialogBrowse>& dialog,
@@ -453,59 +369,22 @@ void App::dialog_save()
void App::dialog_export(std::string ext) void App::dialog_export(std::string ext)
{ {
if (!can_start_document_export(*this, true)) pp::panopainter::open_document_export_dialog(*this, ext);
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);
} }
void App::dialog_export_layers() void App::dialog_export_layers()
{ {
if (!can_start_document_export(*this, true)) pp::panopainter::open_document_export_layers_dialog(*this);
return;
start_document_export_collection(
*this,
pp::app::DocumentExportCollectionKind::layers);
} }
void App::dialog_export_anim_frames() void App::dialog_export_anim_frames()
{ {
if (!can_start_document_export(*this, true)) pp::panopainter::open_document_export_anim_frames_dialog(*this);
return;
start_document_export_collection(
*this,
pp::app::DocumentExportCollectionKind::animation_frames);
} }
void App::dialog_export_depth() void App::dialog_export_depth()
{ {
if (!can_start_document_export(*this, true)) pp::panopainter::open_document_export_depth_dialog(*this);
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);
} }
void App::dialog_resize() void App::dialog_resize()
@@ -551,15 +430,7 @@ void App::dialog_resize()
void App::dialog_export_cube_faces() void App::dialog_export_cube_faces()
{ {
if (!can_start_document_export(*this, false)) pp::panopainter::open_document_export_cube_faces_dialog(*this);
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);
} }
void App::dialog_layer_rename() void App::dialog_layer_rename()
@@ -613,169 +484,17 @@ void App::dialog_preset_download()
void App::dialog_ppbr_export() void App::dialog_ppbr_export()
{ {
auto* overlay_anchor = layout[main_id]; pp::panopainter::open_ppbr_export_dialog(*this);
if (!overlay_anchor) {
LOG("PPBR export dialog open failed: main layout anchor is missing");
return;
}
auto dialog = pp::panopainter::make_legacy_overlay_node<NodeDialogExportPPBR>(*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();
};
} }
void App::dialog_timelapse_export() void App::dialog_timelapse_export()
{ {
if (!can_start_document_export(*this, false)) pp::panopainter::open_document_timelapse_export_dialog(*this);
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);
});
} }
void App::dialog_export_mp4() void App::dialog_export_mp4()
{ {
if (!can_start_document_export(*this, false)) pp::panopainter::open_document_export_mp4_dialog(*this);
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);
});
} }
void App::dialog_whatsnew(bool force_show) void App::dialog_whatsnew(bool force_show)

316
src/app_dialogs_export.cpp Normal file
View File

@@ -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 <codec_api.h>
#define MP4V2_NO_STDINT_DEFS
#include <mp4v2/mp4v2.h>
#include <utility>
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<NodeDialogExportPPBR>(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

View File

@@ -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<bool(Node*)>& observer)
{
if (!layout)
return;
if (auto* root = layout->get(main_id)) {
for (int i = start_index; i < static_cast<int>(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<pp::app::AppUiObserverParentClip> 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<bool(Node*)>& 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<CanvasModePen>();
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<NodeButtonCustom>("btn-pick")->set_active(toolbar.pick_active);
app.layout[app.main_id]->find<NodeButtonCustom>("btn-touchlock")->set_active(toolbar.touch_lock_active);
app.layout[app.main_id]->find<NodeButtonCustom>("btn-pen")->set_active(toolbar.pen_active);
app.layout[app.main_id]->find<NodeButtonCustom>("btn-erase")->set_active(toolbar.erase_active);
app.layout[app.main_id]->find<NodeButton>("btn-cam")->set_active(toolbar.camera_active);
app.layout[app.main_id]->find<NodeButtonCustom>("btn-line")->set_active(toolbar.line_active);
app.layout[app.main_id]->find<NodeButton>("btn-grid")->set_active(toolbar.grid_active);
app.layout[app.main_id]->find<NodeButton>("btn-copy")->set_active(toolbar.copy_active);
app.layout[app.main_id]->find<NodeButton>("btn-cut")->set_active(toolbar.cut_active);
app.layout[app.main_id]->find<NodeButtonCustom>("btn-mask-free")->set_active(toolbar.mask_free_active);
app.layout[app.main_id]->find<NodeButtonCustom>("btn-mask-line")->set_active(toolbar.mask_line_active);
app.layout[app.main_id]->find<NodeButtonCustom>("btn-bucket")->set_active(toolbar.flood_fill_active);
}
}

View File

@@ -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<BindBlenderFramebuffer>(bind_blender_framebuffer),
.clear_blender_framebuffer = std::forward<ClearBlenderFramebuffer>(clear_blender_framebuffer),
.unbind_blender_framebuffer = std::forward<UnbindBlenderFramebuffer>(unbind_blender_framebuffer),
.bind_sampler = std::forward<BindSampler>(bind_sampler),
.bind_nearest_sampler = std::forward<BindNearestSampler>(bind_nearest_sampler),
.bind_stencil_sampler = std::forward<BindStencilSampler>(bind_stencil_sampler),
.set_active_texture_unit = std::forward<SetActiveTextureUnit>(set_active_texture_unit),
.bind_temporary_texture = std::forward<BindTemporaryTexture>(bind_temporary_texture),
.unbind_temporary_texture = std::forward<UnbindTemporaryTexture>(unbind_temporary_texture),
.bind_smask_texture = std::forward<BindSmaskTexture>(bind_smask_texture),
.unbind_smask_texture = std::forward<UnbindSmaskTexture>(unbind_smask_texture),
.bind_temporary_dual_texture = std::forward<BindTemporaryDualTexture>(bind_temporary_dual_texture),
.unbind_temporary_dual_texture = std::forward<UnbindTemporaryDualTexture>(unbind_temporary_dual_texture),
.bind_pattern_texture = std::forward<BindPatternTexture>(bind_pattern_texture),
.draw_face = std::forward<DrawFace>(draw_face),
.bind_blender_texture = std::forward<BindBlenderTexture>(bind_blender_texture),
.unbind_blender_texture = std::forward<UnbindBlenderTexture>(unbind_blender_texture),
.bind_destination_texture = std::forward<BindDestinationTexture>(bind_destination_texture),
.unbind_destination_texture = std::forward<UnbindDestinationTexture>(unbind_destination_texture),
.copy_destination_framebuffer = std::forward<CopyDestinationFramebuffer>(copy_destination_framebuffer),
.draw_debug_outline = std::forward<DrawDebugOutline>(draw_debug_outline),
.draw_frame = std::forward<DrawFrame>(draw_frame),
});
}
template <typename PlanOnionRange, typename ShouldDrawPlane, typename VisitLayerPlane, typename LogOnionRangeFailure> template <typename PlanOnionRange, typename ShouldDrawPlane, typename VisitLayerPlane, typename LogOnionRangeFailure>
inline void execute_legacy_canvas_draw_layer_traversal( inline void execute_legacy_canvas_draw_layer_traversal(
size_t layer_count, size_t layer_count,

View File

@@ -231,6 +231,170 @@ void run_canvas_tool_mode(pp::app::CanvasToolMode mode)
LOG("Canvas input tool action failed: %s", status.message); 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<float>(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 Node* NodeCanvas::clone_instantiate() const
@@ -467,161 +631,14 @@ void NodeCanvas::draw()
glm::eulerAngleYXZ(yaw, pitch, roll) * glm::eulerAngleYXZ(yaw, pitch, roll) *
m_canvas->m_plane_transform[plane_index] * m_canvas->m_plane_transform[plane_index] *
glm::translate(glm::vec3(0, 0, -1)); glm::translate(glm::vec3(0, 0, -1));
const auto layer_path_execution = make_node_canvas_layer_path_execution(
const auto draw_layer_frame = pp::panopainter::make_legacy_canvas_draw_merge_layer_frame_draw( *this,
m_canvas->m_layers[layer_index].get(), layer_index,
&m_face_plane,
set_active_texture_unit,
plane_index, plane_index,
m_canvas->m_layers[layer_index]->m_opacity); plane_mvp_z,
b.get(),
glm::vec2 patt_scale = glm::vec2(b->m_pattern_scale); copy_blend_destination,
if (b->m_pattern_flipx) m_canvas->m_cam_fov < 20.f);
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<float>(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,
});
pp::panopainter::execute_legacy_canvas_draw_merge_layer_path( 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, 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,