Extract draw toolbar and thin NodeCanvas and Win32 shell

This commit is contained in:
2026-06-16 13:37:08 +02:00
parent 8ea56cbd30
commit 9c33ecc22b
10 changed files with 285 additions and 186 deletions

View File

@@ -10,15 +10,12 @@
#include "app_core/about_menu.h"
#include "app_core/app_preferences.h"
#include "app_core/brush_ui.h"
#include "app_core/canvas_tool_ui.h"
#include "app_core/document_layer.h"
#include "app_core/document_canvas.h"
#include "app_core/app_status.h"
#include "app_core/tools_menu.h"
#include "legacy_app_preference_services.h"
#include "legacy_app_shell_services.h"
#include "legacy_brush_ui_services.h"
#include "legacy_canvas_tool_services.h"
#include "legacy_document_layer_services.h"
#include "legacy_preference_storage.h"
#include "font.h"
@@ -38,21 +35,6 @@ void bind_legacy_tools_menu(App& app);
namespace {
bool apply_brush_color_plan(App& app, glm::vec4 color, bool update_quick, bool update_color_panel)
{
return pp::panopainter::apply_legacy_brush_color_plan(app, color, update_quick, update_color_panel);
}
bool apply_brush_texture_plan(App& app, pp::app::BrushUiTextureSlot slot, const std::string& path, const std::string& thumb)
{
return pp::panopainter::apply_legacy_brush_texture_plan(app, slot, path, thumb);
}
bool apply_brush_preset_plan(App& app, const std::shared_ptr<Brush>& brush)
{
return pp::panopainter::apply_legacy_brush_preset_plan(app, brush);
}
[[nodiscard]] bool should_open_tools_panel(const pp::app::ToolsPanelPlan& plan) noexcept
{
return plan.action == pp::app::ToolsPanelAction::open_floating_panel;
@@ -112,64 +94,6 @@ void App::init_toolbar_main()
pp::panopainter::bind_legacy_main_toolbar(*this);
}
[[nodiscard]] bool current_canvas_mode_is_draw(App& app) noexcept
{
return app.canvas && app.canvas->m_canvas && app.canvas->m_canvas->m_current_mode == kCanvasMode::Draw;
}
template<class T>
void execute_canvas_tool_toolbar_binding(
App& app,
const pp::app::CanvasToolToolbarBinding& binding,
T* button)
{
const auto plan = pp::app::plan_canvas_tool_toolbar_binding_action(
binding,
current_canvas_mode_is_draw(app));
const auto status = binding.action == pp::app::CanvasToolToolbarAction::select_mode
? pp::panopainter::execute_legacy_canvas_tool_plan(app, plan, button)
: pp::panopainter::execute_legacy_canvas_tool_plan(app, plan);
if (!status.ok())
LOG("Canvas toolbar action failed: %s", status.message);
}
template<class T>
void bind_canvas_tool_toolbar_button(
App& app,
const pp::app::CanvasToolToolbarBinding& binding,
T* button)
{
button->on_click = [&app, binding, button](Node*) {
execute_canvas_tool_toolbar_binding(app, binding, button);
};
}
void App::init_toolbar_draw()
{
const auto toolbar = pp::app::plan_canvas_tool_toolbar();
bool apply_default_tool = false;
for (const auto& binding : toolbar.bindings) {
if (binding.custom_button) {
if (auto* button = layout[main_id]->find<NodeButtonCustom>(binding.button_id.data())) {
bind_canvas_tool_toolbar_button(*this, binding, button);
apply_default_tool = apply_default_tool || binding.applies_default_on_init;
}
} else {
if (auto* button = layout[main_id]->find<NodeButton>(binding.button_id.data())) {
bind_canvas_tool_toolbar_button(*this, binding, button);
apply_default_tool = apply_default_tool || binding.applies_default_on_init;
}
}
}
if (apply_default_tool) {
const auto default_plan = pp::app::plan_canvas_tool_select(toolbar.default_mode);
const auto status = pp::panopainter::execute_legacy_canvas_tool_plan(*this, default_plan);
if (!status.ok())
LOG("Canvas default tool action failed: %s", status.message);
}
}
void App::init_menu_file()
{
pp::panopainter::bind_legacy_file_menu(*this);

View File

@@ -0,0 +1,68 @@
#include "pch.h"
#include "app.h"
#include "app_core/canvas_tool_ui.h"
#include "legacy_canvas_tool_services.h"
#include "node_button.h"
#include "node_button_custom.h"
namespace {
[[nodiscard]] bool current_canvas_mode_is_draw(App& app) noexcept
{
return app.canvas && app.canvas->m_canvas && app.canvas->m_canvas->m_current_mode == kCanvasMode::Draw;
}
template<class T>
void execute_canvas_tool_toolbar_binding(
App& app,
const pp::app::CanvasToolToolbarBinding& binding,
T* button)
{
const auto plan = pp::app::plan_canvas_tool_toolbar_binding_action(
binding,
current_canvas_mode_is_draw(app));
const auto status = binding.action == pp::app::CanvasToolToolbarAction::select_mode
? pp::panopainter::execute_legacy_canvas_tool_plan(app, plan, button)
: pp::panopainter::execute_legacy_canvas_tool_plan(app, plan);
if (!status.ok())
LOG("Canvas toolbar action failed: %s", status.message);
}
template<class T>
void bind_canvas_tool_toolbar_button(
App& app,
const pp::app::CanvasToolToolbarBinding& binding,
T* button)
{
button->on_click = [&app, binding, button](Node*) {
execute_canvas_tool_toolbar_binding(app, binding, button);
};
}
} // namespace
void App::init_toolbar_draw()
{
const auto toolbar = pp::app::plan_canvas_tool_toolbar();
bool apply_default_tool = false;
for (const auto& binding : toolbar.bindings) {
if (binding.custom_button) {
if (auto* button = layout[main_id]->find<NodeButtonCustom>(binding.button_id.data())) {
bind_canvas_tool_toolbar_button(*this, binding, button);
apply_default_tool = apply_default_tool || binding.applies_default_on_init;
}
} else {
if (auto* button = layout[main_id]->find<NodeButton>(binding.button_id.data())) {
bind_canvas_tool_toolbar_button(*this, binding, button);
apply_default_tool = apply_default_tool || binding.applies_default_on_init;
}
}
}
if (apply_default_tool) {
const auto default_plan = pp::app::plan_canvas_tool_select(toolbar.default_mode);
const auto status = pp::panopainter::execute_legacy_canvas_tool_plan(*this, default_plan);
if (!status.ok())
LOG("Canvas default tool action failed: %s", status.message);
}
}

View File

@@ -1,5 +1,7 @@
#pragma once
#include "app.h"
#include "app_core/document_animation.h"
#include "legacy_canvas_stroke_composite_services.h"
#include "legacy_canvas_stroke_erase_services.h"
#include "shader.h"
@@ -988,6 +990,128 @@ inline void execute_legacy_canvas_draw_unmerged_shell(
}
}
template <
typename MakeLayerPathExecution,
typename ShouldDrawTemporaryErase,
typename ShouldDrawTemporaryPaint,
typename FrameAlpha>
inline void execute_legacy_canvas_draw_unmerged_layer_path(
const LegacyCanvasDrawLayerVisit& visit,
const auto& onion_range,
bool use_blend,
const glm::mat4& proj,
const glm::mat4& camera,
const glm::mat4& orientation,
const auto& plane_transform,
MakeLayerPathExecution&& make_layer_path_execution,
ShouldDrawTemporaryErase&& should_draw_temporary_erase,
ShouldDrawTemporaryPaint&& should_draw_temporary_paint,
FrameAlpha&& frame_alpha)
{
const auto plane_index = visit.plane_index;
const auto plane_mvp_z = proj * camera *
glm::scale(glm::vec3(visit.z + 1)) *
orientation *
plane_transform[plane_index] *
glm::translate(glm::vec3(0, 0, -1));
const auto layer_path_execution = make_layer_path_execution(visit.layer_index, plane_index, plane_mvp_z);
execute_legacy_canvas_draw_merge_layer_path(
should_draw_temporary_erase(visit),
should_draw_temporary_paint(visit),
use_blend,
visit.first_frame,
visit.last_frame,
[&](int frame) {
return frame_alpha(onion_range, frame);
},
layer_path_execution);
}
template <
typename NodeCanvasT,
typename PrepareBlendCache,
typename DrawBackground,
typename ConfigureBlendState,
typename DisableDepthTest,
typename MakeLayerPathExecution,
typename LogOnionRangeFailure,
typename CompositeBlendCache>
inline void execute_legacy_canvas_draw_unmerged_node_canvas_shell(
NodeCanvasT& node_canvas,
bool use_blend,
const glm::mat4& proj,
const glm::mat4& camera,
const glm::mat4& orientation,
PrepareBlendCache&& prepare_blend_cache,
DrawBackground&& draw_background,
ConfigureBlendState&& configure_blend_state,
DisableDepthTest&& disable_depth_test,
MakeLayerPathExecution&& make_layer_path_execution,
LogOnionRangeFailure&& log_onion_range_failure,
CompositeBlendCache&& composite_blend_cache)
{
if (use_blend) {
prepare_blend_cache();
}
draw_background();
configure_blend_state(use_blend);
disable_depth_test();
const auto plan_onion_range = [&](size_t layer_index) {
return pp::app::plan_animation_onion_frame_range(
node_canvas.m_canvas->m_layers[layer_index]->frames_count(),
node_canvas.m_canvas->m_layers[layer_index]->m_frame_index,
App::I->animation->get_onion_size());
};
const auto should_draw_plane = [&](size_t layer_index, int plane_index, int first_frame, int last_frame) {
bool faces = false;
for (int frame = first_frame; frame <= last_frame; ++frame) {
faces |= node_canvas.m_canvas->m_layers[layer_index]->face(plane_index, frame);
}
if (node_canvas.m_canvas->m_show_tmp && node_canvas.m_canvas->m_current_layer_idx == layer_index) {
return true;
}
return node_canvas.m_canvas->m_layers[layer_index]->m_visible &&
node_canvas.m_canvas->m_layers[layer_index]->m_opacity != .0f &&
faces;
};
execute_legacy_canvas_draw_layer_traversal(
node_canvas.m_canvas->m_layers.size(),
plan_onion_range,
should_draw_plane,
[&](const LegacyCanvasDrawLayerVisit& visit, const auto& onion_range) {
execute_legacy_canvas_draw_unmerged_layer_path(
visit,
onion_range,
use_blend,
proj,
camera,
orientation,
node_canvas.m_canvas->m_plane_transform,
make_layer_path_execution,
[&](const LegacyCanvasDrawLayerVisit& visit) {
return node_canvas.m_canvas->m_current_stroke && node_canvas.m_canvas->m_current_mode == kCanvasMode::Erase && node_canvas.m_canvas->m_show_tmp && node_canvas.m_canvas->m_current_layer_idx == visit.layer_index;
},
[&](const LegacyCanvasDrawLayerVisit& visit) {
return node_canvas.m_canvas->m_current_stroke && node_canvas.m_canvas->m_show_tmp && node_canvas.m_canvas->m_current_layer_idx == visit.layer_index;
},
[&](const auto& onion_range, int frame) {
return pp::app::animation_onion_frame_alpha(onion_range, frame);
});
},
std::forward<LogOnionRangeFailure>(log_onion_range_failure));
if (use_blend) {
composite_blend_cache();
}
}
inline void execute_legacy_canvas_draw_merge_plane_setup(
const LegacyCanvasDrawMergePlaneSetupUniforms& uniforms,
const LegacyCanvasDrawMergePlaneSetupExecution& execution)

View File

@@ -21,10 +21,6 @@
#include "wacom.h"
#include "abr.h"
#include <iomanip>
#include <ctime>
#include <sstream>
namespace pp::platform::windows {
void set_async_render_context(HDC hdc, HGLRC hrc);
void lock_async_render_context();
@@ -323,62 +319,6 @@ int read_WMI_info()
return 0;
}
INT_PTR g_iLogHandle = -1;
static void SetupExceptionHandler()
{
// Setup exception handler
BT_SetAppName(_T("PanoPainter"));
BT_SetAppVersion(g_version_w);
//BT_SetSupportEMail(_T("your@email.com"));
BT_SetFlags(BTF_DETAILEDMODE | BTF_ATTACHREPORT | BTF_SCREENCAPTURE);
// = BugTrapServer ===========================================
//BT_SetSupportServer(_T("omigamedev.ddns.net"), 8088);
BT_SetSupportEMail(_T("info@panopainter.com"));
// - or -
//BT_SetSupportServer(_T("127.0.0.1"), 9999);
// = BugTrapWebServer ========================================
//BT_SetSupportServer(_T("http://localhost/BugTrapWebServer/RequestHandler.aspx"), BUGTRAP_HTTP_PORT);
//BT_SetSupportServer(_T("http://omigamedev.ddns.net:8088/source/Server/BugTrapWebServer/RequestHandler.aspx"), BUGTRAP_HTTP_PORT);
BT_SetSupportServer(_T("http://panopainter.com/bug/"), BUGTRAP_HTTP_PORT);
// required for VS 2005 & 2008
BT_InstallSehFilter();
// Add custom log file using default name
// g_iLogHandle = BT_OpenLogFile(NULL, BTLF_TEXT);
// BT_SetLogSizeInEntries(g_iLogHandle, 100);
// BT_SetLogFlags(g_iLogHandle, BTLF_SHOWTIMESTAMP);
// BT_SetLogEchoMode(g_iLogHandle, BTLE_STDERR | BTLE_DBGOUT);
//
// PCTSTR pszLogFileName = BT_GetLogFileName(g_iLogHandle);
TCHAR wpath[MAX_PATH];
//GetFullPathNameW(L"panopainter-log.txt", 1024, wpath, nullptr);
auto log_file = App::I->data_path + "/panopainter-log.txt";
std::mbstowcs(wpath, log_file.c_str(), log_file.size());
BT_AddLogFile(wpath);
BT_SetPreErrHandler([](INT_PTR){
if (Canvas::I && Canvas::I->m_unsaved)
{
auto t = std::time(nullptr);
auto tm = *std::localtime(&t);
std::ostringstream oss;
oss << std::put_time(&tm, "%d-%m-%Y %H-%M-%S");
auto path = App::I->data_path + "/" + App::I->doc_name + "-recovery (" + oss.str() + ").ppi";
Canvas::I->project_save_thread(path, false);
static char abspath[MAX_PATH];
GetFullPathNameA(path.c_str(), MAX_PATH, abspath, NULL);
static char message[4096];
snprintf(message, sizeof(message), "File recovered in: %s", abspath);
MessageBoxA(retained_state().hWnd, message, "File Recovery", MB_OK | MB_ICONWARNING);
}
LogRemote::I.file_close();
}, 0);
}
// create a reverse map from kKey to VK_XXX
void init_vk_map()
{
@@ -439,8 +379,7 @@ int main(int argc, char** argv)
init_vk_map();
SetupExceptionHandler();
BT_SetTerminate();
pp::platform::windows::setup_exception_handler();
read_WMI_info();

View File

@@ -667,11 +667,15 @@ void NodeCanvas::draw()
m_canvas->m_current_stroke ? m_canvas->m_current_stroke->m_brush.get() : nullptr);
const bool use_blend = blend_gate.shader_blend;
const bool copy_blend_destination = use_blend && !blend_gate.reads_destination_color;
const auto layer_orientation = glm::eulerAngleYXZ(yaw, pitch, roll);
const auto& b = m_canvas->m_current_stroke->m_brush;
pp::panopainter::execute_legacy_canvas_draw_unmerged_shell(
pp::panopainter::execute_legacy_canvas_draw_unmerged_node_canvas_shell(
*this,
use_blend,
m_canvas->m_layers.size(),
proj,
camera,
layer_orientation,
[&] {
apply_node_canvas_viewport(0, 0, m_cache_rtt.getWidth(), m_cache_rtt.getHeight());
m_cache_rtt.bindFramebuffer();
@@ -703,33 +707,8 @@ void NodeCanvas::draw()
[&] {
apply_node_canvas_capability(pp::renderer::gl::depth_test_state(), false);
},
[&](size_t layer_index) {
return pp::app::plan_animation_onion_frame_range(
m_canvas->m_layers[layer_index]->frames_count(),
m_canvas->m_layers[layer_index]->m_frame_index,
App::I->animation->get_onion_size());
},
[&](size_t layer_index, int plane_index, int first_frame, int last_frame) {
bool faces = false;
for (int frame = first_frame; frame <= last_frame; ++frame)
faces |= m_canvas->m_layers[layer_index]->face(plane_index, frame);
if (m_canvas->m_show_tmp && m_canvas->m_current_layer_idx == layer_index)
return true;
return m_canvas->m_layers[layer_index]->m_visible &&
m_canvas->m_layers[layer_index]->m_opacity != .0f &&
faces;
},
[&](const pp::panopainter::LegacyCanvasDrawLayerVisit& visit, const auto& onion_range) {
const auto layer_index = visit.layer_index;
const auto plane_index = visit.plane_index;
const auto plane_mvp_z = proj * camera *
glm::scale(glm::vec3(visit.z + 1)) *
glm::eulerAngleYXZ(yaw, pitch, roll) *
m_canvas->m_plane_transform[plane_index] *
glm::translate(glm::vec3(0, 0, -1));
const auto layer_path_execution = make_node_canvas_layer_path_execution(
[&](size_t layer_index, int plane_index, const glm::mat4& plane_mvp_z) {
return make_node_canvas_layer_path_execution(
*this,
layer_index,
plane_index,
@@ -737,17 +716,6 @@ void NodeCanvas::draw()
b.get(),
copy_blend_destination,
m_canvas->m_cam_fov < 20.f);
pp::panopainter::execute_legacy_canvas_draw_merge_layer_path(
m_canvas->m_current_stroke && m_canvas->m_current_mode == kCanvasMode::Erase && m_canvas->m_show_tmp && m_canvas->m_current_layer_idx == layer_index,
m_canvas->m_current_stroke && m_canvas->m_show_tmp && m_canvas->m_current_layer_idx == layer_index,
use_blend,
visit.first_frame,
visit.last_frame,
[&](int frame) {
return pp::app::animation_onion_frame_alpha(onion_range, frame);
},
layer_path_execution);
},
[&](const char* message) {
LOG("NodeCanvas onion frame range failed: %s", message);

View File

@@ -4,10 +4,16 @@
#include "platform_windows/windows_window_shell.h"
#include "app.h"
#include "canvas.h"
#include "legacy_gl_runtime_dispatch.h"
#include "legacy_preference_storage.h"
#include "log.h"
#include <cstdio>
#include <cstdlib>
#include <ctime>
#include <iomanip>
#include <sstream>
#include <shellscalingapi.h>
#include <string>
@@ -105,6 +111,57 @@ void ensure_runtime_data_directory()
LOG("data files ok");
}
void setup_exception_handler()
{
// Setup exception handler
BT_SetAppName(_T("PanoPainter"));
BT_SetAppVersion(g_version_w);
//BT_SetSupportEMail(_T("your@email.com"));
BT_SetFlags(BTF_DETAILEDMODE | BTF_ATTACHREPORT | BTF_SCREENCAPTURE);
// = BugTrapServer ===========================================
//BT_SetSupportServer(_T("omigamedev.ddns.net"), 8088);
BT_SetSupportEMail(_T("info@panopainter.com"));
// - or -
//BT_SetSupportServer(_T("127.0.0.1"), 9999);
// = BugTrapWebServer ========================================
//BT_SetSupportServer(_T("http://localhost/BugTrapWebServer/RequestHandler.aspx"), BUGTRAP_HTTP_PORT);
//BT_SetSupportServer(_T("http://omigamedev.ddns.net:8088/source/Server/BugTrapWebServer/RequestHandler.aspx"), BUGTRAP_HTTP_PORT);
BT_SetSupportServer(_T("http://panopainter.com/bug/"), BUGTRAP_HTTP_PORT);
// required for VS 2005 & 2008
BT_InstallSehFilter();
// Add custom log file using default name
TCHAR wpath[MAX_PATH];
//GetFullPathNameW(L"panopainter-log.txt", 1024, wpath, nullptr);
auto log_file = App::I->data_path + "/panopainter-log.txt";
std::mbstowcs(wpath, log_file.c_str(), log_file.size());
BT_AddLogFile(wpath);
BT_SetPreErrHandler([](INT_PTR){
if (Canvas::I && Canvas::I->m_unsaved)
{
auto t = std::time(nullptr);
auto tm = *std::localtime(&t);
std::ostringstream oss;
oss << std::put_time(&tm, "%d-%m-%Y %H-%M-%S");
auto path = App::I->data_path + "/" + App::I->doc_name + "-recovery (" + oss.str() + ").ppi";
Canvas::I->project_save_thread(path, false);
static char abspath[MAX_PATH];
GetFullPathNameA(path.c_str(), MAX_PATH, abspath, NULL);
static char message[4096];
snprintf(message, sizeof(message), "File recovered in: %s", abspath);
MessageBoxA(retained_state().hWnd, message, "File Recovery", MB_OK | MB_ICONWARNING);
}
LogRemote::I.file_close();
}, 0);
BT_SetTerminate();
}
MainWindowStartupState initialize_main_window_startup_state()
{
auto startup = MainWindowStartupState {};

View File

@@ -46,6 +46,7 @@ enum class MainStartupResult
};
void ensure_runtime_data_directory();
void setup_exception_handler();
MainWindowStartupState initialize_main_window_startup_state();
void create_main_window(const MainWindowStartupState& startup, HWND& hWnd, HINSTANCE hInst, const wchar_t* window_title);
void initialize_pixel_format_descriptor(PIXELFORMATDESCRIPTOR& pixel_format);