Extract file menu binding and Win32 splash helper

This commit is contained in:
2026-06-16 11:10:31 +02:00
parent 8afeb087b8
commit d135835787
8 changed files with 369 additions and 232 deletions

View File

@@ -94,6 +94,7 @@ set(PP_PANOPAINTER_APP_SOURCES
src/app_dialogs.cpp
src/app_events.cpp
src/app_layout.cpp
src/app_layout_file_menu.cpp
src/app_layout_tools_menu.cpp
src/app_shaders.cpp
src/app_vr.cpp
@@ -161,6 +162,8 @@ set(PP_WINDOWS_PLATFORM_SOURCES
src/main.cpp
src/platform_windows/windows_platform_services.cpp
src/platform_windows/windows_platform_services.h
src/platform_windows/windows_splash.cpp
src/platform_windows/windows_splash.h
)
set(PP_WINDOWS_APP_SOURCES

View File

@@ -80,10 +80,10 @@ What is still carrying too much live ownership:
Current hotspot files:
- `src/canvas.cpp`: 2645 lines
- `src/app_layout.cpp`: 1498 lines
- `src/app_layout.cpp`: 1360 lines
- `src/canvas_modes.cpp`: 1798 lines
- `src/node.cpp`: 1551 lines
- `src/main.cpp`: 1374 lines
- `src/main.cpp`: 1181 lines
- `src/node_panel_brush.cpp`: 1197 lines
- `src/node_stroke_preview.cpp`: 933 lines
- `src/node_canvas.cpp`: 888 lines
@@ -127,7 +127,9 @@ Current architecture mismatches that must be treated as real blockers:
rather than thin composition/binding surfaces, even though tools-menu binding
plus nested panels/options submenu wiring now live in
`src/app_layout_tools_menu.cpp` and `App::init_menu_tools()` is now a thin
call-through.
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
thin call-through.
- `App`, `Canvas`, `Node`, retained workers, and platform entrypoints still use
global singleton reach, raw observer pointers, retained static worker
ownership in several app families, and ad hoc mutex/condition-variable
@@ -149,7 +151,10 @@ Current architecture mismatches that must be treated as real blockers:
local worker-state helper instead of a bare static accessor, the
prepared-file worker and the canvas async import/export/save/open worker now
live under `AppRuntime` instead of retained static app-events/canvas
workers, `App::rec_loop()` now delegates worker-iteration orchestration into
workers, and the splash-screen dialog loop, HWND ownership, and bitmap setup
now live in `src/platform_windows/windows_splash.cpp` instead of
`src/main.cpp`,
while `App::rec_loop()` now delegates worker-iteration orchestration into
the retained recording bridge, `App::update_rec_frames()` now delegates
recording label refresh through that same retained recording path, and the
canvas state-management cluster for picking, clear/clear-all, layer

View File

@@ -271,7 +271,7 @@ targets look like helpers under one old monolith.
Status: In Progress
Why now:
`src/app_layout.cpp` is still a 1498-line mixed file that builds menus,
`src/app_layout.cpp` is still a 1360-line mixed file that builds menus,
attaches callbacks, computes planner inputs, and mutates UI state directly.
Current slice:
@@ -279,6 +279,10 @@ Current slice:
now lives in `src/app_layout_tools_menu.cpp`, and `App::init_menu_tools()`
is now a thin call-through, but file/about/layer/sidebar and broader layout
composition are still inline in `src/app_layout.cpp`.
- File-menu UI binding, including the export submenu wiring, now also lives in
`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
still inline in `src/app_layout.cpp`.
Write scope:
- `src/app_layout.cpp`
@@ -392,6 +396,9 @@ Current slice:
- `main.cpp` Win32 window handles, GL task/mutex state, splash-dialog state,
stylus timers, and VR worker state now sit behind one retained local state
object instead of separate file-scope globals
- the splash-screen dialog loop, HWND ownership, and bitmap setup now also live
in `src/platform_windows/windows_splash.cpp` instead of `src/main.cpp`, and
`main.cpp` now just owns the helper lifecycle
- Win32 async GL/context lock state now lives in
`src/platform_windows/windows_platform_services.cpp` instead of `main.cpp`
retained state, and `main.cpp` only seeds that platform-owned context handle

View File

@@ -13,8 +13,6 @@
#include "app_core/canvas_tool_ui.h"
#include "app_core/document_layer.h"
#include "app_core/document_canvas.h"
#include "app_core/document_export.h"
#include "app_core/file_menu.h"
#include "app_core/app_status.h"
#include "app_core/main_toolbar.h"
#include "app_core/tools_menu.h"
@@ -34,6 +32,7 @@
#include <vector>
namespace pp::panopainter {
void bind_legacy_file_menu(App& app);
void bind_legacy_tools_menu(App& app);
}
@@ -54,16 +53,6 @@ bool apply_brush_preset_plan(App& app, const std::shared_ptr<Brush>& brush)
return pp::panopainter::apply_legacy_brush_preset_plan(app, brush);
}
bool apply_document_export_menu_plan(App& app, pp::app::DocumentExportMenuKind kind)
{
return pp::panopainter::apply_legacy_document_export_menu_plan(app, kind);
}
void apply_file_menu_plan(App& app, pp::app::FileMenuCommand command)
{
pp::panopainter::apply_legacy_file_menu_command(app, command);
}
std::shared_ptr<NodePopupMenu> add_menu_popup(
App& app,
const char* template_id,
@@ -752,138 +741,7 @@ void App::init_toolbar_draw()
void App::init_menu_file()
{
if (auto* menu_file = layout[main_id]->find<NodeButtonCustom>("menu-file"))
{
menu_file->on_click = [this, menu_file](Node*) {
auto* popup_root = layout[main_id];
if (!popup_root) {
return;
}
const auto open_checked_menu_popup = [this, popup_root](const char* id, glm::vec2 pos, float width)
-> std::pair<std::shared_ptr<NodePopupMenu>, pp::ui::NodeHandle>
{
auto popup = add_menu_popup(*this, id, pos, width);
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 {};
}
return { popup, popup_overlay.value() };
};
glm::vec2 pos = menu_file->m_pos + glm::vec2(0, menu_file->m_size.y);
const auto [popup, popup_handle] = open_checked_menu_popup("file-menu", pos, menu_file->m_size.x);
if (!popup)
return;
if (auto b = popup->find<NodeButtonCustom>("file-newdoc"))
b->on_click = [this, popup_root, popup_handle](Node*) {
apply_file_menu_plan(*this, pp::app::FileMenuCommand::new_document);
close_legacy_overlay_handle_ignoring_status(*popup_root, popup_handle);
};
if (auto b = popup->find<NodeButtonCustom>("file-import"))
b->on_click = [this, popup_root, popup_handle](Node*) {
apply_file_menu_plan(*this, pp::app::FileMenuCommand::import_image);
close_legacy_overlay_handle_ignoring_status(*popup_root, popup_handle);
};
if (auto b = popup->find<NodeButtonCustom>("file-open"))
b->on_click = [this, popup_root, popup_handle](Node*) {
apply_file_menu_plan(*this, pp::app::FileMenuCommand::open_project);
close_legacy_overlay_handle_ignoring_status(*popup_root, popup_handle);
};
if (auto b = popup->find<NodeButtonCustom>("file-browse"))
b->on_click = [this, popup_root, popup_handle](Node*) {
apply_file_menu_plan(*this, pp::app::FileMenuCommand::browse_cloud);
close_legacy_overlay_handle_ignoring_status(*popup_root, popup_handle);
};
if (auto b = popup->find<NodeButtonCustom>("file-save"))
b->on_click = [this, popup_root, popup_handle](Node*) {
apply_file_menu_plan(*this, pp::app::FileMenuCommand::save);
close_legacy_overlay_handle_ignoring_status(*popup_root, popup_handle);
};
if (auto b = popup->find<NodeButtonCustom>("file-save-as"))
b->on_click = [this, popup_root, popup_handle](Node*) {
apply_file_menu_plan(*this, pp::app::FileMenuCommand::save_as);
close_legacy_overlay_handle_ignoring_status(*popup_root, popup_handle);
};
if (auto b = popup->find<NodeButtonCustom>("file-save-ver"))
b->on_click = [this, popup_root, popup_handle](Node*) {
apply_file_menu_plan(*this, pp::app::FileMenuCommand::save_version);
close_legacy_overlay_handle_ignoring_status(*popup_root, popup_handle);
};
if (auto b = popup->find<NodeButtonCustom>("file-export"))
b->on_click = [this, popup_root, popup_handle](Node*) {
apply_file_menu_plan(*this, pp::app::FileMenuCommand::export_jpeg);
close_legacy_overlay_handle_ignoring_status(*popup_root, popup_handle);
};
if (auto b = popup->find<NodeButtonCustom>("file-export-tick"))
b->on_click = [this, b, popup_root, popup_handle, open_checked_menu_popup](Node*) {
glm::vec2 pos = b->m_pos + glm::vec2(b->m_size.x, 0);
const auto [subpopup, subpopup_handle] = open_checked_menu_popup("file-submenu-export", pos, b->m_size.x);
if (!subpopup)
return;
subpopup->find<NodeButtonCustom>("file-submenu-export-png")->on_click = [this, popup_root, popup_handle, subpopup_handle](Node*) {
apply_document_export_menu_plan(*this, pp::app::DocumentExportMenuKind::png);
close_legacy_overlay_handle_ignoring_status(*popup_root, popup_handle);
close_legacy_overlay_handle_ignoring_status(*popup_root, subpopup_handle);
};
subpopup->find<NodeButtonCustom>("file-submenu-export-layers")->on_click = [this, popup_root, popup_handle, subpopup_handle](Node*) {
apply_document_export_menu_plan(*this, pp::app::DocumentExportMenuKind::layers);
close_legacy_overlay_handle_ignoring_status(*popup_root, popup_handle);
close_legacy_overlay_handle_ignoring_status(*popup_root, subpopup_handle);
};
subpopup->find<NodeButtonCustom>("file-submenu-export-cube")->on_click = [this, popup_root, popup_handle, subpopup_handle](Node*) {
apply_document_export_menu_plan(*this, pp::app::DocumentExportMenuKind::cube_faces);
close_legacy_overlay_handle_ignoring_status(*popup_root, popup_handle);
close_legacy_overlay_handle_ignoring_status(*popup_root, subpopup_handle);
};
subpopup->find<NodeButtonCustom>("file-submenu-export-depth")->on_click = [this, popup_root, popup_handle, subpopup_handle](Node*) {
apply_document_export_menu_plan(*this, pp::app::DocumentExportMenuKind::depth);
close_legacy_overlay_handle_ignoring_status(*popup_root, popup_handle);
close_legacy_overlay_handle_ignoring_status(*popup_root, subpopup_handle);
};
subpopup->find<NodeButtonCustom>("file-submenu-export-anim")->on_click = [this, popup_root, popup_handle, subpopup_handle](Node*) {
apply_document_export_menu_plan(*this, pp::app::DocumentExportMenuKind::animation_frames);
close_legacy_overlay_handle_ignoring_status(*popup_root, popup_handle);
close_legacy_overlay_handle_ignoring_status(*popup_root, subpopup_handle);
};
subpopup->find<NodeButtonCustom>("file-submenu-export-anim-mp4")->on_click = [this, popup_root, popup_handle, subpopup_handle](Node*) {
apply_document_export_menu_plan(*this, pp::app::DocumentExportMenuKind::animation_mp4);
close_legacy_overlay_handle_ignoring_status(*popup_root, popup_handle);
close_legacy_overlay_handle_ignoring_status(*popup_root, subpopup_handle);
};
subpopup->find<NodeButtonCustom>("file-submenu-export-timelapse")->on_click = [this, popup_root, popup_handle, subpopup_handle](Node*) {
apply_document_export_menu_plan(*this, pp::app::DocumentExportMenuKind::timelapse);
close_legacy_overlay_handle_ignoring_status(*popup_root, popup_handle);
close_legacy_overlay_handle_ignoring_status(*popup_root, subpopup_handle);
};
};
if (auto b = popup->find<NodeButtonCustom>("file-share"))
b->on_click = [this, popup_root, popup_handle](Node*) {
apply_file_menu_plan(*this, pp::app::FileMenuCommand::share);
close_legacy_overlay_handle_ignoring_status(*popup_root, popup_handle);
};
if (auto b = popup->find<NodeButtonCustom>("file-resize"))
b->on_click = [this, popup_root, popup_handle](Node*) {
apply_file_menu_plan(*this, pp::app::FileMenuCommand::resize);
close_legacy_overlay_handle_ignoring_status(*popup_root, popup_handle);
};
if (auto b = popup->find<NodeButtonCustom>("file-cloud-upload"))
b->on_click = [this, popup_root, popup_handle](Node*) {
apply_file_menu_plan(*this, pp::app::FileMenuCommand::cloud_upload);
close_legacy_overlay_handle_ignoring_status(*popup_root, popup_handle);
};
if (auto b = popup->find<NodeButtonCustom>("file-cloud-browse"))
b->on_click = [this, popup_root, popup_handle](Node*) {
apply_file_menu_plan(*this, pp::app::FileMenuCommand::cloud_browse);
close_legacy_overlay_handle_ignoring_status(*popup_root, popup_handle);
};
};
}
pp::panopainter::bind_legacy_file_menu(*this);
}
void App::init_menu_edit()

View File

@@ -0,0 +1,190 @@
#include "pch.h"
#include "app.h"
#include "app_core/document_export.h"
#include "app_core/file_menu.h"
#include "legacy_app_shell_services.h"
#include "legacy_ui_overlay_services.h"
#include "node_button_custom.h"
#include "node_popup_menu.h"
namespace {
void apply_document_export_menu_plan(App& app, pp::app::DocumentExportMenuKind kind)
{
(void)pp::panopainter::apply_legacy_document_export_menu_plan(app, kind);
}
void apply_file_menu_plan(App& app, pp::app::FileMenuCommand command)
{
pp::panopainter::apply_legacy_file_menu_command(app, command);
}
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);
}
} // namespace
namespace pp::panopainter {
void bind_legacy_file_menu(App& app)
{
auto main = app.layout[app.main_id];
if (auto* menu_file = main->find<NodeButtonCustom>("menu-file"))
{
menu_file->on_click = [&app, menu_file](Node*) {
auto* popup_root = app.layout[app.main_id];
if (!popup_root) {
return;
}
const auto open_checked_menu_popup = [popup_root](App& app_ref, const char* id, glm::vec2 pos, float width)
-> std::pair<std::shared_ptr<NodePopupMenu>, pp::ui::NodeHandle>
{
auto popup = add_menu_popup(app_ref, id, pos, width);
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 {};
}
return { popup, popup_overlay.value() };
};
glm::vec2 pos = menu_file->m_pos + glm::vec2(0, menu_file->m_size.y);
const auto [popup, popup_handle] = open_checked_menu_popup(app, "file-menu", pos, menu_file->m_size.x);
if (!popup)
return;
if (auto b = popup->find<NodeButtonCustom>("file-newdoc"))
b->on_click = [&app, popup_root, popup_handle](Node*) {
apply_file_menu_plan(app, pp::app::FileMenuCommand::new_document);
close_legacy_overlay_handle_ignoring_status(*popup_root, popup_handle);
};
if (auto b = popup->find<NodeButtonCustom>("file-import"))
b->on_click = [&app, popup_root, popup_handle](Node*) {
apply_file_menu_plan(app, pp::app::FileMenuCommand::import_image);
close_legacy_overlay_handle_ignoring_status(*popup_root, popup_handle);
};
if (auto b = popup->find<NodeButtonCustom>("file-open"))
b->on_click = [&app, popup_root, popup_handle](Node*) {
apply_file_menu_plan(app, pp::app::FileMenuCommand::open_project);
close_legacy_overlay_handle_ignoring_status(*popup_root, popup_handle);
};
if (auto b = popup->find<NodeButtonCustom>("file-browse"))
b->on_click = [&app, popup_root, popup_handle](Node*) {
apply_file_menu_plan(app, pp::app::FileMenuCommand::browse_cloud);
close_legacy_overlay_handle_ignoring_status(*popup_root, popup_handle);
};
if (auto b = popup->find<NodeButtonCustom>("file-save"))
b->on_click = [&app, popup_root, popup_handle](Node*) {
apply_file_menu_plan(app, pp::app::FileMenuCommand::save);
close_legacy_overlay_handle_ignoring_status(*popup_root, popup_handle);
};
if (auto b = popup->find<NodeButtonCustom>("file-save-as"))
b->on_click = [&app, popup_root, popup_handle](Node*) {
apply_file_menu_plan(app, pp::app::FileMenuCommand::save_as);
close_legacy_overlay_handle_ignoring_status(*popup_root, popup_handle);
};
if (auto b = popup->find<NodeButtonCustom>("file-save-ver"))
b->on_click = [&app, popup_root, popup_handle](Node*) {
apply_file_menu_plan(app, pp::app::FileMenuCommand::save_version);
close_legacy_overlay_handle_ignoring_status(*popup_root, popup_handle);
};
if (auto b = popup->find<NodeButtonCustom>("file-export"))
b->on_click = [&app, popup_root, popup_handle](Node*) {
apply_file_menu_plan(app, pp::app::FileMenuCommand::export_jpeg);
close_legacy_overlay_handle_ignoring_status(*popup_root, popup_handle);
};
if (auto b = popup->find<NodeButtonCustom>("file-export-tick"))
b->on_click = [&app, b, popup_root, popup_handle, open_checked_menu_popup](Node*) {
glm::vec2 pos = b->m_pos + glm::vec2(b->m_size.x, 0);
const auto [subpopup, subpopup_handle] = open_checked_menu_popup(app, "file-submenu-export", pos, b->m_size.x);
if (!subpopup)
return;
subpopup->find<NodeButtonCustom>("file-submenu-export-png")->on_click = [&app, popup_root, popup_handle, subpopup_handle](Node*) {
apply_document_export_menu_plan(app, pp::app::DocumentExportMenuKind::png);
close_legacy_overlay_handle_ignoring_status(*popup_root, popup_handle);
close_legacy_overlay_handle_ignoring_status(*popup_root, subpopup_handle);
};
subpopup->find<NodeButtonCustom>("file-submenu-export-layers")->on_click = [&app, popup_root, popup_handle, subpopup_handle](Node*) {
apply_document_export_menu_plan(app, pp::app::DocumentExportMenuKind::layers);
close_legacy_overlay_handle_ignoring_status(*popup_root, popup_handle);
close_legacy_overlay_handle_ignoring_status(*popup_root, subpopup_handle);
};
subpopup->find<NodeButtonCustom>("file-submenu-export-cube")->on_click = [&app, popup_root, popup_handle, subpopup_handle](Node*) {
apply_document_export_menu_plan(app, pp::app::DocumentExportMenuKind::cube_faces);
close_legacy_overlay_handle_ignoring_status(*popup_root, popup_handle);
close_legacy_overlay_handle_ignoring_status(*popup_root, subpopup_handle);
};
subpopup->find<NodeButtonCustom>("file-submenu-export-depth")->on_click = [&app, popup_root, popup_handle, subpopup_handle](Node*) {
apply_document_export_menu_plan(app, pp::app::DocumentExportMenuKind::depth);
close_legacy_overlay_handle_ignoring_status(*popup_root, popup_handle);
close_legacy_overlay_handle_ignoring_status(*popup_root, subpopup_handle);
};
subpopup->find<NodeButtonCustom>("file-submenu-export-anim")->on_click = [&app, popup_root, popup_handle, subpopup_handle](Node*) {
apply_document_export_menu_plan(app, pp::app::DocumentExportMenuKind::animation_frames);
close_legacy_overlay_handle_ignoring_status(*popup_root, popup_handle);
close_legacy_overlay_handle_ignoring_status(*popup_root, subpopup_handle);
};
subpopup->find<NodeButtonCustom>("file-submenu-export-anim-mp4")->on_click = [&app, popup_root, popup_handle, subpopup_handle](Node*) {
apply_document_export_menu_plan(app, pp::app::DocumentExportMenuKind::animation_mp4);
close_legacy_overlay_handle_ignoring_status(*popup_root, popup_handle);
close_legacy_overlay_handle_ignoring_status(*popup_root, subpopup_handle);
};
subpopup->find<NodeButtonCustom>("file-submenu-export-timelapse")->on_click = [&app, popup_root, popup_handle, subpopup_handle](Node*) {
apply_document_export_menu_plan(app, pp::app::DocumentExportMenuKind::timelapse);
close_legacy_overlay_handle_ignoring_status(*popup_root, popup_handle);
close_legacy_overlay_handle_ignoring_status(*popup_root, subpopup_handle);
};
};
if (auto b = popup->find<NodeButtonCustom>("file-share"))
b->on_click = [&app, popup_root, popup_handle](Node*) {
apply_file_menu_plan(app, pp::app::FileMenuCommand::share);
close_legacy_overlay_handle_ignoring_status(*popup_root, popup_handle);
};
if (auto b = popup->find<NodeButtonCustom>("file-resize"))
b->on_click = [&app, popup_root, popup_handle](Node*) {
apply_file_menu_plan(app, pp::app::FileMenuCommand::resize);
close_legacy_overlay_handle_ignoring_status(*popup_root, popup_handle);
};
if (auto b = popup->find<NodeButtonCustom>("file-cloud-upload"))
b->on_click = [&app, popup_root, popup_handle](Node*) {
apply_file_menu_plan(app, pp::app::FileMenuCommand::cloud_upload);
close_legacy_overlay_handle_ignoring_status(*popup_root, popup_handle);
};
if (auto b = popup->find<NodeButtonCustom>("file-cloud-browse"))
b->on_click = [&app, popup_root, popup_handle](Node*) {
apply_file_menu_plan(app, pp::app::FileMenuCommand::cloud_browse);
close_legacy_overlay_handle_ignoring_status(*popup_root, popup_handle);
};
};
}
}
} // namespace pp::panopainter

View File

@@ -12,6 +12,7 @@
#include "legacy_preference_storage.h"
#include "renderer_gl/opengl_capabilities.h"
#include "platform_windows/windows_platform_services.h"
#include "platform_windows/windows_splash.h"
#include "../resource.h"
#include <shellscalingapi.h>
@@ -61,7 +62,6 @@ struct RetainedState
std::mutex hmd_render_mutex;
std::condition_variable hmd_render_cv;
std::unique_ptr<Vive> vive;
HWND splash_dialog{};
};
RetainedState& retained_state()
@@ -669,84 +669,6 @@ void win32_save_window_state()
p.rcNormalPosition.right,
p.rcNormalPosition.bottom });
}
HBITMAP image_to_hbitmap(const Image& img)
{
BITMAPINFOHEADER bmih;
memset(&bmih, 0, sizeof(BITMAPINFOHEADER));
bmih.biWidth = img.width;
bmih.biHeight = img.height;
bmih.biBitCount = 32;
bmih.biCompression = BI_RGB;
bmih.biSize = sizeof(BITMAPINFOHEADER);
bmih.biPlanes = 1;
BITMAPINFO* bmi = (BITMAPINFO*)&bmih;
return CreateDIBitmap(GetDC(NULL), &bmih, CBM_INIT, img.data(), bmi, DIB_RGB_COLORS);
}
LRESULT CALLBACK splash_proc(HWND hWndDlg, UINT Msg, WPARAM wParam, LPARAM lParam)
{
switch (Msg)
{
case WM_INITDIALOG:
{
auto monitor = MonitorFromWindow(0, MONITOR_DEFAULTTOPRIMARY);
auto x = unsigned{ 96 };
auto y = unsigned{ 96 };
if (GetDpiForMonitor_fn)
GetDpiForMonitor_fn(monitor, MDT_EFFECTIVE_DPI, &x, &y);
float z = (float)x / 96.f;
static char base_path[MAX_PATH];
GetCurrentDirectoryA(MAX_PATH, base_path);
std::string path = std::string(base_path) + "\\data\\splash.jpg";
//auto hbitmap = (HBITMAP)LoadImageA(NULL, path.c_str(), IMAGE_BITMAP, 0, 0, LR_LOADFROMFILE);
Image img;
img.load_file(path);
img.flip();
auto hbitmap = image_to_hbitmap(img.resize(512 * z, 200 * z));
SendMessage(GetDlgItem(hWndDlg, IDC_STATIC_IMAGE), STM_SETIMAGE, (WPARAM)IMAGE_BITMAP, (LPARAM)hbitmap);
SetDlgItemText(hWndDlg, IDC_STATIC_VERSION, g_version_number_w);
RECT r;
GetClientRect(hWndDlg, &r);
MoveWindow(GetDlgItem(hWndDlg, IDC_STATIC_IMAGE), 0, 0, 512 * z, 200 * z, TRUE);
SetWindowPos(hWndDlg, HWND_TOP, 0, 0, 512 * z, 200 * z, SWP_NOMOVE);
HWND hWndVersion = GetDlgItem(hWndDlg, IDC_STATIC_VERSION);
RECT rv;
GetClientRect(hWndVersion, &rv);
MoveWindow(hWndVersion, 0, 200 * z - (rv.bottom - rv.top), r.right - r.left, rv.bottom - rv.top, TRUE);
return TRUE;
}
case WM_USER_CLOSE:
PostQuitMessage(0);
return TRUE;
}
return DefWindowProc(hWndDlg, Msg, wParam, lParam);;
}
void splash_thread_loop()
{
auto& state = retained_state();
BT_SetTerminate();
state.splash_dialog = CreateDialog(state.hInst, MAKEINTRESOURCE(IDD_SPLASH), NULL, reinterpret_cast<DLGPROC>(splash_proc));
MSG msg;
while (GetMessage(&msg, 0, 0, 0) > 0)
{
if (IsDialogMessage(state.splash_dialog, &msg))
{
DispatchMessage(&msg);
TranslateMessage(&msg);
}
}
DestroyWindow(state.splash_dialog);
}
BOOL UnadjustWindowRectEx(LPRECT prc, DWORD dwStyle, BOOL fMenu, DWORD dwExStyle)
{
RECT rc;
@@ -811,7 +733,7 @@ int main(int argc, char** argv)
LOG("data files ok");
}
std::jthread dialog_thread(splash_thread_loop);
pp::platform::windows::SplashScreen splash(GetModuleHandle(NULL));
init_vk_map();
@@ -1022,9 +944,7 @@ int main(int argc, char** argv)
SetForegroundWindow(state.hWnd);
//ShowWindow(hWnd, show_cmd);
SendMessage(state.splash_dialog, WM_USER_CLOSE, 0, 0);
if (dialog_thread.joinable())
dialog_thread.join();
splash.dismiss();
MSG msg;
LOG("start main loop");

View File

@@ -0,0 +1,124 @@
#include "pch.h"
#include "platform_windows/windows_splash.h"
#include "app.h"
#include "image.h"
#include "log.h"
#include "version.h"
#include "../resource.h"
#include <chrono>
#include <shellscalingapi.h>
#include <string>
#include <stop_token>
extern HRESULT(*GetDpiForMonitor_fn)(HMONITOR hmonitor, MONITOR_DPI_TYPE dpiType, UINT* dpiX, UINT* dpiY);
namespace {
constexpr UINT kSplashCloseMessage = WM_USER + 1;
HBITMAP image_to_hbitmap(const Image& img)
{
BITMAPINFOHEADER bmih;
memset(&bmih, 0, sizeof(BITMAPINFOHEADER));
bmih.biWidth = img.width;
bmih.biHeight = img.height;
bmih.biBitCount = 32;
bmih.biCompression = BI_RGB;
bmih.biSize = sizeof(BITMAPINFOHEADER);
bmih.biPlanes = 1;
BITMAPINFO* bmi = (BITMAPINFO*)&bmih;
return CreateDIBitmap(GetDC(NULL), &bmih, CBM_INIT, img.data(), bmi, DIB_RGB_COLORS);
}
LRESULT CALLBACK splash_proc(HWND hWndDlg, UINT Msg, WPARAM wParam, LPARAM lParam)
{
switch (Msg)
{
case WM_INITDIALOG:
{
auto monitor = MonitorFromWindow(0, MONITOR_DEFAULTTOPRIMARY);
auto x = unsigned{ 96 };
auto y = unsigned{ 96 };
if (GetDpiForMonitor_fn)
GetDpiForMonitor_fn(monitor, MDT_EFFECTIVE_DPI, &x, &y);
float z = (float)x / 96.f;
static char base_path[MAX_PATH];
GetCurrentDirectoryA(MAX_PATH, base_path);
std::string path = std::string(base_path) + "\\data\\splash.jpg";
Image img;
img.load_file(path);
img.flip();
auto hbitmap = image_to_hbitmap(img.resize(512 * z, 200 * z));
SendMessage(GetDlgItem(hWndDlg, IDC_STATIC_IMAGE), STM_SETIMAGE, (WPARAM)IMAGE_BITMAP, (LPARAM)hbitmap);
SetDlgItemText(hWndDlg, IDC_STATIC_VERSION, g_version_number_w);
RECT r;
GetClientRect(hWndDlg, &r);
MoveWindow(GetDlgItem(hWndDlg, IDC_STATIC_IMAGE), 0, 0, 512 * z, 200 * z, TRUE);
SetWindowPos(hWndDlg, HWND_TOP, 0, 0, 512 * z, 200 * z, SWP_NOMOVE);
HWND hWndVersion = GetDlgItem(hWndDlg, IDC_STATIC_VERSION);
RECT rv;
GetClientRect(hWndVersion, &rv);
MoveWindow(hWndVersion, 0, 200 * z - (rv.bottom - rv.top), r.right - r.left, rv.bottom - rv.top, TRUE);
return TRUE;
}
case kSplashCloseMessage:
PostQuitMessage(0);
return TRUE;
}
return DefWindowProc(hWndDlg, Msg, wParam, lParam);
}
}
namespace pp::platform::windows {
SplashScreen::SplashScreen(HINSTANCE hInst)
: hInst_(hInst)
, thread_([this](std::stop_token) { thread_main(); })
{
}
SplashScreen::~SplashScreen()
{
dismiss();
}
void SplashScreen::dismiss()
{
if (HWND dialog = dialog_.load(std::memory_order_acquire))
SendMessage(dialog, kSplashCloseMessage, 0, 0);
if (thread_.joinable())
thread_.join();
}
void SplashScreen::thread_main()
{
BT_SetTerminate();
HWND dialog = CreateDialog(hInst_, MAKEINTRESOURCE(IDD_SPLASH), NULL, reinterpret_cast<DLGPROC>(splash_proc));
dialog_.store(dialog, std::memory_order_release);
if (!dialog)
return;
MSG msg;
while (GetMessage(&msg, 0, 0, 0) > 0)
{
if (IsDialogMessage(dialog, &msg))
{
DispatchMessage(&msg);
TranslateMessage(&msg);
}
}
if (HWND dialog = dialog_.exchange(nullptr))
DestroyWindow(dialog);
}
}

View File

@@ -0,0 +1,30 @@
#pragma once
#include <windows.h>
#include <atomic>
#include <thread>
namespace pp::platform::windows {
class SplashScreen final
{
public:
explicit SplashScreen(HINSTANCE hInst);
~SplashScreen();
SplashScreen(const SplashScreen&) = delete;
SplashScreen& operator=(const SplashScreen&) = delete;
SplashScreen(SplashScreen&&) = delete;
SplashScreen& operator=(SplashScreen&&) = delete;
void dismiss();
private:
void thread_main();
HINSTANCE hInst_{};
std::atomic<HWND> dialog_{};
std::jthread thread_;
};
}