Thin app dialog export and popup seams

This commit is contained in:
2026-06-17 19:49:35 +02:00
parent b505d9f727
commit c63a96cc87
20 changed files with 670 additions and 522 deletions

View File

@@ -163,6 +163,8 @@ set(PP_PANOPAINTER_APP_SOURCES
src/legacy_draw_toolbar_binding_services.h
src/app_layout_ui_state.cpp
src/app_layout_sidebar.cpp
src/legacy_sidebar_grid_popup_services.cpp
src/legacy_sidebar_grid_popup_services.h
src/legacy_sidebar_stroke_popup_services.cpp
src/legacy_sidebar_stroke_popup_services.h
src/legacy_sidebar_color_popup_services.cpp

View File

@@ -792,26 +792,40 @@ agent or engineer to remove them without reconstructing context from chat.
`bind_legacy_click_destroys_node(...)`; menu/layout-owned popup families still
remain on separate retained cleanup paths.
- 2026-06-17: `DEBT-0058`/`DEBT-0063` were narrowed again. The retained
usermanual, changelog, and about openers in `src/app_dialogs_info_openers.cpp`
now delegate retained dialog construction and overlay close wiring through
usermanual, changelog, about, What's New, and shortcuts openers in
`src/app_dialogs_info_openers.cpp` now delegate retained dialog construction,
overlay close wiring, and remote-page/button ownership through
`src/legacy_info_dialog_services.*` with explicit `App&` plus overlay-anchor
dependencies, so the app dialog shell no longer owns that info-dialog family
inline while `open_whatsnew_dialog()` still owns the retained whats-new flow
inline.
- 2026-06-17: `DEBT-0058`/`DEBT-0063` were narrowed again. The retained New
Document and Save dialog openers in `src/app_dialogs_workflow.cpp` now
delegate retained dialog construction and button wiring through
`src/legacy_document_session_services.*` with explicit `App&` ownership, so
the app dialog shell no longer owns that document-session dialog family
inline while browse, open, and resize flows still remain there.
inline.
- 2026-06-17: `DEBT-0058`/`DEBT-0063` were narrowed again. The retained Open,
Browse, and Resize dialog openers in `src/app_dialogs_workflow.cpp` now
delegate retained dialog construction and overlay/button wiring through
`src/legacy_document_open_services.*` and
`src/legacy_document_session_services.*`, so the app dialog shell no longer
owns those workflow dialog families inline.
- 2026-06-17: `DEBT-0035` was narrowed again. `App::title_update()` and the
draw-toolbar/stroke-popup binding families now delegate through
draw-toolbar/stroke-popup/grid-popup binding families now delegate through
`src/legacy_app_status_services.*`,
`src/legacy_draw_toolbar_binding_services.*`, and
`src/legacy_sidebar_stroke_popup_services.*`, so
`src/legacy_sidebar_stroke_popup_services.*`, and
`src/legacy_sidebar_grid_popup_services.*`, so
`src/app_layout.cpp`, `src/app_layout_draw_toolbar.cpp`, and part of
`src/app_layout_sidebar.cpp` are thinner adapters while retained app-shell
execution and remaining popup families still stay open under the same debt.
execution and the remaining layer popup family still stay open under the same
debt.
- 2026-06-17: `DEBT-0030`/`DEBT-0043`/`DEBT-0044` were narrowed again.
`src/app_dialogs_export.cpp` is now a thin forwarding adapter. Retained
document export start/branching flows now live in
`src/legacy_document_export_services.*`, and the PPBR export dialog opener
now lives in `src/legacy_brush_package_export_services.*`; retained export
execution still remains behind those legacy bridges.
- 2026-06-15: `DEBT-0036` was narrowed again. `NodeStrokePreview` now drops the
retained pass-sequence, mix-execution, final-composite-request, background
capture, and preview-copy wrapper structs/functions in favor of direct

View File

@@ -93,12 +93,13 @@ Current conclusion:
app shell is down to adapter calls even though runtime draw/event/sidebar
execution still remains.
- New-document/save dialog session wiring, app-title rendering, draw-toolbar
binding, sidebar stroke-popup binding, and usermanual/changelog/about opener
wiring now live in dedicated `legacy_*services.*` seams, so
`src/app_dialogs_workflow.cpp`, `src/app_layout.cpp`,
`src/app_layout_draw_toolbar.cpp`, `src/app_layout_sidebar.cpp`, and
`src/app_dialogs_info_openers.cpp` are thinner adapters even though broader
retained dialog/sidebar execution still remains.
binding, sidebar stroke/grid-popup binding, export-dialog start wiring, and
the full info-opener family now live in dedicated `legacy_*services.*` seams,
so `src/app_dialogs_workflow.cpp`, `src/app_dialogs_export.cpp`,
`src/app_layout.cpp`, `src/app_layout_draw_toolbar.cpp`,
`src/app_layout_sidebar.cpp`, and `src/app_dialogs_info_openers.cpp` are
thinner adapters even though broader retained dialog/sidebar execution still
remains.
- Platform extraction improved substantially and the root app source group no
longer compiles Web platform sources directly, but broader CMake and
entrypoint cleanup are not complete.

View File

@@ -65,6 +65,12 @@ Key facts:
- `App::dialog_browse()` no longer owns browse-dialog button wiring inline; the
retained document-open bridge now owns that handoff in
`src/legacy_document_open_services.*`.
- `App::dialog_open()`, `App::dialog_browse()`, and `App::dialog_resize()` now
delegate retained dialog construction and overlay/button wiring through
`src/legacy_document_open_services.*` and
`src/legacy_document_session_services.*`, so
`src/app_dialogs_workflow.cpp` is thinner while the save-before-workflow
policy seam remains local.
- `App::init_toolbar_main()` now delegates retained main-toolbar button wiring
through `src/legacy_main_toolbar_binding_services.*`, so
`src/app_layout_main_toolbar.cpp` is down to a thin root lookup and adapter
@@ -104,13 +110,13 @@ Key facts:
- `App::dialog_usermanual()`, `App::dialog_changelog()`, and
`App::dialog_about()` now delegate the retained info-dialog construction and
overlay close wiring through `src/legacy_info_dialog_services.*` with
explicit `App&` plus overlay-anchor dependencies, so
`src/app_dialogs_info_openers.cpp` is thinner while `open_whatsnew_dialog()`
still owns the retained whats-new flow inline.
explicit `App&` plus overlay-anchor dependencies, and the remaining
What's New plus shortcuts flows now route through the same seam, so
`src/app_dialogs_info_openers.cpp` is down to thin forwarding only.
- `App::dialog_newdoc()` and `App::dialog_save()` now delegate retained dialog
construction and button wiring through `src/legacy_document_session_services.*`,
so `src/app_dialogs_workflow.cpp` is thinner at the document-session seam
while browse, open, and resize retained workflows still remain there.
construction and button wiring through
`src/legacy_document_session_services.*`, so
`src/app_dialogs_workflow.cpp` is thinner at the document-session seam.
- `App::title_update()` now delegates retained document-title and DPI-label
rendering through `src/legacy_app_status_services.*`, so
`src/app_layout.cpp` no longer owns that app-status family inline.
@@ -122,8 +128,16 @@ Key facts:
- `App::init_sidebar()` now delegates the retained stroke-popup open/anchor/tick
wiring through `src/legacy_sidebar_stroke_popup_services.*` with explicit
`App&`, popup-root, trigger-button, and panel dependencies, so
`src/app_layout_sidebar.cpp` is thinner while the retained grid and layer
popup families still remain inline.
`src/app_layout_sidebar.cpp` is thinner while the retained layer popup family
still remains inline.
- `App::init_sidebar()` now delegates the retained grid-popup
open/anchor/tick/close wiring through
`src/legacy_sidebar_grid_popup_services.*`, so the `btn-grids-panel` path in
`src/app_layout_sidebar.cpp` is down to a thin adapter.
- `src/app_dialogs_export.cpp` is now a forwarding adapter; the retained
document export start/branching flows live in
`src/legacy_document_export_services.*`, and the PPBR dialog opener now lives
in `src/legacy_brush_package_export_services.*`.
## Parallel Assignment Rules

View File

@@ -1,316 +1,51 @@
#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);
open_legacy_document_export_dialog(app, std::move(ext));
}
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);
open_legacy_document_export_layers_dialog(app);
}
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);
open_legacy_document_export_anim_frames_dialog(app);
}
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);
open_legacy_document_export_depth_dialog(app);
}
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);
open_legacy_document_export_cube_faces_dialog(app);
}
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;
}
open_legacy_ppbr_export_dialog(app);
}
auto dialog = pp::panopainter::make_legacy_overlay_node<NodeDialogExportPPBR>(app);
void open_document_timelapse_export_dialog(App& app)
{
open_legacy_document_timelapse_export_dialog(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();
};
void open_document_export_mp4_dialog(App& app)
{
open_legacy_document_export_mp4_dialog(app);
}
} // namespace pp::panopainter

