Extract menu, stylus, and canvas draw helpers

This commit is contained in:
2026-06-16 11:25:09 +02:00
parent d135835787
commit 18665bdffc
10 changed files with 510 additions and 361 deletions

View File

@@ -94,6 +94,7 @@ set(PP_PANOPAINTER_APP_SOURCES
src/app_dialogs.cpp src/app_dialogs.cpp
src/app_events.cpp src/app_events.cpp
src/app_layout.cpp src/app_layout.cpp
src/app_layout_about_layer_menu.cpp
src/app_layout_file_menu.cpp src/app_layout_file_menu.cpp
src/app_layout_tools_menu.cpp src/app_layout_tools_menu.cpp
src/app_shaders.cpp src/app_shaders.cpp
@@ -164,6 +165,8 @@ set(PP_WINDOWS_PLATFORM_SOURCES
src/platform_windows/windows_platform_services.h src/platform_windows/windows_platform_services.h
src/platform_windows/windows_splash.cpp src/platform_windows/windows_splash.cpp
src/platform_windows/windows_splash.h src/platform_windows/windows_splash.h
src/platform_windows/windows_stylus_input.cpp
src/platform_windows/windows_stylus_input.h
) )
set(PP_WINDOWS_APP_SOURCES set(PP_WINDOWS_APP_SOURCES

View File

@@ -80,13 +80,13 @@ What is still carrying too much live ownership:
Current hotspot files: Current hotspot files:
- `src/canvas.cpp`: 2645 lines - `src/canvas.cpp`: 2645 lines
- `src/app_layout.cpp`: 1360 lines - `src/app_layout.cpp`: 1249 lines
- `src/canvas_modes.cpp`: 1798 lines - `src/canvas_modes.cpp`: 1798 lines
- `src/node.cpp`: 1551 lines - `src/node.cpp`: 1551 lines
- `src/main.cpp`: 1181 lines - `src/main.cpp`: 1218 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`: 888 lines - `src/node_canvas.cpp`: 968 lines
- `src/app.cpp`: 950 lines - `src/app.cpp`: 950 lines
- `src/app_dialogs.cpp`: 908 lines - `src/app_dialogs.cpp`: 908 lines
@@ -122,14 +122,19 @@ Current architecture mismatches that must be treated as real blockers:
current-mode, and grid-mode callback setup now routed through the same current-mode, and grid-mode callback setup now routed through the same
retained helper family, while post-draw callback assembly and the remaining retained helper family, while post-draw callback assembly and the remaining
per-layer render-path orchestration now also route through retained per-layer render-path orchestration now also route through retained
draw-merge helpers even though the broader node draw loop is still inline. draw-merge helpers even though the broader node draw loop is still inline,
with the non-`draw_merged` outer layer/plane traversal now also routing
through `execute_legacy_canvas_draw_layer_traversal(...)` while the heavier
per-layer GL setup and draw lambdas still live in `src/node_canvas.cpp`.
- `app_layout.cpp` and `app_dialogs.cpp` are still mixed shell/controller files - `app_layout.cpp` and `app_dialogs.cpp` are still mixed shell/controller files
rather than thin composition/binding surfaces, even though tools-menu binding rather than thin composition/binding surfaces, even though tools-menu binding
plus nested panels/options submenu wiring now live in plus nested panels/options submenu wiring now live in
`src/app_layout_tools_menu.cpp` and `App::init_menu_tools()` is now a thin `src/app_layout_tools_menu.cpp` and `App::init_menu_tools()` is now a thin
call-through, while file-menu binding plus the export submenu wiring now also call-through, while file-menu binding plus the export submenu wiring now also
live in `src/app_layout_file_menu.cpp` and `App::init_menu_file()` is now a live in `src/app_layout_file_menu.cpp` and `App::init_menu_file()` is now a
thin call-through. thin call-through, while about-menu and layer-menu wiring now also live in
`src/app_layout_about_layer_menu.cpp` and `App::init_menu_about()` plus
`App::init_menu_layer()` are now thin call-throughs.
- `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
@@ -153,7 +158,9 @@ Current architecture mismatches that must be treated as real blockers:
live under `AppRuntime` instead of retained static app-events/canvas live under `AppRuntime` instead of retained static app-events/canvas
workers, and the splash-screen dialog loop, HWND ownership, and bitmap setup workers, and the splash-screen dialog loop, HWND ownership, and bitmap setup
now live in `src/platform_windows/windows_splash.cpp` instead of now live in `src/platform_windows/windows_splash.cpp` instead of
`src/main.cpp`, `src/main.cpp`, while Win32 pointer API loading, stylus/ink timer decay,
Wintab packet reset, and `WM_POINTERUPDATE` pen/touch handling now also live
in `src/platform_windows/windows_stylus_input.cpp` instead of `src/main.cpp`,
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

View File

@@ -193,6 +193,10 @@ Current slice:
remaining per-layer render-path orchestration now also routes through remaining per-layer render-path orchestration now also routes through
`execute_legacy_canvas_draw_merge_layer_path(...)`, but the node still owns `execute_legacy_canvas_draw_merge_layer_path(...)`, but the node still owns
broader draw-loop and renderer-state shell sequencing. broader draw-loop and renderer-state shell sequencing.
- `NodeCanvas` outer non-`draw_merged` layer/plane traversal, onion-range
failure handling, and visit payload setup now also route through
`execute_legacy_canvas_draw_layer_traversal(...)`, but the node still owns
the heavier per-layer GL setup and draw lambdas.
Write scope: Write scope:
- `src/node_stroke_preview.cpp` - `src/node_stroke_preview.cpp`
@@ -271,7 +275,7 @@ targets look like helpers under one old monolith.
Status: In Progress Status: In Progress
Why now: Why now:
`src/app_layout.cpp` is still a 1360-line mixed file that builds menus, `src/app_layout.cpp` is still a 1249-line mixed file that builds menus,
attaches callbacks, computes planner inputs, and mutates UI state directly. attaches callbacks, computes planner inputs, and mutates UI state directly.
Current slice: Current slice:
@@ -283,6 +287,10 @@ Current slice:
`src/app_layout_file_menu.cpp`, and `App::init_menu_file()` is now a thin `src/app_layout_file_menu.cpp`, and `App::init_menu_file()` is now a thin
call-through, but about/layer/sidebar and broader layout composition are call-through, but about/layer/sidebar and broader layout composition are
still inline in `src/app_layout.cpp`. still inline in `src/app_layout.cpp`.
- About-menu and layer-menu wiring now also live in
`src/app_layout_about_layer_menu.cpp`, and `App::init_menu_about()` plus
`App::init_menu_layer()` are now thin call-throughs, but edit/sidebar and
broader layout composition are still inline in `src/app_layout.cpp`.
Write scope: Write scope:
- `src/app_layout.cpp` - `src/app_layout.cpp`
@@ -405,6 +413,10 @@ Current slice:
pair during initialization and context recreation pair during initialization and context recreation
- `main.cpp` main-thread queued task state now sits behind a narrow retained - `main.cpp` main-thread queued task state now sits behind a narrow retained
helper instead of `RetainedState.main_tasklist` / `main_task_mutex` directly helper instead of `RetainedState.main_tasklist` / `main_task_mutex` directly
- Win32 pointer API loading, stylus/ink timer ownership and decay, `WT_PACKET`
reset, and `WM_POINTERUPDATE` pen/touch handling now live in
`src/platform_windows/windows_stylus_input.cpp` instead of `src/main.cpp`,
but broader retained Win32 shell state is still open
- prepared-file background work now runs through an `AppRuntime`-owned worker - prepared-file background work now runs through an `AppRuntime`-owned worker
queue instead of a retained static worker in `src/app_events.cpp` queue instead of a retained static worker in `src/app_events.cpp`
- canvas async import/export/save/open background work now also runs through an - canvas async import/export/save/open background work now also runs through an

View File

@@ -33,6 +33,8 @@
namespace pp::panopainter { namespace pp::panopainter {
void bind_legacy_file_menu(App& app); void bind_legacy_file_menu(App& app);
void bind_legacy_about_menu(App& app);
void bind_legacy_layer_menu(App& app);
void bind_legacy_tools_menu(App& app); void bind_legacy_tools_menu(App& app);
} }
@@ -93,39 +95,6 @@ void close_legacy_overlay_handles_if_open(
} }
} }
pp::app::DocumentLayerMenuPlan make_layer_menu_plan(
pp::app::DocumentLayerMenuCommand command,
App& app)
{
const bool has_current_layer = app.layers && app.layers->m_current_layer;
const int current_index = app.canvas && app.canvas->m_canvas
? app.canvas->m_canvas->m_current_layer_idx
: 0;
const int animation_duration = Canvas::I
? Canvas::I->anim_duration()
: 0;
const std::string current_name = has_current_layer
? app.layers->m_current_layer->m_label_text
: std::string {};
std::string lower_name;
if (app.canvas && app.canvas->m_canvas && current_index > 0
&& current_index - 1 < static_cast<int>(app.canvas->m_canvas->m_layers.size()))
{
lower_name = app.canvas->m_canvas->m_layers[current_index - 1]->m_name;
}
const auto plan = pp::app::plan_document_layer_menu(
command,
has_current_layer,
current_index,
animation_duration,
current_name,
lower_name);
if (plan)
return plan.value();
return {};
}
[[nodiscard]] bool should_open_tools_panel(const pp::app::ToolsPanelPlan& plan) noexcept [[nodiscard]] bool should_open_tools_panel(const pp::app::ToolsPanelPlan& plan) noexcept
{ {
return plan.action == pp::app::ToolsPanelAction::open_floating_panel; return plan.action == pp::app::ToolsPanelAction::open_floating_panel;
@@ -154,23 +123,11 @@ void execute_main_toolbar_plan(App& app, const pp::app::MainToolbarPlan& plan)
pp::panopainter::execute_legacy_main_toolbar_plan(app, plan); pp::panopainter::execute_legacy_main_toolbar_plan(app, plan);
} }
void execute_about_menu_plan(App& app, const pp::app::AboutMenuPlan& plan)
{
pp::panopainter::execute_legacy_about_menu_plan(app, plan);
}
void execute_tools_menu_plan(App& app, const pp::app::ToolsMenuPlan& plan) void execute_tools_menu_plan(App& app, const pp::app::ToolsMenuPlan& plan)
{ {
pp::panopainter::execute_legacy_tools_menu_plan(app, plan); pp::panopainter::execute_legacy_tools_menu_plan(app, plan);
} }
void execute_document_layer_menu_plan(App& app, const pp::app::DocumentLayerMenuPlan& plan)
{
const auto status = pp::panopainter::execute_legacy_document_layer_menu_plan(app, plan);
if (!status.ok())
LOG("Layer menu action failed: %s", status.message);
}
void execute_document_layer_merge_plan(App& app, const pp::app::DocumentLayerMergePlan& plan) void execute_document_layer_merge_plan(App& app, const pp::app::DocumentLayerMergePlan& plan)
{ {
const auto status = pp::panopainter::execute_legacy_document_layer_merge_plan(app, plan); const auto status = pp::panopainter::execute_legacy_document_layer_merge_plan(app, plan);
@@ -764,113 +721,7 @@ void App::init_menu_tools()
void App::init_menu_about() void App::init_menu_about()
{ {
if (auto* menu_file = layout[main_id]->find<NodeButtonCustom>("menu-about")) pp::panopainter::bind_legacy_about_menu(*this);
{
menu_file->on_click = [=](Node*) {
auto* popup_root = layout[main_id];
if (!popup_root) {
return;
}
glm::vec2 pos = menu_file->m_pos + glm::vec2(0, menu_file->m_size.y);
auto popup = add_menu_popup(*this, "about-menu", pos, menu_file->m_size.x);
if (!popup)
return;
pp::panopainter::detach_legacy_node_from_parent(*popup);
auto popup_overlay = pp::panopainter::open_legacy_overlay_node_with_handle(*popup_root, popup);
if (!popup_overlay) {
pp::panopainter::destroy_legacy_node(*popup);
return;
}
auto popup_handle = popup_overlay.value();
popup->find<NodeButtonCustom>("about-app")->on_click = [this, popup_root, popup_handle](Node*) {
const auto plan = pp::app::plan_about_menu_command(
pp::app::AboutMenuCommand::about_app,
g_version_major,
g_version_minor,
g_version_fix);
execute_about_menu_plan(*this, plan);
if (plan.closes_root_popup)
{
close_legacy_overlay_handle_ignoring_status(*popup_root, popup_handle);
}
};
popup->find<NodeButtonCustom>("about-doc")->on_click = [this, popup_root, popup_handle](Node*) {
// auto path = Asset::absolute("data/doc/test.pdf");
// display_file(path);
const auto plan = pp::app::plan_about_menu_command(
pp::app::AboutMenuCommand::help_guide,
g_version_major,
g_version_minor,
g_version_fix);
execute_about_menu_plan(*this, plan);
if (plan.closes_root_popup)
{
close_legacy_overlay_handle_ignoring_status(*popup_root, popup_handle);
}
};
if (auto item = popup->find<NodeButtonCustom>("about-news"))
{
if (auto text = item->find<NodeText>("menu-label"))
{
const auto plan = pp::app::plan_about_menu_command(
pp::app::AboutMenuCommand::whats_new,
g_version_major,
g_version_minor,
g_version_fix);
text->set_text(plan.label.c_str());
}
item->on_click = [this, popup_root, popup_handle](Node*) {
const auto plan = pp::app::plan_about_menu_command(
pp::app::AboutMenuCommand::whats_new,
g_version_major,
g_version_minor,
g_version_fix);
execute_about_menu_plan(*this, plan);
if (plan.closes_root_popup)
{
close_legacy_overlay_handle_ignoring_status(*popup_root, popup_handle);
}
};
}
if (auto b = popup->find<NodeButtonCustom>("about-crash"))
{
b->on_click = [this, popup_root, popup_handle](Node*) {
const auto plan = pp::app::plan_about_menu_command(
pp::app::AboutMenuCommand::induce_crash,
g_version_major,
g_version_minor,
g_version_fix);
execute_about_menu_plan(*this, plan);
if (plan.closes_root_popup)
{
close_legacy_overlay_handle_ignoring_status(*popup_root, popup_handle);
}
};
}
if (auto b = popup->find<NodeButtonCustom>("about-perf"))
{
b->on_click = [this, popup_root, popup_handle](Node*) {
const auto plan = pp::app::plan_about_menu_command(
pp::app::AboutMenuCommand::performance_test,
g_version_major,
g_version_minor,
g_version_fix,
true,
Canvas::I != nullptr);
execute_about_menu_plan(*this, plan);
if (plan.closes_root_popup)
{
close_legacy_overlay_handle_ignoring_status(*popup_root, popup_handle);
}
};
}
};
}
} }
void App::brush_update(bool update_color, bool update_brush) void App::brush_update(bool update_color, bool update_brush)
@@ -933,63 +784,7 @@ void App::brush_update(bool update_color, bool update_brush)
void App::init_menu_layer() void App::init_menu_layer()
{ {
if (auto* menu_file = layout[main_id]->find<NodeButtonCustom>("menu-layers")) pp::panopainter::bind_legacy_layer_menu(*this);
{
menu_file->on_click = [=](Node*) {
auto* popup_root = layout[main_id];
if (!popup_root) {
return;
}
glm::vec2 pos = menu_file->m_pos + glm::vec2(0, menu_file->m_size.y);
auto popup = add_menu_popup(*this, "layers-menu", pos, menu_file->m_size.x);
if (!popup)
return;
pp::panopainter::detach_legacy_node_from_parent(*popup);
auto popup_overlay = pp::panopainter::open_legacy_overlay_node_with_handle(*popup_root, popup);
if (!popup_overlay) {
pp::panopainter::destroy_legacy_node(*popup);
return;
}
auto popup_handle = popup_overlay.value();
popup->find<NodeButtonCustom>("layer-clear")->on_click = [this, popup_root, popup_handle](Node*) {
const auto plan = make_layer_menu_plan(pp::app::DocumentLayerMenuCommand::clear, *this);
execute_document_layer_menu_plan(*this, plan);
close_legacy_overlay_handle_ignoring_status(*popup_root, popup_handle);
};
{
const auto plan = make_layer_menu_plan(pp::app::DocumentLayerMenuCommand::clear, *this);
popup->find<NodeButtonCustom>("layer-clear")->
find<NodeText>("menu-label")->
set_text(plan.label.c_str());
}
popup->find<NodeButtonCustom>("layer-rename")->on_click = [this, popup_root, popup_handle](Node*) {
const auto plan = make_layer_menu_plan(pp::app::DocumentLayerMenuCommand::rename, *this);
execute_document_layer_menu_plan(*this, plan);
close_legacy_overlay_handle_ignoring_status(*popup_root, popup_handle);
};
{
const auto plan = make_layer_menu_plan(pp::app::DocumentLayerMenuCommand::rename, *this);
popup->find<NodeButtonCustom>("layer-rename")->
find<NodeText>("menu-label")->
set_text(plan.label.c_str());
}
popup->find<NodeButtonCustom>("layer-merge")->on_click = [this, popup_root, popup_handle](Node*) {
const auto plan = make_layer_menu_plan(pp::app::DocumentLayerMenuCommand::merge_down, *this);
execute_document_layer_menu_plan(*this, plan);
close_legacy_overlay_handle_ignoring_status(*popup_root, popup_handle);
};
{
const auto plan = make_layer_menu_plan(pp::app::DocumentLayerMenuCommand::merge_down, *this);
popup->find<NodeButtonCustom>("layer-merge")->
find<NodeText>("menu-label")->
set_text(plan.label.c_str());
}
};
}
} }
void App::initLayout() void App::initLayout()

View File

@@ -0,0 +1,264 @@
#include "pch.h"
#include "app.h"
#include "app_core/about_menu.h"
#include "app_core/document_layer.h"
#include "legacy_app_shell_services.h"
#include "legacy_document_layer_services.h"
#include "legacy_ui_overlay_services.h"
#include "node_button_custom.h"
#include "node_popup_menu.h"
#include "node_text.h"
namespace {
std::shared_ptr<NodePopupMenu> add_menu_popup(
App& app,
const char* template_id,
glm::vec2 position,
float rtl_anchor_width)
{
const auto popup = pp::panopainter::add_legacy_popup_menu(
app,
template_id,
position.x,
position.y,
rtl_anchor_width);
if (!popup) {
LOG("Popup menu '%s' failed: %s", template_id ? template_id : "<null>", popup.status().message);
return nullptr;
}
return popup.value();
}
void close_legacy_overlay_handle_ignoring_status(
Node& anchor,
pp::ui::NodeHandle overlay) noexcept
{
(void)pp::panopainter::close_legacy_overlay_node(anchor, overlay);
}
pp::app::DocumentLayerMenuPlan make_layer_menu_plan(
pp::app::DocumentLayerMenuCommand command,
App& app)
{
const bool has_current_layer = app.layers && app.layers->m_current_layer;
const int current_index = app.canvas && app.canvas->m_canvas
? app.canvas->m_canvas->m_current_layer_idx
: 0;
const int animation_duration = Canvas::I
? Canvas::I->anim_duration()
: 0;
const std::string current_name = has_current_layer
? app.layers->m_current_layer->m_label_text
: std::string {};
std::string lower_name;
if (app.canvas && app.canvas->m_canvas && current_index > 0
&& current_index - 1 < static_cast<int>(app.canvas->m_canvas->m_layers.size()))
{
lower_name = app.canvas->m_canvas->m_layers[current_index - 1]->m_name;
}
const auto plan = pp::app::plan_document_layer_menu(
command,
has_current_layer,
current_index,
animation_duration,
current_name,
lower_name);
if (plan)
return plan.value();
return {};
}
void execute_about_menu_plan(App& app, const pp::app::AboutMenuPlan& plan)
{
pp::panopainter::execute_legacy_about_menu_plan(app, plan);
}
void execute_document_layer_menu_plan(App& app, const pp::app::DocumentLayerMenuPlan& plan)
{
const auto status = pp::panopainter::execute_legacy_document_layer_menu_plan(app, plan);
if (!status.ok())
LOG("Layer menu action failed: %s", status.message);
}
} // namespace
namespace pp::panopainter {
void bind_legacy_about_menu(App& app)
{
auto main = app.layout[app.main_id];
if (auto* menu_file = main->find<NodeButtonCustom>("menu-about"))
{
menu_file->on_click = [&app, menu_file](Node*) {
auto* popup_root = app.layout[app.main_id];
if (!popup_root) {
return;
}
glm::vec2 pos = menu_file->m_pos + glm::vec2(0, menu_file->m_size.y);
auto popup = add_menu_popup(app, "about-menu", pos, menu_file->m_size.x);
if (!popup)
return;
pp::panopainter::detach_legacy_node_from_parent(*popup);
auto popup_overlay = pp::panopainter::open_legacy_overlay_node_with_handle(*popup_root, popup);
if (!popup_overlay) {
pp::panopainter::destroy_legacy_node(*popup);
return;
}
auto popup_handle = popup_overlay.value();
popup->find<NodeButtonCustom>("about-app")->on_click = [&app, popup_root, popup_handle](Node*) {
const auto plan = pp::app::plan_about_menu_command(
pp::app::AboutMenuCommand::about_app,
g_version_major,
g_version_minor,
g_version_fix);
execute_about_menu_plan(app, plan);
if (plan.closes_root_popup)
{
close_legacy_overlay_handle_ignoring_status(*popup_root, popup_handle);
}
};
popup->find<NodeButtonCustom>("about-doc")->on_click = [&app, popup_root, popup_handle](Node*) {
// auto path = Asset::absolute("data/doc/test.pdf");
// display_file(path);
const auto plan = pp::app::plan_about_menu_command(
pp::app::AboutMenuCommand::help_guide,
g_version_major,
g_version_minor,
g_version_fix);
execute_about_menu_plan(app, plan);
if (plan.closes_root_popup)
{
close_legacy_overlay_handle_ignoring_status(*popup_root, popup_handle);
}
};
if (auto item = popup->find<NodeButtonCustom>("about-news"))
{
if (auto text = item->find<NodeText>("menu-label"))
{
const auto plan = pp::app::plan_about_menu_command(
pp::app::AboutMenuCommand::whats_new,
g_version_major,
g_version_minor,
g_version_fix);
text->set_text(plan.label.c_str());
}
item->on_click = [&app, popup_root, popup_handle](Node*) {
const auto plan = pp::app::plan_about_menu_command(
pp::app::AboutMenuCommand::whats_new,
g_version_major,
g_version_minor,
g_version_fix);
execute_about_menu_plan(app, plan);
if (plan.closes_root_popup)
{
close_legacy_overlay_handle_ignoring_status(*popup_root, popup_handle);
}
};
}
if (auto b = popup->find<NodeButtonCustom>("about-crash"))
{
b->on_click = [&app, popup_root, popup_handle](Node*) {
const auto plan = pp::app::plan_about_menu_command(
pp::app::AboutMenuCommand::induce_crash,
g_version_major,
g_version_minor,
g_version_fix);
execute_about_menu_plan(app, plan);
if (plan.closes_root_popup)
{
close_legacy_overlay_handle_ignoring_status(*popup_root, popup_handle);
}
};
}
if (auto b = popup->find<NodeButtonCustom>("about-perf"))
{
b->on_click = [&app, popup_root, popup_handle](Node*) {
const auto plan = pp::app::plan_about_menu_command(
pp::app::AboutMenuCommand::performance_test,
g_version_major,
g_version_minor,
g_version_fix,
true,
Canvas::I != nullptr);
execute_about_menu_plan(app, plan);
if (plan.closes_root_popup)
{
close_legacy_overlay_handle_ignoring_status(*popup_root, popup_handle);
}
};
}
};
}
}
void bind_legacy_layer_menu(App& app)
{
auto main = app.layout[app.main_id];
if (auto* menu_file = main->find<NodeButtonCustom>("menu-layers"))
{
menu_file->on_click = [&app, menu_file](Node*) {
auto* popup_root = app.layout[app.main_id];
if (!popup_root) {
return;
}
glm::vec2 pos = menu_file->m_pos + glm::vec2(0, menu_file->m_size.y);
auto popup = add_menu_popup(app, "layers-menu", pos, menu_file->m_size.x);
if (!popup)
return;
pp::panopainter::detach_legacy_node_from_parent(*popup);
auto popup_overlay = pp::panopainter::open_legacy_overlay_node_with_handle(*popup_root, popup);
if (!popup_overlay) {
pp::panopainter::destroy_legacy_node(*popup);
return;
}
auto popup_handle = popup_overlay.value();
popup->find<NodeButtonCustom>("layer-clear")->on_click = [&app, popup_root, popup_handle](Node*) {
const auto plan = make_layer_menu_plan(pp::app::DocumentLayerMenuCommand::clear, app);
execute_document_layer_menu_plan(app, plan);
close_legacy_overlay_handle_ignoring_status(*popup_root, popup_handle);
};
{
const auto plan = make_layer_menu_plan(pp::app::DocumentLayerMenuCommand::clear, app);
popup->find<NodeButtonCustom>("layer-clear")->
find<NodeText>("menu-label")->
set_text(plan.label.c_str());
}
popup->find<NodeButtonCustom>("layer-rename")->on_click = [&app, popup_root, popup_handle](Node*) {
const auto plan = make_layer_menu_plan(pp::app::DocumentLayerMenuCommand::rename, app);
execute_document_layer_menu_plan(app, plan);
close_legacy_overlay_handle_ignoring_status(*popup_root, popup_handle);
};
{
const auto plan = make_layer_menu_plan(pp::app::DocumentLayerMenuCommand::rename, app);
popup->find<NodeButtonCustom>("layer-rename")->
find<NodeText>("menu-label")->
set_text(plan.label.c_str());
}
popup->find<NodeButtonCustom>("layer-merge")->on_click = [&app, popup_root, popup_handle](Node*) {
const auto plan = make_layer_menu_plan(pp::app::DocumentLayerMenuCommand::merge_down, app);
execute_document_layer_menu_plan(app, plan);
close_legacy_overlay_handle_ignoring_status(*popup_root, popup_handle);
};
{
const auto plan = make_layer_menu_plan(pp::app::DocumentLayerMenuCommand::merge_down, app);
popup->find<NodeButtonCustom>("layer-merge")->
find<NodeText>("menu-label")->
set_text(plan.label.c_str());
}
};
}
}
} // namespace pp::panopainter

View File

@@ -130,6 +130,14 @@ struct LegacyCanvasDrawMergeLayerPathExecution {
std::function<void(int, float)> draw_frame; std::function<void(int, float)> draw_frame;
}; };
struct LegacyCanvasDrawLayerVisit {
size_t layer_index = 0;
int plane_index = 0;
int z = 0;
int first_frame = 0;
int last_frame = 0;
};
struct LegacyCanvasDrawMergeTemporaryCompositeExecution { struct LegacyCanvasDrawMergeTemporaryCompositeExecution {
std::function<void()> setup; std::function<void()> setup;
std::function<void()> bind_samplers; std::function<void()> bind_samplers;
@@ -671,6 +679,39 @@ inline void execute_legacy_canvas_draw_merge_layer_path(
execution.draw_debug_outline(); execution.draw_debug_outline();
} }
template <typename PlanOnionRange, typename ShouldDrawPlane, typename VisitLayerPlane, typename LogOnionRangeFailure>
inline void execute_legacy_canvas_draw_layer_traversal(
size_t layer_count,
PlanOnionRange&& plan_onion_range,
ShouldDrawPlane&& should_draw_plane,
VisitLayerPlane&& visit_layer_plane,
LogOnionRangeFailure&& log_onion_range_failure)
{
for (size_t i = 0; i < layer_count; ++i) {
const auto layer_index = i;
const auto onion_range_result = plan_onion_range(layer_index);
if (!onion_range_result) {
log_onion_range_failure(onion_range_result.status().message);
continue;
}
const auto onion_range = onion_range_result.value();
for (int plane_index = 0; plane_index < 6; ++plane_index) {
if (!should_draw_plane(layer_index, plane_index, onion_range.first_frame, onion_range.last_frame)) {
continue;
}
visit_layer_plane(LegacyCanvasDrawLayerVisit {
.layer_index = layer_index,
.plane_index = plane_index,
.z = static_cast<int>(layer_count - i),
.first_frame = onion_range.first_frame,
.last_frame = onion_range.last_frame,
}, onion_range);
}
}
}
inline void execute_legacy_canvas_draw_merge_temporary_composite( inline void execute_legacy_canvas_draw_merge_temporary_composite(
const LegacyCanvasDrawMergeTemporaryCompositeExecution& execution) const LegacyCanvasDrawMergeTemporaryCompositeExecution& execution)
{ {

View File

@@ -13,6 +13,7 @@
#include "renderer_gl/opengl_capabilities.h" #include "renderer_gl/opengl_capabilities.h"
#include "platform_windows/windows_platform_services.h" #include "platform_windows/windows_platform_services.h"
#include "platform_windows/windows_splash.h" #include "platform_windows/windows_splash.h"
#include "platform_windows/windows_stylus_input.h"
#include "../resource.h" #include "../resource.h"
#include <shellscalingapi.h> #include <shellscalingapi.h>
@@ -55,9 +56,6 @@ struct RetainedState
std::map<kKey, int> vkey_map; std::map<kKey, int> vkey_map;
wchar_t window_title[512]{}; wchar_t window_title[512]{};
std::jthread hmd_renderer; std::jthread hmd_renderer;
float timer_stylus = 0;
float timer_ink_touch = 0;
float timer_ink_pen = 0;
bool sandboxed = false; bool sandboxed = false;
std::mutex hmd_render_mutex; std::mutex hmd_render_mutex;
std::condition_variable hmd_render_cv; std::condition_variable hmd_render_cv;
@@ -176,25 +174,6 @@ void init_shcore_API()
SetProcessDpiAwareness_fn = (decltype(SetProcessDpiAwareness_fn))GetProcAddress(dll, "SetProcessDpiAwareness"); SetProcessDpiAwareness_fn = (decltype(SetProcessDpiAwareness_fn))GetProcAddress(dll, "SetProcessDpiAwareness");
} }
BOOL(*GetPointerInfo_fn)(UINT32 pointerId, POINTER_INFO* pointerInfo);
BOOL(*GetPointerType_fn)(UINT32 pointerId, POINTER_INPUT_TYPE* pointerType);
BOOL(*GetPointerTouchInfo_fn)(UINT32 pointerId, POINTER_TOUCH_INFO* touchInfo);
BOOL(*GetPointerPenInfo_fn)(UINT32 pointerId, POINTER_PEN_INFO* penInfo);
void init_ink_API()
{
HMODULE dll = LoadLibrary(L"User32.dll");
if (!dll)
{
LOG("cannot load User32.dll");
return;
}
LOG("loaded User32.dll");
GetPointerInfo_fn = (decltype(GetPointerInfo_fn))GetProcAddress(dll, "GetPointerInfo");
GetPointerType_fn = (decltype(GetPointerType_fn))GetProcAddress(dll, "GetPointerType");
GetPointerTouchInfo_fn = (decltype(GetPointerTouchInfo_fn))GetProcAddress(dll, "GetPointerTouchInfo");
GetPointerPenInfo_fn = (decltype(GetPointerPenInfo_fn))GetProcAddress(dll, "GetPointerPenInfo");
}
//Returns the last Win32 error, in string format. Returns an empty string if there is no error. //Returns the last Win32 error, in string format. Returns an empty string if there is no error.
std::string GetLastErrorAsString() std::string GetLastErrorAsString()
{ {
@@ -245,27 +224,7 @@ void async_unlock()
void win32_update_stylus(float dt) void win32_update_stylus(float dt)
{ {
auto& state = retained_state(); pp::platform::windows::update_stylus_state(dt);
state.timer_stylus += dt;
state.timer_ink_touch += dt;
state.timer_ink_pen += dt;
if (state.timer_stylus > 0.1 && (WacomTablet::I.m_stylus || WacomTablet::I.m_eraser))
{
WacomTablet::I.m_stylus = false;
WacomTablet::I.m_eraser = false;
App::I->redraw = true;
}
if (state.timer_ink_pen > 0.1 && WacomTablet::I.m_ink_pen)
{
WacomTablet::I.m_ink_pen = false;
App::I->redraw = true;
}
if (state.timer_ink_touch > 0.1 && WacomTablet::I.m_ink_touch)
{
WacomTablet::I.m_ink_touch = false;
App::I->redraw = true;
}
} }
void win32_update_fps(int frames) void win32_update_fps(int frames)
@@ -710,7 +669,7 @@ int main(int argc, char** argv)
App::I->initLog(); App::I->initLog();
init_shcore_API(); init_shcore_API();
init_ink_API(); pp::platform::windows::initialize_stylus_input();
if(SetProcessDpiAwareness_fn) if(SetProcessDpiAwareness_fn)
SetProcessDpiAwareness_fn(PROCESS_PER_MONITOR_DPI_AWARE); SetProcessDpiAwareness_fn(PROCESS_PER_MONITOR_DPI_AWARE);
@@ -1064,8 +1023,7 @@ LRESULT CALLBACK WndProc(HWND hWnd, UINT msg, WPARAM wp, LPARAM lp)
// } // }
case WT_PACKET: case WT_PACKET:
{ {
App::I->set_stylus(); pp::platform::windows::note_wintab_packet();
state.timer_stylus = 0;
App::I->ui_task_async([=] { App::I->ui_task_async([=] {
WacomTablet::I.handle_message(hWnd, msg, wp, lp); WacomTablet::I.handle_message(hWnd, msg, wp, lp);
}); });
@@ -1229,79 +1187,7 @@ LRESULT CALLBACK WndProc(HWND hWnd, UINT msg, WPARAM wp, LPARAM lp)
} }
case WM_POINTERUPDATE: case WM_POINTERUPDATE:
{ {
POINTER_TOUCH_INFO touchInfo; pp::platform::windows::handle_pointer_update_message(wp);
POINTER_PEN_INFO penInfo;
POINTER_INFO pointerInfo;
UINT32 pointerId = GET_POINTERID_WPARAM(wp);
POINTER_INPUT_TYPE pointerType = PT_POINTER;
if(!GetPointerInfo_fn)
break;
// Retrieve common pointer information
if (!GetPointerInfo_fn(pointerId, &pointerInfo))
{
// failure, call GetLastError()
}
else
{
// success, process pointerInfo
if (!GetPointerType_fn(pointerId, &pointerType))
{
// failure, call GetLastError()
// set PT_POINTER to fall to default case below
pointerType = PT_POINTER;
}
switch (pointerType)
{
case PT_TOUCH:
// Retrieve touch information
if (!GetPointerTouchInfo_fn(pointerId, &touchInfo))
{
// failure, call GetLastError()
}
else
{
// success, process touchInfo
// mark as handled to skip call to DefWindowProc
//fHandled = TRUE;
state.timer_ink_touch = 0;
WacomTablet::I.m_ink_touch = true;
WacomTablet::I.m_pen_pres = 1;
}
break;
case PT_PEN:
// Retrieve pen information
if (!GetPointerPenInfo_fn(pointerId, &penInfo))
{
// failure, call GetLastError()
}
else
{
// success, process penInfo
// mark as handled to skip call to DefWindowProc
//fHandled = TRUE;
//penInfo.pressure
state.timer_ink_pen = 0;
WacomTablet::I.m_ink_pen = true;
WacomTablet::I.m_pen_pres = (float)penInfo.pressure / 1024.f;
App::I->set_stylus();
}
break;
default:
if (!GetPointerInfo_fn(pointerId, &pointerInfo))
{
// failure.
}
else
{
// success, proceed with pointerInfo.
//fHandled = HandleGenericPointerInfo(&pointerInfo);
}
break;
}
}
break; break;
} }
} }

View File

@@ -439,32 +439,31 @@ void NodeCanvas::draw()
apply_node_canvas_capability(pp::renderer::gl::depth_test_state(), false); apply_node_canvas_capability(pp::renderer::gl::depth_test_state(), false);
const auto& b = m_canvas->m_current_stroke->m_brush; const auto& b = m_canvas->m_current_stroke->m_brush;
pp::panopainter::execute_legacy_canvas_draw_layer_traversal(
for (size_t i = 0; i < m_canvas->m_layers.size(); i++) m_canvas->m_layers.size(),
{ [&](size_t layer_index) {
auto layer_index = i; return pp::app::plan_animation_onion_frame_range(
for (int plane_index = 0; plane_index < 6; plane_index++)
{
const auto onion_range_result = pp::app::plan_animation_onion_frame_range(
m_canvas->m_layers[layer_index]->frames_count(), m_canvas->m_layers[layer_index]->frames_count(),
m_canvas->m_layers[layer_index]->m_frame_index, m_canvas->m_layers[layer_index]->m_frame_index,
App::I->animation->get_onion_size()); App::I->animation->get_onion_size());
if (!onion_range_result) { },
LOG("NodeCanvas onion frame range failed: %s", onion_range_result.status().message); [&](size_t layer_index, int plane_index, int first_frame, int last_frame) {
continue;
}
const auto onion_range = onion_range_result.value();
bool faces = false; bool faces = false;
for (int frame = onion_range.first_frame; frame <= onion_range.last_frame; frame++) for (int frame = first_frame; frame <= last_frame; ++frame)
faces |= m_canvas->m_layers[layer_index]->face(plane_index, 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) &&
(!m_canvas->m_layers[layer_index]->m_visible ||
m_canvas->m_layers[layer_index]->m_opacity == .0f || !faces))
continue;
int z = (int)(m_canvas->m_layers.size() - i); if (m_canvas->m_show_tmp && m_canvas->m_current_layer_idx == layer_index)
auto plane_mvp_z = proj * camera * return true;
glm::scale(glm::vec3(z + 1)) *
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) * 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));
@@ -480,8 +479,8 @@ void NodeCanvas::draw()
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,
m_canvas->m_current_stroke && 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, use_blend,
onion_range.first_frame, visit.first_frame,
onion_range.last_frame, visit.last_frame,
[&](int frame) { [&](int frame) {
return pp::app::animation_onion_frame_alpha(onion_range, frame); return pp::app::animation_onion_frame_alpha(onion_range, frame);
}, },
@@ -649,9 +648,10 @@ void NodeCanvas::draw()
#endif #endif
.draw_frame = draw_layer_frame, .draw_frame = draw_layer_frame,
}); });
},
} [&](const char* message) {
} LOG("NodeCanvas onion frame range failed: %s", message);
});
if (use_blend) if (use_blend)
{ {
m_cache_rtt.unbindFramebuffer(); m_cache_rtt.unbindFramebuffer();

View File

@@ -0,0 +1,129 @@
#include "pch.h"
#include "platform_windows/windows_stylus_input.h"
#include "app.h"
#include "log.h"
#include "wacom.h"
namespace pp::platform::windows {
namespace {
struct StylusInputState final
{
float timer_stylus = 0.0f;
float timer_ink_touch = 0.0f;
float timer_ink_pen = 0.0f;
BOOL (*get_pointer_info)(UINT32, POINTER_INFO*) = nullptr;
BOOL (*get_pointer_type)(UINT32, POINTER_INPUT_TYPE*) = nullptr;
BOOL (*get_pointer_touch_info)(UINT32, POINTER_TOUCH_INFO*) = nullptr;
BOOL (*get_pointer_pen_info)(UINT32, POINTER_PEN_INFO*) = nullptr;
};
StylusInputState& stylus_input_state()
{
static StylusInputState state;
return state;
}
}
void initialize_stylus_input()
{
auto& state = stylus_input_state();
HMODULE dll = LoadLibrary(L"User32.dll");
if (!dll)
{
LOG("cannot load User32.dll");
return;
}
LOG("loaded User32.dll");
state.get_pointer_info =
reinterpret_cast<decltype(state.get_pointer_info)>(GetProcAddress(dll, "GetPointerInfo"));
state.get_pointer_type =
reinterpret_cast<decltype(state.get_pointer_type)>(GetProcAddress(dll, "GetPointerType"));
state.get_pointer_touch_info =
reinterpret_cast<decltype(state.get_pointer_touch_info)>(GetProcAddress(dll, "GetPointerTouchInfo"));
state.get_pointer_pen_info =
reinterpret_cast<decltype(state.get_pointer_pen_info)>(GetProcAddress(dll, "GetPointerPenInfo"));
}
void update_stylus_state(float dt)
{
auto& state = stylus_input_state();
state.timer_stylus += dt;
state.timer_ink_touch += dt;
state.timer_ink_pen += dt;
if (state.timer_stylus > 0.1f && (WacomTablet::I.m_stylus || WacomTablet::I.m_eraser))
{
WacomTablet::I.m_stylus = false;
WacomTablet::I.m_eraser = false;
App::I->redraw = true;
}
if (state.timer_ink_pen > 0.1f && WacomTablet::I.m_ink_pen)
{
WacomTablet::I.m_ink_pen = false;
App::I->redraw = true;
}
if (state.timer_ink_touch > 0.1f && WacomTablet::I.m_ink_touch)
{
WacomTablet::I.m_ink_touch = false;
App::I->redraw = true;
}
}
void note_wintab_packet()
{
auto& state = stylus_input_state();
App::I->set_stylus();
state.timer_stylus = 0.0f;
}
void handle_pointer_update_message(WPARAM wp)
{
auto& state = stylus_input_state();
if (!state.get_pointer_info)
return;
POINTER_TOUCH_INFO touch_info {};
POINTER_PEN_INFO pen_info {};
POINTER_INFO pointer_info {};
const UINT32 pointer_id = GET_POINTERID_WPARAM(wp);
POINTER_INPUT_TYPE pointer_type = PT_POINTER;
if (!state.get_pointer_info(pointer_id, &pointer_info))
return;
if (!state.get_pointer_type(pointer_id, &pointer_type))
pointer_type = PT_POINTER;
switch (pointer_type)
{
case PT_TOUCH:
if (!state.get_pointer_touch_info(pointer_id, &touch_info))
return;
state.timer_ink_touch = 0.0f;
WacomTablet::I.m_ink_touch = true;
WacomTablet::I.m_pen_pres = 1.0f;
return;
case PT_PEN:
if (!state.get_pointer_pen_info(pointer_id, &pen_info))
return;
state.timer_ink_pen = 0.0f;
WacomTablet::I.m_ink_pen = true;
WacomTablet::I.m_pen_pres = static_cast<float>(pen_info.pressure) / 1024.0f;
App::I->set_stylus();
return;
default:
return;
}
}
}

View File

@@ -0,0 +1,12 @@
#pragma once
#include <windows.h>
namespace pp::platform::windows {
void initialize_stylus_input();
void update_stylus_state(float dt);
void note_wintab_packet();
void handle_pointer_update_message(WPARAM wp);
}