Use checked handles for app dialogs

This commit is contained in:
2026-06-15 19:31:38 +02:00
parent e1e686d3f7
commit 168404433c
11 changed files with 271 additions and 49 deletions

View File

@@ -18,6 +18,13 @@ agent or engineer to remove them without reconstructing context from chat.
## Recent Reductions
- 2026-06-15: `DEBT-0058`/`DEBT-0063` were narrowed again. App-owned About,
Changelog, User Manual, New Document, Open, Browse, Save, Resize, Layer
Rename, and PPBR Export dialogs now open and close through checked overlay
handles in `src/app_dialogs.cpp`, and the corresponding retained dialog nodes
no longer self-destroy their cancel/OK buttons through
`bind_legacy_click_destroys_node(...)`; menu/layout-owned popup families still
remain on separate retained cleanup paths.
- 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

@@ -151,17 +151,80 @@ std::shared_ptr<NodeInputBox> App::input_box(const std::string& title,
void App::dialog_usermanual()
{
(void)pp::panopainter::add_legacy_overlay_node<NodeUserManual>(*this);
auto* overlay_anchor = layout[main_id];
if (!overlay_anchor) {
LOG("User manual dialog open failed: main layout anchor is missing");
return;
}
auto dialog = pp::panopainter::make_legacy_overlay_node<NodeUserManual>(*this);
const auto overlay = pp::panopainter::open_legacy_overlay_node_with_handle(*overlay_anchor, dialog);
if (!overlay) {
LOG("User manual 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 = [close_dialog](Node*) {
close_dialog();
};
}
void App::dialog_changelog()
{
(void)pp::panopainter::add_legacy_overlay_node<NodeChangelog>(*this);
auto* overlay_anchor = layout[main_id];
if (!overlay_anchor) {
LOG("Changelog dialog open failed: main layout anchor is missing");
return;
}
auto dialog = pp::panopainter::make_legacy_overlay_node<NodeChangelog>(*this);
const auto overlay = pp::panopainter::open_legacy_overlay_node_with_handle(*overlay_anchor, dialog);
if (!overlay) {
LOG("Changelog 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 = [close_dialog](Node*) {
close_dialog();
};
}
void App::dialog_about()
{
(void)pp::panopainter::add_legacy_overlay_node<NodeAbout>(*this);
auto* overlay_anchor = layout[main_id];
if (!overlay_anchor) {
LOG("About dialog open failed: main layout anchor is missing");
return;
}
auto dialog = pp::panopainter::make_legacy_overlay_node<NodeAbout>(*this);
const auto overlay = pp::panopainter::open_legacy_overlay_node_with_handle(*overlay_anchor, dialog);
if (!overlay) {
LOG("About 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 = [close_dialog](Node*) {
close_dialog();
};
}
void App::continue_document_workflow_after_optional_save(std::function<void()> action)
@@ -180,13 +243,31 @@ void App::continue_document_workflow_after_optional_save(std::function<void()> a
void App::dialog_newdoc()
{
auto show_dialog = [this] {
auto* overlay_anchor = layout[main_id];
if (!overlay_anchor) {
LOG("New document dialog open failed: main layout anchor is missing");
return;
}
auto dialog = pp::panopainter::make_legacy_overlay_node<NodeDialogNewDoc>(*this);
dialog->input->set_text("name");
(void)pp::panopainter::attach_legacy_overlay_node(*this, dialog);
App::I->showKeyboard();
const auto overlay = pp::panopainter::open_legacy_overlay_node_with_handle(*overlay_anchor, dialog);
if (!overlay) {
App::I->hideKeyboard();
LOG("New document dialog open failed: %s", overlay.status().message);
return;
}
const auto overlay_handle = overlay.value();
const auto close_dialog = [this, overlay_anchor, overlay_handle]() {
const auto close_status = pp::panopainter::close_legacy_overlay_node(*overlay_anchor, overlay_handle);
(void)close_status;
App::I->hideKeyboard();
};
dialog->btn_ok->on_click = [this, dialog](Node*)
{
std::string name = dialog->input->m_text;
@@ -212,9 +293,9 @@ void App::dialog_newdoc()
LOG("New document action failed: %s", status.message);
};
dialog->btn_cancel->on_click = [this, dialog](Node*)
dialog->btn_cancel->on_click = [close_dialog](Node*)
{
pp::panopainter::close_legacy_dialog_and_hide_keyboard(*this, *dialog);
close_dialog();
};
};
@@ -226,9 +307,25 @@ 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);
(void)pp::panopainter::attach_legacy_overlay_node(*this, dialog);
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*)
{
@@ -246,6 +343,10 @@ void App::dialog_open()
// dialog->destroy();
// ActionManager::clear();
};
dialog->btn_cancel->on_click = [close_dialog](Node*)
{
close_dialog();
};
};
continue_document_workflow_after_optional_save(show_dialog);
@@ -254,21 +355,40 @@ void App::dialog_open()
void App::dialog_browse()
{
auto show_dialog = [this] {
// load thumbnail test
auto dialog = std::make_shared<NodeDialogBrowse>();
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();
pp::panopainter::initialize_legacy_overlay_node(*this, *dialog);
(void)pp::panopainter::attach_legacy_overlay_node(*this, dialog);
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();
dialog->btn_ok->on_click = [this, dialog](Node*)
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*)
{
if (dialog->is_selected())
{
open_document(dialog->selected_path);
pp::panopainter::close_legacy_dialog_node(*dialog);
close_dialog();
}
};
dialog->btn_cancel->on_click = [close_dialog](Node*)
{
close_dialog();
};
};
continue_document_workflow_after_optional_save(show_dialog);
@@ -319,11 +439,31 @@ void App::dialog_save()
if (canvas)
{
auto* overlay_anchor = layout[main_id];
if (!overlay_anchor) {
LOG("Save document dialog open failed: main layout anchor is missing");
return;
}
auto dialog = pp::panopainter::make_legacy_overlay_node<NodeDialogSave>(*this);
dialog->input->set_text(doc_name);
App::I->showKeyboard();
const auto overlay = pp::panopainter::open_legacy_overlay_node_with_handle(*overlay_anchor, dialog);
if (!overlay) {
App::I->hideKeyboard();
LOG("Save document dialog open failed: %s", overlay.status().message);
return;
}
const auto overlay_handle = overlay.value();
const auto close_dialog = [this, overlay_anchor, overlay_handle]() {
const auto close_status = pp::panopainter::close_legacy_overlay_node(*overlay_anchor, overlay_handle);
(void)close_status;
App::I->hideKeyboard();
};
dialog->btn_ok->on_click = [this, dialog](Node*)
{
std::string name = dialog->input->m_text;
@@ -343,12 +483,10 @@ void App::dialog_save()
if (!status.ok())
LOG("Document file save action failed: %s", status.message);
};
dialog->btn_cancel->on_click = [this, dialog](Node*)
dialog->btn_cancel->on_click = [close_dialog](Node*)
{
pp::panopainter::close_legacy_dialog_and_hide_keyboard(*this, *dialog);
close_dialog();
};
(void)pp::panopainter::attach_legacy_overlay_node(*this, dialog);
}
}
@@ -411,23 +549,42 @@ void App::dialog_export_depth()
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();
(void)pp::panopainter::attach_legacy_overlay_node(*this, dialog);
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*)
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)
{
pp::panopainter::close_legacy_dialog_node(*dialog);
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);
pp::panopainter::close_legacy_dialog_node(*dialog);
close_dialog();
};
dialog->btn_cancel->on_click = [close_dialog](Node*) {
close_dialog();
};
}
@@ -446,12 +603,31 @@ void App::dialog_export_cube_faces()
void App::dialog_layer_rename()
{
auto* overlay_anchor = layout[main_id];
if (!overlay_anchor) {
LOG("Layer rename dialog open failed: main layout anchor is missing");
return;
}
auto dialog = pp::panopainter::make_legacy_overlay_node<NodeDialogLayerRename>(*this);
dialog->input->set_text(layers->m_current_layer->m_label_text);
App::I->showKeyboard();
(void)pp::panopainter::attach_legacy_overlay_node(*this, dialog);
const auto overlay = pp::panopainter::open_legacy_overlay_node_with_handle(*overlay_anchor, dialog);
if (!overlay) {
App::I->hideKeyboard();
LOG("Layer rename dialog open failed: %s", overlay.status().message);
return;
}
const auto overlay_handle = overlay.value();
const auto close_dialog = [this, overlay_anchor, overlay_handle]() {
const auto close_status =
pp::panopainter::close_legacy_overlay_node(*overlay_anchor, overlay_handle);
(void)close_status;
App::I->hideKeyboard();
};
dialog->btn_ok->on_click = [this,dialog](Node*)
{
@@ -463,9 +639,9 @@ void App::dialog_layer_rename()
if (!status.ok())
LOG("Layer rename failed: %s", status.message);
};
dialog->btn_cancel->on_click = [this, dialog](Node*)
dialog->btn_cancel->on_click = [close_dialog](Node*)
{
pp::panopainter::close_legacy_dialog_and_hide_keyboard(*this, *dialog);
close_dialog();
};
}
@@ -476,7 +652,26 @@ void App::dialog_preset_download()
void App::dialog_ppbr_export()
{
auto dialog = pp::panopainter::add_legacy_overlay_node<NodeDialogExportPPBR>(*this);
auto* overlay_anchor = layout[main_id];
if (!overlay_anchor) {
LOG("PPBR export dialog open failed: main layout anchor is missing");
return;
}
auto dialog = pp::panopainter::make_legacy_overlay_node<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);
@@ -512,6 +707,10 @@ void App::dialog_ppbr_export()
LOG("PPBR export failed: %s", status.message);
});
};
dialog->btn_cancel->on_click = [close_dialog](Node*)
{
close_dialog();
};
}
void App::dialog_timelapse_export()
@@ -620,11 +819,39 @@ void App::dialog_export_mp4()
void App::dialog_whatsnew(bool force_show)
{
auto* overlay_anchor = layout[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 = pp::panopainter::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 = pp::panopainter::close_legacy_overlay_node(*overlay_anchor, *overlay_handle);
(void)close_status;
*overlay_handle = {};
};
auto whatsnew = std::make_shared<NodeRemotePage>();
whatsnew->set_manager(&layout);
whatsnew->init();
std::string url = fmt::format("https://panopainter.com/app-content/whatsnew/?version={}", g_version_build);
whatsnew->load_url(url, [this, whatsnew, force_show](bool success) {
whatsnew->load_url(url, [whatsnew, force_show, open_overlay](bool success) {
if (success)
{
int last_id = pp::panopainter::legacy_whatsnew_id_or(0);
@@ -632,25 +859,25 @@ void App::dialog_whatsnew(bool force_show)
{
whatsnew->set_title(fmt::format("What's new in version {}", g_version_number));
if (!force_show)
(void)pp::panopainter::attach_legacy_overlay_node(*this, whatsnew);
open_overlay(whatsnew);
}
}
});
whatsnew->add_button("Reload", 120, [this, whatsnew](Node*) {
whatsnew->add_button("Reload", 120, [whatsnew](Node*) {
whatsnew->reload();
});
whatsnew->add_button("Read Later", 120, [this, whatsnew](Node*) {
whatsnew->add_button("Read Later", 120, [whatsnew, close_overlay](Node*) {
pp::panopainter::clear_legacy_whatsnew_id();
pp::panopainter::save_legacy_preferences();
pp::panopainter::close_legacy_dialog_node(*whatsnew);
close_overlay();
});
whatsnew->add_button("Close", 100, [this, whatsnew](Node*) {
whatsnew->add_button("Close", 100, [whatsnew, close_overlay](Node*) {
pp::panopainter::set_legacy_whatsnew_id(whatsnew->m_page_id);
pp::panopainter::save_legacy_preferences();
pp::panopainter::close_legacy_dialog_node(*whatsnew);
close_overlay();
});
if (force_show)
(void)pp::panopainter::attach_legacy_overlay_node(*this, whatsnew);
open_overlay(whatsnew);
}
void App::dialog_shortcuts()

View File

@@ -1,6 +1,5 @@
#include "pch.h"
#include "log.h"
#include "legacy_ui_overlay_services.h"
#include "node_about.h"
#include "layout.h"
@@ -17,7 +16,6 @@ void NodeAbout::init()
SetPositioning(YGPositionTypeAbsolute);
add_child_file("data/dialogs/about.xml", "about");
btn_ok = find<NodeButton>("btn-ok");
pp::panopainter::bind_legacy_click_destroys_node(*btn_ok, *this);
}
kEventResult NodeAbout::handle_event(Event* e)

View File

@@ -4,8 +4,8 @@
class NodeAbout : public Node
{
NodeButton* btn_ok;
public:
NodeButton* btn_ok;
virtual Node* clone_instantiate() const override;
virtual void init() override;
virtual kEventResult handle_event(Event* e) override;

View File

@@ -1,6 +1,5 @@
#include "pch.h"
#include "log.h"
#include "legacy_ui_overlay_services.h"
#include "node_changelog.h"
#include "layout.h"
@@ -17,7 +16,6 @@ void NodeChangelog::init()
SetPositioning(YGPositionTypeAbsolute);
add_child_file("data/dialogs/changelog.xml", "changelog");
btn_ok = find<NodeButton>("btn-ok");
pp::panopainter::bind_legacy_click_destroys_node(*btn_ok, *this);
}
kEventResult NodeChangelog::handle_event(Event* e)

View File

@@ -4,8 +4,8 @@
class NodeChangelog : public Node
{
NodeButton* btn_ok;
public:
NodeButton* btn_ok;
virtual Node* clone_instantiate() const override;
virtual void init() override;
virtual kEventResult handle_event(Event* e) override;

View File

@@ -3,7 +3,6 @@
#include "node_dialog_export_ppbr.h"
#include "app.h"
#include "image.h"
#include "legacy_ui_overlay_services.h"
Node* NodeDialogExportPPBR::clone_instantiate() const
{
@@ -31,7 +30,6 @@ void NodeDialogExportPPBR::init_controls()
m_dest_path_txt->SetVisibility(false);
btn_ok = find<NodeButton>("btn-ok");
btn_cancel = find<NodeButton>("btn-cancel");
pp::panopainter::bind_legacy_click_destroys_node(*btn_cancel, *this);
btn_header_open = find<NodeButton>("header-open");
btn_header_open->on_click = [this] (Node*) {
open_header();

View File

@@ -2,7 +2,6 @@
#include "log.h"
#include "node_dialog_layer_rename.h"
#include "canvas.h"
#include "legacy_ui_overlay_services.h"
#include "node_image_texture.h"
Node* NodeDialogLayerRename::clone_instantiate() const
@@ -27,7 +26,6 @@ void NodeDialogLayerRename::init_controls()
btn_ok = find<NodeButton>("btn-ok");
btn_cancel = find<NodeButton>("btn-cancel");
input = find<NodeTextInput>("txt-input");
pp::panopainter::bind_legacy_click_destroys_node(*btn_cancel, *this);
}
void NodeDialogLayerRename::loaded()

View File

@@ -3,7 +3,6 @@
#include "node_dialog_resize.h"
#include "app_core/document_resize.h"
#include "canvas.h"
#include "legacy_ui_overlay_services.h"
#include "node_image_texture.h"
#include <array>
@@ -37,7 +36,6 @@ void NodeDialogResize::init_controls()
&& state.current_resolution_index < static_cast<int>(combo->m_items.size())) {
combo->m_current_index = state.current_resolution_index;
}
pp::panopainter::bind_legacy_click_destroys_node(*btn_cancel, *this);
}
void NodeDialogResize::loaded()

View File

@@ -1,6 +1,5 @@
#include "pch.h"
#include "log.h"
#include "legacy_ui_overlay_services.h"
#include "node_usermanual.h"
#include "layout.h"
@@ -17,7 +16,6 @@ void NodeUserManual::init()
SetPositioning(YGPositionTypeAbsolute);
add_child_file("data/dialogs/usermanual.xml", "usermanual");
btn_ok = find<NodeButton>("btn-ok");
pp::panopainter::bind_legacy_click_destroys_node(*btn_ok, *this);
}
kEventResult NodeUserManual::handle_event(Event* e)

View File

@@ -4,8 +4,8 @@
class NodeUserManual : public Node
{
NodeButton* btn_ok;
public:
NodeButton* btn_ok;
virtual Node* clone_instantiate() const override;
virtual void init() override;
virtual kEventResult handle_event(Event* e) override;