View File

@@ -1,11 +1,6 @@
#include "pch.h"
#include "app.h"
#include "legacy_preference_storage.h"
#include "legacy_ui_overlay_services.h"
#include "legacy_info_dialog_services.h"
#include "node_remote_page.h"
#include "node_shorcuts.h"
#include "version.h"
namespace pp::panopainter {
@@ -45,70 +40,12 @@ void open_about_dialog(App& app)
void open_whatsnew_dialog(App& app, bool force_show)
{
auto* overlay_anchor = app.layout[app.main_id];
if (!overlay_anchor) {
LOG("What's new dialog open failed: main layout anchor is missing");
return;
}
const auto overlay_handle = std::make_shared<pp::ui::NodeHandle>();
const auto open_overlay = [overlay_anchor, overlay_handle](const std::shared_ptr<NodeRemotePage>& page) {
if (overlay_handle->valid()) {
return;
}
const auto overlay = open_legacy_overlay_node_with_handle(*overlay_anchor, page);
if (!overlay) {
return;
}
*overlay_handle = overlay.value();
};
const auto close_overlay = [overlay_anchor, overlay_handle]() {
if (!overlay_handle->valid()) {
return;
}
const auto close_status = close_legacy_overlay_node(*overlay_anchor, *overlay_handle);
(void)close_status;
*overlay_handle = {};
};
auto whatsnew = std::make_shared<NodeRemotePage>();
whatsnew->set_manager(&app.layout);
whatsnew->init();
std::string url = fmt::format("https://panopainter.com/app-content/whatsnew/?version={}", g_version_build);
whatsnew->load_url(url, [whatsnew, force_show, open_overlay](bool success) {
if (success)
{
int last_id = legacy_whatsnew_id_or(0);
if (force_show || (whatsnew->m_page_id <= g_version_build && whatsnew->m_page_id > last_id))
{
whatsnew->set_title(fmt::format("What's new in version {}", g_version_number));
if (!force_show)
open_overlay(whatsnew);
}
}
});
whatsnew->add_button("Reload", 120, [whatsnew](Node*) {
whatsnew->reload();
});
whatsnew->add_button("Read Later", 120, [whatsnew, close_overlay](Node*) {
clear_legacy_whatsnew_id();
save_legacy_preferences();
close_overlay();
});
whatsnew->add_button("Close", 100, [whatsnew, close_overlay](Node*) {
set_legacy_whatsnew_id(whatsnew->m_page_id);
save_legacy_preferences();
close_overlay();
});
if (force_show)
open_overlay(whatsnew);
pp::panopainter::open_legacy_whatsnew_dialog(app, force_show);
}
void open_shortcuts_dialog(App& app)
{
(void)add_legacy_overlay_node<NodeShortcuts>(app);
pp::panopainter::open_legacy_shortcuts_dialog(app);
}
} // namespace pp::panopainter

