Extract app status planning into app core
This commit is contained in:
@@ -224,6 +224,7 @@ target_link_libraries(pp_platform_api
|
||||
|
||||
add_library(pp_app_core STATIC
|
||||
src/app_core/app_preferences.h
|
||||
src/app_core/app_status.h
|
||||
src/app_core/document_cloud.h
|
||||
src/app_core/document_export.cpp
|
||||
src/app_core/document_platform_io.h
|
||||
|
||||
@@ -22,7 +22,7 @@ agent or engineer to remove them without reconstructing context from chat.
|
||||
| --- | --- | --- | --- | --- | --- | --- |
|
||||
| DEBT-0001 | Open | Modernization | Existing platform build files remain alongside new CMake | Required for incremental migration without losing platform coverage | Existing platform builds plus new CMake configure | Remove after all platform builds consume shared CMake targets |
|
||||
| DEBT-0002 | Open | Modernization | Vendored SDK and patched libraries retained initially | Some dependencies are SDK-only, patched, or have platform-specific binaries | Dependency inventory and platform build smoke tests | Replace with vcpkg packages or document permanent vendored status after triplet evaluation |
|
||||
| DEBT-0003 | Open | Modernization | Existing singletons remain during initial split; `App::open_document`, `App::request_close`, `App::share_file`, `App::cloud_upload`, `App::cloud_upload_all`, `App::cloud_browse`, `App::rec_start`, `App::rec_stop`, `App::rec_clear`, `App::rec_export`, file-menu save actions, `NodeCanvas` save hotkeys, new/open/browse dirty-document workflow prompts, new-document target/resolution/overwrite decisions, save-as document file naming and overwrite decisions, save-version target decisions, export start/target naming/path decisions, share-file saved-path decisions, file/image/save/directory picker selected-path decisions, display-file external-open decisions, virtual-keyboard visibility decisions, recording lifecycle/export progress decisions, cloud-upload prompt/save-before-upload decisions, cloud-browse availability and selected-download decisions, bulk cloud-upload progress decisions, tools/options app preference decisions, `pano_cli classify-open`, `pano_cli plan-open-route`, `pano_cli plan-new-document`, `pano_cli plan-document-file`, `pano_cli plan-document-version`, `pano_cli plan-export-start`, `pano_cli plan-export-target`, `pano_cli plan-recording-session`, `pano_cli plan-app-preferences`, `pano_cli plan-share-file`, `pano_cli plan-picked-path`, `pano_cli plan-display-file`, `pano_cli plan-keyboard-visibility`, `pano_cli plan-cloud-upload`, `pano_cli plan-cloud-browse`, `pano_cli plan-cloud-upload-all`, and `pano_cli simulate-app-session` now consume pure `pp_app_core` route/session/export/recording/preferences/share/platform-I/O/display/keyboard/cloud contracts, but document creation/loading, brush import execution, saving, export execution, tools/options UI execution, settings persistence, platform share service execution, picker service execution, display-file service execution, keyboard service execution, recording/MP4 execution, cloud upload execution, and cloud browse/download execution still reach legacy `Canvas::I`/UI/network/video/platform singletons | Avoid behavior changes while introducing component boundaries | App launch and component tests; `pp_app_core_document_route_tests`; `pp_app_core_document_export_tests`; `pp_app_core_document_recording_tests`; `pp_app_core_app_preferences_tests`; `pp_app_core_document_sharing_tests`; `pp_app_core_document_platform_io_tests`; `pp_app_core_document_cloud_tests`; `pp_app_core_document_session_tests`; `pano_cli classify-open --path D:/Paint/demo.ppi`; `pano_cli plan-open-route --path D:/Paint/demo.ppi --unsaved`; `pano_cli plan-new-document --work-dir D:/Paint --name demo --resolution-index 3 --target-exists`; `pano_cli plan-document-file --work-dir D:/Paint --name demo --target-exists`; `pano_cli plan-document-version --directory D:/Paint --doc-name demo.01 --existing-path D:/Paint/demo.02.ppi`; `pano_cli plan-export-start --requires-license --demo`; `pano_cli plan-export-target --kind file --work-dir D:/Paint --doc-name demo --extension .png`; `pano_cli plan-recording-session --running --frame-count 12`; `pano_cli plan-app-preferences --ui-scale 1.5 --display-density 2 --current-scale 1.6 --scale-option 1 --scale-option 1.5 --rtl`; `pano_cli plan-share-file --path D:/Paint/demo.ppi`; `pano_cli plan-picked-path --path D:/Paint/demo.ppi`; `pano_cli plan-display-file --path D:/Paint/export.png`; `pano_cli plan-keyboard-visibility --visible`; `pano_cli plan-cloud-upload --new-document --unsaved`; `pano_cli plan-cloud-browse --selected-file demo.ppi`; `pano_cli plan-cloud-upload-all --file-count 3`; `pano_cli simulate-app-session --unsaved --save-intent save-dirty-version`; `pano_cli simulate-app-session --no-canvas`; `ctest --preset desktop-fast --build-config Debug` | Replace singleton reaches with context/service injection at component boundaries |
|
||||
| DEBT-0003 | Open | Modernization | Existing singletons remain during initial split; `App::open_document`, `App::request_close`, `App::share_file`, `App::cloud_upload`, `App::cloud_upload_all`, `App::cloud_browse`, `App::rec_start`, `App::rec_stop`, `App::rec_clear`, `App::rec_export`, file-menu save actions, `NodeCanvas` save hotkeys, new/open/browse dirty-document workflow prompts, new-document target/resolution/overwrite decisions, save-as document file naming and overwrite decisions, save-version target decisions, export start/target naming/path decisions, share-file saved-path decisions, file/image/save/directory picker selected-path decisions, display-file external-open decisions, virtual-keyboard visibility decisions, recording lifecycle/export progress decisions, cloud-upload prompt/save-before-upload decisions, cloud-browse availability and selected-download decisions, bulk cloud-upload progress decisions, tools/options app preference decisions, app status/display decisions, `pano_cli classify-open`, `pano_cli plan-open-route`, `pano_cli plan-new-document`, `pano_cli plan-document-file`, `pano_cli plan-document-version`, `pano_cli plan-export-start`, `pano_cli plan-export-target`, `pano_cli plan-recording-session`, `pano_cli plan-app-preferences`, `pano_cli plan-app-status`, `pano_cli plan-share-file`, `pano_cli plan-picked-path`, `pano_cli plan-display-file`, `pano_cli plan-keyboard-visibility`, `pano_cli plan-cloud-upload`, `pano_cli plan-cloud-browse`, `pano_cli plan-cloud-upload-all`, and `pano_cli simulate-app-session` now consume pure `pp_app_core` route/session/export/recording/preferences/status/share/platform-I/O/display/keyboard/cloud contracts, but document creation/loading, brush import execution, saving, export execution, tools/options UI execution, status/display UI rendering, settings persistence, platform share service execution, picker service execution, display-file service execution, keyboard service execution, recording/MP4 execution, cloud upload execution, and cloud browse/download execution still reach legacy `Canvas::I`/UI/network/video/platform singletons | Avoid behavior changes while introducing component boundaries | App launch and component tests; `pp_app_core_document_route_tests`; `pp_app_core_document_export_tests`; `pp_app_core_document_recording_tests`; `pp_app_core_app_preferences_tests`; `pp_app_core_app_status_tests`; `pp_app_core_document_sharing_tests`; `pp_app_core_document_platform_io_tests`; `pp_app_core_document_cloud_tests`; `pp_app_core_document_session_tests`; `pano_cli classify-open --path D:/Paint/demo.ppi`; `pano_cli plan-open-route --path D:/Paint/demo.ppi --unsaved`; `pano_cli plan-new-document --work-dir D:/Paint --name demo --resolution-index 3 --target-exists`; `pano_cli plan-document-file --work-dir D:/Paint --name demo --target-exists`; `pano_cli plan-document-version --directory D:/Paint --doc-name demo.01 --existing-path D:/Paint/demo.02.ppi`; `pano_cli plan-export-start --requires-license --demo`; `pano_cli plan-export-target --kind file --work-dir D:/Paint --doc-name demo --extension .png`; `pano_cli plan-recording-session --running --frame-count 12`; `pano_cli plan-app-preferences --ui-scale 1.5 --display-density 2 --current-scale 1.6 --scale-option 1 --scale-option 1.5 --rtl`; `pano_cli plan-app-status --doc-name demo --unsaved --resolution 2048 --resolution-index 3 --zoom 1.25 --history-bytes 1572864 --recording-running --encoder-available --encoded-frames 12`; `pano_cli plan-share-file --path D:/Paint/demo.ppi`; `pano_cli plan-picked-path --path D:/Paint/demo.ppi`; `pano_cli plan-display-file --path D:/Paint/export.png`; `pano_cli plan-keyboard-visibility --visible`; `pano_cli plan-cloud-upload --new-document --unsaved`; `pano_cli plan-cloud-browse --selected-file demo.ppi`; `pano_cli plan-cloud-upload-all --file-count 3`; `pano_cli simulate-app-session --unsaved --save-intent save-dirty-version`; `pano_cli simulate-app-session --no-canvas`; `ctest --preset desktop-fast --build-config Debug` | Replace singleton reaches with context/service injection at component boundaries |
|
||||
| DEBT-0004 | Open | Modernization | Android, Linux, WebGL, Apple, and AppX build files remain platform-specific until root CMake alignment reaches them | Prevent platform regressions during incremental migration; raw Windows `.sln/.vcxproj` files were removed on 2026-05-31 by user decision | `cmake --preset windows-msvc-default`; platform-specific configure/build smoke checks as each platform is migrated | Root CMake owns every platform source list and package path |
|
||||
| DEBT-0005 | Open | Modernization | Temporary local CTest harness is used before Catch2 is wired through vcpkg | `vcpkg` is not currently on PATH, but headless tests need to run now | `ctest --preset desktop-fast --build-config Debug` | Replace `tests/test_harness.h` tests with Catch2 tests once vcpkg toolchain/presets are validated |
|
||||
| DEBT-0007 | Open | Modernization | `vcpkg.json` and `windows-msvc-vcpkg-headless` are validated for the headless Windows component matrix, but app targets still use vendored libraries and Android/Apple triplets are not proven | Dependency migration must stay incremental while SDK/patched/vendor dependencies remain in use | `$env:VCPKG_ROOT="C:\Program Files\Microsoft Visual Studio\2022\Community\VC\vcpkg"; cmake --preset windows-msvc-vcpkg-headless`; `ctest --preset desktop-fast-vcpkg --build-config Debug` | Component targets consume vcpkg packages where reliable and desktop app, Android, and Apple triplets are validated or explicitly documented as permanent vendor exceptions |
|
||||
|
||||
@@ -180,6 +180,11 @@ scale option selection, viewport scale, RTL layout direction, timelapse
|
||||
recording toggles, VR controller enablement, and canvas cursor mode;
|
||||
the live tools/options menu and `pano_cli plan-app-preferences` consume those
|
||||
contracts while legacy widgets and settings persistence execute them.
|
||||
It also owns tested app status/display plans for document title text,
|
||||
resolution mapping/labels, DPI text, history-memory text, and recording-frame
|
||||
status text; `App::title_update`, `App::update_memory_usage`,
|
||||
`App::update_rec_frames`, resolution helpers, and `pano_cli plan-app-status`
|
||||
consume those contracts while legacy UI nodes still render the strings.
|
||||
`panopainter_app` is now a real static target that owns app orchestration
|
||||
sources, app version metadata, and version-header generation.
|
||||
`pp_panopainter_ui` now owns app-specific modal, dialog, panel, canvas,
|
||||
|
||||
30
src/app.cpp
30
src/app.cpp
@@ -5,6 +5,7 @@
|
||||
#include "node_dialog_open.h"
|
||||
#include "node_progress_bar.h"
|
||||
#include "mp4enc.h"
|
||||
#include "app_core/app_status.h"
|
||||
#include "app_core/document_recording.h"
|
||||
#include "app_core/document_route.h"
|
||||
#include "app_core/document_session.h"
|
||||
@@ -682,9 +683,8 @@ void App::update_memory_usage(size_t bytes)
|
||||
{
|
||||
if (auto txt = layout[main_id]->find<NodeText>("txt-memory"))
|
||||
{
|
||||
static char buffer[128];
|
||||
sprintf(buffer, "History memory: %.2f Mb", bytes / 1024.f / 1024.f);
|
||||
txt->set_text(buffer);
|
||||
const auto label = pp::app::make_history_memory_label(bytes);
|
||||
txt->set_text(label.c_str());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -692,32 +692,30 @@ void App::update_rec_frames()
|
||||
{
|
||||
if (auto txt = layout[main_id]->find<NodeText>("txt-rec"))
|
||||
{
|
||||
if (rec_running && Canvas::I->m_encoder)
|
||||
{
|
||||
static char buffer[128];
|
||||
sprintf(buffer, "Recorded %d frames", Canvas::I->m_encoder->frames_count());
|
||||
txt->set_text(buffer);
|
||||
}
|
||||
else
|
||||
{
|
||||
txt->set_text("");
|
||||
}
|
||||
const auto label = pp::app::make_recording_frame_label(
|
||||
rec_running,
|
||||
Canvas::I->m_encoder != nullptr,
|
||||
Canvas::I->m_encoder ? Canvas::I->m_encoder->frames_count() : 0);
|
||||
txt->set_text(label.text.c_str());
|
||||
}
|
||||
}
|
||||
|
||||
int App::res_from_index(int i)
|
||||
{
|
||||
return res_map[i];
|
||||
const auto resolution = pp::app::display_resolution_from_index(i);
|
||||
return resolution ? resolution.value() : pp::app::document_resolution_values.front();
|
||||
}
|
||||
|
||||
int App::res_to_index(int res)
|
||||
{
|
||||
return (int)std::distance(res_map.begin(), std::find(res_map.begin(), res_map.end(), res));
|
||||
const auto index = pp::app::document_resolution_to_index(res);
|
||||
return index ? static_cast<int>(index.value()) : static_cast<int>(pp::app::document_resolution_values.size());
|
||||
}
|
||||
|
||||
std::string App::res_to_string(int res)
|
||||
{
|
||||
return res_map_str[res_to_index(res)];
|
||||
const auto label = pp::app::document_resolution_label(res);
|
||||
return label ? std::string(label.value()) : std::string("unknown");
|
||||
}
|
||||
|
||||
void App::renderdoc_frame_start()
|
||||
|
||||
119
src/app_core/app_status.h
Normal file
119
src/app_core/app_status.h
Normal file
@@ -0,0 +1,119 @@
|
||||
#pragma once
|
||||
|
||||
#include "foundation/result.h"
|
||||
|
||||
#include <array>
|
||||
#include <cstdio>
|
||||
#include <cstddef>
|
||||
#include <string>
|
||||
#include <string_view>
|
||||
|
||||
namespace pp::app {
|
||||
|
||||
inline constexpr std::array<int, 6> document_resolution_values {
|
||||
512,
|
||||
1024,
|
||||
1536,
|
||||
2048,
|
||||
4096,
|
||||
8192,
|
||||
};
|
||||
|
||||
inline constexpr std::array<std::string_view, 6> document_resolution_labels {
|
||||
"2K",
|
||||
"4K",
|
||||
"6K",
|
||||
"8K",
|
||||
"16K",
|
||||
"32K",
|
||||
};
|
||||
|
||||
struct RecordingFrameLabel {
|
||||
bool visible = false;
|
||||
std::string text;
|
||||
};
|
||||
|
||||
[[nodiscard]] inline pp::foundation::Result<int> display_resolution_from_index(int index)
|
||||
{
|
||||
if (index < 0 || static_cast<std::size_t>(index) >= document_resolution_values.size()) {
|
||||
return pp::foundation::Result<int>::failure(
|
||||
pp::foundation::Status::out_of_range("document resolution index is out of range"));
|
||||
}
|
||||
return pp::foundation::Result<int>::success(
|
||||
document_resolution_values[static_cast<std::size_t>(index)]);
|
||||
}
|
||||
|
||||
[[nodiscard]] inline pp::foundation::Result<std::size_t> document_resolution_to_index(int resolution)
|
||||
{
|
||||
for (std::size_t index = 0; index < document_resolution_values.size(); ++index) {
|
||||
if (document_resolution_values[index] == resolution) {
|
||||
return pp::foundation::Result<std::size_t>::success(index);
|
||||
}
|
||||
}
|
||||
return pp::foundation::Result<std::size_t>::failure(
|
||||
pp::foundation::Status::out_of_range("document resolution is not supported"));
|
||||
}
|
||||
|
||||
[[nodiscard]] inline pp::foundation::Result<std::string_view> document_resolution_label(int resolution)
|
||||
{
|
||||
const auto index = document_resolution_to_index(resolution);
|
||||
if (!index) {
|
||||
return pp::foundation::Result<std::string_view>::failure(index.status());
|
||||
}
|
||||
return pp::foundation::Result<std::string_view>::success(document_resolution_labels[index.value()]);
|
||||
}
|
||||
|
||||
[[nodiscard]] inline std::string make_document_title(
|
||||
std::string_view document_name,
|
||||
bool has_unsaved_changes,
|
||||
int resolution)
|
||||
{
|
||||
const auto label = document_resolution_label(resolution);
|
||||
const auto resolution_label = label ? label.value() : std::string_view("unknown");
|
||||
std::string title = "Panodoc: ";
|
||||
title.append(document_name);
|
||||
if (has_unsaved_changes) {
|
||||
title.push_back('*');
|
||||
}
|
||||
title.append(" (");
|
||||
title.append(resolution_label);
|
||||
title.push_back(')');
|
||||
return title;
|
||||
}
|
||||
|
||||
[[nodiscard]] inline std::string make_dpi_label(float zoom)
|
||||
{
|
||||
char buffer[64] {};
|
||||
std::snprintf(buffer, sizeof(buffer), "%.1fx-dpi", zoom);
|
||||
return buffer;
|
||||
}
|
||||
|
||||
[[nodiscard]] inline std::string make_history_memory_label(std::size_t bytes)
|
||||
{
|
||||
char buffer[128] {};
|
||||
std::snprintf(
|
||||
buffer,
|
||||
sizeof(buffer),
|
||||
"History memory: %.2f Mb",
|
||||
static_cast<double>(bytes) / 1024.0 / 1024.0);
|
||||
return buffer;
|
||||
}
|
||||
|
||||
[[nodiscard]] inline RecordingFrameLabel make_recording_frame_label(
|
||||
bool is_recording,
|
||||
bool encoder_available,
|
||||
int encoded_frames)
|
||||
{
|
||||
if (!is_recording || !encoder_available) {
|
||||
return {};
|
||||
}
|
||||
|
||||
char buffer[128] {};
|
||||
std::snprintf(buffer, sizeof(buffer), "Recorded %d frames", encoded_frames);
|
||||
return {
|
||||
true,
|
||||
buffer,
|
||||
};
|
||||
}
|
||||
|
||||
}
|
||||
@@ -7,6 +7,7 @@
|
||||
#include "node_dialog_picker.h"
|
||||
#include "node_panel_floating.h"
|
||||
#include "app_core/app_preferences.h"
|
||||
#include "app_core/app_status.h"
|
||||
#include "settings.h"
|
||||
#include "serializer.h"
|
||||
#include "font.h"
|
||||
@@ -17,12 +18,19 @@
|
||||
|
||||
void App::title_update()
|
||||
{
|
||||
static char str[256];
|
||||
snprintf(str, 256, "Panodoc: %s%s (%s)", doc_name.c_str(), canvas->m_canvas->m_unsaved ? "*" : "", res_to_string(canvas->m_canvas->m_width).c_str());
|
||||
if (auto docname = layout[main_id]->find<NodeText>("txt-docname"))
|
||||
docname->set_text(str);
|
||||
{
|
||||
const auto title = pp::app::make_document_title(
|
||||
doc_name,
|
||||
canvas->m_canvas->m_unsaved,
|
||||
canvas->m_canvas->m_width);
|
||||
docname->set_text(title.c_str());
|
||||
}
|
||||
if (auto node = layout[main_id]->find<NodeText>("txt-dpi"))
|
||||
node->set_text(fmt::format("{:.1f}x-dpi", zoom).c_str());
|
||||
{
|
||||
const auto label = pp::app::make_dpi_label(zoom);
|
||||
node->set_text(label.c_str());
|
||||
}
|
||||
}
|
||||
|
||||
void App::init_toolbar_main()
|
||||
|
||||
@@ -328,6 +328,16 @@ add_test(NAME pp_app_core_app_preferences_tests COMMAND pp_app_core_app_preferen
|
||||
set_tests_properties(pp_app_core_app_preferences_tests PROPERTIES
|
||||
LABELS "app;desktop-fast;fuzz")
|
||||
|
||||
add_executable(pp_app_core_app_status_tests
|
||||
app_core/app_status_tests.cpp)
|
||||
target_link_libraries(pp_app_core_app_status_tests PRIVATE
|
||||
pp_app_core
|
||||
pp_test_harness)
|
||||
|
||||
add_test(NAME pp_app_core_app_status_tests COMMAND pp_app_core_app_status_tests)
|
||||
set_tests_properties(pp_app_core_app_status_tests PROPERTIES
|
||||
LABELS "app;desktop-fast;fuzz")
|
||||
|
||||
add_executable(pp_app_core_document_sharing_tests
|
||||
app_core/document_sharing_tests.cpp)
|
||||
target_link_libraries(pp_app_core_document_sharing_tests PRIVATE
|
||||
@@ -637,6 +647,27 @@ if(TARGET pano_cli)
|
||||
LABELS "app;integration;desktop-fast;fuzz"
|
||||
PASS_REGULAR_EXPRESSION "\"command\":\"plan-app-preferences\".*\"scaleSelection\":\\{\"hasSelection\":false,\"index\":0\\}.*\"direction\":\"left-to-right\".*\"timelapse\":\\{\"enabled\":false,\"recordingAction\":\"stop-recording\"\\}.*\"vrControllers\":\\{\"enabled\":false\\}")
|
||||
|
||||
add_test(NAME pano_cli_plan_app_status_smoke
|
||||
COMMAND pano_cli plan-app-status
|
||||
--doc-name demo
|
||||
--unsaved
|
||||
--resolution 2048
|
||||
--resolution-index 3
|
||||
--zoom 1.26
|
||||
--history-bytes 1572864
|
||||
--recording-running
|
||||
--encoder-available
|
||||
--encoded-frames 12)
|
||||
set_tests_properties(pano_cli_plan_app_status_smoke PROPERTIES
|
||||
LABELS "app;integration;desktop-fast"
|
||||
PASS_REGULAR_EXPRESSION "\"command\":\"plan-app-status\".*\"title\":\"Panodoc: demo\\* \\(8K\\)\".*\"dpi\":\"1.3x-dpi\".*\"memory\":\"History memory: 1.50 Mb\".*\"recording\":\\{\"visible\":true,\"text\":\"Recorded 12 frames\"\\}.*\"fromIndexValid\":true.*\"fromIndex\":2048.*\"toIndexValid\":true.*\"toIndex\":3.*\"labelValid\":true.*\"label\":\"8K\"")
|
||||
|
||||
add_test(NAME pano_cli_plan_app_status_unknown_resolution_smoke
|
||||
COMMAND pano_cli plan-app-status --doc-name demo --resolution 1234 --resolution-index 9 --recording-running)
|
||||
set_tests_properties(pano_cli_plan_app_status_unknown_resolution_smoke PROPERTIES
|
||||
LABELS "app;integration;desktop-fast;fuzz"
|
||||
PASS_REGULAR_EXPRESSION "\"command\":\"plan-app-status\".*\"title\":\"Panodoc: demo \\(unknown\\)\".*\"recording\":\\{\"visible\":false,\"text\":\"\"\\}.*\"fromIndexValid\":false.*\"toIndexValid\":false.*\"labelValid\":false.*\"label\":\"\"")
|
||||
|
||||
add_test(NAME pano_cli_plan_share_file_unsaved_smoke
|
||||
COMMAND pano_cli plan-share-file)
|
||||
set_tests_properties(pano_cli_plan_share_file_unsaved_smoke PROPERTIES
|
||||
|
||||
86
tests/app_core/app_status_tests.cpp
Normal file
86
tests/app_core/app_status_tests.cpp
Normal file
@@ -0,0 +1,86 @@
|
||||
#include "app_core/app_status.h"
|
||||
#include "test_harness.h"
|
||||
|
||||
namespace {
|
||||
|
||||
void resolution_maps_supported_indices_and_labels(pp::tests::Harness& harness)
|
||||
{
|
||||
const auto resolution = pp::app::display_resolution_from_index(3);
|
||||
PP_EXPECT(harness, resolution);
|
||||
if (resolution) {
|
||||
PP_EXPECT(harness, resolution.value() == 2048);
|
||||
}
|
||||
|
||||
const auto index = pp::app::document_resolution_to_index(4096);
|
||||
PP_EXPECT(harness, index);
|
||||
if (index) {
|
||||
PP_EXPECT(harness, index.value() == 4U);
|
||||
}
|
||||
|
||||
const auto label = pp::app::document_resolution_label(8192);
|
||||
PP_EXPECT(harness, label);
|
||||
if (label) {
|
||||
PP_EXPECT(harness, label.value() == "32K");
|
||||
}
|
||||
}
|
||||
|
||||
void resolution_mapping_rejects_out_of_range_values(pp::tests::Harness& harness)
|
||||
{
|
||||
PP_EXPECT(harness, !pp::app::display_resolution_from_index(-1));
|
||||
PP_EXPECT(harness, !pp::app::display_resolution_from_index(6));
|
||||
PP_EXPECT(harness, !pp::app::document_resolution_to_index(1234));
|
||||
PP_EXPECT(harness, !pp::app::document_resolution_label(1234));
|
||||
}
|
||||
|
||||
void document_title_marks_unsaved_documents(pp::tests::Harness& harness)
|
||||
{
|
||||
PP_EXPECT(
|
||||
harness,
|
||||
pp::app::make_document_title("demo", false, 2048) == "Panodoc: demo (8K)");
|
||||
PP_EXPECT(
|
||||
harness,
|
||||
pp::app::make_document_title("demo", true, 2048) == "Panodoc: demo* (8K)");
|
||||
}
|
||||
|
||||
void document_title_survives_unknown_resolution(pp::tests::Harness& harness)
|
||||
{
|
||||
PP_EXPECT(
|
||||
harness,
|
||||
pp::app::make_document_title("demo", true, 1234) == "Panodoc: demo* (unknown)");
|
||||
}
|
||||
|
||||
void status_labels_match_legacy_text(pp::tests::Harness& harness)
|
||||
{
|
||||
PP_EXPECT(harness, pp::app::make_dpi_label(1.25F) == "1.2x-dpi");
|
||||
PP_EXPECT(harness, pp::app::make_dpi_label(1.26F) == "1.3x-dpi");
|
||||
PP_EXPECT(harness, pp::app::make_history_memory_label(1024U * 1024U * 3U / 2U) == "History memory: 1.50 Mb");
|
||||
}
|
||||
|
||||
void recording_label_only_shows_when_recording_with_encoder(pp::tests::Harness& harness)
|
||||
{
|
||||
const auto inactive = pp::app::make_recording_frame_label(false, true, 12);
|
||||
PP_EXPECT(harness, !inactive.visible);
|
||||
PP_EXPECT(harness, inactive.text.empty());
|
||||
|
||||
const auto missing_encoder = pp::app::make_recording_frame_label(true, false, 12);
|
||||
PP_EXPECT(harness, !missing_encoder.visible);
|
||||
PP_EXPECT(harness, missing_encoder.text.empty());
|
||||
|
||||
const auto active = pp::app::make_recording_frame_label(true, true, 12);
|
||||
PP_EXPECT(harness, active.visible);
|
||||
PP_EXPECT(harness, active.text == "Recorded 12 frames");
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
int main()
|
||||
{
|
||||
pp::tests::Harness harness;
|
||||
harness.run("resolution maps supported indices and labels", resolution_maps_supported_indices_and_labels);
|
||||
harness.run("resolution mapping rejects out of range values", resolution_mapping_rejects_out_of_range_values);
|
||||
harness.run("document title marks unsaved documents", document_title_marks_unsaved_documents);
|
||||
harness.run("document title survives unknown resolution", document_title_survives_unknown_resolution);
|
||||
harness.run("status labels match legacy text", status_labels_match_legacy_text);
|
||||
harness.run("recording label only shows when recording with encoder", recording_label_only_shows_when_recording_with_encoder);
|
||||
return harness.finish();
|
||||
}
|
||||
@@ -1,4 +1,5 @@
|
||||
#include "app_core/app_preferences.h"
|
||||
#include "app_core/app_status.h"
|
||||
#include "app_core/document_export.h"
|
||||
#include "app_core/document_cloud.h"
|
||||
#include "app_core/document_platform_io.h"
|
||||
@@ -200,6 +201,18 @@ struct PlanAppPreferencesArgs {
|
||||
int cursor_mode = 0;
|
||||
};
|
||||
|
||||
struct PlanAppStatusArgs {
|
||||
std::string document_name = "no-name";
|
||||
bool unsaved = false;
|
||||
int resolution = 512;
|
||||
int resolution_index = 0;
|
||||
float zoom = 1.0F;
|
||||
std::uint32_t history_bytes = 0;
|
||||
bool recording_running = false;
|
||||
bool encoder_available = false;
|
||||
std::uint32_t encoded_frames = 0;
|
||||
};
|
||||
|
||||
struct SimulateAppSessionArgs {
|
||||
bool has_canvas = true;
|
||||
bool new_document = false;
|
||||
@@ -668,6 +681,7 @@ void print_help()
|
||||
<< " plan-cloud-upload-all [--file-count N] [--no-progress-ui]\n"
|
||||
<< " plan-recording-session [--running] [--frame-count N] [--platform-deletes-recorded-files]\n"
|
||||
<< " plan-app-preferences [--ui-scale N] [--display-density N] [--current-scale N] [--scale-option N] [--viewport-scale N] [--rtl] [--timelapse-disabled] [--recording-running] [--vr-controllers-disabled] [--cursor-mode N]\n"
|
||||
<< " plan-app-status [--doc-name NAME] [--unsaved] [--resolution N] [--resolution-index N] [--zoom N] [--history-bytes N] [--recording-running] [--encoder-available] [--encoded-frames N]\n"
|
||||
<< " plan-share-file [--path FILE]\n"
|
||||
<< " plan-picked-path [--path FILE]\n"
|
||||
<< " plan-display-file [--path FILE]\n"
|
||||
@@ -2107,6 +2121,104 @@ int plan_app_preferences(int argc, char** argv)
|
||||
return 0;
|
||||
}
|
||||
|
||||
pp::foundation::Status parse_plan_app_status_args(
|
||||
int argc,
|
||||
char** argv,
|
||||
PlanAppStatusArgs& args)
|
||||
{
|
||||
for (int i = 2; i < argc; ++i) {
|
||||
const std::string_view key(argv[i]);
|
||||
if (key == "--doc-name") {
|
||||
if (i + 1 >= argc) {
|
||||
return pp::foundation::Status::invalid_argument("missing value for option");
|
||||
}
|
||||
args.document_name = argv[++i];
|
||||
} else if (key == "--unsaved") {
|
||||
args.unsaved = true;
|
||||
} else if (key == "--resolution" || key == "--resolution-index" || key == "--history-bytes"
|
||||
|| key == "--encoded-frames") {
|
||||
if (i + 1 >= argc) {
|
||||
return pp::foundation::Status::invalid_argument("missing value for option");
|
||||
}
|
||||
const auto value = pp::foundation::parse_u32(argv[++i]);
|
||||
if (!value) {
|
||||
return value.status();
|
||||
}
|
||||
if (key == "--resolution") {
|
||||
args.resolution = static_cast<int>(value.value());
|
||||
} else if (key == "--resolution-index") {
|
||||
args.resolution_index = static_cast<int>(value.value());
|
||||
} else if (key == "--history-bytes") {
|
||||
args.history_bytes = value.value();
|
||||
} else {
|
||||
args.encoded_frames = value.value();
|
||||
}
|
||||
} else if (key == "--zoom") {
|
||||
if (i + 1 >= argc) {
|
||||
return pp::foundation::Status::invalid_argument("missing value for option");
|
||||
}
|
||||
const auto value = parse_float_arg(argv[++i]);
|
||||
if (!value) {
|
||||
return value.status();
|
||||
}
|
||||
args.zoom = value.value();
|
||||
} else if (key == "--recording-running") {
|
||||
args.recording_running = true;
|
||||
} else if (key == "--encoder-available") {
|
||||
args.encoder_available = true;
|
||||
} else {
|
||||
return pp::foundation::Status::invalid_argument("unknown option");
|
||||
}
|
||||
}
|
||||
|
||||
return pp::foundation::Status::success();
|
||||
}
|
||||
|
||||
int plan_app_status(int argc, char** argv)
|
||||
{
|
||||
PlanAppStatusArgs args;
|
||||
const auto status = parse_plan_app_status_args(argc, argv, args);
|
||||
if (!status.ok()) {
|
||||
print_error("plan-app-status", status.message);
|
||||
return 2;
|
||||
}
|
||||
|
||||
const auto resolution_from_index = pp::app::display_resolution_from_index(args.resolution_index);
|
||||
const auto resolution_index = pp::app::document_resolution_to_index(args.resolution);
|
||||
const auto resolution_label = pp::app::document_resolution_label(args.resolution);
|
||||
const auto recording_label = pp::app::make_recording_frame_label(
|
||||
args.recording_running,
|
||||
args.encoder_available,
|
||||
static_cast<int>(args.encoded_frames));
|
||||
|
||||
std::cout << "{\"ok\":true,\"command\":\"plan-app-status\""
|
||||
<< ",\"state\":{\"documentName\":\"" << json_escape(args.document_name)
|
||||
<< "\",\"unsaved\":" << json_bool(args.unsaved)
|
||||
<< ",\"resolution\":" << args.resolution
|
||||
<< ",\"resolutionIndex\":" << args.resolution_index
|
||||
<< ",\"zoom\":" << args.zoom
|
||||
<< ",\"historyBytes\":" << args.history_bytes
|
||||
<< ",\"recordingRunning\":" << json_bool(args.recording_running)
|
||||
<< ",\"encoderAvailable\":" << json_bool(args.encoder_available)
|
||||
<< ",\"encodedFrames\":" << args.encoded_frames
|
||||
<< "},\"title\":\"" << json_escape(pp::app::make_document_title(
|
||||
args.document_name,
|
||||
args.unsaved,
|
||||
args.resolution))
|
||||
<< "\",\"dpi\":\"" << json_escape(pp::app::make_dpi_label(args.zoom))
|
||||
<< "\",\"memory\":\"" << json_escape(pp::app::make_history_memory_label(args.history_bytes))
|
||||
<< "\",\"recording\":{\"visible\":" << json_bool(recording_label.visible)
|
||||
<< ",\"text\":\"" << json_escape(recording_label.text)
|
||||
<< "\"},\"resolutionMap\":{\"fromIndexValid\":" << json_bool(static_cast<bool>(resolution_from_index))
|
||||
<< ",\"fromIndex\":" << (resolution_from_index ? resolution_from_index.value() : 0)
|
||||
<< ",\"toIndexValid\":" << json_bool(static_cast<bool>(resolution_index))
|
||||
<< ",\"toIndex\":" << (resolution_index ? resolution_index.value() : 0)
|
||||
<< ",\"labelValid\":" << json_bool(static_cast<bool>(resolution_label))
|
||||
<< ",\"label\":\"" << json_escape(resolution_label ? std::string(resolution_label.value()) : std::string())
|
||||
<< "\"}}\n";
|
||||
return 0;
|
||||
}
|
||||
|
||||
pp::foundation::Status parse_plan_share_file_args(
|
||||
int argc,
|
||||
char** argv,
|
||||
@@ -4503,6 +4615,10 @@ int main(int argc, char** argv)
|
||||
return plan_app_preferences(argc, argv);
|
||||
}
|
||||
|
||||
if (command == "plan-app-status") {
|
||||
return plan_app_status(argc, argv);
|
||||
}
|
||||
|
||||
if (command == "plan-share-file") {
|
||||
return plan_share_file(argc, argv);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user