View File

@@ -1,13 +1,7 @@
#include "pch.h"
#include "app.h"
#include "app_core/document_resize.h"
#include "legacy_document_open_services.h"
#include "legacy_document_canvas_services.h"
#include "legacy_document_session_services.h"
#include "legacy_ui_overlay_services.h"
#include "node_dialog_browse.h"
#include "node_dialog_open.h"
#include "node_dialog_resize.h"
void App::continue_document_workflow_after_optional_save(std::function<void()> action)
{
@@ -34,80 +28,16 @@ void App::dialog_newdoc()
// DEPRECATED
void App::dialog_open()
{
auto show_dialog = [this] {
// load thumbnail test
auto* overlay_anchor = layout[main_id];
if (!overlay_anchor) {
LOG("Open document dialog open failed: main layout anchor is missing");
return;
}
auto dialog = pp::panopainter::make_legacy_overlay_node<NodeDialogOpen>(*this);
const auto overlay = pp::panopainter::open_legacy_overlay_node_with_handle(*overlay_anchor, dialog);
if (!overlay) {
LOG("Open document 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*)
{
// canvas->reset_camera();
// layers->clear();
// doc_name = dialog->selected_name;
// canvas->m_canvas->project_open(dialog->selected_path, [this](bool success) {
// // on complete
// async_start();
// title_update();
// for (auto& i : canvas->m_canvas->m_order)
// layers->add_layer(canvas->m_canvas->m_layers[i]->m_name.c_str());
// async_end();
// });
// dialog->destroy();
// ActionManager::clear();
};
dialog->btn_cancel->on_click = [close_dialog](Node*)
{
close_dialog();
};
};
continue_document_workflow_after_optional_save(show_dialog);
continue_document_workflow_after_optional_save([this] {
pp::panopainter::open_legacy_document_open_dialog(*this);
});
}
void App::dialog_browse()
{
auto show_dialog = [this] {
auto* overlay_anchor = layout[main_id];
if (!overlay_anchor) {
LOG("Browse document dialog open failed: main layout anchor is missing");
return;
}
auto dialog = pp::panopainter::make_legacy_overlay_node<NodeDialogBrowse>(*this);
dialog->search_paths = document_browse_roots();
const auto overlay = pp::panopainter::open_legacy_overlay_node_with_handle(*overlay_anchor, dialog);
if (!overlay) {
LOG("Browse document dialog open failed: %s", overlay.status().message);
return;
}
const auto overlay_handle = overlay.value();
pp::panopainter::wire_legacy_document_browse_dialog_actions(
*this,
dialog,
*overlay_anchor,
overlay_handle);
};
continue_document_workflow_after_optional_save(show_dialog);
continue_document_workflow_after_optional_save([this] {
pp::panopainter::open_legacy_document_browse_dialog(*this);
});
}
void App::dialog_save_ver()
@@ -148,41 +78,5 @@ void App::dialog_save()
void App::dialog_resize()
{
auto* overlay_anchor = layout[main_id];
if (!overlay_anchor) {
LOG("Resize dialog open failed: main layout anchor is missing");
return;
}
auto dialog = pp::panopainter::make_legacy_overlay_node<NodeDialogResize>(*this);
const auto overlay = pp::panopainter::open_legacy_overlay_node_with_handle(*overlay_anchor, dialog);
if (!overlay) {
LOG("Resize 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, close_dialog](Node*)
{
const auto plan = pp::app::plan_document_resize(
dialog->combo ? dialog->combo->m_current_index : 0);
if (!plan)
{
close_dialog();
return;
}
const auto status = pp::panopainter::execute_legacy_document_resize_plan(*this, plan.value());
if (!status.ok())
LOG("Document resize failed: %s", status.message);
close_dialog();
};
dialog->btn_cancel->on_click = [close_dialog](Node*) {
close_dialog();
};
pp::panopainter::open_legacy_document_resize_dialog(*this);
}

View File

@@ -4,6 +4,7 @@
#include "node_panel_floating.h"
#include "app_core/brush_ui.h"
#include "app_core/document_layer.h"
#include "legacy_sidebar_grid_popup_services.h"
#include "legacy_sidebar_color_popup_services.h"
#include "legacy_sidebar_stroke_popup_services.h"
#include "legacy_brush_ui_services.h"
@@ -321,49 +322,7 @@ void App::init_sidebar()
if (!popup_root) {
return;
}
auto screen = popup_root->m_size;
glm::vec2 pos = button->m_pos + glm::vec2(button->m_size.x * 0.5f, button->m_size.y);
grid->find("title")->SetVisibility(true);
grid->SetSize(350, YGUndefined);
if (grid->m_parent)
{
if (auto fp = dynamic_cast<NodePanelFloating*>(grid->m_parent->m_parent))
{
pp::panopainter::detach_legacy_node_from_parent(*grid);
pp::panopainter::close_legacy_dialog_node(*fp);
}
}
const auto popup_overlay = pp::panopainter::open_legacy_overlay_node_with_handle(*popup_root, grid);
if (!popup_overlay)
{
LOG("Grid popup overlay failed: %s", popup_overlay.status().message);
return;
}
auto tick = pp::panopainter::make_legacy_overlay_node_for_anchor<NodeImage>(*popup_root);
tick->SetPositioning(YGPositionTypeAbsolute);
tick->SetSize(32, 16);
tick->SetPosition(pos.x - 16, pos.y);
tick->set_image("data/ui/popup-tick-up.png");
auto tick_overlay = pp::panopainter::open_legacy_overlay_node_with_handle(*popup_root, tick);
if (!popup_overlay || !tick_overlay)
{
close_legacy_overlay_handles_if_open(*popup_root, popup_overlay, tick_overlay);
return;
}
const auto popup_handle = popup_overlay.value();
const auto tick_handle = tick_overlay.value();
popup_root->update();
grid->SetPosition(pos.x - grid->m_size.x / 2.f, pos.y + 16);
grid->SetPositioning(YGPositionTypeAbsolute);
pp::panopainter::activate_legacy_popup_overlay(*grid);
auto scroll = grid->find<NodeScroll>("scroller");
scroll->SetMaxHeight(glm::max(100.f, screen.y - pos.y - 250.f));
grid->on_popup_close = [popup_root, popup_handle, tick_handle](Node*) {
close_legacy_overlay_handle_ignoring_status(*popup_root, popup_handle);
close_legacy_overlay_handle_ignoring_status(*popup_root, tick_handle);
};
pp::panopainter::open_legacy_sidebar_grid_popup(*popup_root, *button, *grid);
};
}
}

View File

@@ -9,6 +9,7 @@
#include <functional>
#include <string>
#include <utility>
namespace pp::panopainter {
namespace {
@@ -134,4 +135,67 @@ void complete_legacy_brush_package_export(NodeDialogExportPPBR& dialog, bool sav
}
}
void open_legacy_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

@@ -26,5 +26,6 @@ enum class LegacyBrushPackageExportMode {
LegacyBrushPackageExportMode mode);
void complete_legacy_brush_package_export(NodeDialogExportPPBR& dialog, bool saved);
void open_legacy_ppbr_export_dialog(App& app);
} // namespace pp::panopainter

View File

@@ -13,10 +13,34 @@
#include <limits>
#include <span>
#include <string>
#include <utility>
namespace pp::panopainter {
namespace {
[[nodiscard]] bool can_start_legacy_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 show_export_success_dialog(
App& app,
const pp::app::DocumentExportSuccessDialogPlan& plan)
@@ -26,6 +50,124 @@ void show_export_success_dialog(
}
}
void start_legacy_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_legacy_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_legacy_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);
});
}
struct LegacyDocumentExportSnapshotReports {
pp::app::DocumentCanvasSnapshotResult snapshot;
pp::paint_renderer::DocumentFrameFacePngExportResult face_pngs;
@@ -834,6 +976,96 @@ void execute_legacy_document_export_equirectangular_thread(std::string file_path
execute_legacy_document_export_equirectangular_thread_impl(std::move(file_path));
}
void open_legacy_document_export_dialog(App& app, std::string ext)
{
if (!can_start_legacy_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_legacy_document_export_layers_dialog(App& app)
{
if (!can_start_legacy_document_export(app, true))
return;
start_legacy_document_export_collection(
app,
pp::app::DocumentExportCollectionKind::layers);
}
void open_legacy_document_export_anim_frames_dialog(App& app)
{
if (!can_start_legacy_document_export(app, true))
return;
start_legacy_document_export_collection(
app,
pp::app::DocumentExportCollectionKind::animation_frames);
}
void open_legacy_document_export_depth_dialog(App& app)
{
if (!can_start_legacy_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_legacy_document_export_cube_faces_dialog(App& app)
{
if (!can_start_legacy_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_legacy_document_timelapse_export_dialog(App& app)
{
start_legacy_document_video_export(
app,
pp::app::DocumentVideoExportKind::timelapse,
pp::app::DocumentExportSuccessKind::timelapse,
"-timelapse",
pp::app::DocumentExportExecutionKind::timelapse);
}
void open_legacy_document_export_mp4_dialog(App& app)
{
start_legacy_document_video_export(
app,
pp::app::DocumentVideoExportKind::animation_mp4,
pp::app::DocumentExportSuccessKind::animation_mp4,
"-animation",
pp::app::DocumentExportExecutionKind::animation_mp4);
}
pp::foundation::Status execute_legacy_document_export_file(
App& app,
const pp::app::DocumentExportFileTarget& target)

View File

@@ -7,6 +7,7 @@ class App;
#include <functional>
#include <string>
#include <string_view>
namespace pp::panopainter {
@@ -38,6 +39,14 @@ namespace pp::panopainter {
std::string_view path,
bool asynchronous);
void open_legacy_document_export_dialog(App& app, std::string ext);
void open_legacy_document_export_layers_dialog(App& app);
void open_legacy_document_export_anim_frames_dialog(App& app);
void open_legacy_document_export_depth_dialog(App& app);
void open_legacy_document_export_cube_faces_dialog(App& app);
void open_legacy_document_timelapse_export_dialog(App& app);
void open_legacy_document_export_mp4_dialog(App& app);
void execute_legacy_document_export_equirectangular(
std::string file_path,
std::function<void()> on_complete);

View File

@@ -9,6 +9,7 @@
#include "legacy_history_services.h"
#include "legacy_ui_overlay_services.h"
#include "log.h"
#include "node_dialog_open.h"
#include "node_dialog_browse.h"
#include "node_panel_brush.h"
#include "node_panel_layer.h"
@@ -112,6 +113,38 @@ void prompt_discard_unsaved_project(App& app, const pp::app::DocumentOpenRoute&
};
}
void open_legacy_document_open_dialog_impl(App& app)
{
auto* overlay_anchor = app.layout[app.main_id];
if (!overlay_anchor) {
LOG("Open document dialog open failed: main layout anchor is missing");
return;
}
auto dialog = pp::panopainter::make_legacy_overlay_node<NodeDialogOpen>(app);
const auto overlay = pp::panopainter::open_legacy_overlay_node_with_handle(*overlay_anchor, dialog);
if (!overlay) {
LOG("Open document 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 = [](Node*)
{
};
dialog->btn_cancel->on_click = [close_dialog](Node*)
{
close_dialog();
};
}
class LegacyDocumentOpenServices final : public pp::app::DocumentOpenServices {
public:
explicit LegacyDocumentOpenServices(App& app) noexcept
@@ -181,6 +214,30 @@ void wire_legacy_document_browse_dialog_actions(
};
}
void open_legacy_document_browse_dialog(App& app)
{
auto* overlay_anchor = app.layout[app.main_id];
if (!overlay_anchor) {
LOG("Browse document dialog open failed: main layout anchor is missing");
return;
}
auto dialog = pp::panopainter::make_legacy_overlay_node<NodeDialogBrowse>(app);
dialog->search_paths = app.document_browse_roots();
const auto overlay = pp::panopainter::open_legacy_overlay_node_with_handle(*overlay_anchor, dialog);
if (!overlay) {
LOG("Browse document dialog open failed: %s", overlay.status().message);
return;
}
pp::panopainter::wire_legacy_document_browse_dialog_actions(
app,
dialog,
*overlay_anchor,
overlay.value());
}
pp::foundation::Status execute_legacy_document_open_plan(
App& app,
pp::app::DocumentOpenPlanAction action,
@@ -219,4 +276,9 @@ void execute_legacy_downloaded_project_open(
reconcile_downloaded_project_open(app);
}
void open_legacy_document_open_dialog(App& app)
{
open_legacy_document_open_dialog_impl(app);
}
} // namespace pp::panopainter

View File

@@ -10,6 +10,7 @@
class App;
class Node;
class NodeDialogOpen;
class NodeDialogBrowse;
namespace pp::panopainter {
@@ -18,11 +19,15 @@ namespace pp::panopainter {
App& app,
std::string path);
void open_legacy_document_open_dialog(App& app);
[[nodiscard]] pp::foundation::Status execute_legacy_document_open_plan(
App& app,
pp::app::DocumentOpenPlanAction action,
const pp::app::DocumentOpenRoute& route);
void open_legacy_document_browse_dialog(App& app);
void wire_legacy_document_browse_dialog_actions(
App& app,
const std::shared_ptr<NodeDialogBrowse>& dialog,

View File

@@ -10,6 +10,7 @@
#include "legacy_history_services.h"
#include "legacy_ui_overlay_services.h"
#include "node_dialog_open.h"
#include "node_dialog_resize.h"
#include <utility>
@@ -487,6 +488,47 @@ void open_legacy_document_file_save_dialog_impl(App& app)
wire_document_file_save_dialog_buttons(app, dialog);
}
void open_legacy_document_resize_dialog_impl(App& app)
{
auto* overlay_anchor = app.layout[app.main_id];
if (!overlay_anchor) {
LOG("Resize dialog open failed: main layout anchor is missing");
return;
}
auto dialog = pp::panopainter::make_legacy_overlay_node<NodeDialogResize>(app);
const auto overlay = pp::panopainter::open_legacy_overlay_node_with_handle(*overlay_anchor, dialog);
if (!overlay) {
LOG("Resize 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, close_dialog](Node*)
{
const auto plan = pp::app::plan_document_resize(
dialog->combo ? dialog->combo->m_current_index : 0);
if (!plan)
{
close_dialog();
return;
}
const auto status = pp::panopainter::execute_legacy_document_resize_plan(app, plan.value());
if (!status.ok())
LOG("Document resize failed: %s", status.message);
close_dialog();
};
dialog->btn_cancel->on_click = [close_dialog](Node*) {
close_dialog();
};
}
} // namespace
pp::foundation::Status execute_legacy_close_request_decision(
@@ -561,6 +603,11 @@ void open_legacy_document_file_save_dialog(App& app)
open_legacy_document_file_save_dialog_impl(app);
}
void open_legacy_document_resize_dialog(App& app)
{
open_legacy_document_resize_dialog_impl(app);
}
pp::foundation::Status execute_legacy_document_version_save(
App& app,
const pp::app::DocumentVersionTarget& target)

View File

@@ -9,6 +9,7 @@
class App;
class NodeDialogNewDoc;
class NodeDialogSave;
class NodeDialogResize;
namespace pp::panopainter {
@@ -44,6 +45,8 @@ void open_legacy_new_document_dialog(App& app);
void open_legacy_document_file_save_dialog(App& app);
void open_legacy_document_resize_dialog(App& app);
[[nodiscard]] pp::foundation::Status execute_legacy_document_version_save(
App& app,
const pp::app::DocumentVersionTarget& target);

View File

@@ -3,14 +3,27 @@
#include "legacy_info_dialog_services.h"
#include "app.h"
#include "legacy_preference_storage.h"
#include "legacy_ui_overlay_services.h"
#include "node_remote_page.h"
#include "node_shorcuts.h"
#include "node_about.h"
#include "node_changelog.h"
#include "node_usermanual.h"
#include "version.h"
namespace pp::panopainter {
namespace {
Node* get_legacy_info_dialog_overlay_anchor(App& app, const char* log_name)
{
auto* overlay_anchor = app.layout[app.main_id];
if (!overlay_anchor) {
LOG("%s dialog open failed: main layout anchor is missing", log_name);
}
return overlay_anchor;
}
template <typename DialogNode>
void open_legacy_info_dialog(App& app, Node& overlay_anchor, const char* log_name)
{
@@ -49,4 +62,71 @@ void open_about_dialog(App& app, Node& overlay_anchor, const char* log_name)
open_legacy_info_dialog<NodeAbout>(app, overlay_anchor, log_name);
}
void open_legacy_whatsnew_dialog(App& app, bool force_show)
{
auto* overlay_anchor = get_legacy_info_dialog_overlay_anchor(app, "What's new");
if (!overlay_anchor) {
return;
}
const auto overlay_handle = std::make_shared<pp::ui::NodeHandle>();
const auto open_overlay = [overlay_anchor, overlay_handle](const std::shared_ptr<NodeRemotePage>& page) {
if (overlay_handle->valid()) {
return;
}
const auto overlay = open_legacy_overlay_node_with_handle(*overlay_anchor, page);
if (!overlay) {
return;
}
*overlay_handle = overlay.value();
};
const auto close_overlay = [overlay_anchor, overlay_handle]() {
if (!overlay_handle->valid()) {
return;
}
const auto close_status = close_legacy_overlay_node(*overlay_anchor, *overlay_handle);
(void)close_status;
*overlay_handle = {};
};
auto whatsnew = std::make_shared<NodeRemotePage>();
whatsnew->set_manager(&app.layout);
whatsnew->init();
const std::string url = fmt::format("https://panopainter.com/app-content/whatsnew/?version={}", g_version_build);
whatsnew->load_url(url, [whatsnew, force_show, open_overlay](bool success) {
if (success) {
const int last_id = legacy_whatsnew_id_or(0);
if (force_show || (whatsnew->m_page_id <= g_version_build && whatsnew->m_page_id > last_id)) {
whatsnew->set_title(fmt::format("What's new in version {}", g_version_number));
if (!force_show) {
open_overlay(whatsnew);
}
}
}
});
whatsnew->add_button("Reload", 120, [whatsnew](Node*) {
whatsnew->reload();
});
whatsnew->add_button("Read Later", 120, [whatsnew, close_overlay](Node*) {
clear_legacy_whatsnew_id();
save_legacy_preferences();
close_overlay();
});
whatsnew->add_button("Close", 100, [whatsnew, close_overlay](Node*) {
set_legacy_whatsnew_id(whatsnew->m_page_id);
save_legacy_preferences();
close_overlay();
});
if (force_show) {
open_overlay(whatsnew);
}
}
void open_legacy_shortcuts_dialog(App& app)
{
(void)add_legacy_overlay_node<NodeShortcuts>(app);
}
} // namespace pp::panopainter

View File

@@ -8,5 +8,7 @@ namespace pp::panopainter {
void open_usermanual_dialog(App& app, Node& overlay_anchor, const char* log_name);
void open_changelog_dialog(App& app, Node& overlay_anchor, const char* log_name);
void open_about_dialog(App& app, Node& overlay_anchor, const char* log_name);
void open_legacy_whatsnew_dialog(App& app, bool force_show);
void open_legacy_shortcuts_dialog(App& app);
} // namespace pp::panopainter

View File

@@ -0,0 +1,73 @@
#include "pch.h"
#include "legacy_sidebar_grid_popup_services.h"
#include "legacy_ui_overlay_services.h"
#include "log.h"
#include "node_button_custom.h"
#include "node_image.h"
#include "node_panel_floating.h"
#include "node_panel_grid.h"
#include "node_scroll.h"
namespace pp::panopainter {
void open_legacy_sidebar_grid_popup(
Node& popup_root,
NodeButtonCustom& trigger_button,
NodePanelGrid& popup_panel)
{
const glm::vec2 pos = trigger_button.m_pos + glm::vec2(trigger_button.m_size.x * 0.5f, trigger_button.m_size.y);
const auto screen = popup_root.m_size;
popup_panel.find("title")->SetVisibility(true);
popup_panel.SetSize(350, YGUndefined);
if (popup_panel.m_parent)
{
if (auto fp = dynamic_cast<NodePanelFloating*>(popup_panel.m_parent->m_parent))
{
detach_legacy_node_from_parent(popup_panel);
close_legacy_dialog_node(*fp);
}
}
const auto popup_overlay = open_legacy_overlay_node_with_handle(
popup_root,
std::static_pointer_cast<NodePanelGrid>(popup_panel.shared_from_this()));
if (!popup_overlay)
{
LOG("Grid popup overlay failed: %s", popup_overlay.status().message);
return;
}
auto tick = make_legacy_overlay_node_for_anchor<NodeImage>(popup_root);
tick->SetPositioning(YGPositionTypeAbsolute);
tick->SetSize(32, 16);
tick->SetPosition(pos.x - 16, pos.y);
tick->set_image("data/ui/popup-tick-up.png");
const auto tick_overlay = open_legacy_overlay_node_with_handle(popup_root, tick);
if (!popup_overlay || !tick_overlay)
{
close_legacy_overlay_handles_if_open(popup_root, popup_overlay, tick_overlay);
return;
}
const auto popup_handle = popup_overlay.value();
const auto tick_handle = tick_overlay.value();
popup_root.update();
popup_panel.SetPosition(pos.x - popup_panel.m_size.x / 2.f, pos.y + 16);
popup_panel.SetPositioning(YGPositionTypeAbsolute);
activate_legacy_popup_overlay(popup_panel);
auto scroll = popup_panel.find<NodeScroll>("scroller");
scroll->SetMaxHeight(glm::max(100.f, screen.y - pos.y - 250.f));
popup_panel.on_popup_close = [&popup_root, popup_handle, tick_handle](Node*) {
close_legacy_overlay_handle_ignoring_status(popup_root, popup_handle);
close_legacy_overlay_handle_ignoring_status(popup_root, tick_handle);
};
}
} // namespace pp::panopainter

View File

@@ -0,0 +1,14 @@
#pragma once
class Node;
class NodeButtonCustom;
class NodePanelGrid;
namespace pp::panopainter {
void open_legacy_sidebar_grid_popup(
Node& popup_root,
NodeButtonCustom& trigger_button,
NodePanelGrid& popup_panel);
} // namespace pp::panopainter