Compare commits
497 Commits
main
...
codex/mode
| Author | SHA1 | Date | |
|---|---|---|---|
| a44222813f | |||
| c8b55b36f7 | |||
| f3834827b1 | |||
| a03db82307 | |||
| ed9709ade8 | |||
| 9d9b93abb1 | |||
| 772dc7332b | |||
| 9d9c87c0cb | |||
| 09df47879d | |||
| 41279c8743 | |||
| 7575f51c45 | |||
| 6c772a1c84 | |||
| ab36af0a8f | |||
| 5ff2992c0e | |||
| 65c7716d62 | |||
| 59c9b05d6c | |||
| 3101e65dd3 | |||
| 4071919124 | |||
| d963daae70 | |||
| 7a9dd150e3 | |||
| bd416f8473 | |||
| 875a0127d9 | |||
| 3be7171010 | |||
| 3c36be4b43 | |||
| 77268a28fb | |||
| ebc84373e6 | |||
| 2d33f9d928 | |||
| af28da4e83 | |||
| 27e7c60413 | |||
| 6151fb7a3d | |||
| 693923b7bd | |||
| 81898a5dcc | |||
| 9cafc39788 | |||
| ba5c3069e1 | |||
| 9a75782891 | |||
| f4f6eb903e | |||
| d0412e3bf9 | |||
| a9ef2c598c | |||
| d0e023556b | |||
| 7c6c5f3e36 | |||
| d4dad133ea | |||
| ee46a6497f | |||
| 26a2349c5f | |||
| 0e6c61e8a9 | |||
| bdd7a32ff5 | |||
| 308fb13075 | |||
| 0fb3bd09ac | |||
| 26470e0fe8 | |||
| 96d1903cf2 | |||
| ad9b91eeda | |||
| 96ff1c41e2 | |||
| d719a5a5e5 | |||
| 84e63c0d34 | |||
| 421f2713db | |||
| df21d673dd | |||
| 76a8db1ef8 | |||
| 65bf047d77 | |||
| 2641db35ac | |||
| 745a5898da | |||
| 03b999e60f | |||
| 92fa5b224a | |||
| 8f062fb0c4 | |||
| 321e5d6287 | |||
| 35477978e5 | |||
| 8a4ca331cb | |||
| e731c06330 | |||
| 3e5340b696 | |||
| 97fd7de955 | |||
| 1dc2ae4f21 | |||
| 711a9b5037 | |||
| c761cd39fd | |||
| ac4fef8346 | |||
| e17463bf5a | |||
| 0236fc6620 | |||
| c4d00258ff | |||
| b1d71f2621 | |||
| ab3637af9c | |||
| 48f98d337b | |||
| b534c4a4da | |||
| 407297dc2e | |||
| 903fe2d5a1 | |||
| 73564342fc | |||
| d9f294e8e6 | |||
| f225a81ec4 | |||
| fcc0e577b8 | |||
| 808a084ee3 | |||
| f46839bf5c | |||
| e5526c6d0a | |||
| 5def47cdcc | |||
| 062fdaa982 | |||
| a79ef4cda8 | |||
| a104f88360 | |||
| 942c053c19 | |||
| c50ea14a2a | |||
| 32c95b224f | |||
| b825d920d2 | |||
| 1df93c23f7 | |||
| 548b6d3ae5 | |||
| 48a4547f51 | |||
| f7979be80f | |||
| 678bf2dcd6 | |||
| e42afcc83f | |||
| 9373e07d3e | |||
| f42a6540be | |||
| e95861e9b7 | |||
| 31c26c3127 | |||
| d5403f082c | |||
| 75fd7faeb0 | |||
| bd6cdc20c5 | |||
| a9e12f2219 | |||
| 59210c28ea | |||
| 2feeffd6c8 | |||
| 841fbac8eb | |||
| db0ecb590c | |||
| 4ccedaae4c | |||
| c514ac99aa | |||
| 3cd1d46025 | |||
| 111cc8c892 | |||
| c9fb91ab48 | |||
| 2a4698e9f6 | |||
| 9190e9053a | |||
| b8c7cd6e99 | |||
| b65db6f617 | |||
| 7ade927beb | |||
| d0510e9fd2 | |||
| 5aa07b2953 | |||
| d55f26d637 | |||
| 24197c5f7e | |||
| abe3a86cc5 | |||
| 4c61a490ce | |||
| ce787ce186 | |||
| fc20851462 | |||
| 45802dfc7c | |||
| 6440bde002 | |||
| 15c58bfb21 | |||
| b9dbcd10d7 | |||
| f55b1882c0 | |||
| 967a15f15f | |||
| 51601adf6d | |||
| 1b771287f9 | |||
| 0bd1e92ee1 | |||
| f2cb0f2276 | |||
| 1057dd488a | |||
| 11c7d87330 | |||
| 0489c4229e | |||
| b2334e65c9 | |||
| e6831fcb28 | |||
| 63ea626cef | |||
| 08d8c1e82c | |||
| 7aadd1041a | |||
| 4bd29bee9f | |||
| e52fd3cbb5 | |||
| b1acd5118b | |||
| 52cf7628da | |||
| 148aceb705 | |||
| c698de1482 | |||
| 883be98557 | |||
| 401ce33498 | |||
| be4b88dec8 | |||
| 104358bc62 | |||
| cabfa44729 | |||
| dc369c89b0 | |||
| 7d992931d9 | |||
| 6419645e03 | |||
| 3c709f07e6 | |||
| ca452f46e1 | |||
| 576b58b061 | |||
| bc3973ef15 | |||
| 0c7bc98d5b | |||
| 47c35fb859 | |||
| 79942113ef | |||
| 394979e4fc | |||
| 6ab64ccc82 | |||
| 78185b8fd5 | |||
| 2bd1b12ade | |||
| 884a6d4940 | |||
| f8243566c4 | |||
| ca5b94b044 | |||
| 78003923ca | |||
| ab6223c256 | |||
| 8a0810acb3 | |||
| 4528edfb2c | |||
| d980b81bd7 | |||
| 1984b71a0a | |||
| a9ed201adf | |||
| 65e9fdf1b9 | |||
| bd2ee54617 | |||
| a2e795a356 | |||
| c3d85074ac | |||
| 22bbc93b43 | |||
| 7460453b80 | |||
| 855c388027 | |||
| d1bd4e9b46 | |||
| 6945ce7e23 | |||
| 16a1d1e15b | |||
| b184b3e075 | |||
| 10c995f1da | |||
| 921fc8f00b | |||
| 164f99fe48 | |||
| fa1493b843 | |||
| 6b92d0bfea | |||
| 2ac2c45b11 | |||
| b576143afb | |||
| bc5b39057d | |||
| 1369a9048e | |||
| a89f5e6cf2 | |||
| 2ec11e5099 | |||
| 94a6877e7c | |||
| dc23a5648d | |||
| 9adfad9609 | |||
| cee5f141a3 | |||
| 603bb0c4e7 | |||
| 5752bc6ae9 | |||
| 93f3037410 | |||
| 9c7c89fed4 | |||
| 45a7d49d40 | |||
| de9bca8bb5 | |||
| 6427f218e7 | |||
| 6d0cc4eb15 | |||
| a6306c2759 | |||
| 7c76703355 | |||
| 9c3f56954e | |||
| e880f23040 | |||
| defa9fc212 | |||
| ea96f38875 | |||
| b67f3d63cf | |||
| fb111dcdc9 | |||
| 62561624ed | |||
| b5bd6d42f7 | |||
| c640519772 | |||
| fb844f79fd | |||
| 6dac909869 | |||
| 65b262207c | |||
| ef50f4a361 | |||
| 888e94a77c | |||
| c56d301b29 | |||
| 91e1c2c9a3 | |||
| 2087505921 | |||
| 58afa672c7 | |||
| 8dc476d205 | |||
| 73fac0f8e4 | |||
| a487b0ba48 | |||
| efd568a416 | |||
| 4f0909f30c | |||
| fdc1defaba | |||
| 07ed23c2d1 | |||
| 5d5bb24711 | |||
| 21c448d6f1 | |||
| cd9206344d | |||
| 4d06608cc9 | |||
| a64a63def7 | |||
| 19cb14b5dc | |||
| 4de6f496ad | |||
| e1cce05bd6 | |||
| 1ae79ab3c1 | |||
| acdaf3bb8e | |||
| f20595aff6 | |||
| 779d6b0387 | |||
| 3128a0d309 | |||
| ae69f7437f | |||
| 9971b2b7f2 | |||
| 3e15b2f46c | |||
| 7dcf76c3aa | |||
| 155e67fcec | |||
| 2a030318b1 | |||
| 103fe4fb12 | |||
| b2335b1656 | |||
| 692fe08d9f | |||
| 8b12ae35d4 | |||
| 87b1851d59 | |||
| 389cd93e68 | |||
| 6652127545 | |||
| e152616d7f | |||
| ac4d065c78 | |||
| 578b1f6082 | |||
| beb7f717f1 | |||
| 7a9b14a86f | |||
| f3925f8423 | |||
| dd641c047b | |||
| 22006eaf47 | |||
| 537f0dcb2f | |||
| 2ea850cbcc | |||
| e10e16f491 | |||
| 6369c3c969 | |||
| ead7f58285 | |||
| 0e77ca6ba8 | |||
| 1e0500a3f7 | |||
| 4ed72ebc80 | |||
| 6960bd3410 | |||
| 5ee2dd271c | |||
| 5ac807c6bd | |||
| 4af55a7d3f | |||
| 712c28068d | |||
| 777723b68c | |||
| cc3490d9d8 | |||
| d9be3f910a | |||
| 8a7db3bca8 | |||
| 3a78361aea | |||
| 6e3296469a | |||
| 561193b2ab | |||
| 8de9dadf1d | |||
| 853307697a | |||
| fd1772a417 | |||
| 1df506a176 | |||
| b349f24931 | |||
| 5841878df9 | |||
| c8d769c02c | |||
| d28aa25358 | |||
| 76808d60e3 | |||
| 9dd53f9212 | |||
| 0e03e5940a | |||
| e15894e4ea | |||
| 37b1cf82f3 | |||
| 39444af84e | |||
| da584ce0f0 | |||
| 455c91bf29 | |||
| 6fda4d4a90 | |||
| b84dfc049d | |||
| 3a1ca7a8e6 | |||
| b80bd759aa | |||
| a2e47c862e | |||
| 7b882896f1 | |||
| def1a170dc | |||
| 6a3cd867f0 | |||
| 55b725e876 | |||
| d664e9fc39 | |||
| 1dcd96ab36 | |||
| b6a25474ff | |||
| b4c2117992 | |||
| 9a4c595f64 | |||
| ce33eaaef2 | |||
| cc33fbdde2 | |||
| c18297f221 | |||
| 2f8f12a8fd | |||
| 728116da8f | |||
| 36f9e73dd4 | |||
| 9b6c5b0849 | |||
| cc4eaef3e6 | |||
| 77c2a68cc5 | |||
| 647dd81992 | |||
| c5c31f0a56 | |||
| b6c66f3e41 | |||
| 1065183e75 | |||
| dc03491b0d | |||
| 8c99454bf5 | |||
| 0fc73d51d2 | |||
| 831e5deeae | |||
| 22dfde8e7c | |||
| 9a7f4bc0d2 | |||
| 860e5ad31e | |||
| 9b00acec6f | |||
| 53fc5f9a57 | |||
| f6780d183c | |||
| 48fdfd849d | |||
| 52da64fc96 | |||
| 9759abde44 | |||
| 06a44705d0 | |||
| 3ae84de123 | |||
| 8c0784f9c3 | |||
| 995752da75 | |||
| 18617cdbd2 | |||
| 56cb9eaacb | |||
| a5dbf05ab5 | |||
| bbe3db1747 | |||
| 07293c0590 | |||
| 901aff1051 | |||
| 75dd5cfdc9 | |||
| 483bbb4a9c | |||
| 58f163788b | |||
| 8232b0efc8 | |||
| 23c308db1b | |||
| 881b5271a2 | |||
| 952a00e7d3 | |||
| b68ddc42c6 | |||
| 9a7e1c4def | |||
| 5226746c1a | |||
| 5dbeb0504d | |||
| ee3fb36047 | |||
| 1c40602744 | |||
| 818014127a | |||
| d37145660a | |||
| c58b9a3718 | |||
| a6a4e7b249 | |||
| 2b50c2157f | |||
| 0eded78c4c | |||
| 99b2eeb99d | |||
| 7b14c356db | |||
| bad2670f87 | |||
| b3710498f3 | |||
| 1bc90d88b4 | |||
| ddca24779e | |||
| 1ab2a9b846 | |||
| 9c6b52eb8e | |||
| 9d05d193a7 | |||
| e6e80b94ba | |||
| 4e70c90ca8 | |||
| a8faa82b70 | |||
| 4f4ac380ac | |||
| 374cb5b075 | |||
| b0445382dd | |||
| 3701fd2a71 | |||
| 1e4b4cad73 | |||
| a6aa31da79 | |||
| b82cc1e4bd | |||
| 1d44036933 | |||
| 61f86f5aae | |||
| acd8ef6658 | |||
| c22f2e7fa2 | |||
| b7d9dfbf31 | |||
| 92338a0911 | |||
| f7d32f2835 | |||
| 737c29cca4 | |||
| 37a59c01ac | |||
| 7ae37038b3 | |||
| bbe8378630 | |||
| 466c1d0cc0 | |||
| 4a7eff24bf | |||
| f968488e34 | |||
| a12a3454c4 | |||
| 8a92bc973b | |||
| dbaf50cb6e | |||
| 92e9de0441 | |||
| 7280678593 | |||
| b7c087617b | |||
| d8e958769b | |||
| b85c530df7 | |||
| 3823a612ae | |||
| 2a3402e991 | |||
| bbb85bb133 | |||
| d0b0dc3865 | |||
| 6fc8b9e5d2 | |||
| 217450e161 | |||
| 3930f39b14 | |||
| 19f815e3d2 | |||
| 9e0a88726c | |||
| 47eb1ec0b2 | |||
| 0d2a1bd0ae | |||
| 85a5d19a3e | |||
| 02f14f1bf5 | |||
| e00eec30d4 | |||
| 43e3a74c42 | |||
| 75dfc85978 | |||
| 9ce49ef19c | |||
| 36fea6b870 | |||
| 8130a922d0 | |||
| f1e2743d58 | |||
| 7d80afce2f | |||
| 4212387b70 | |||
| bdcd44b340 | |||
| 05064b3a0d | |||
| aa32c47e18 | |||
| 2e0ebd0e13 | |||
| 2754df9f46 | |||
| 9ab73a0354 | |||
| d61c7f37c3 | |||
| ad255a6ddf | |||
| 88507df90e | |||
| 10e5d5b5ae | |||
| c16cab87bd | |||
| 7319cb9aa9 | |||
| 677d0b33a8 | |||
| f1ee1b28a1 | |||
| 2da247f0fb | |||
| 37854ea8b9 | |||
| dc252b2f24 | |||
| d0ef88be89 | |||
| 4ec2d093e8 | |||
| 4eee018367 | |||
| 44aebf61b2 | |||
| f6d3de8cbf | |||
| c62bc4d744 | |||
| 8ebb22325c | |||
| e5d98c2dc3 | |||
| abe578a338 | |||
| 313a360c01 | |||
| 551013c771 | |||
| cc377b5eb5 | |||
| 6c435dafb7 | |||
| 3f5711773e | |||
| a7bb04f54b | |||
| 6604f30ef3 | |||
| 93d8aaaffd | |||
| f9e4bcaeea | |||
| 3d80791245 | |||
| 126280ff7c | |||
| 20b5dba41e | |||
| dfdb7a4468 | |||
| 4d715afd60 | |||
| ac0d0ab49c | |||
| a67e7fc9bb | |||
| 31322bbd83 | |||
| 23eba07901 | |||
| 8014345b99 | |||
| 99eda95cee | |||
| ec5ecbdb54 | |||
| e0ea4597e6 | |||
| c38ff8209b |
4
.gitignore
vendored
4
.gitignore
vendored
@@ -39,6 +39,8 @@ PanoPainterPackage/_pkginfo.txt
|
||||
PanoPainterPackage/AppPackages/
|
||||
PanoPainterPackage/BundleArtifacts/
|
||||
Thumbs.db
|
||||
__pycache__/
|
||||
*.pyc
|
||||
|
||||
steam/content/
|
||||
steam/output/
|
||||
@@ -53,3 +55,5 @@ linux/Makefile
|
||||
|
||||
webgl/build
|
||||
webgl/.vscode
|
||||
|
||||
out/
|
||||
|
||||
161
AGENTS.md
Normal file
161
AGENTS.md
Normal file
@@ -0,0 +1,161 @@
|
||||
# AGENTS.md
|
||||
|
||||
This file is the quick-start map for agents working in this repository. Keep it
|
||||
small and point to the live docs instead of duplicating the whole plan.
|
||||
|
||||
## Current Modernization Goal
|
||||
|
||||
PanoPainter is being incrementally modernized into independently testable C++23
|
||||
components while preserving current behavior. OpenGL remains the working backend
|
||||
until the renderer boundary is proven; Vulkan/Metal/WebGPU work waits behind that
|
||||
boundary.
|
||||
|
||||
Read these first:
|
||||
|
||||
- `docs/modernization/roadmap.md` - live phase roadmap, recent results, and next
|
||||
work queue.
|
||||
- `docs/modernization/debt.md` - required debt log. Every temporary adapter,
|
||||
retained legacy dependency, skipped platform, fallback, or shortcut needs an
|
||||
entry with validation and removal conditions.
|
||||
- `docs/modernization/capability-map.md` - behavior that must be preserved.
|
||||
- `docs/modernization/build-inventory.md` - current build/component inventory.
|
||||
- `docs/adr/0001-modernization-boundaries.md` - component boundary decisions.
|
||||
|
||||
## Working Rules
|
||||
|
||||
- Work on `codex/modernization-cmake-foundation` unless the user says otherwise.
|
||||
- Commit and push each verified successful progress slice.
|
||||
- Prefer larger coherent slices over tiny checkpoints, but keep docs/debt updated
|
||||
with each slice.
|
||||
- Do not revert user changes. Unrelated untracked notes, such as
|
||||
`docs/human-review-notes.md`, should be left alone unless explicitly requested.
|
||||
- Use CMake as the source of truth. Legacy Visual Studio project files are not the
|
||||
modernization path.
|
||||
- Use `apply_patch` for manual source/doc edits.
|
||||
|
||||
## Build And Test
|
||||
|
||||
Primary Windows configure/build:
|
||||
|
||||
```powershell
|
||||
cmake --preset windows-msvc-default
|
||||
cmake --build --preset windows-msvc-default --config Debug --target PanoPainter pano_cli
|
||||
```
|
||||
|
||||
Quiet checkpoint validation, preferred when working through Codex token-limited
|
||||
sessions:
|
||||
|
||||
```powershell
|
||||
powershell -ExecutionPolicy Bypass -File scripts\automation\quiet-validate.ps1 -BuildTargets PanoPainter,pano_cli -TestRegex "pp_app_core|pano_cli_plan"
|
||||
```
|
||||
|
||||
The quiet wrapper writes full command logs under `out/logs/quiet-validation`,
|
||||
prints only a compact summary, and applies editable warning/noise filters from
|
||||
`scripts/automation/quiet-validation-ignore.txt`. If a step fails, read the
|
||||
reported log file instead of rerunning with verbose output.
|
||||
|
||||
Focused fast validation:
|
||||
|
||||
```powershell
|
||||
ctest --preset desktop-fast --build-config Debug --output-on-failure
|
||||
```
|
||||
|
||||
Useful targeted pattern:
|
||||
|
||||
```powershell
|
||||
ctest --preset desktop-fast --build-config Debug -R "pp_app_core|pano_cli_plan" --output-on-failure
|
||||
```
|
||||
|
||||
Apple compile validation runs through the Mac mini SSH alias `panopainter-mac`:
|
||||
|
||||
```powershell
|
||||
powershell -ExecutionPolicy Bypass -File scripts\automation\apple-remote-build.ps1 -Presets macos,ios-simulator,ios-device
|
||||
```
|
||||
|
||||
This is a headless component/test/tool compile gate for macOS, iOS simulator,
|
||||
and iOS device. Signed Apple bundles remain tracked in the debt log.
|
||||
|
||||
If MSVC reports corrupt debug information or stale PDB/object errors, clean the
|
||||
generated build tree target before judging source changes:
|
||||
|
||||
```powershell
|
||||
cmake --build --preset windows-msvc-default --config Debug --target clean
|
||||
```
|
||||
|
||||
## Code Navigation
|
||||
|
||||
Codex has a repo-specific skill named `panopainter-code-navigation`. Agents must
|
||||
use it when necessary: follow this path before broad text search when a task is
|
||||
about C++ symbol identity, symbol families, declarations/definitions, override
|
||||
groups, or platform/backend boundaries. Use it when
|
||||
following C++ symbols, finding symbol families with regular expressions, tracing
|
||||
service/interface wiring, or checking platform/backend boundary usage.
|
||||
Reach for `--name-regex`,
|
||||
`--detail-regex`, or `--path-regex` when looking for generated-style name
|
||||
families, override groups, command/service families, signatures, or
|
||||
platform/backend path slices. Use normal `rg` for docs, build files, comments,
|
||||
string literals, generated command names, or exact non-C++ text.
|
||||
|
||||
Prefer compiler-aware navigation when following C++ symbols across the legacy
|
||||
flat source tree and extracted components:
|
||||
|
||||
```powershell
|
||||
python scripts/dev/clangd_nav.py symbols --file src/app_core/brush_ui.h --name execute_brush
|
||||
python scripts/dev/clangd_nav.py symbols --file src/app_core/brush_ui.h --name-regex "execute_.*preset"
|
||||
python scripts/dev/clangd_nav.py symbols --file src/app_core/document_export.h --detail-regex "Export.*Plan"
|
||||
python scripts/dev/clangd_nav.py definition --file src/node_panel_brush.cpp --line 511 --column 39
|
||||
python scripts/dev/clangd_nav.py references --file src/app_core/brush_ui.h --line 783 --column 45 --path-regex "src[\\/]app_core"
|
||||
python scripts/dev/clangd_nav.py self-test
|
||||
```
|
||||
|
||||
The helper talks to `clangd` using an existing `compile_commands.json`. It
|
||||
defaults to `out/build/windows-clangcl-asan` and then `out/build/android-arm64`;
|
||||
pass `--compile-commands-dir` or set `PP_CLANGD_COMPILE_COMMANDS_DIR` when using
|
||||
another Ninja build tree. Use `--name` and `--max-results` to keep output small.
|
||||
Use `--name-regex` for regex filtering against `qualifiedName`,
|
||||
`--detail-regex` for symbol detail/signature filtering, and `--path-regex` for
|
||||
definition/declaration/implementation/reference location filtering. Regex
|
||||
matching is case-insensitive by default, and `--no-ignore-case` makes it
|
||||
case-sensitive. Run `python scripts/dev/clangd_nav.py self-test` or the
|
||||
`panopainter_clangd_nav_regex_self_test` CTest before relying on regex behavior
|
||||
after tool changes.
|
||||
Treat symbol, hover, declaration, definition, and implementation lookups as the
|
||||
reliable path. Reference lookups are riskier because a one-shot clangd process
|
||||
may not have a complete project index; the helper refuses reference queries
|
||||
unless callers pass `--background-index` for broader best-effort results or
|
||||
`--allow-incomplete-references` for explicitly current-translation-unit-only
|
||||
results. Do not use incomplete reference output as proof that a symbol has no
|
||||
other users.
|
||||
|
||||
## Current Architecture Direction
|
||||
|
||||
The desired component split is documented in the roadmap. Current extracted or
|
||||
in-progress boundaries include:
|
||||
|
||||
- `pp_foundation`
|
||||
- `pp_assets`
|
||||
- `pp_paint`
|
||||
- `pp_document`
|
||||
- `pp_renderer_api`
|
||||
- `pp_renderer_gl`
|
||||
- `pp_paint_renderer`
|
||||
- `pp_app_core`
|
||||
- `pp_panopainter_ui`
|
||||
- `pp_platform_*`
|
||||
- `panopainter_app`
|
||||
|
||||
Legacy UI/app code still exists and is being reduced through pure planners,
|
||||
service interfaces, CLI automation, and CTest coverage. When moving behavior out
|
||||
of a legacy node, preserve current behavior first, add focused tests, document
|
||||
remaining shortcuts in `docs/modernization/debt.md`, then wire the live adapter.
|
||||
|
||||
## Legacy Context
|
||||
|
||||
PanoPainter started as a flat C++17 OpenGL panoramic painting app centered on
|
||||
singletons such as `App::I`, `Canvas::I`, `Settings`, and `WacomTablet::I`.
|
||||
Rendering uses GLAD/OpenGL and shaders in `data/shaders/`. UI is XML/Yoga based
|
||||
with many `Node*` subclasses. Project files are PPI, brush packages are PPBR,
|
||||
and Photoshop brushes import through ABR support.
|
||||
|
||||
Exceptions are disabled in app code. Public modernization APIs should return
|
||||
explicit status/result objects.
|
||||
640
CMakeLists.txt
Normal file
640
CMakeLists.txt
Normal file
@@ -0,0 +1,640 @@
|
||||
cmake_minimum_required(VERSION 3.29)
|
||||
|
||||
project(PanoPainter
|
||||
VERSION 0.0.0
|
||||
DESCRIPTION "Panoramic painting and animation application"
|
||||
LANGUAGES C CXX)
|
||||
|
||||
if(POLICY CMP0091)
|
||||
cmake_policy(SET CMP0091 NEW)
|
||||
endif()
|
||||
|
||||
list(APPEND CMAKE_MODULE_PATH "${CMAKE_CURRENT_SOURCE_DIR}/cmake")
|
||||
|
||||
include(PanoPainterOptions)
|
||||
if(PP_ENABLE_ASAN AND MSVC AND CMAKE_CXX_COMPILER_ID MATCHES "Clang")
|
||||
set(CMAKE_MSVC_RUNTIME_LIBRARY "MultiThreadedDLL")
|
||||
else()
|
||||
set(CMAKE_MSVC_RUNTIME_LIBRARY "MultiThreaded$<$<CONFIG:Debug>:Debug>DLL")
|
||||
endif()
|
||||
include(PanoPainterWarnings)
|
||||
include(PanoPainterSources)
|
||||
include(PanoPainterVersion)
|
||||
include(PanoPainterRuntime)
|
||||
include(PanoPainterPackageTargets)
|
||||
include(PanoPainterPlatformTargets)
|
||||
|
||||
if(PP_ENABLE_CLANG_TIDY)
|
||||
find_program(PP_CLANG_TIDY_EXE NAMES clang-tidy)
|
||||
if(PP_CLANG_TIDY_EXE)
|
||||
set(CMAKE_CXX_CLANG_TIDY "${PP_CLANG_TIDY_EXE}")
|
||||
else()
|
||||
message(WARNING "PP_ENABLE_CLANG_TIDY is ON but clang-tidy was not found.")
|
||||
endif()
|
||||
endif()
|
||||
|
||||
if(PP_ENABLE_CPPCHECK)
|
||||
find_program(PP_CPPCHECK_EXE NAMES cppcheck)
|
||||
if(PP_CPPCHECK_EXE)
|
||||
set(CMAKE_CXX_CPPCHECK
|
||||
"${PP_CPPCHECK_EXE}"
|
||||
"--enable=warning,style,performance,portability"
|
||||
"--inline-suppr"
|
||||
"--suppress=missingIncludeSystem")
|
||||
else()
|
||||
message(WARNING "PP_ENABLE_CPPCHECK is ON but cppcheck was not found.")
|
||||
endif()
|
||||
endif()
|
||||
|
||||
add_library(pp_project_options INTERFACE)
|
||||
target_compile_features(pp_project_options INTERFACE cxx_std_23)
|
||||
|
||||
add_library(pp_project_warnings INTERFACE)
|
||||
pp_configure_project_warnings(pp_project_warnings)
|
||||
|
||||
if(CMAKE_SYSTEM_NAME STREQUAL "iOS")
|
||||
set(CMAKE_XCODE_ATTRIBUTE_PRODUCT_BUNDLE_IDENTIFIER
|
||||
"com.omigamedev.panopainter.$(PRODUCT_NAME:rfc1034identifier)")
|
||||
set(CMAKE_XCODE_ATTRIBUTE_CODE_SIGNING_ALLOWED "NO")
|
||||
set(CMAKE_XCODE_ATTRIBUTE_CODE_SIGNING_REQUIRED "NO")
|
||||
endif()
|
||||
|
||||
if(PP_USE_VCPKG_TINYXML2)
|
||||
find_package(tinyxml2 CONFIG REQUIRED)
|
||||
add_library(pp_xml_tinyxml2 INTERFACE)
|
||||
target_link_libraries(pp_xml_tinyxml2
|
||||
INTERFACE
|
||||
tinyxml2::tinyxml2)
|
||||
else()
|
||||
add_library(pp_vendor_tinyxml2 STATIC
|
||||
libs/tinyxml2/tinyxml2.cpp)
|
||||
target_include_directories(pp_vendor_tinyxml2
|
||||
SYSTEM PUBLIC
|
||||
"${CMAKE_CURRENT_SOURCE_DIR}/libs/tinyxml2")
|
||||
target_link_libraries(pp_vendor_tinyxml2
|
||||
PUBLIC
|
||||
pp_project_options)
|
||||
add_library(pp_xml_tinyxml2 ALIAS pp_vendor_tinyxml2)
|
||||
endif()
|
||||
|
||||
add_custom_target(panopainter_modernization_status
|
||||
COMMAND "${CMAKE_COMMAND}" -E echo "PanoPainter modernization scaffold configured."
|
||||
COMMAND "${CMAKE_COMMAND}" -E echo "Roadmap: docs/modernization/roadmap.md"
|
||||
COMMAND "${CMAKE_COMMAND}" -E echo "Debt log: docs/modernization/debt.md"
|
||||
VERBATIM)
|
||||
|
||||
add_custom_target(panopainter_validate_shaders
|
||||
COMMAND "${CMAKE_COMMAND}"
|
||||
"-DPP_SHADER_DIR=${CMAKE_CURRENT_SOURCE_DIR}/data/shaders"
|
||||
-P "${CMAKE_CURRENT_SOURCE_DIR}/cmake/ValidatePanoPainterShaders.cmake"
|
||||
VERBATIM)
|
||||
|
||||
add_library(pp_foundation STATIC
|
||||
src/foundation/binary_stream.cpp
|
||||
src/foundation/event.cpp
|
||||
src/foundation/log.cpp
|
||||
src/foundation/parse.cpp
|
||||
src/foundation/task_queue.cpp
|
||||
src/foundation/trace.cpp)
|
||||
target_include_directories(pp_foundation
|
||||
PUBLIC
|
||||
"${CMAKE_CURRENT_SOURCE_DIR}/src")
|
||||
target_link_libraries(pp_foundation
|
||||
PUBLIC
|
||||
pp_project_options
|
||||
PRIVATE
|
||||
pp_project_warnings)
|
||||
|
||||
add_library(pp_assets STATIC
|
||||
src/assets/brush_package.cpp
|
||||
src/assets/image_format.cpp
|
||||
src/assets/image_metadata.cpp
|
||||
src/assets/image_pixels.cpp
|
||||
src/assets/ppi_header.cpp
|
||||
src/assets/settings_document.cpp)
|
||||
target_include_directories(pp_assets
|
||||
PUBLIC
|
||||
"${CMAKE_CURRENT_SOURCE_DIR}/src")
|
||||
target_include_directories(pp_assets
|
||||
SYSTEM PRIVATE
|
||||
"${CMAKE_CURRENT_SOURCE_DIR}/libs/stb")
|
||||
if(MSVC)
|
||||
set_source_files_properties(src/assets/image_pixels.cpp
|
||||
PROPERTIES
|
||||
COMPILE_OPTIONS "/analyze-")
|
||||
endif()
|
||||
target_link_libraries(pp_assets
|
||||
PUBLIC
|
||||
pp_foundation
|
||||
pp_project_options
|
||||
PRIVATE
|
||||
pp_project_warnings)
|
||||
|
||||
add_library(pp_paint STATIC
|
||||
src/paint/brush.cpp
|
||||
src/paint/blend.cpp
|
||||
src/paint/stroke.cpp
|
||||
src/paint/stroke_script.cpp)
|
||||
target_include_directories(pp_paint
|
||||
PUBLIC
|
||||
"${CMAKE_CURRENT_SOURCE_DIR}/src")
|
||||
target_link_libraries(pp_paint
|
||||
PUBLIC
|
||||
pp_foundation
|
||||
pp_project_options
|
||||
PRIVATE
|
||||
pp_project_warnings)
|
||||
|
||||
add_library(pp_document STATIC
|
||||
src/document/document.cpp
|
||||
src/document/ppi_export.cpp
|
||||
src/document/ppi_import.cpp)
|
||||
target_include_directories(pp_document
|
||||
PUBLIC
|
||||
"${CMAKE_CURRENT_SOURCE_DIR}/src")
|
||||
target_link_libraries(pp_document
|
||||
PUBLIC
|
||||
pp_foundation
|
||||
pp_assets
|
||||
pp_paint
|
||||
pp_project_options
|
||||
PRIVATE
|
||||
pp_project_warnings)
|
||||
|
||||
add_library(pp_renderer_api STATIC
|
||||
src/renderer_api/recording_renderer.cpp
|
||||
src/renderer_api/renderer_api.cpp
|
||||
src/renderer_api/shader_catalog.cpp)
|
||||
target_include_directories(pp_renderer_api
|
||||
PUBLIC
|
||||
"${CMAKE_CURRENT_SOURCE_DIR}/src")
|
||||
target_link_libraries(pp_renderer_api
|
||||
PUBLIC
|
||||
pp_foundation
|
||||
pp_project_options
|
||||
PRIVATE
|
||||
pp_project_warnings)
|
||||
|
||||
if(PP_ENABLE_OPENGL)
|
||||
add_library(pp_renderer_gl STATIC
|
||||
src/renderer_gl/command_plan.cpp
|
||||
src/renderer_gl/opengl_capabilities.cpp
|
||||
src/renderer_gl/shader_bindings.cpp)
|
||||
target_include_directories(pp_renderer_gl
|
||||
PUBLIC
|
||||
"${CMAKE_CURRENT_SOURCE_DIR}/src")
|
||||
target_link_libraries(pp_renderer_gl
|
||||
PUBLIC
|
||||
pp_renderer_api
|
||||
pp_project_options
|
||||
PRIVATE
|
||||
pp_project_warnings)
|
||||
if(EMSCRIPTEN)
|
||||
target_compile_definitions(pp_renderer_gl PRIVATE
|
||||
PP_RENDERER_GL_RUNTIME_GLES=1
|
||||
PP_RENDERER_GL_RUNTIME_WEB=1)
|
||||
elseif(ANDROID OR CMAKE_SYSTEM_NAME STREQUAL "iOS")
|
||||
target_compile_definitions(pp_renderer_gl PRIVATE
|
||||
PP_RENDERER_GL_RUNTIME_GLES=1)
|
||||
else()
|
||||
target_compile_definitions(pp_renderer_gl PRIVATE
|
||||
PP_RENDERER_GL_RUNTIME_DESKTOP=1)
|
||||
endif()
|
||||
endif()
|
||||
|
||||
add_library(pp_paint_renderer STATIC
|
||||
src/paint_renderer/compositor.cpp)
|
||||
target_include_directories(pp_paint_renderer
|
||||
PUBLIC
|
||||
"${CMAKE_CURRENT_SOURCE_DIR}/src")
|
||||
target_link_libraries(pp_paint_renderer
|
||||
PUBLIC
|
||||
pp_foundation
|
||||
pp_document
|
||||
pp_paint
|
||||
pp_renderer_api
|
||||
pp_project_options
|
||||
PRIVATE
|
||||
pp_project_warnings)
|
||||
|
||||
add_library(pp_ui_core STATIC
|
||||
src/ui_core/color.cpp
|
||||
src/ui_core/layout_value.cpp
|
||||
src/ui_core/layout_xml.cpp
|
||||
src/ui_core/node_lifetime.cpp
|
||||
src/ui_core/overlay_lifetime.cpp)
|
||||
target_include_directories(pp_ui_core
|
||||
PUBLIC
|
||||
"${CMAKE_CURRENT_SOURCE_DIR}/src")
|
||||
target_link_libraries(pp_ui_core
|
||||
PUBLIC
|
||||
pp_foundation
|
||||
pp_project_options
|
||||
PRIVATE
|
||||
pp_xml_tinyxml2
|
||||
pp_project_warnings)
|
||||
|
||||
add_library(pp_platform_api STATIC
|
||||
src/platform_api/asset_file_load_policy.cpp
|
||||
src/platform_api/asset_file_load_policy.h
|
||||
src/platform_api/network_tls_policy.cpp
|
||||
src/platform_api/network_tls_policy.h
|
||||
src/platform_api/platform_policy.cpp
|
||||
src/platform_api/platform_policy.h
|
||||
src/platform_api/platform_services.cpp
|
||||
src/platform_api/platform_services.h)
|
||||
target_include_directories(pp_platform_api
|
||||
PUBLIC
|
||||
"${CMAKE_CURRENT_SOURCE_DIR}/src")
|
||||
target_link_libraries(pp_platform_api
|
||||
PUBLIC
|
||||
pp_project_options
|
||||
PRIVATE
|
||||
pp_project_warnings)
|
||||
|
||||
add_library(pp_app_core STATIC
|
||||
src/app_core/about_menu.h
|
||||
src/app_core/app_dialog.h
|
||||
src/app_core/app_frame.h
|
||||
src/app_core/app_input.h
|
||||
src/app_core/app_preferences.h
|
||||
src/app_core/app_shutdown.h
|
||||
src/app_core/app_status.h
|
||||
src/app_core/app_startup.h
|
||||
src/app_core/app_thread.h
|
||||
src/app_core/brush_package_import.h
|
||||
src/app_core/brush_package_export.h
|
||||
src/app_core/brush_ui.h
|
||||
src/app_core/canvas_hotkey.h
|
||||
src/app_core/canvas_tool_ui.h
|
||||
src/app_core/canvas_view.h
|
||||
src/app_core/command_convert.h
|
||||
src/app_core/document_animation.h
|
||||
src/app_core/document_canvas.h
|
||||
src/app_core/document_cloud.h
|
||||
src/app_core/document_export.cpp
|
||||
src/app_core/document_import.h
|
||||
src/app_core/document_layer.h
|
||||
src/app_core/document_platform_io.h
|
||||
src/app_core/document_recording.h
|
||||
src/app_core/document_resize.h
|
||||
src/app_core/document_route.cpp
|
||||
src/app_core/document_sharing.h
|
||||
src/app_core/document_session.cpp
|
||||
src/app_core/file_menu.h
|
||||
src/app_core/grid_ui.h
|
||||
src/app_core/history_ui.h
|
||||
src/app_core/main_toolbar.h
|
||||
src/app_core/quick_ui.h
|
||||
src/app_core/tools_menu.h)
|
||||
target_include_directories(pp_app_core
|
||||
PUBLIC
|
||||
"${CMAKE_CURRENT_SOURCE_DIR}/src")
|
||||
target_link_libraries(pp_app_core
|
||||
PUBLIC
|
||||
pp_foundation
|
||||
pp_document
|
||||
pp_project_options
|
||||
PRIVATE
|
||||
pp_project_warnings)
|
||||
|
||||
if(PP_BUILD_TOOLS)
|
||||
add_subdirectory(tools/pano_cli)
|
||||
endif()
|
||||
|
||||
if(PP_BUILD_TESTS)
|
||||
enable_testing()
|
||||
add_subdirectory(tests)
|
||||
endif()
|
||||
|
||||
if(PP_BUILD_APP)
|
||||
if(WIN32)
|
||||
add_library(pp_legacy_vendor OBJECT
|
||||
${PP_VENDOR_SOURCES})
|
||||
target_link_libraries(pp_legacy_vendor
|
||||
PUBLIC
|
||||
pp_project_options
|
||||
PRIVATE
|
||||
pp_project_warnings)
|
||||
target_include_directories(pp_legacy_vendor
|
||||
PUBLIC
|
||||
${PP_LEGACY_INCLUDE_DIRS})
|
||||
if(MSVC_VERSION GREATER_EQUAL 1945)
|
||||
set(PP_FMT_VS2026_COMPAT_INCLUDE_DIR
|
||||
"${CMAKE_CURRENT_BINARY_DIR}/compat/fmt-vs2026/include")
|
||||
set(PP_FMT_VS2026_COMPAT_FORMAT_H
|
||||
"${PP_FMT_VS2026_COMPAT_INCLUDE_DIR}/fmt/format.h")
|
||||
file(MAKE_DIRECTORY "${PP_FMT_VS2026_COMPAT_INCLUDE_DIR}")
|
||||
file(COPY
|
||||
"${CMAKE_CURRENT_SOURCE_DIR}/libs/fmt/include/fmt"
|
||||
DESTINATION "${PP_FMT_VS2026_COMPAT_INCLUDE_DIR}")
|
||||
file(READ
|
||||
"${CMAKE_CURRENT_SOURCE_DIR}/libs/fmt/include/fmt/format.h"
|
||||
PP_FMT_FORMAT_H)
|
||||
# VS 2026 removed stdext::checked_array_iterator; keep the fmt
|
||||
# submodule clean by disabling that legacy-only branch in the
|
||||
# generated overlay.
|
||||
string(REPLACE
|
||||
"#ifdef _SECURE_SCL"
|
||||
"#if 0"
|
||||
PP_FMT_FORMAT_H
|
||||
"${PP_FMT_FORMAT_H}")
|
||||
file(WRITE "${PP_FMT_VS2026_COMPAT_FORMAT_H}" "${PP_FMT_FORMAT_H}")
|
||||
target_include_directories(pp_legacy_vendor
|
||||
BEFORE PRIVATE
|
||||
"${PP_FMT_VS2026_COMPAT_INCLUDE_DIR}")
|
||||
endif()
|
||||
target_compile_definitions(pp_legacy_vendor
|
||||
PUBLIC
|
||||
ENUM_BITFIELDS_NOT_SUPPORTED
|
||||
UNICODE
|
||||
_UNICODE
|
||||
_CRT_SECURE_NO_WARNINGS
|
||||
_SCL_SECURE_NO_WARNINGS
|
||||
_SILENCE_CXX17_CODECVT_HEADER_DEPRECATION_WARNING
|
||||
_SILENCE_STDEXT_ARR_ITERS_DEPRECATION_WARNING
|
||||
_CONSOLE
|
||||
WITH_CURL=1)
|
||||
set_target_properties(pp_legacy_vendor PROPERTIES
|
||||
VS_GLOBAL_CharacterSet "Unicode")
|
||||
|
||||
add_library(pp_legacy_renderer_gl OBJECT
|
||||
${PP_LEGACY_RENDERER_GL_SOURCES})
|
||||
target_link_libraries(pp_legacy_renderer_gl
|
||||
PUBLIC
|
||||
pp_project_options
|
||||
PRIVATE
|
||||
pp_renderer_api
|
||||
pp_project_warnings)
|
||||
if(TARGET pp_renderer_gl)
|
||||
target_link_libraries(pp_legacy_renderer_gl PRIVATE pp_renderer_gl)
|
||||
endif()
|
||||
target_include_directories(pp_legacy_renderer_gl
|
||||
PUBLIC
|
||||
${PP_LEGACY_INCLUDE_DIRS})
|
||||
target_compile_definitions(pp_legacy_renderer_gl
|
||||
PUBLIC
|
||||
ENUM_BITFIELDS_NOT_SUPPORTED
|
||||
UNICODE
|
||||
_UNICODE
|
||||
_CRT_SECURE_NO_WARNINGS
|
||||
_SCL_SECURE_NO_WARNINGS
|
||||
_SILENCE_CXX17_CODECVT_HEADER_DEPRECATION_WARNING
|
||||
_SILENCE_STDEXT_ARR_ITERS_DEPRECATION_WARNING
|
||||
_CONSOLE
|
||||
WITH_CURL=1)
|
||||
set_target_properties(pp_legacy_renderer_gl PROPERTIES
|
||||
VS_GLOBAL_CharacterSet "Unicode")
|
||||
target_precompile_headers(pp_legacy_renderer_gl PRIVATE src/pch.h)
|
||||
|
||||
add_library(pp_legacy_assets_io OBJECT
|
||||
${PP_LEGACY_ASSETS_IO_SOURCES})
|
||||
target_link_libraries(pp_legacy_assets_io
|
||||
PUBLIC
|
||||
pp_project_options
|
||||
PRIVATE
|
||||
pp_assets
|
||||
pp_platform_api
|
||||
pp_project_warnings)
|
||||
target_include_directories(pp_legacy_assets_io
|
||||
PUBLIC
|
||||
${PP_LEGACY_INCLUDE_DIRS})
|
||||
target_compile_definitions(pp_legacy_assets_io
|
||||
PUBLIC
|
||||
ENUM_BITFIELDS_NOT_SUPPORTED
|
||||
UNICODE
|
||||
_UNICODE
|
||||
_CRT_SECURE_NO_WARNINGS
|
||||
_SCL_SECURE_NO_WARNINGS
|
||||
_SILENCE_CXX17_CODECVT_HEADER_DEPRECATION_WARNING
|
||||
_SILENCE_STDEXT_ARR_ITERS_DEPRECATION_WARNING
|
||||
_CONSOLE
|
||||
WITH_CURL=1)
|
||||
set_target_properties(pp_legacy_assets_io PROPERTIES
|
||||
VS_GLOBAL_CharacterSet "Unicode")
|
||||
target_precompile_headers(pp_legacy_assets_io PRIVATE src/pch.h)
|
||||
|
||||
add_library(pp_legacy_paint_document OBJECT
|
||||
${PP_LEGACY_PAINT_DOCUMENT_SOURCES})
|
||||
target_link_libraries(pp_legacy_paint_document
|
||||
PUBLIC
|
||||
pp_project_options
|
||||
PRIVATE
|
||||
pp_assets
|
||||
pp_document
|
||||
pp_paint
|
||||
pp_paint_renderer
|
||||
pp_platform_api
|
||||
pp_renderer_api
|
||||
pp_project_warnings)
|
||||
if(TARGET pp_renderer_gl)
|
||||
target_link_libraries(pp_legacy_paint_document PRIVATE pp_renderer_gl)
|
||||
endif()
|
||||
target_include_directories(pp_legacy_paint_document
|
||||
PUBLIC
|
||||
${PP_LEGACY_INCLUDE_DIRS})
|
||||
target_compile_definitions(pp_legacy_paint_document
|
||||
PUBLIC
|
||||
ENUM_BITFIELDS_NOT_SUPPORTED
|
||||
UNICODE
|
||||
_UNICODE
|
||||
_CRT_SECURE_NO_WARNINGS
|
||||
_SCL_SECURE_NO_WARNINGS
|
||||
_SILENCE_CXX17_CODECVT_HEADER_DEPRECATION_WARNING
|
||||
_SILENCE_STDEXT_ARR_ITERS_DEPRECATION_WARNING
|
||||
_CONSOLE
|
||||
WITH_CURL=1)
|
||||
set_target_properties(pp_legacy_paint_document PROPERTIES
|
||||
VS_GLOBAL_CharacterSet "Unicode")
|
||||
target_precompile_headers(pp_legacy_paint_document PRIVATE src/pch.h)
|
||||
|
||||
add_library(pp_legacy_engine STATIC
|
||||
${PP_LEGACY_ENGINE_SOURCES}
|
||||
$<TARGET_OBJECTS:pp_legacy_assets_io>
|
||||
$<TARGET_OBJECTS:pp_legacy_paint_document>
|
||||
$<TARGET_OBJECTS:pp_legacy_renderer_gl>
|
||||
$<TARGET_OBJECTS:pp_legacy_vendor>)
|
||||
|
||||
target_link_libraries(pp_legacy_engine
|
||||
PUBLIC
|
||||
pp_project_options
|
||||
PRIVATE
|
||||
pp_assets
|
||||
pp_document
|
||||
pp_paint
|
||||
pp_paint_renderer
|
||||
pp_renderer_api
|
||||
pp_project_warnings)
|
||||
if(TARGET pp_renderer_gl)
|
||||
target_link_libraries(pp_legacy_engine PRIVATE pp_renderer_gl)
|
||||
endif()
|
||||
|
||||
target_include_directories(pp_legacy_engine
|
||||
PUBLIC
|
||||
${PP_LEGACY_INCLUDE_DIRS})
|
||||
|
||||
target_compile_definitions(pp_legacy_engine
|
||||
PUBLIC
|
||||
ENUM_BITFIELDS_NOT_SUPPORTED
|
||||
UNICODE
|
||||
_UNICODE
|
||||
_CRT_SECURE_NO_WARNINGS
|
||||
_SCL_SECURE_NO_WARNINGS
|
||||
_SILENCE_CXX17_CODECVT_HEADER_DEPRECATION_WARNING
|
||||
_SILENCE_STDEXT_ARR_ITERS_DEPRECATION_WARNING
|
||||
_CONSOLE
|
||||
WITH_CURL=1)
|
||||
set_target_properties(pp_legacy_engine PROPERTIES
|
||||
VS_GLOBAL_CharacterSet "Unicode")
|
||||
|
||||
target_precompile_headers(pp_legacy_engine PRIVATE src/pch.h)
|
||||
|
||||
add_library(pp_legacy_ui_core OBJECT
|
||||
${PP_LEGACY_UI_CORE_SOURCES})
|
||||
|
||||
target_link_libraries(pp_legacy_ui_core
|
||||
PUBLIC
|
||||
pp_app_core
|
||||
pp_legacy_engine
|
||||
pp_project_options
|
||||
PRIVATE
|
||||
pp_paint_renderer
|
||||
pp_platform_api
|
||||
pp_renderer_api
|
||||
pp_project_warnings)
|
||||
if(TARGET pp_renderer_gl)
|
||||
target_link_libraries(pp_legacy_ui_core PRIVATE pp_renderer_gl)
|
||||
endif()
|
||||
|
||||
target_include_directories(pp_legacy_ui_core
|
||||
PUBLIC
|
||||
${PP_LEGACY_INCLUDE_DIRS})
|
||||
|
||||
target_compile_definitions(pp_legacy_ui_core
|
||||
PUBLIC
|
||||
ENUM_BITFIELDS_NOT_SUPPORTED
|
||||
UNICODE
|
||||
_UNICODE
|
||||
_CRT_SECURE_NO_WARNINGS
|
||||
_SCL_SECURE_NO_WARNINGS
|
||||
_SILENCE_CXX17_CODECVT_HEADER_DEPRECATION_WARNING
|
||||
_SILENCE_STDEXT_ARR_ITERS_DEPRECATION_WARNING
|
||||
_CONSOLE
|
||||
WITH_CURL=1)
|
||||
set_target_properties(pp_legacy_ui_core PROPERTIES
|
||||
VS_GLOBAL_CharacterSet "Unicode")
|
||||
|
||||
target_precompile_headers(pp_legacy_ui_core PRIVATE src/pch.h)
|
||||
|
||||
add_library(pp_legacy_app STATIC
|
||||
${PP_LEGACY_APP_SOURCES}
|
||||
$<TARGET_OBJECTS:pp_legacy_ui_core>)
|
||||
|
||||
target_link_libraries(pp_legacy_app
|
||||
PUBLIC
|
||||
pp_legacy_engine
|
||||
pp_legacy_ui_core
|
||||
pp_project_options
|
||||
PRIVATE
|
||||
pp_platform_api
|
||||
pp_renderer_api
|
||||
pp_project_warnings)
|
||||
if(TARGET pp_renderer_gl)
|
||||
target_link_libraries(pp_legacy_app PRIVATE pp_renderer_gl)
|
||||
endif()
|
||||
|
||||
target_include_directories(pp_legacy_app
|
||||
PUBLIC
|
||||
${PP_LEGACY_INCLUDE_DIRS})
|
||||
|
||||
target_compile_definitions(pp_legacy_app
|
||||
PUBLIC
|
||||
ENUM_BITFIELDS_NOT_SUPPORTED
|
||||
UNICODE
|
||||
_UNICODE
|
||||
_CRT_SECURE_NO_WARNINGS
|
||||
_SCL_SECURE_NO_WARNINGS
|
||||
_SILENCE_CXX17_CODECVT_HEADER_DEPRECATION_WARNING
|
||||
_SILENCE_STDEXT_ARR_ITERS_DEPRECATION_WARNING
|
||||
_CONSOLE
|
||||
WITH_CURL=1)
|
||||
set_target_properties(pp_legacy_app PROPERTIES
|
||||
VS_GLOBAL_CharacterSet "Unicode")
|
||||
|
||||
target_precompile_headers(pp_legacy_app PRIVATE src/pch.h)
|
||||
|
||||
add_library(pp_panopainter_ui STATIC
|
||||
${PP_PANOPAINTER_UI_SOURCES})
|
||||
target_link_libraries(pp_panopainter_ui
|
||||
PUBLIC
|
||||
pp_legacy_app
|
||||
pp_project_options
|
||||
PRIVATE
|
||||
pp_assets
|
||||
pp_platform_api
|
||||
pp_project_warnings)
|
||||
target_precompile_headers(pp_panopainter_ui REUSE_FROM pp_legacy_app)
|
||||
set_target_properties(pp_panopainter_ui PROPERTIES
|
||||
VS_GLOBAL_CharacterSet "Unicode")
|
||||
|
||||
add_library(panopainter_app STATIC
|
||||
${PP_PANOPAINTER_APP_SOURCES})
|
||||
target_include_directories(panopainter_app
|
||||
PUBLIC
|
||||
"${CMAKE_CURRENT_SOURCE_DIR}/src")
|
||||
target_link_libraries(panopainter_app
|
||||
PUBLIC
|
||||
pp_app_core
|
||||
pp_legacy_app
|
||||
pp_panopainter_ui
|
||||
pp_platform_api
|
||||
pp_project_options
|
||||
PRIVATE
|
||||
pp_project_warnings)
|
||||
pp_add_version_generation(panopainter_app "$<IF:$<CONFIG:Debug>,debug,release>")
|
||||
|
||||
add_library(pp_platform_windows OBJECT
|
||||
${PP_WINDOWS_PLATFORM_SOURCES})
|
||||
target_link_libraries(pp_platform_windows
|
||||
PUBLIC
|
||||
panopainter_app
|
||||
"${CMAKE_CURRENT_SOURCE_DIR}/libs/bugtrap-client/lib/BugTrapU-x64.lib"
|
||||
"$<$<CONFIG:Debug>:${CMAKE_CURRENT_SOURCE_DIR}/libs/curl-win/lib/dll-debug-x64/libcurl_debug.lib>"
|
||||
"$<$<NOT:$<CONFIG:Debug>>:${CMAKE_CURRENT_SOURCE_DIR}/libs/curl-win/lib/dll-release-x64/libcurl.lib>"
|
||||
"${CMAKE_CURRENT_SOURCE_DIR}/libs/libyuv/lib/win/yuv.lib"
|
||||
"${CMAKE_CURRENT_SOURCE_DIR}/libs/mp4v2/lib/win/libmp4v2.lib"
|
||||
"${CMAKE_CURRENT_SOURCE_DIR}/libs/openh264/lib/openh264-2.0.0-win64.lib"
|
||||
"${CMAKE_CURRENT_SOURCE_DIR}/libs/openvr/lib/win64/openvr_api.lib"
|
||||
comdlg32
|
||||
gdi32
|
||||
opengl32
|
||||
ole32
|
||||
shell32
|
||||
shlwapi
|
||||
user32
|
||||
wbemuuid
|
||||
PRIVATE
|
||||
pp_project_options
|
||||
pp_project_warnings)
|
||||
target_precompile_headers(pp_platform_windows REUSE_FROM pp_legacy_app)
|
||||
set_target_properties(pp_platform_windows PROPERTIES
|
||||
VS_GLOBAL_CharacterSet "Unicode")
|
||||
|
||||
add_executable(PanoPainter WIN32
|
||||
${PP_WINDOWS_APP_SOURCES}
|
||||
$<TARGET_OBJECTS:pp_platform_windows>)
|
||||
|
||||
target_link_libraries(PanoPainter
|
||||
PRIVATE
|
||||
pp_project_options
|
||||
pp_project_warnings
|
||||
pp_platform_windows)
|
||||
|
||||
set_target_properties(PanoPainter PROPERTIES
|
||||
VS_GLOBAL_CharacterSet "Unicode")
|
||||
|
||||
pp_configure_windows_runtime_payloads(PanoPainter)
|
||||
else()
|
||||
message(WARNING "PP_BUILD_APP is enabled, but the root CMake app target is currently Windows-only. Platform alignment is tracked in Phase 6.")
|
||||
endif()
|
||||
endif()
|
||||
297
CMakePresets.json
Normal file
297
CMakePresets.json
Normal file
@@ -0,0 +1,297 @@
|
||||
{
|
||||
"version": 8,
|
||||
"cmakeMinimumRequired": {
|
||||
"major": 3,
|
||||
"minor": 29,
|
||||
"patch": 0
|
||||
},
|
||||
"configurePresets": [
|
||||
{
|
||||
"name": "base",
|
||||
"hidden": true,
|
||||
"binaryDir": "${sourceDir}/out/build/${presetName}",
|
||||
"cacheVariables": {
|
||||
"CMAKE_EXPORT_COMPILE_COMMANDS": "ON",
|
||||
"PP_BUILD_APP": "ON",
|
||||
"PP_BUILD_TESTS": "ON",
|
||||
"PP_BUILD_TOOLS": "ON",
|
||||
"PP_ENABLE_OPENGL": "ON",
|
||||
"PP_ENABLE_VULKAN_EXPERIMENTAL": "OFF",
|
||||
"PP_ENABLE_VR": "ON",
|
||||
"PP_ENABLE_CLOUD": "ON",
|
||||
"PP_ENABLE_VIDEO": "ON"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "platform-headless-base",
|
||||
"hidden": true,
|
||||
"inherits": "base",
|
||||
"cacheVariables": {
|
||||
"PP_BUILD_APP": "OFF",
|
||||
"PP_ENABLE_CLOUD": "OFF",
|
||||
"PP_ENABLE_VIDEO": "OFF"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "windows-vs2026-x64",
|
||||
"inherits": "base",
|
||||
"displayName": "Windows VS 2026 x64",
|
||||
"generator": "Visual Studio 18 2026",
|
||||
"architecture": "x64"
|
||||
},
|
||||
{
|
||||
"name": "windows-msvc-default",
|
||||
"inherits": "base",
|
||||
"displayName": "Windows MSVC default generator",
|
||||
"generator": "Visual Studio 18 2026",
|
||||
"architecture": "x64"
|
||||
},
|
||||
{
|
||||
"name": "windows-msvc-vcpkg-headless",
|
||||
"inherits": "platform-headless-base",
|
||||
"displayName": "Windows MSVC vcpkg headless",
|
||||
"generator": "Visual Studio 18 2026",
|
||||
"architecture": "x64",
|
||||
"toolchainFile": "$env{VCPKG_ROOT}/scripts/buildsystems/vcpkg.cmake",
|
||||
"cacheVariables": {
|
||||
"PP_USE_VCPKG_TINYXML2": "ON"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "windows-clangcl-asan",
|
||||
"inherits": "platform-headless-base",
|
||||
"displayName": "Windows clang-cl ASan",
|
||||
"generator": "Ninja",
|
||||
"cacheVariables": {
|
||||
"CMAKE_C_COMPILER": "clang-cl",
|
||||
"CMAKE_CXX_COMPILER": "clang-cl",
|
||||
"CMAKE_MSVC_RUNTIME_LIBRARY": "MultiThreadedDLL",
|
||||
"PP_ENABLE_ASAN": "ON",
|
||||
"PP_ENABLE_UBSAN": "OFF"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "linux-clang",
|
||||
"inherits": "platform-headless-base",
|
||||
"displayName": "Linux clang",
|
||||
"generator": "Ninja",
|
||||
"cacheVariables": {
|
||||
"CMAKE_C_COMPILER": "clang",
|
||||
"CMAKE_CXX_COMPILER": "clang++"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "android-base",
|
||||
"hidden": true,
|
||||
"inherits": "platform-headless-base",
|
||||
"generator": "Ninja",
|
||||
"toolchainFile": "$env{ANDROID_NDK_HOME}/build/cmake/android.toolchain.cmake",
|
||||
"cacheVariables": {
|
||||
"ANDROID_PLATFORM": "android-26",
|
||||
"ANDROID_STL": "c++_shared",
|
||||
"PP_ENABLE_VR": "OFF",
|
||||
"PP_ENABLE_OPENGL": "ON"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "android-arm64",
|
||||
"inherits": "android-base",
|
||||
"displayName": "Android arm64-v8a",
|
||||
"cacheVariables": {
|
||||
"ANDROID_ABI": "arm64-v8a"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "android-x64",
|
||||
"inherits": "android-base",
|
||||
"displayName": "Android x86_64",
|
||||
"cacheVariables": {
|
||||
"ANDROID_ABI": "x86_64"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "android-quest-arm64",
|
||||
"inherits": "android-base",
|
||||
"displayName": "Android Quest arm64-v8a",
|
||||
"cacheVariables": {
|
||||
"ANDROID_ABI": "arm64-v8a",
|
||||
"PP_ENABLE_VR": "ON",
|
||||
"PP_ANDROID_FLAVOR": "quest"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "android-focus-arm64",
|
||||
"inherits": "android-base",
|
||||
"displayName": "Android Focus/Wave arm64-v8a",
|
||||
"cacheVariables": {
|
||||
"ANDROID_ABI": "arm64-v8a",
|
||||
"PP_ENABLE_VR": "ON",
|
||||
"PP_ANDROID_FLAVOR": "focus"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "emscripten",
|
||||
"inherits": "platform-headless-base",
|
||||
"displayName": "Emscripten WebGL",
|
||||
"generator": "Ninja",
|
||||
"cacheVariables": {
|
||||
"PP_ENABLE_VR": "OFF",
|
||||
"PP_ENABLE_VIDEO": "OFF"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "macos",
|
||||
"inherits": "platform-headless-base",
|
||||
"displayName": "macOS",
|
||||
"generator": "Ninja"
|
||||
},
|
||||
{
|
||||
"name": "ios-device",
|
||||
"inherits": "platform-headless-base",
|
||||
"displayName": "iOS device",
|
||||
"generator": "Xcode",
|
||||
"cacheVariables": {
|
||||
"CMAKE_SYSTEM_NAME": "iOS",
|
||||
"CMAKE_OSX_SYSROOT": "iphoneos"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "ios-simulator",
|
||||
"inherits": "platform-headless-base",
|
||||
"displayName": "iOS simulator",
|
||||
"generator": "Xcode",
|
||||
"cacheVariables": {
|
||||
"CMAKE_SYSTEM_NAME": "iOS",
|
||||
"CMAKE_OSX_SYSROOT": "iphonesimulator"
|
||||
}
|
||||
}
|
||||
],
|
||||
"buildPresets": [
|
||||
{
|
||||
"name": "windows-vs2026-x64",
|
||||
"configurePreset": "windows-vs2026-x64"
|
||||
},
|
||||
{
|
||||
"name": "windows-msvc-default",
|
||||
"configurePreset": "windows-msvc-default"
|
||||
},
|
||||
{
|
||||
"name": "windows-msvc-vcpkg-headless",
|
||||
"configurePreset": "windows-msvc-vcpkg-headless"
|
||||
},
|
||||
{
|
||||
"name": "windows-clangcl-asan",
|
||||
"configurePreset": "windows-clangcl-asan"
|
||||
},
|
||||
{
|
||||
"name": "linux-clang",
|
||||
"configurePreset": "linux-clang"
|
||||
},
|
||||
{
|
||||
"name": "android-arm64",
|
||||
"configurePreset": "android-arm64"
|
||||
},
|
||||
{
|
||||
"name": "android-x64",
|
||||
"configurePreset": "android-x64"
|
||||
},
|
||||
{
|
||||
"name": "android-quest-arm64",
|
||||
"configurePreset": "android-quest-arm64"
|
||||
},
|
||||
{
|
||||
"name": "android-focus-arm64",
|
||||
"configurePreset": "android-focus-arm64"
|
||||
},
|
||||
{
|
||||
"name": "emscripten",
|
||||
"configurePreset": "emscripten"
|
||||
},
|
||||
{
|
||||
"name": "macos",
|
||||
"configurePreset": "macos"
|
||||
},
|
||||
{
|
||||
"name": "ios-device",
|
||||
"configurePreset": "ios-device"
|
||||
},
|
||||
{
|
||||
"name": "ios-simulator",
|
||||
"configurePreset": "ios-simulator"
|
||||
}
|
||||
],
|
||||
"testPresets": [
|
||||
{
|
||||
"name": "desktop-fast",
|
||||
"configurePreset": "windows-msvc-default",
|
||||
"output": {
|
||||
"outputOnFailure": true
|
||||
},
|
||||
"filter": {
|
||||
"exclude": {
|
||||
"label": "gpu|slow|platform"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "desktop-fast-vs2026",
|
||||
"configurePreset": "windows-vs2026-x64",
|
||||
"output": {
|
||||
"outputOnFailure": true
|
||||
},
|
||||
"filter": {
|
||||
"exclude": {
|
||||
"label": "gpu|slow|platform"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "desktop-fast-vcpkg",
|
||||
"configurePreset": "windows-msvc-vcpkg-headless",
|
||||
"output": {
|
||||
"outputOnFailure": true
|
||||
},
|
||||
"filter": {
|
||||
"exclude": {
|
||||
"label": "gpu|slow|platform"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "desktop-gpu",
|
||||
"configurePreset": "windows-msvc-default",
|
||||
"output": {
|
||||
"outputOnFailure": true
|
||||
},
|
||||
"filter": {
|
||||
"include": {
|
||||
"label": "gpu"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "fuzz",
|
||||
"configurePreset": "windows-msvc-default",
|
||||
"output": {
|
||||
"outputOnFailure": true
|
||||
},
|
||||
"filter": {
|
||||
"include": {
|
||||
"label": "fuzz"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "stress",
|
||||
"configurePreset": "windows-msvc-default",
|
||||
"output": {
|
||||
"outputOnFailure": true
|
||||
},
|
||||
"filter": {
|
||||
"include": {
|
||||
"label": "stress"
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -1,57 +0,0 @@
|
||||
|
||||
Microsoft Visual Studio Solution File, Format Version 12.00
|
||||
# Visual Studio 15
|
||||
VisualStudioVersion = 15.0.28010.2026
|
||||
MinimumVisualStudioVersion = 10.0.40219.1
|
||||
Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "PanoPainter", "PanoPainter.vcxproj", "{6D5028CE-4D76-4B6A-A7C2-DE5A3268D433}"
|
||||
EndProject
|
||||
Project("{C7167F0D-BC9F-4E6E-AFE1-012C56B48DB5}") = "PanoPainterPackage", "PanoPainterPackage\PanoPainterPackage.wapproj", "{3A716FB6-DE62-439F-83B6-3C40915D6678}"
|
||||
EndProject
|
||||
Global
|
||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||
Debug|Any CPU = Debug|Any CPU
|
||||
Debug|x64 = Debug|x64
|
||||
Debug|x86 = Debug|x86
|
||||
Release|Any CPU = Release|Any CPU
|
||||
Release|x64 = Release|x64
|
||||
Release|x86 = Release|x86
|
||||
EndGlobalSection
|
||||
GlobalSection(ProjectConfigurationPlatforms) = postSolution
|
||||
{6D5028CE-4D76-4B6A-A7C2-DE5A3268D433}.Debug|Any CPU.ActiveCfg = Debug|Win32
|
||||
{6D5028CE-4D76-4B6A-A7C2-DE5A3268D433}.Debug|x64.ActiveCfg = Debug|x64
|
||||
{6D5028CE-4D76-4B6A-A7C2-DE5A3268D433}.Debug|x64.Build.0 = Debug|x64
|
||||
{6D5028CE-4D76-4B6A-A7C2-DE5A3268D433}.Debug|x64.Deploy.0 = Debug|x64
|
||||
{6D5028CE-4D76-4B6A-A7C2-DE5A3268D433}.Debug|x86.ActiveCfg = Debug|Win32
|
||||
{6D5028CE-4D76-4B6A-A7C2-DE5A3268D433}.Debug|x86.Build.0 = Debug|Win32
|
||||
{6D5028CE-4D76-4B6A-A7C2-DE5A3268D433}.Release|Any CPU.ActiveCfg = Release|Win32
|
||||
{6D5028CE-4D76-4B6A-A7C2-DE5A3268D433}.Release|x64.ActiveCfg = Release|x64
|
||||
{6D5028CE-4D76-4B6A-A7C2-DE5A3268D433}.Release|x64.Build.0 = Release|x64
|
||||
{6D5028CE-4D76-4B6A-A7C2-DE5A3268D433}.Release|x64.Deploy.0 = Release|x64
|
||||
{6D5028CE-4D76-4B6A-A7C2-DE5A3268D433}.Release|x86.ActiveCfg = Release|Win32
|
||||
{6D5028CE-4D76-4B6A-A7C2-DE5A3268D433}.Release|x86.Build.0 = Release|Win32
|
||||
{3A716FB6-DE62-439F-83B6-3C40915D6678}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{3A716FB6-DE62-439F-83B6-3C40915D6678}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{3A716FB6-DE62-439F-83B6-3C40915D6678}.Debug|Any CPU.Deploy.0 = Debug|Any CPU
|
||||
{3A716FB6-DE62-439F-83B6-3C40915D6678}.Debug|x64.ActiveCfg = Debug|x64
|
||||
{3A716FB6-DE62-439F-83B6-3C40915D6678}.Debug|x64.Build.0 = Debug|x64
|
||||
{3A716FB6-DE62-439F-83B6-3C40915D6678}.Debug|x64.Deploy.0 = Debug|x64
|
||||
{3A716FB6-DE62-439F-83B6-3C40915D6678}.Debug|x86.ActiveCfg = Debug|x86
|
||||
{3A716FB6-DE62-439F-83B6-3C40915D6678}.Debug|x86.Build.0 = Debug|x86
|
||||
{3A716FB6-DE62-439F-83B6-3C40915D6678}.Debug|x86.Deploy.0 = Debug|x86
|
||||
{3A716FB6-DE62-439F-83B6-3C40915D6678}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{3A716FB6-DE62-439F-83B6-3C40915D6678}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{3A716FB6-DE62-439F-83B6-3C40915D6678}.Release|Any CPU.Deploy.0 = Release|Any CPU
|
||||
{3A716FB6-DE62-439F-83B6-3C40915D6678}.Release|x64.ActiveCfg = Release|x64
|
||||
{3A716FB6-DE62-439F-83B6-3C40915D6678}.Release|x64.Build.0 = Release|x64
|
||||
{3A716FB6-DE62-439F-83B6-3C40915D6678}.Release|x64.Deploy.0 = Release|x64
|
||||
{3A716FB6-DE62-439F-83B6-3C40915D6678}.Release|x86.ActiveCfg = Release|x86
|
||||
{3A716FB6-DE62-439F-83B6-3C40915D6678}.Release|x86.Build.0 = Release|x86
|
||||
{3A716FB6-DE62-439F-83B6-3C40915D6678}.Release|x86.Deploy.0 = Release|x86
|
||||
EndGlobalSection
|
||||
GlobalSection(SolutionProperties) = preSolution
|
||||
HideSolutionNode = FALSE
|
||||
EndGlobalSection
|
||||
GlobalSection(ExtensibilityGlobals) = postSolution
|
||||
SolutionGuid = {3E8EFC4B-CEA1-4408-8628-7D2C0F6C43C8}
|
||||
EndGlobalSection
|
||||
EndGlobal
|
||||
@@ -1,634 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<Project DefaultTargets="Build" ToolsVersion="15.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
|
||||
<ItemGroup Label="ProjectConfigurations">
|
||||
<ProjectConfiguration Include="Debug|Win32">
|
||||
<Configuration>Debug</Configuration>
|
||||
<Platform>Win32</Platform>
|
||||
</ProjectConfiguration>
|
||||
<ProjectConfiguration Include="Release|Win32">
|
||||
<Configuration>Release</Configuration>
|
||||
<Platform>Win32</Platform>
|
||||
</ProjectConfiguration>
|
||||
<ProjectConfiguration Include="Debug|x64">
|
||||
<Configuration>Debug</Configuration>
|
||||
<Platform>x64</Platform>
|
||||
</ProjectConfiguration>
|
||||
<ProjectConfiguration Include="Release|x64">
|
||||
<Configuration>Release</Configuration>
|
||||
<Platform>x64</Platform>
|
||||
</ProjectConfiguration>
|
||||
</ItemGroup>
|
||||
<PropertyGroup Label="Globals">
|
||||
<ProjectGuid>{6D5028CE-4D76-4B6A-A7C2-DE5A3268D433}</ProjectGuid>
|
||||
<Keyword>Win32Proj</Keyword>
|
||||
<RootNamespace>PanoPainter</RootNamespace>
|
||||
<WindowsTargetPlatformVersion>8.1</WindowsTargetPlatformVersion>
|
||||
<ProjectName>PanoPainter</ProjectName>
|
||||
</PropertyGroup>
|
||||
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.Default.props" />
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'" Label="Configuration">
|
||||
<ConfigurationType>Application</ConfigurationType>
|
||||
<UseDebugLibraries>true</UseDebugLibraries>
|
||||
<PlatformToolset>v141</PlatformToolset>
|
||||
<CharacterSet>Unicode</CharacterSet>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'" Label="Configuration">
|
||||
<ConfigurationType>Application</ConfigurationType>
|
||||
<UseDebugLibraries>false</UseDebugLibraries>
|
||||
<PlatformToolset>v141</PlatformToolset>
|
||||
<WholeProgramOptimization>true</WholeProgramOptimization>
|
||||
<CharacterSet>Unicode</CharacterSet>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'" Label="Configuration">
|
||||
<ConfigurationType>Application</ConfigurationType>
|
||||
<UseDebugLibraries>true</UseDebugLibraries>
|
||||
<PlatformToolset>v142</PlatformToolset>
|
||||
<CharacterSet>Unicode</CharacterSet>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'" Label="Configuration">
|
||||
<ConfigurationType>Application</ConfigurationType>
|
||||
<UseDebugLibraries>false</UseDebugLibraries>
|
||||
<PlatformToolset>v142</PlatformToolset>
|
||||
<WholeProgramOptimization>true</WholeProgramOptimization>
|
||||
<CharacterSet>Unicode</CharacterSet>
|
||||
</PropertyGroup>
|
||||
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.props" />
|
||||
<ImportGroup Label="ExtensionSettings">
|
||||
</ImportGroup>
|
||||
<ImportGroup Label="Shared">
|
||||
</ImportGroup>
|
||||
<ImportGroup Label="PropertySheets" Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">
|
||||
<Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
|
||||
</ImportGroup>
|
||||
<ImportGroup Label="PropertySheets" Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">
|
||||
<Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
|
||||
</ImportGroup>
|
||||
<ImportGroup Label="PropertySheets" Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">
|
||||
<Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
|
||||
</ImportGroup>
|
||||
<ImportGroup Label="PropertySheets" Condition="'$(Configuration)|$(Platform)'=='Release|x64'">
|
||||
<Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
|
||||
</ImportGroup>
|
||||
<PropertyGroup Label="UserMacros" />
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">
|
||||
<LinkIncremental>true</LinkIncremental>
|
||||
<IncludePath>libs\glm;libs\glew-2.0.0\include;libs\stb;libs\tinyxml2;libs\yoga;libs\curl-win\include;libs\jpeg;libs\wacom;C:\Users\omar\Downloads\BugTrap-master\BugTrap-master\source\Client;$(IncludePath)</IncludePath>
|
||||
<LibraryPath>libs\curl-win\lib\dll-$(Configuration)-$(PlatformShortName);libs\glew-2.0.0\lib\Release\$(Platform);C:\Users\omar\Downloads\BugTrap-master\BugTrap-master\bin;$(LibraryPath)</LibraryPath>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">
|
||||
<LinkIncremental>true</LinkIncremental>
|
||||
<IncludePath>libs\glm;libs\glew-2.0.0\include;libs\stb;libs\tinyxml2;libs\yoga;libs\curl-win\include;libs\jpeg;libs\wacom;libs\bugtrap-client\include;libs\poly2tri\poly2tri;libs\base64;libs\sqlite3;libs\openvr\headers;libs\nanort;libs\hash-library;libs\fmt\include;libs\glad\include;libs\openh264\include;libs\mp4v2\include;libs\libyuv\include;C:\Program Files\RenderDoc;$(IncludePath)</IncludePath>
|
||||
<LibraryPath>libs\curl-win\lib\dll-$(Configuration)-$(PlatformShortName);libs\glew-2.0.0\lib\Release\$(Platform);libs\bugtrap-client\lib;libs\openvr\lib\win64;libs\openh264\lib;libs\mp4v2\lib\win;libs\libyuv\lib\win;$(LibraryPath)</LibraryPath>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">
|
||||
<LinkIncremental>false</LinkIncremental>
|
||||
<IncludePath>libs\glm;libs\glew-2.0.0\include;libs\stb;libs\tinyxml2;libs\yoga;libs\curl-win\include;libs\jpeg;libs\wacom;C:\Users\omar\Downloads\BugTrap-master\BugTrap-master\source\Client;$(IncludePath)</IncludePath>
|
||||
<LibraryPath>libs\curl-win\lib\dll-$(Configuration)-$(PlatformShortName);libs\glew-2.0.0\lib\Release\$(Platform);C:\Users\omar\Downloads\BugTrap-master\BugTrap-master\bin;$(LibraryPath)</LibraryPath>
|
||||
</PropertyGroup>
|
||||
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'">
|
||||
<LinkIncremental>false</LinkIncremental>
|
||||
<IncludePath>libs\glm;libs\glew-2.0.0\include;libs\stb;libs\tinyxml2;libs\yoga;libs\curl-win\include;libs\jpeg;libs\wacom;libs\bugtrap-client\include;libs\poly2tri\poly2tri;libs\base64;libs\sqlite3;libs\openvr\headers;libs\nanort;libs\hash-library;libs\fmt\include;libs\glad\include;libs\openh264\include;libs\mp4v2\include;libs\libyuv\include;C:\Program Files\RenderDoc;$(IncludePath)</IncludePath>
|
||||
<LibraryPath>libs\curl-win\lib\dll-$(Configuration)-$(PlatformShortName);libs\glew-2.0.0\lib\Release\$(Platform);libs\bugtrap-client\lib;libs\openvr\lib\win64;libs\openh264\lib;libs\mp4v2\lib\win;libs\libyuv\lib\win;$(LibraryPath)</LibraryPath>
|
||||
</PropertyGroup>
|
||||
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">
|
||||
<ClCompile>
|
||||
<PrecompiledHeader>Use</PrecompiledHeader>
|
||||
<WarningLevel>Level3</WarningLevel>
|
||||
<Optimization>Disabled</Optimization>
|
||||
<PreprocessorDefinitions>WIN32;_DEBUG;_CONSOLE;%(PreprocessorDefinitions)</PreprocessorDefinitions>
|
||||
<PrecompiledHeaderFile>pch.h</PrecompiledHeaderFile>
|
||||
</ClCompile>
|
||||
<Link>
|
||||
<SubSystem>Console</SubSystem>
|
||||
<GenerateDebugInformation>true</GenerateDebugInformation>
|
||||
</Link>
|
||||
<PreBuildEvent>
|
||||
<Command>
|
||||
</Command>
|
||||
</PreBuildEvent>
|
||||
</ItemDefinitionGroup>
|
||||
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">
|
||||
<ClCompile>
|
||||
<PrecompiledHeader>Use</PrecompiledHeader>
|
||||
<WarningLevel>Level3</WarningLevel>
|
||||
<Optimization>Disabled</Optimization>
|
||||
<PreprocessorDefinitions>ENUM_BITFIELDS_NOT_SUPPORTED;DEBUG;_DEBUG;_CONSOLE;%(PreprocessorDefinitions)</PreprocessorDefinitions>
|
||||
<PrecompiledHeaderFile>pch.h</PrecompiledHeaderFile>
|
||||
<ExceptionHandling>false</ExceptionHandling>
|
||||
</ClCompile>
|
||||
<Link>
|
||||
<SubSystem>Console</SubSystem>
|
||||
<GenerateDebugInformation>true</GenerateDebugInformation>
|
||||
</Link>
|
||||
<PreBuildEvent>
|
||||
<Command>python .\scripts\pre-build.py debug</Command>
|
||||
</PreBuildEvent>
|
||||
</ItemDefinitionGroup>
|
||||
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">
|
||||
<ClCompile>
|
||||
<WarningLevel>Level3</WarningLevel>
|
||||
<PrecompiledHeader>Use</PrecompiledHeader>
|
||||
<Optimization>MaxSpeed</Optimization>
|
||||
<FunctionLevelLinking>true</FunctionLevelLinking>
|
||||
<IntrinsicFunctions>true</IntrinsicFunctions>
|
||||
<PreprocessorDefinitions>WIN32;NDEBUG;_CONSOLE;%(PreprocessorDefinitions)</PreprocessorDefinitions>
|
||||
<PrecompiledHeaderFile>pch.h</PrecompiledHeaderFile>
|
||||
<RuntimeLibrary>MultiThreadedDLL</RuntimeLibrary>
|
||||
</ClCompile>
|
||||
<Link>
|
||||
<SubSystem>Console</SubSystem>
|
||||
<EnableCOMDATFolding>true</EnableCOMDATFolding>
|
||||
<OptimizeReferences>true</OptimizeReferences>
|
||||
<GenerateDebugInformation>true</GenerateDebugInformation>
|
||||
</Link>
|
||||
<PreBuildEvent>
|
||||
<Command>
|
||||
</Command>
|
||||
</PreBuildEvent>
|
||||
</ItemDefinitionGroup>
|
||||
<ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'">
|
||||
<ClCompile>
|
||||
<WarningLevel>Level3</WarningLevel>
|
||||
<PrecompiledHeader>Use</PrecompiledHeader>
|
||||
<Optimization>MaxSpeed</Optimization>
|
||||
<FunctionLevelLinking>true</FunctionLevelLinking>
|
||||
<IntrinsicFunctions>true</IntrinsicFunctions>
|
||||
<PreprocessorDefinitions>ENUM_BITFIELDS_NOT_SUPPORTED;NDEBUG;_CONSOLE;%(PreprocessorDefinitions)</PreprocessorDefinitions>
|
||||
<PrecompiledHeaderFile>pch.h</PrecompiledHeaderFile>
|
||||
<RuntimeLibrary>MultiThreadedDLL</RuntimeLibrary>
|
||||
<ExceptionHandling>false</ExceptionHandling>
|
||||
</ClCompile>
|
||||
<Link>
|
||||
<SubSystem>Windows</SubSystem>
|
||||
<EnableCOMDATFolding>true</EnableCOMDATFolding>
|
||||
<OptimizeReferences>true</OptimizeReferences>
|
||||
<GenerateDebugInformation>true</GenerateDebugInformation>
|
||||
</Link>
|
||||
<PreBuildEvent>
|
||||
<Command>python .\scripts\pre-build.py release</Command>
|
||||
</PreBuildEvent>
|
||||
</ItemDefinitionGroup>
|
||||
<ItemGroup>
|
||||
<ClCompile Include="libs\fmt\src\format.cc">
|
||||
<PrecompiledHeader Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">NotUsing</PrecompiledHeader>
|
||||
<PrecompiledHeader Condition="'$(Configuration)|$(Platform)'=='Release|x64'">NotUsing</PrecompiledHeader>
|
||||
</ClCompile>
|
||||
<ClCompile Include="libs\fmt\src\posix.cc">
|
||||
<PrecompiledHeader Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">NotUsing</PrecompiledHeader>
|
||||
<PrecompiledHeader Condition="'$(Configuration)|$(Platform)'=='Release|x64'">NotUsing</PrecompiledHeader>
|
||||
</ClCompile>
|
||||
<ClCompile Include="libs\glad\src\glad.c">
|
||||
<PrecompiledHeader Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">NotUsing</PrecompiledHeader>
|
||||
<PrecompiledHeader Condition="'$(Configuration)|$(Platform)'=='Release|x64'">NotUsing</PrecompiledHeader>
|
||||
</ClCompile>
|
||||
<ClCompile Include="libs\glad\src\glad_wgl.c">
|
||||
<PrecompiledHeader Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">NotUsing</PrecompiledHeader>
|
||||
<PrecompiledHeader Condition="'$(Configuration)|$(Platform)'=='Release|x64'">NotUsing</PrecompiledHeader>
|
||||
</ClCompile>
|
||||
<ClCompile Include="libs\hash-library\md5.cpp">
|
||||
<PrecompiledHeader Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">NotUsing</PrecompiledHeader>
|
||||
<PrecompiledHeader Condition="'$(Configuration)|$(Platform)'=='Release|x64'">NotUsing</PrecompiledHeader>
|
||||
</ClCompile>
|
||||
<ClCompile Include="libs\nanort\nanort.cc">
|
||||
<PrecompiledHeader Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">NotUsing</PrecompiledHeader>
|
||||
<PrecompiledHeader Condition="'$(Configuration)|$(Platform)'=='Release|x64'">NotUsing</PrecompiledHeader>
|
||||
</ClCompile>
|
||||
<ClCompile Include="libs\sqlite3\sqlite3.c">
|
||||
<PrecompiledHeader Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">NotUsing</PrecompiledHeader>
|
||||
<PrecompiledHeader Condition="'$(Configuration)|$(Platform)'=='Release|x64'">NotUsing</PrecompiledHeader>
|
||||
</ClCompile>
|
||||
<ClCompile Include="libs\yoga\yoga\event\event.cpp">
|
||||
<PrecompiledHeader Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">NotUsing</PrecompiledHeader>
|
||||
<PrecompiledHeader Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">NotUsing</PrecompiledHeader>
|
||||
<PrecompiledHeader Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">NotUsing</PrecompiledHeader>
|
||||
<PrecompiledHeader Condition="'$(Configuration)|$(Platform)'=='Release|x64'">NotUsing</PrecompiledHeader>
|
||||
<AssemblerListingLocation Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">$(IntDir)yoga\</AssemblerListingLocation>
|
||||
<ObjectFileName Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">$(IntDir)yoga\</ObjectFileName>
|
||||
<XMLDocumentationFileName Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">$(IntDir)yoga\</XMLDocumentationFileName>
|
||||
<AssemblerListingLocation Condition="'$(Configuration)|$(Platform)'=='Release|x64'">$(IntDir)yoga\</AssemblerListingLocation>
|
||||
<ObjectFileName Condition="'$(Configuration)|$(Platform)'=='Release|x64'">$(IntDir)yoga\</ObjectFileName>
|
||||
<XMLDocumentationFileName Condition="'$(Configuration)|$(Platform)'=='Release|x64'">$(IntDir)yoga\</XMLDocumentationFileName>
|
||||
</ClCompile>
|
||||
<ClCompile Include="libs\yoga\yoga\internal\experiments.cpp">
|
||||
<PrecompiledHeader Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">NotUsing</PrecompiledHeader>
|
||||
<PrecompiledHeader Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">NotUsing</PrecompiledHeader>
|
||||
<PrecompiledHeader Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">NotUsing</PrecompiledHeader>
|
||||
<PrecompiledHeader Condition="'$(Configuration)|$(Platform)'=='Release|x64'">NotUsing</PrecompiledHeader>
|
||||
<AssemblerListingLocation Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">$(IntDir)yoga\</AssemblerListingLocation>
|
||||
<ObjectFileName Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">$(IntDir)yoga\</ObjectFileName>
|
||||
<XMLDocumentationFileName Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">$(IntDir)yoga\</XMLDocumentationFileName>
|
||||
<AssemblerListingLocation Condition="'$(Configuration)|$(Platform)'=='Release|x64'">$(IntDir)yoga\</AssemblerListingLocation>
|
||||
<ObjectFileName Condition="'$(Configuration)|$(Platform)'=='Release|x64'">$(IntDir)yoga\</ObjectFileName>
|
||||
<XMLDocumentationFileName Condition="'$(Configuration)|$(Platform)'=='Release|x64'">$(IntDir)yoga\</XMLDocumentationFileName>
|
||||
</ClCompile>
|
||||
<ClCompile Include="libs\yoga\yoga\log.cpp">
|
||||
<PrecompiledHeader Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">NotUsing</PrecompiledHeader>
|
||||
<PrecompiledHeader Condition="'$(Configuration)|$(Platform)'=='Release|x64'">NotUsing</PrecompiledHeader>
|
||||
<AssemblerListingLocation Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">$(IntDir)yoga\</AssemblerListingLocation>
|
||||
<ObjectFileName Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">$(IntDir)yoga\</ObjectFileName>
|
||||
<XMLDocumentationFileName Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">$(IntDir)yoga\</XMLDocumentationFileName>
|
||||
<AssemblerListingLocation Condition="'$(Configuration)|$(Platform)'=='Release|x64'">$(IntDir)yoga\</AssemblerListingLocation>
|
||||
<ObjectFileName Condition="'$(Configuration)|$(Platform)'=='Release|x64'">$(IntDir)yoga\</ObjectFileName>
|
||||
<XMLDocumentationFileName Condition="'$(Configuration)|$(Platform)'=='Release|x64'">$(IntDir)yoga\</XMLDocumentationFileName>
|
||||
</ClCompile>
|
||||
<ClCompile Include="libs\yoga\yoga\Utils.cpp">
|
||||
<PrecompiledHeader Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">NotUsing</PrecompiledHeader>
|
||||
<PrecompiledHeader Condition="'$(Configuration)|$(Platform)'=='Release|x64'">NotUsing</PrecompiledHeader>
|
||||
<AssemblerListingLocation Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">$(IntDir)yoga\</AssemblerListingLocation>
|
||||
<ObjectFileName Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">$(IntDir)yoga\</ObjectFileName>
|
||||
<XMLDocumentationFileName Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">$(IntDir)yoga\</XMLDocumentationFileName>
|
||||
<AssemblerListingLocation Condition="'$(Configuration)|$(Platform)'=='Release|x64'">$(IntDir)yoga\</AssemblerListingLocation>
|
||||
<ObjectFileName Condition="'$(Configuration)|$(Platform)'=='Release|x64'">$(IntDir)yoga\</ObjectFileName>
|
||||
<XMLDocumentationFileName Condition="'$(Configuration)|$(Platform)'=='Release|x64'">$(IntDir)yoga\</XMLDocumentationFileName>
|
||||
</ClCompile>
|
||||
<ClCompile Include="libs\yoga\yoga\YGConfig.cpp">
|
||||
<PrecompiledHeader Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">NotUsing</PrecompiledHeader>
|
||||
<PrecompiledHeader Condition="'$(Configuration)|$(Platform)'=='Release|x64'">NotUsing</PrecompiledHeader>
|
||||
<AssemblerListingLocation Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">$(IntDir)yoga\</AssemblerListingLocation>
|
||||
<ObjectFileName Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">$(IntDir)yoga\</ObjectFileName>
|
||||
<XMLDocumentationFileName Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">$(IntDir)yoga\</XMLDocumentationFileName>
|
||||
<AssemblerListingLocation Condition="'$(Configuration)|$(Platform)'=='Release|x64'">$(IntDir)yoga\</AssemblerListingLocation>
|
||||
<ObjectFileName Condition="'$(Configuration)|$(Platform)'=='Release|x64'">$(IntDir)yoga\</ObjectFileName>
|
||||
<XMLDocumentationFileName Condition="'$(Configuration)|$(Platform)'=='Release|x64'">$(IntDir)yoga\</XMLDocumentationFileName>
|
||||
</ClCompile>
|
||||
<ClCompile Include="libs\yoga\yoga\YGEnums.cpp">
|
||||
<PrecompiledHeader Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">NotUsing</PrecompiledHeader>
|
||||
<PrecompiledHeader Condition="'$(Configuration)|$(Platform)'=='Release|x64'">NotUsing</PrecompiledHeader>
|
||||
<AssemblerListingLocation Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">$(IntDir)yoga\</AssemblerListingLocation>
|
||||
<ObjectFileName Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">$(IntDir)yoga\</ObjectFileName>
|
||||
<XMLDocumentationFileName Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">$(IntDir)yoga\</XMLDocumentationFileName>
|
||||
<AssemblerListingLocation Condition="'$(Configuration)|$(Platform)'=='Release|x64'">$(IntDir)yoga\</AssemblerListingLocation>
|
||||
<ObjectFileName Condition="'$(Configuration)|$(Platform)'=='Release|x64'">$(IntDir)yoga\</ObjectFileName>
|
||||
<XMLDocumentationFileName Condition="'$(Configuration)|$(Platform)'=='Release|x64'">$(IntDir)yoga\</XMLDocumentationFileName>
|
||||
</ClCompile>
|
||||
<ClCompile Include="libs\yoga\yoga\YGLayout.cpp">
|
||||
<PrecompiledHeader Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">NotUsing</PrecompiledHeader>
|
||||
<PrecompiledHeader Condition="'$(Configuration)|$(Platform)'=='Release|x64'">NotUsing</PrecompiledHeader>
|
||||
<AssemblerListingLocation Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">$(IntDir)yoga\</AssemblerListingLocation>
|
||||
<ObjectFileName Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">$(IntDir)yoga\</ObjectFileName>
|
||||
<XMLDocumentationFileName Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">$(IntDir)yoga\</XMLDocumentationFileName>
|
||||
<AssemblerListingLocation Condition="'$(Configuration)|$(Platform)'=='Release|x64'">$(IntDir)yoga\</AssemblerListingLocation>
|
||||
<ObjectFileName Condition="'$(Configuration)|$(Platform)'=='Release|x64'">$(IntDir)yoga\</ObjectFileName>
|
||||
<XMLDocumentationFileName Condition="'$(Configuration)|$(Platform)'=='Release|x64'">$(IntDir)yoga\</XMLDocumentationFileName>
|
||||
</ClCompile>
|
||||
<ClCompile Include="libs\yoga\yoga\YGNode.cpp">
|
||||
<PrecompiledHeader Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">NotUsing</PrecompiledHeader>
|
||||
<PrecompiledHeader Condition="'$(Configuration)|$(Platform)'=='Release|x64'">NotUsing</PrecompiledHeader>
|
||||
<AssemblerListingLocation Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">$(IntDir)yoga\</AssemblerListingLocation>
|
||||
<ObjectFileName Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">$(IntDir)yoga\</ObjectFileName>
|
||||
<XMLDocumentationFileName Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">$(IntDir)yoga\</XMLDocumentationFileName>
|
||||
<AssemblerListingLocation Condition="'$(Configuration)|$(Platform)'=='Release|x64'">$(IntDir)yoga\</AssemblerListingLocation>
|
||||
<ObjectFileName Condition="'$(Configuration)|$(Platform)'=='Release|x64'">$(IntDir)yoga\</ObjectFileName>
|
||||
<XMLDocumentationFileName Condition="'$(Configuration)|$(Platform)'=='Release|x64'">$(IntDir)yoga\</XMLDocumentationFileName>
|
||||
</ClCompile>
|
||||
<ClCompile Include="libs\yoga\yoga\YGNodePrint.cpp">
|
||||
<PrecompiledHeader Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">NotUsing</PrecompiledHeader>
|
||||
<PrecompiledHeader Condition="'$(Configuration)|$(Platform)'=='Release|x64'">NotUsing</PrecompiledHeader>
|
||||
<AssemblerListingLocation Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">$(IntDir)yoga\</AssemblerListingLocation>
|
||||
<ObjectFileName Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">$(IntDir)yoga\</ObjectFileName>
|
||||
<XMLDocumentationFileName Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">$(IntDir)yoga\</XMLDocumentationFileName>
|
||||
<AssemblerListingLocation Condition="'$(Configuration)|$(Platform)'=='Release|x64'">$(IntDir)yoga\</AssemblerListingLocation>
|
||||
<ObjectFileName Condition="'$(Configuration)|$(Platform)'=='Release|x64'">$(IntDir)yoga\</ObjectFileName>
|
||||
<XMLDocumentationFileName Condition="'$(Configuration)|$(Platform)'=='Release|x64'">$(IntDir)yoga\</XMLDocumentationFileName>
|
||||
</ClCompile>
|
||||
<ClCompile Include="libs\yoga\yoga\YGStyle.cpp">
|
||||
<PrecompiledHeader Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">NotUsing</PrecompiledHeader>
|
||||
<PrecompiledHeader Condition="'$(Configuration)|$(Platform)'=='Release|x64'">NotUsing</PrecompiledHeader>
|
||||
<AssemblerListingLocation Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">$(IntDir)yoga\</AssemblerListingLocation>
|
||||
<ObjectFileName Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">$(IntDir)yoga\</ObjectFileName>
|
||||
<XMLDocumentationFileName Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">$(IntDir)yoga\</XMLDocumentationFileName>
|
||||
<AssemblerListingLocation Condition="'$(Configuration)|$(Platform)'=='Release|x64'">$(IntDir)yoga\</AssemblerListingLocation>
|
||||
<ObjectFileName Condition="'$(Configuration)|$(Platform)'=='Release|x64'">$(IntDir)yoga\</ObjectFileName>
|
||||
<XMLDocumentationFileName Condition="'$(Configuration)|$(Platform)'=='Release|x64'">$(IntDir)yoga\</XMLDocumentationFileName>
|
||||
</ClCompile>
|
||||
<ClCompile Include="libs\yoga\yoga\YGValue.cpp">
|
||||
<PrecompiledHeader Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">NotUsing</PrecompiledHeader>
|
||||
<PrecompiledHeader Condition="'$(Configuration)|$(Platform)'=='Release|x64'">NotUsing</PrecompiledHeader>
|
||||
<AssemblerListingLocation Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">$(IntDir)yoga\</AssemblerListingLocation>
|
||||
<ObjectFileName Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">$(IntDir)yoga\</ObjectFileName>
|
||||
<XMLDocumentationFileName Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">$(IntDir)yoga\</XMLDocumentationFileName>
|
||||
<AssemblerListingLocation Condition="'$(Configuration)|$(Platform)'=='Release|x64'">$(IntDir)yoga\</AssemblerListingLocation>
|
||||
<ObjectFileName Condition="'$(Configuration)|$(Platform)'=='Release|x64'">$(IntDir)yoga\</ObjectFileName>
|
||||
<XMLDocumentationFileName Condition="'$(Configuration)|$(Platform)'=='Release|x64'">$(IntDir)yoga\</XMLDocumentationFileName>
|
||||
</ClCompile>
|
||||
<ClCompile Include="libs\yoga\yoga\Yoga.cpp">
|
||||
<PrecompiledHeader Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">NotUsing</PrecompiledHeader>
|
||||
<PrecompiledHeader Condition="'$(Configuration)|$(Platform)'=='Release|x64'">NotUsing</PrecompiledHeader>
|
||||
<AssemblerListingLocation Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">$(IntDir)yoga\</AssemblerListingLocation>
|
||||
<ObjectFileName Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">$(IntDir)yoga\</ObjectFileName>
|
||||
<XMLDocumentationFileName Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">$(IntDir)yoga\</XMLDocumentationFileName>
|
||||
<AssemblerListingLocation Condition="'$(Configuration)|$(Platform)'=='Release|x64'">$(IntDir)yoga\</AssemblerListingLocation>
|
||||
<ObjectFileName Condition="'$(Configuration)|$(Platform)'=='Release|x64'">$(IntDir)yoga\</ObjectFileName>
|
||||
<XMLDocumentationFileName Condition="'$(Configuration)|$(Platform)'=='Release|x64'">$(IntDir)yoga\</XMLDocumentationFileName>
|
||||
</ClCompile>
|
||||
<ClCompile Include="src\abr.cpp" />
|
||||
<ClCompile Include="src\action.cpp" />
|
||||
<ClCompile Include="src\app.cpp" />
|
||||
<ClCompile Include="src\app_cloud.cpp" />
|
||||
<ClCompile Include="src\app_commands.cpp" />
|
||||
<ClCompile Include="src\app_dialogs.cpp" />
|
||||
<ClCompile Include="src\app_events.cpp" />
|
||||
<ClCompile Include="src\app_layout.cpp" />
|
||||
<ClCompile Include="src\app_shaders.cpp" />
|
||||
<ClCompile Include="src\app_vr.cpp" />
|
||||
<ClCompile Include="src\asset.cpp" />
|
||||
<ClCompile Include="src\bezier.cpp" />
|
||||
<ClCompile Include="src\binary_stream.cpp" />
|
||||
<ClCompile Include="src\brush.cpp" />
|
||||
<ClCompile Include="src\canvas.cpp" />
|
||||
<ClCompile Include="src\canvas_actions.cpp" />
|
||||
<ClCompile Include="src\canvas_layer.cpp" />
|
||||
<ClCompile Include="src\canvas_modes.cpp" />
|
||||
<ClCompile Include="src\event.cpp" />
|
||||
<ClCompile Include="src\font.cpp" />
|
||||
<ClCompile Include="src\hmd.cpp" />
|
||||
<ClCompile Include="src\image.cpp" />
|
||||
<ClCompile Include="src\layout.cpp" />
|
||||
<ClCompile Include="src\log.cpp" />
|
||||
<ClCompile Include="src\main.cpp" />
|
||||
<ClCompile Include="src\mp4enc.cpp" />
|
||||
<ClCompile Include="src\node.cpp" />
|
||||
<ClCompile Include="src\node_about.cpp" />
|
||||
<ClCompile Include="src\node_border.cpp" />
|
||||
<ClCompile Include="src\node_button.cpp" />
|
||||
<ClCompile Include="src\node_button_custom.cpp" />
|
||||
<ClCompile Include="src\node_canvas.cpp" />
|
||||
<ClCompile Include="src\node_changelog.cpp" />
|
||||
<ClCompile Include="src\node_checkbox.cpp" />
|
||||
<ClCompile Include="src\node_colorwheel.cpp" />
|
||||
<ClCompile Include="src\node_color_quad.cpp" />
|
||||
<ClCompile Include="src\node_combobox.cpp" />
|
||||
<ClCompile Include="src\node_dialog_browse.cpp" />
|
||||
<ClCompile Include="src\node_dialog_cloud.cpp" />
|
||||
<ClCompile Include="src\node_dialog_export_ppbr.cpp" />
|
||||
<ClCompile Include="src\node_dialog_layer_rename.cpp" />
|
||||
<ClCompile Include="src\node_dialog_open.cpp" />
|
||||
<ClCompile Include="src\node_dialog_picker.cpp" />
|
||||
<ClCompile Include="src\node_dialog_resize.cpp" />
|
||||
<ClCompile Include="src\node_icon.cpp" />
|
||||
<ClCompile Include="src\node_image.cpp" />
|
||||
<ClCompile Include="src\node_image_texture.cpp" />
|
||||
<ClCompile Include="src\node_input_box.cpp" />
|
||||
<ClCompile Include="src\node_message_box.cpp" />
|
||||
<ClCompile Include="src\node_metadata.cpp" />
|
||||
<ClCompile Include="src\node_panel_brush.cpp" />
|
||||
<ClCompile Include="src\node_panel_color.cpp" />
|
||||
<ClCompile Include="src\node_panel_floating.cpp" />
|
||||
<ClCompile Include="src\node_panel_grid.cpp" />
|
||||
<ClCompile Include="src\node_panel_layer.cpp" />
|
||||
<ClCompile Include="src\node_panel_quick.cpp" />
|
||||
<ClCompile Include="src\node_panel_stroke.cpp" />
|
||||
<ClCompile Include="src\node_panel_animation.cpp" />
|
||||
<ClCompile Include="src\node_popup_menu.cpp" />
|
||||
<ClCompile Include="src\node_progress_bar.cpp" />
|
||||
<ClCompile Include="src\node_remote_page.cpp" />
|
||||
<ClCompile Include="src\node_scroll.cpp" />
|
||||
<ClCompile Include="src\node_settings.cpp" />
|
||||
<ClCompile Include="src\node_shorcuts.cpp" />
|
||||
<ClCompile Include="src\node_slider.cpp" />
|
||||
<ClCompile Include="src\node_stroke_preview.cpp" />
|
||||
<ClCompile Include="src\node_text.cpp" />
|
||||
<ClCompile Include="src\node_text_input.cpp" />
|
||||
<ClCompile Include="src\node_tool_bucket.cpp" />
|
||||
<ClCompile Include="src\node_usermanual.cpp" />
|
||||
<ClCompile Include="src\node_viewport.cpp" />
|
||||
<ClCompile Include="src\pch.cpp">
|
||||
<PrecompiledHeader Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">Create</PrecompiledHeader>
|
||||
<PrecompiledHeader Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">Create</PrecompiledHeader>
|
||||
<PrecompiledHeader Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">Create</PrecompiledHeader>
|
||||
<PrecompiledHeader Condition="'$(Configuration)|$(Platform)'=='Release|x64'">Create</PrecompiledHeader>
|
||||
</ClCompile>
|
||||
<ClCompile Include="src\rtt.cpp" />
|
||||
<ClCompile Include="src\serializer.cpp" />
|
||||
<ClCompile Include="src\settings.cpp" />
|
||||
<ClCompile Include="src\shader.cpp" />
|
||||
<ClCompile Include="src\shape.cpp" />
|
||||
<ClCompile Include="src\texture.cpp" />
|
||||
<ClCompile Include="src\util.cpp" />
|
||||
<ClCompile Include="src\version.cpp">
|
||||
<PrecompiledHeader Condition="'$(Configuration)|$(Platform)'=='Release|x64'">NotUsing</PrecompiledHeader>
|
||||
<PrecompiledHeader Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">NotUsing</PrecompiledHeader>
|
||||
</ClCompile>
|
||||
<ClCompile Include="src\wacom.cpp" />
|
||||
<ClCompile Include="libs\jpeg\jpgd.cpp">
|
||||
<PrecompiledHeader Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">NotUsing</PrecompiledHeader>
|
||||
<PrecompiledHeader Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">NotUsing</PrecompiledHeader>
|
||||
<PrecompiledHeader Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">NotUsing</PrecompiledHeader>
|
||||
<PrecompiledHeader Condition="'$(Configuration)|$(Platform)'=='Release|x64'">NotUsing</PrecompiledHeader>
|
||||
</ClCompile>
|
||||
<ClCompile Include="libs\jpeg\jpge.cpp">
|
||||
<PrecompiledHeader Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">NotUsing</PrecompiledHeader>
|
||||
<PrecompiledHeader Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">NotUsing</PrecompiledHeader>
|
||||
<PrecompiledHeader Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">NotUsing</PrecompiledHeader>
|
||||
<PrecompiledHeader Condition="'$(Configuration)|$(Platform)'=='Release|x64'">NotUsing</PrecompiledHeader>
|
||||
</ClCompile>
|
||||
<ClCompile Include="libs\poly2tri\poly2tri\common\shapes.cc">
|
||||
<PrecompiledHeader Condition="'$(Configuration)|$(Platform)'=='Release|x64'">NotUsing</PrecompiledHeader>
|
||||
<PrecompiledHeader Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">NotUsing</PrecompiledHeader>
|
||||
</ClCompile>
|
||||
<ClCompile Include="libs\poly2tri\poly2tri\sweep\advancing_front.cc">
|
||||
<PrecompiledHeader Condition="'$(Configuration)|$(Platform)'=='Release|x64'">NotUsing</PrecompiledHeader>
|
||||
<PrecompiledHeader Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">NotUsing</PrecompiledHeader>
|
||||
</ClCompile>
|
||||
<ClCompile Include="libs\poly2tri\poly2tri\sweep\cdt.cc">
|
||||
<PrecompiledHeader Condition="'$(Configuration)|$(Platform)'=='Release|x64'">NotUsing</PrecompiledHeader>
|
||||
<PrecompiledHeader Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">NotUsing</PrecompiledHeader>
|
||||
</ClCompile>
|
||||
<ClCompile Include="libs\poly2tri\poly2tri\sweep\sweep.cc">
|
||||
<PrecompiledHeader Condition="'$(Configuration)|$(Platform)'=='Release|x64'">NotUsing</PrecompiledHeader>
|
||||
<PrecompiledHeader Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">NotUsing</PrecompiledHeader>
|
||||
</ClCompile>
|
||||
<ClCompile Include="libs\poly2tri\poly2tri\sweep\sweep_context.cc">
|
||||
<PrecompiledHeader Condition="'$(Configuration)|$(Platform)'=='Release|x64'">NotUsing</PrecompiledHeader>
|
||||
<PrecompiledHeader Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">NotUsing</PrecompiledHeader>
|
||||
</ClCompile>
|
||||
<ClCompile Include="libs\tinyxml2\tinyxml2.cpp">
|
||||
<PrecompiledHeader Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">NotUsing</PrecompiledHeader>
|
||||
<PrecompiledHeader Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">NotUsing</PrecompiledHeader>
|
||||
<PrecompiledHeader Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">NotUsing</PrecompiledHeader>
|
||||
<PrecompiledHeader Condition="'$(Configuration)|$(Platform)'=='Release|x64'">NotUsing</PrecompiledHeader>
|
||||
</ClCompile>
|
||||
<ClCompile Include="libs\wacom\WinTab\Utils.cpp">
|
||||
<PrecompiledHeader Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">Use</PrecompiledHeader>
|
||||
<PrecompiledHeader Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">Use</PrecompiledHeader>
|
||||
<PrecompiledHeader Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">Use</PrecompiledHeader>
|
||||
<PrecompiledHeader Condition="'$(Configuration)|$(Platform)'=='Release|x64'">Use</PrecompiledHeader>
|
||||
</ClCompile>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ClInclude Include="libs\hash-library\md5.h" />
|
||||
<ClInclude Include="libs\nanort\nanort.h" />
|
||||
<ClInclude Include="libs\sqlite3\sqlite3.h" />
|
||||
<ClInclude Include="libs\sqlite3\sqlite3ext.h" />
|
||||
<ClInclude Include="resource.h" />
|
||||
<ClInclude Include="src\abr.h" />
|
||||
<ClInclude Include="src\action.h" />
|
||||
<ClInclude Include="src\app.h" />
|
||||
<ClInclude Include="src\asset.h" />
|
||||
<ClInclude Include="src\bezier.h" />
|
||||
<ClInclude Include="src\binary_stream.h" />
|
||||
<ClInclude Include="src\brush.h" />
|
||||
<ClInclude Include="src\canvas.h" />
|
||||
<ClInclude Include="src\canvas_actions.h" />
|
||||
<ClInclude Include="src\canvas_layer.h" />
|
||||
<ClInclude Include="src\canvas_modes.h" />
|
||||
<ClInclude Include="src\event.h" />
|
||||
<ClInclude Include="src\font.h" />
|
||||
<ClInclude Include="src\hmd.h" />
|
||||
<ClInclude Include="src\image.h" />
|
||||
<ClInclude Include="src\keymap.h" />
|
||||
<ClInclude Include="src\layout.h" />
|
||||
<ClInclude Include="src\log.h" />
|
||||
<ClInclude Include="src\mp4enc.h" />
|
||||
<ClInclude Include="src\node.h" />
|
||||
<ClInclude Include="src\node_about.h" />
|
||||
<ClInclude Include="src\node_border.h" />
|
||||
<ClInclude Include="src\node_button.h" />
|
||||
<ClInclude Include="src\node_button_custom.h" />
|
||||
<ClInclude Include="src\node_canvas.h" />
|
||||
<ClInclude Include="src\node_changelog.h" />
|
||||
<ClInclude Include="src\node_checkbox.h" />
|
||||
<ClInclude Include="src\node_colorwheel.h" />
|
||||
<ClInclude Include="src\node_color_quad.h" />
|
||||
<ClInclude Include="src\node_combobox.h" />
|
||||
<ClInclude Include="src\node_dialog_browse.h" />
|
||||
<ClInclude Include="src\node_dialog_cloud.h" />
|
||||
<ClInclude Include="src\node_dialog_export_ppbr.h" />
|
||||
<ClInclude Include="src\node_dialog_layer_rename.h" />
|
||||
<ClInclude Include="src\node_dialog_open.h" />
|
||||
<ClInclude Include="src\node_dialog_picker.h" />
|
||||
<ClInclude Include="src\node_dialog_resize.h" />
|
||||
<ClInclude Include="src\node_icon.h" />
|
||||
<ClInclude Include="src\node_image.h" />
|
||||
<ClInclude Include="src\node_image_texture.h" />
|
||||
<ClInclude Include="src\node_input_box.h" />
|
||||
<ClInclude Include="src\node_message_box.h" />
|
||||
<ClInclude Include="src\node_metadata.h" />
|
||||
<ClInclude Include="src\node_panel_brush.h" />
|
||||
<ClInclude Include="src\node_panel_color.h" />
|
||||
<ClInclude Include="src\node_panel_floating.h" />
|
||||
<ClInclude Include="src\node_panel_grid.h" />
|
||||
<ClInclude Include="src\node_panel_layer.h" />
|
||||
<ClInclude Include="src\node_panel_quick.h" />
|
||||
<ClInclude Include="src\node_panel_stroke.h" />
|
||||
<ClInclude Include="src\node_panel_animation.h" />
|
||||
<ClInclude Include="src\node_popup_menu.h" />
|
||||
<ClInclude Include="src\node_progress_bar.h" />
|
||||
<ClInclude Include="src\node_remote_page.h" />
|
||||
<ClInclude Include="src\node_scroll.h" />
|
||||
<ClInclude Include="src\node_settings.h" />
|
||||
<ClInclude Include="src\node_shorcuts.h" />
|
||||
<ClInclude Include="src\node_slider.h" />
|
||||
<ClInclude Include="src\node_stroke_preview.h" />
|
||||
<ClInclude Include="src\node_text.h" />
|
||||
<ClInclude Include="src\node_text_input.h" />
|
||||
<ClInclude Include="src\node_tool_bucket.h" />
|
||||
<ClInclude Include="src\node_usermanual.h" />
|
||||
<ClInclude Include="src\node_viewport.h" />
|
||||
<ClInclude Include="src\pch.h" />
|
||||
<ClInclude Include="src\rtt.h" />
|
||||
<ClInclude Include="src\serializer.h" />
|
||||
<ClInclude Include="src\settings.h" />
|
||||
<ClInclude Include="src\shader.h" />
|
||||
<ClInclude Include="src\shape.h" />
|
||||
<ClInclude Include="src\texture.h" />
|
||||
<ClInclude Include="src\util.h" />
|
||||
<ClInclude Include="src\version.gen.h" />
|
||||
<ClInclude Include="src\version.h" />
|
||||
<ClInclude Include="src\wacom.h" />
|
||||
<ClInclude Include="libs\jpeg\jpgd.h" />
|
||||
<ClInclude Include="libs\jpeg\jpge.h" />
|
||||
<ClInclude Include="libs\tinyxml2\tinyxml2.h" />
|
||||
<ClInclude Include="libs\wacom\WinTab\MSGPACK.H" />
|
||||
<ClInclude Include="libs\wacom\WinTab\PKTDEF.H" />
|
||||
<ClInclude Include="libs\wacom\WinTab\Utils.h" />
|
||||
<ClInclude Include="libs\wacom\WinTab\WINTAB.H" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ResourceCompile Include="PanoPainter.rc" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Natvis Include="libs\glm\util\glm.natvis" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Xml Include="data\dialogs\about.xml" />
|
||||
<Xml Include="data\dialogs\brush-export.xml" />
|
||||
<Xml Include="data\dialogs\changelog.xml" />
|
||||
<Xml Include="data\dialogs\cloud-browse.xml" />
|
||||
<Xml Include="data\dialogs\color-picker.xml" />
|
||||
<Xml Include="data\dialogs\doc-browse.xml" />
|
||||
<Xml Include="data\dialogs\doc-new.xml" />
|
||||
<Xml Include="data\dialogs\doc-open.xml" />
|
||||
<Xml Include="data\dialogs\doc-resize.xml" />
|
||||
<Xml Include="data\dialogs\doc-save.xml" />
|
||||
<Xml Include="data\dialogs\input-box.xml" />
|
||||
<Xml Include="data\dialogs\layer-rename.xml" />
|
||||
<Xml Include="data\dialogs\message-box.xml" />
|
||||
<Xml Include="data\dialogs\panel-animation.xml" />
|
||||
<Xml Include="data\dialogs\panel-floating.xml" />
|
||||
<Xml Include="data\dialogs\panel-grid.xml" />
|
||||
<Xml Include="data\dialogs\panel-layers.xml" />
|
||||
<Xml Include="data\dialogs\panel-brushes.xml" />
|
||||
<Xml Include="data\dialogs\panel-presets.xml" />
|
||||
<Xml Include="data\dialogs\panel-quick.xml" />
|
||||
<Xml Include="data\dialogs\panel-stroke.xml" />
|
||||
<Xml Include="data\dialogs\progress-bar.xml" />
|
||||
<Xml Include="data\dialogs\remote-page.xml" />
|
||||
<Xml Include="data\dialogs\settings.xml" />
|
||||
<Xml Include="data\dialogs\shortcuts.xml" />
|
||||
<Xml Include="data\dialogs\usermanual.xml" />
|
||||
<Xml Include="data\layout.xml">
|
||||
<SubType>Designer</SubType>
|
||||
<DeploymentContent Condition="'$(Configuration)|$(Platform)'=='Release|x64'">
|
||||
</DeploymentContent>
|
||||
</Xml>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<None Include="data\shaders\atlas.glsl" />
|
||||
<None Include="data\shaders\bake-uv.glsl" />
|
||||
<None Include="data\shaders\checkerboard.glsl" />
|
||||
<None Include="data\shaders\color-hue.glsl" />
|
||||
<None Include="data\shaders\color-quad.glsl" />
|
||||
<None Include="data\shaders\color-tri.glsl" />
|
||||
<None Include="data\shaders\color.glsl" />
|
||||
<None Include="data\shaders\comp-draw.glsl" />
|
||||
<None Include="data\shaders\comp-erase.glsl" />
|
||||
<None Include="data\shaders\equirect.glsl" />
|
||||
<None Include="data\shaders\font.glsl" />
|
||||
<None Include="data\shaders\include\blend-stroke.glsl" />
|
||||
<None Include="data\shaders\include\blend.glsl" />
|
||||
<None Include="data\shaders\include\blur.glsl" />
|
||||
<None Include="data\shaders\include\color.glsl" />
|
||||
<None Include="data\shaders\include\ext-fb-fetch.glsl" />
|
||||
<None Include="data\shaders\include\hsv.glsl" />
|
||||
<None Include="data\shaders\include\rand.glsl" />
|
||||
<None Include="data\shaders\lambert.glsl" />
|
||||
<None Include="data\shaders\lightmap.glsl" />
|
||||
<None Include="data\shaders\stroke-dilate.glsl" />
|
||||
<None Include="data\shaders\stroke-instanced.glsl" />
|
||||
<None Include="data\shaders\stroke-pad.glsl" />
|
||||
<None Include="data\shaders\stroke-preview.glsl" />
|
||||
<None Include="data\shaders\stroke.glsl" />
|
||||
<None Include="data\shaders\texture-alpha.glsl" />
|
||||
<None Include="data\shaders\texture-blend.glsl" />
|
||||
<None Include="data\shaders\texture-colorize.glsl" />
|
||||
<None Include="data\shaders\texture-mask.glsl" />
|
||||
<None Include="data\shaders\texture.glsl" />
|
||||
<None Include="data\shaders\uvs.glsl" />
|
||||
<None Include="data\shaders\vertex-color.glsl" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Image Include="icon.ico" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Xsd Include="extra\layout.xsd">
|
||||
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">true</ExcludedFromBuild>
|
||||
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">true</ExcludedFromBuild>
|
||||
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">true</ExcludedFromBuild>
|
||||
<ExcludedFromBuild Condition="'$(Configuration)|$(Platform)'=='Release|x64'">true</ExcludedFromBuild>
|
||||
</Xsd>
|
||||
</ItemGroup>
|
||||
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.targets" />
|
||||
<ImportGroup Label="ExtensionTargets">
|
||||
</ImportGroup>
|
||||
</Project>
|
||||
@@ -1,854 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<Project ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
|
||||
<ItemGroup>
|
||||
<Filter Include="Source Files">
|
||||
<UniqueIdentifier>{4FC737F1-C7A5-4376-A066-2A32D752A2FF}</UniqueIdentifier>
|
||||
<Extensions>cpp;c;cc;cxx;def;odl;idl;hpj;bat;asm;asmx</Extensions>
|
||||
</Filter>
|
||||
<Filter Include="Resource Files">
|
||||
<UniqueIdentifier>{67DA6AB6-F800-4c08-8B7A-83BB121AAD01}</UniqueIdentifier>
|
||||
<Extensions>rc;ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe;resx;tiff;tif;png;wav;mfcribbon-ms</Extensions>
|
||||
</Filter>
|
||||
<Filter Include="Source Files\ui">
|
||||
<UniqueIdentifier>{600b8daa-4234-4c37-b4ba-c22cad7d1dc3}</UniqueIdentifier>
|
||||
</Filter>
|
||||
<Filter Include="libs">
|
||||
<UniqueIdentifier>{6d64b115-02d1-43e0-86c8-c8212f51162d}</UniqueIdentifier>
|
||||
</Filter>
|
||||
<Filter Include="libs\jpeg">
|
||||
<UniqueIdentifier>{dc178d53-6a6d-4a18-a93c-d4994340515f}</UniqueIdentifier>
|
||||
</Filter>
|
||||
<Filter Include="libs\WinTab">
|
||||
<UniqueIdentifier>{54dc9f46-d2e0-466c-90d2-eb5d72d5799d}</UniqueIdentifier>
|
||||
</Filter>
|
||||
<Filter Include="libs\yoga">
|
||||
<UniqueIdentifier>{a4a12057-835e-47ff-be4d-ce58b36cecf5}</UniqueIdentifier>
|
||||
</Filter>
|
||||
<Filter Include="libs\tinyxml2">
|
||||
<UniqueIdentifier>{6fe315aa-e2b9-4f01-8291-683a5fda123b}</UniqueIdentifier>
|
||||
</Filter>
|
||||
<Filter Include="libs\poly2tri">
|
||||
<UniqueIdentifier>{bda6fa93-a186-41ca-9bd9-49b7e0fd1ca4}</UniqueIdentifier>
|
||||
</Filter>
|
||||
<Filter Include="extras">
|
||||
<UniqueIdentifier>{e631ac80-1b9b-424f-8adf-e2bab71a566d}</UniqueIdentifier>
|
||||
</Filter>
|
||||
<Filter Include="libs\sqlite3">
|
||||
<UniqueIdentifier>{ef44d179-f28b-458c-b3df-be2895553149}</UniqueIdentifier>
|
||||
</Filter>
|
||||
<Filter Include="libs\nanort">
|
||||
<UniqueIdentifier>{be0c0053-abd8-4e2d-a294-7c54511b05a6}</UniqueIdentifier>
|
||||
</Filter>
|
||||
<Filter Include="libs\hash">
|
||||
<UniqueIdentifier>{2a784067-6741-47a3-b668-cc45f2224286}</UniqueIdentifier>
|
||||
</Filter>
|
||||
<Filter Include="libs\fmt">
|
||||
<UniqueIdentifier>{7b4f5b47-7a8b-4e4c-9e82-399bb5047ffc}</UniqueIdentifier>
|
||||
</Filter>
|
||||
<Filter Include="shaders">
|
||||
<UniqueIdentifier>{b55fb692-a845-4ef2-9b0e-5b2dd8bd125f}</UniqueIdentifier>
|
||||
</Filter>
|
||||
<Filter Include="shaders\include">
|
||||
<UniqueIdentifier>{a2cacb13-2854-44ee-9511-6cb8ac587428}</UniqueIdentifier>
|
||||
</Filter>
|
||||
<Filter Include="libs\glad">
|
||||
<UniqueIdentifier>{ca37521b-213f-4bcf-acfd-eda1483a30b2}</UniqueIdentifier>
|
||||
</Filter>
|
||||
<Filter Include="extras\dialogs">
|
||||
<UniqueIdentifier>{5ecb54ed-7c3d-46fd-9b5d-227abdbc5954}</UniqueIdentifier>
|
||||
</Filter>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ClCompile Include="src\app.cpp">
|
||||
<Filter>Source Files</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="src\image.cpp">
|
||||
<Filter>Source Files</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="src\main.cpp">
|
||||
<Filter>Source Files</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="src\shader.cpp">
|
||||
<Filter>Source Files</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="src\shape.cpp">
|
||||
<Filter>Source Files</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="src\texture.cpp">
|
||||
<Filter>Source Files</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="src\pch.cpp">
|
||||
<Filter>Source Files</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="src\font.cpp">
|
||||
<Filter>Source Files</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="src\util.cpp">
|
||||
<Filter>Source Files</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="src\asset.cpp">
|
||||
<Filter>Source Files</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="src\rtt.cpp">
|
||||
<Filter>Source Files</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="src\bezier.cpp">
|
||||
<Filter>Source Files</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="src\canvas.cpp">
|
||||
<Filter>Source Files</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="src\brush.cpp">
|
||||
<Filter>Source Files</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="src\log.cpp">
|
||||
<Filter>Source Files</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="src\action.cpp">
|
||||
<Filter>Source Files</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="src\event.cpp">
|
||||
<Filter>Source Files</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="src\canvas_modes.cpp">
|
||||
<Filter>Source Files</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="src\node.cpp">
|
||||
<Filter>Source Files\ui</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="src\node_border.cpp">
|
||||
<Filter>Source Files\ui</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="src\node_button.cpp">
|
||||
<Filter>Source Files\ui</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="src\node_button_custom.cpp">
|
||||
<Filter>Source Files\ui</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="src\node_canvas.cpp">
|
||||
<Filter>Source Files\ui</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="src\node_checkbox.cpp">
|
||||
<Filter>Source Files\ui</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="src\node_color_quad.cpp">
|
||||
<Filter>Source Files\ui</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="src\node_dialog_open.cpp">
|
||||
<Filter>Source Files\ui</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="src\node_icon.cpp">
|
||||
<Filter>Source Files\ui</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="src\node_image.cpp">
|
||||
<Filter>Source Files\ui</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="src\node_image_texture.cpp">
|
||||
<Filter>Source Files\ui</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="src\node_message_box.cpp">
|
||||
<Filter>Source Files\ui</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="src\node_panel_brush.cpp">
|
||||
<Filter>Source Files\ui</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="src\node_panel_color.cpp">
|
||||
<Filter>Source Files\ui</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="src\node_panel_layer.cpp">
|
||||
<Filter>Source Files\ui</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="src\node_panel_stroke.cpp">
|
||||
<Filter>Source Files\ui</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="src\node_popup_menu.cpp">
|
||||
<Filter>Source Files\ui</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="src\node_settings.cpp">
|
||||
<Filter>Source Files\ui</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="src\node_slider.cpp">
|
||||
<Filter>Source Files\ui</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="src\node_stroke_preview.cpp">
|
||||
<Filter>Source Files\ui</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="src\node_text.cpp">
|
||||
<Filter>Source Files\ui</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="src\node_text_input.cpp">
|
||||
<Filter>Source Files\ui</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="src\node_viewport.cpp">
|
||||
<Filter>Source Files\ui</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="src\layout.cpp">
|
||||
<Filter>Source Files\ui</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="src\node_scroll.cpp">
|
||||
<Filter>Source Files\ui</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="src\app_shaders.cpp">
|
||||
<Filter>Source Files</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="src\app_layout.cpp">
|
||||
<Filter>Source Files</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="src\app_events.cpp">
|
||||
<Filter>Source Files</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="libs\jpeg\jpgd.cpp">
|
||||
<Filter>libs\jpeg</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="libs\jpeg\jpge.cpp">
|
||||
<Filter>libs\jpeg</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="libs\tinyxml2\tinyxml2.cpp">
|
||||
<Filter>libs\tinyxml2</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="src\wacom.cpp">
|
||||
<Filter>Source Files</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="libs\wacom\WinTab\Utils.cpp">
|
||||
<Filter>libs\WinTab</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="src\node_dialog_layer_rename.cpp">
|
||||
<Filter>Source Files\ui</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="src\app_dialogs.cpp">
|
||||
<Filter>Source Files</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="libs\poly2tri\poly2tri\common\shapes.cc">
|
||||
<Filter>libs\poly2tri</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="libs\poly2tri\poly2tri\sweep\advancing_front.cc">
|
||||
<Filter>libs\poly2tri</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="libs\poly2tri\poly2tri\sweep\cdt.cc">
|
||||
<Filter>libs\poly2tri</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="libs\poly2tri\poly2tri\sweep\sweep.cc">
|
||||
<Filter>libs\poly2tri</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="libs\poly2tri\poly2tri\sweep\sweep_context.cc">
|
||||
<Filter>libs\poly2tri</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="src\node_progress_bar.cpp">
|
||||
<Filter>Source Files\ui</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="src\node_dialog_browse.cpp">
|
||||
<Filter>Source Files\ui</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="src\app_commands.cpp">
|
||||
<Filter>Source Files</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="src\node_dialog_cloud.cpp">
|
||||
<Filter>Source Files\ui</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="src\app_cloud.cpp">
|
||||
<Filter>Source Files</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="src\node_combobox.cpp">
|
||||
<Filter>Source Files\ui</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="src\node_dialog_picker.cpp">
|
||||
<Filter>Source Files\ui</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="src\node_colorwheel.cpp">
|
||||
<Filter>Source Files\ui</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="src\node_panel_grid.cpp">
|
||||
<Filter>Source Files\ui</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="src\version.cpp">
|
||||
<Filter>Source Files</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="src\node_about.cpp">
|
||||
<Filter>Source Files\ui</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="src\node_changelog.cpp">
|
||||
<Filter>Source Files\ui</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="src\node_usermanual.cpp">
|
||||
<Filter>Source Files\ui</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="src\node_dialog_resize.cpp">
|
||||
<Filter>Source Files\ui</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="libs\sqlite3\sqlite3.c">
|
||||
<Filter>libs\sqlite3</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="src\hmd.cpp">
|
||||
<Filter>Source Files</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="src\app_vr.cpp">
|
||||
<Filter>Source Files</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="libs\nanort\nanort.cc">
|
||||
<Filter>libs\nanort</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="libs\hash-library\md5.cpp">
|
||||
<Filter>libs\hash</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="src\abr.cpp">
|
||||
<Filter>Source Files</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="libs\fmt\src\format.cc">
|
||||
<Filter>libs\fmt</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="libs\fmt\src\posix.cc">
|
||||
<Filter>libs\fmt</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="src\node_panel_quick.cpp">
|
||||
<Filter>Source Files\ui</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="src\binary_stream.cpp">
|
||||
<Filter>Source Files</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="src\serializer.cpp">
|
||||
<Filter>Source Files</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="libs\yoga\yoga\YGConfig.cpp">
|
||||
<Filter>libs\yoga</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="libs\yoga\yoga\YGEnums.cpp">
|
||||
<Filter>libs\yoga</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="libs\yoga\yoga\YGLayout.cpp">
|
||||
<Filter>libs\yoga</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="libs\yoga\yoga\YGNode.cpp">
|
||||
<Filter>libs\yoga</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="libs\yoga\yoga\YGNodePrint.cpp">
|
||||
<Filter>libs\yoga</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="libs\yoga\yoga\YGStyle.cpp">
|
||||
<Filter>libs\yoga</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="libs\yoga\yoga\YGValue.cpp">
|
||||
<Filter>libs\yoga</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="libs\yoga\yoga\Yoga.cpp">
|
||||
<Filter>libs\yoga</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="libs\yoga\yoga\log.cpp">
|
||||
<Filter>libs\yoga</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="libs\yoga\yoga\Utils.cpp">
|
||||
<Filter>libs\yoga</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="src\node_panel_floating.cpp">
|
||||
<Filter>Source Files\ui</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="src\settings.cpp">
|
||||
<Filter>Source Files</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="src\canvas_actions.cpp">
|
||||
<Filter>Source Files</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="src\canvas_layer.cpp">
|
||||
<Filter>Source Files</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="src\node_tool_bucket.cpp">
|
||||
<Filter>Source Files\ui</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="libs\glad\src\glad.c">
|
||||
<Filter>libs\glad</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="libs\glad\src\glad_wgl.c">
|
||||
<Filter>libs\glad</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="src\node_dialog_export_ppbr.cpp">
|
||||
<Filter>Source Files\ui</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="src\node_input_box.cpp">
|
||||
<Filter>Source Files\ui</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="src\node_panel_animation.cpp">
|
||||
<Filter>Source Files\ui</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="libs\yoga\yoga\internal\experiments.cpp">
|
||||
<Filter>libs\yoga</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="libs\yoga\yoga\event\event.cpp">
|
||||
<Filter>libs\yoga</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="src\mp4enc.cpp">
|
||||
<Filter>Source Files</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="src\node_remote_page.cpp">
|
||||
<Filter>Source Files\ui</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="src\node_metadata.cpp">
|
||||
<Filter>Source Files\ui</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="src\node_shorcuts.cpp">
|
||||
<Filter>Source Files\ui</Filter>
|
||||
</ClCompile>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ClInclude Include="libs\jpeg\jpgd.h">
|
||||
<Filter>libs\jpeg</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="libs\jpeg\jpge.h">
|
||||
<Filter>libs\jpeg</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="libs\tinyxml2\tinyxml2.h">
|
||||
<Filter>libs\tinyxml2</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="libs\wacom\WinTab\PKTDEF.H">
|
||||
<Filter>libs\WinTab</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="libs\wacom\WinTab\Utils.h">
|
||||
<Filter>libs\WinTab</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="libs\wacom\WinTab\WINTAB.H">
|
||||
<Filter>libs\WinTab</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="libs\wacom\WinTab\MSGPACK.H">
|
||||
<Filter>libs\WinTab</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="libs\sqlite3\sqlite3.h">
|
||||
<Filter>libs\sqlite3</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="libs\sqlite3\sqlite3ext.h">
|
||||
<Filter>libs\sqlite3</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="libs\nanort\nanort.h">
|
||||
<Filter>libs\nanort</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="libs\hash-library\md5.h">
|
||||
<Filter>libs\hash</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="src\abr.h">
|
||||
<Filter>Source Files</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="src\action.h">
|
||||
<Filter>Source Files</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="src\app.h">
|
||||
<Filter>Source Files</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="src\asset.h">
|
||||
<Filter>Source Files</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="src\bezier.h">
|
||||
<Filter>Source Files</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="src\binary_stream.h">
|
||||
<Filter>Source Files</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="src\brush.h">
|
||||
<Filter>Source Files</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="src\canvas.h">
|
||||
<Filter>Source Files</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="src\canvas_actions.h">
|
||||
<Filter>Source Files</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="src\canvas_layer.h">
|
||||
<Filter>Source Files</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="src\canvas_modes.h">
|
||||
<Filter>Source Files</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="src\event.h">
|
||||
<Filter>Source Files</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="src\font.h">
|
||||
<Filter>Source Files</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="src\hmd.h">
|
||||
<Filter>Source Files</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="src\image.h">
|
||||
<Filter>Source Files</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="src\keymap.h">
|
||||
<Filter>Source Files</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="src\log.h">
|
||||
<Filter>Source Files</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="src\pch.h">
|
||||
<Filter>Source Files</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="resource.h">
|
||||
<Filter>Source Files</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="src\rtt.h">
|
||||
<Filter>Source Files</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="src\serializer.h">
|
||||
<Filter>Source Files</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="src\settings.h">
|
||||
<Filter>Source Files</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="src\shader.h">
|
||||
<Filter>Source Files</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="src\shape.h">
|
||||
<Filter>Source Files</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="src\texture.h">
|
||||
<Filter>Source Files</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="src\util.h">
|
||||
<Filter>Source Files</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="src\version.gen.h">
|
||||
<Filter>Source Files</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="src\version.h">
|
||||
<Filter>Source Files</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="src\wacom.h">
|
||||
<Filter>Source Files</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="src\layout.h">
|
||||
<Filter>Source Files\ui</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="src\node.h">
|
||||
<Filter>Source Files\ui</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="src\node_about.h">
|
||||
<Filter>Source Files\ui</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="src\node_border.h">
|
||||
<Filter>Source Files\ui</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="src\node_button.h">
|
||||
<Filter>Source Files\ui</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="src\node_button_custom.h">
|
||||
<Filter>Source Files\ui</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="src\node_canvas.h">
|
||||
<Filter>Source Files\ui</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="src\node_changelog.h">
|
||||
<Filter>Source Files\ui</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="src\node_checkbox.h">
|
||||
<Filter>Source Files\ui</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="src\node_color_quad.h">
|
||||
<Filter>Source Files\ui</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="src\node_colorwheel.h">
|
||||
<Filter>Source Files\ui</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="src\node_combobox.h">
|
||||
<Filter>Source Files\ui</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="src\node_dialog_browse.h">
|
||||
<Filter>Source Files\ui</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="src\node_dialog_cloud.h">
|
||||
<Filter>Source Files\ui</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="src\node_dialog_export_ppbr.h">
|
||||
<Filter>Source Files\ui</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="src\node_dialog_layer_rename.h">
|
||||
<Filter>Source Files\ui</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="src\node_dialog_open.h">
|
||||
<Filter>Source Files\ui</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="src\node_dialog_picker.h">
|
||||
<Filter>Source Files\ui</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="src\node_dialog_resize.h">
|
||||
<Filter>Source Files\ui</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="src\node_icon.h">
|
||||
<Filter>Source Files\ui</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="src\node_image.h">
|
||||
<Filter>Source Files\ui</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="src\node_image_texture.h">
|
||||
<Filter>Source Files\ui</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="src\node_input_box.h">
|
||||
<Filter>Source Files\ui</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="src\node_message_box.h">
|
||||
<Filter>Source Files\ui</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="src\node_panel_brush.h">
|
||||
<Filter>Source Files\ui</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="src\node_panel_color.h">
|
||||
<Filter>Source Files\ui</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="src\node_panel_floating.h">
|
||||
<Filter>Source Files\ui</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="src\node_panel_grid.h">
|
||||
<Filter>Source Files\ui</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="src\node_panel_layer.h">
|
||||
<Filter>Source Files\ui</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="src\node_panel_quick.h">
|
||||
<Filter>Source Files\ui</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="src\node_panel_stroke.h">
|
||||
<Filter>Source Files\ui</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="src\node_popup_menu.h">
|
||||
<Filter>Source Files\ui</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="src\node_progress_bar.h">
|
||||
<Filter>Source Files\ui</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="src\node_scroll.h">
|
||||
<Filter>Source Files\ui</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="src\node_settings.h">
|
||||
<Filter>Source Files\ui</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="src\node_slider.h">
|
||||
<Filter>Source Files\ui</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="src\node_stroke_preview.h">
|
||||
<Filter>Source Files\ui</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="src\node_text.h">
|
||||
<Filter>Source Files\ui</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="src\node_text_input.h">
|
||||
<Filter>Source Files\ui</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="src\node_tool_bucket.h">
|
||||
<Filter>Source Files\ui</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="src\node_usermanual.h">
|
||||
<Filter>Source Files\ui</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="src\node_viewport.h">
|
||||
<Filter>Source Files\ui</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="src\node_panel_animation.h">
|
||||
<Filter>Source Files\ui</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="src\node_remote_page.h">
|
||||
<Filter>Source Files\ui</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="src\node_metadata.h">
|
||||
<Filter>Source Files\ui</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="src\node_shorcuts.h">
|
||||
<Filter>Source Files\ui</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="src\mp4enc.h">
|
||||
<Filter>Source Files</Filter>
|
||||
</ClInclude>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ResourceCompile Include="PanoPainter.rc">
|
||||
<Filter>Resource Files</Filter>
|
||||
</ResourceCompile>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Natvis Include="libs\glm\util\glm.natvis">
|
||||
<Filter>extras</Filter>
|
||||
</Natvis>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Xml Include="data\layout.xml">
|
||||
<Filter>extras</Filter>
|
||||
</Xml>
|
||||
<Xml Include="data\dialogs\changelog.xml">
|
||||
<Filter>extras\dialogs</Filter>
|
||||
</Xml>
|
||||
<Xml Include="data\dialogs\about.xml">
|
||||
<Filter>extras\dialogs</Filter>
|
||||
</Xml>
|
||||
<Xml Include="data\dialogs\usermanual.xml">
|
||||
<Filter>extras\dialogs</Filter>
|
||||
</Xml>
|
||||
<Xml Include="data\dialogs\brush-export.xml">
|
||||
<Filter>extras\dialogs</Filter>
|
||||
</Xml>
|
||||
<Xml Include="data\dialogs\panel-layers.xml">
|
||||
<Filter>extras\dialogs</Filter>
|
||||
</Xml>
|
||||
<Xml Include="data\dialogs\panel-brushes.xml">
|
||||
<Filter>extras\dialogs</Filter>
|
||||
</Xml>
|
||||
<Xml Include="data\dialogs\panel-stroke.xml">
|
||||
<Filter>extras\dialogs</Filter>
|
||||
</Xml>
|
||||
<Xml Include="data\dialogs\panel-grid.xml">
|
||||
<Filter>extras\dialogs</Filter>
|
||||
</Xml>
|
||||
<Xml Include="data\dialogs\panel-quick.xml">
|
||||
<Filter>extras\dialogs</Filter>
|
||||
</Xml>
|
||||
<Xml Include="data\dialogs\color-picker.xml">
|
||||
<Filter>extras\dialogs</Filter>
|
||||
</Xml>
|
||||
<Xml Include="data\dialogs\input-box.xml">
|
||||
<Filter>extras\dialogs</Filter>
|
||||
</Xml>
|
||||
<Xml Include="data\dialogs\message-box.xml">
|
||||
<Filter>extras\dialogs</Filter>
|
||||
</Xml>
|
||||
<Xml Include="data\dialogs\progress-bar.xml">
|
||||
<Filter>extras\dialogs</Filter>
|
||||
</Xml>
|
||||
<Xml Include="data\dialogs\layer-rename.xml">
|
||||
<Filter>extras\dialogs</Filter>
|
||||
</Xml>
|
||||
<Xml Include="data\dialogs\doc-resize.xml">
|
||||
<Filter>extras\dialogs</Filter>
|
||||
</Xml>
|
||||
<Xml Include="data\dialogs\doc-browse.xml">
|
||||
<Filter>extras\dialogs</Filter>
|
||||
</Xml>
|
||||
<Xml Include="data\dialogs\doc-new.xml">
|
||||
<Filter>extras\dialogs</Filter>
|
||||
</Xml>
|
||||
<Xml Include="data\dialogs\doc-save.xml">
|
||||
<Filter>extras\dialogs</Filter>
|
||||
</Xml>
|
||||
<Xml Include="data\dialogs\cloud-browse.xml">
|
||||
<Filter>extras\dialogs</Filter>
|
||||
</Xml>
|
||||
<Xml Include="data\dialogs\settings.xml">
|
||||
<Filter>extras\dialogs</Filter>
|
||||
</Xml>
|
||||
<Xml Include="data\dialogs\doc-open.xml">
|
||||
<Filter>extras\dialogs</Filter>
|
||||
</Xml>
|
||||
<Xml Include="data\dialogs\panel-floating.xml">
|
||||
<Filter>extras\dialogs</Filter>
|
||||
</Xml>
|
||||
<Xml Include="data\dialogs\panel-presets.xml">
|
||||
<Filter>extras\dialogs</Filter>
|
||||
</Xml>
|
||||
<Xml Include="data\dialogs\panel-animation.xml">
|
||||
<Filter>extras\dialogs</Filter>
|
||||
</Xml>
|
||||
<Xml Include="data\dialogs\remote-page.xml">
|
||||
<Filter>extras\dialogs</Filter>
|
||||
</Xml>
|
||||
<Xml Include="data\dialogs\shortcuts.xml">
|
||||
<Filter>extras\dialogs</Filter>
|
||||
</Xml>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<None Include="data\shaders\texture.glsl">
|
||||
<Filter>shaders</Filter>
|
||||
</None>
|
||||
<None Include="data\shaders\include\blend-stroke.glsl">
|
||||
<Filter>shaders\include</Filter>
|
||||
</None>
|
||||
<None Include="data\shaders\include\blur.glsl">
|
||||
<Filter>shaders\include</Filter>
|
||||
</None>
|
||||
<None Include="data\shaders\include\color.glsl">
|
||||
<Filter>shaders\include</Filter>
|
||||
</None>
|
||||
<None Include="data\shaders\include\ext-fb-fetch.glsl">
|
||||
<Filter>shaders\include</Filter>
|
||||
</None>
|
||||
<None Include="data\shaders\include\hsv.glsl">
|
||||
<Filter>shaders\include</Filter>
|
||||
</None>
|
||||
<None Include="data\shaders\include\rand.glsl">
|
||||
<Filter>shaders\include</Filter>
|
||||
</None>
|
||||
<None Include="data\shaders\include\blend.glsl">
|
||||
<Filter>shaders\include</Filter>
|
||||
</None>
|
||||
<None Include="data\shaders\comp-draw.glsl">
|
||||
<Filter>shaders</Filter>
|
||||
</None>
|
||||
<None Include="data\shaders\comp-erase.glsl">
|
||||
<Filter>shaders</Filter>
|
||||
</None>
|
||||
<None Include="data\shaders\equirect.glsl">
|
||||
<Filter>shaders</Filter>
|
||||
</None>
|
||||
<None Include="data\shaders\font.glsl">
|
||||
<Filter>shaders</Filter>
|
||||
</None>
|
||||
<None Include="data\shaders\lambert.glsl">
|
||||
<Filter>shaders</Filter>
|
||||
</None>
|
||||
<None Include="data\shaders\lightmap.glsl">
|
||||
<Filter>shaders</Filter>
|
||||
</None>
|
||||
<None Include="data\shaders\stroke.glsl">
|
||||
<Filter>shaders</Filter>
|
||||
</None>
|
||||
<None Include="data\shaders\stroke-instanced.glsl">
|
||||
<Filter>shaders</Filter>
|
||||
</None>
|
||||
<None Include="data\shaders\stroke-preview.glsl">
|
||||
<Filter>shaders</Filter>
|
||||
</None>
|
||||
<None Include="data\shaders\texture-alpha.glsl">
|
||||
<Filter>shaders</Filter>
|
||||
</None>
|
||||
<None Include="data\shaders\texture-blend.glsl">
|
||||
<Filter>shaders</Filter>
|
||||
</None>
|
||||
<None Include="data\shaders\uvs.glsl">
|
||||
<Filter>shaders</Filter>
|
||||
</None>
|
||||
<None Include="data\shaders\vertex-color.glsl">
|
||||
<Filter>shaders</Filter>
|
||||
</None>
|
||||
<None Include="data\shaders\atlas.glsl">
|
||||
<Filter>shaders</Filter>
|
||||
</None>
|
||||
<None Include="data\shaders\bake-uv.glsl">
|
||||
<Filter>shaders</Filter>
|
||||
</None>
|
||||
<None Include="data\shaders\checkerboard.glsl">
|
||||
<Filter>shaders</Filter>
|
||||
</None>
|
||||
<None Include="data\shaders\color.glsl">
|
||||
<Filter>shaders</Filter>
|
||||
</None>
|
||||
<None Include="data\shaders\color-hue.glsl">
|
||||
<Filter>shaders</Filter>
|
||||
</None>
|
||||
<None Include="data\shaders\color-quad.glsl">
|
||||
<Filter>shaders</Filter>
|
||||
</None>
|
||||
<None Include="data\shaders\color-tri.glsl">
|
||||
<Filter>shaders</Filter>
|
||||
</None>
|
||||
<None Include="data\shaders\texture-colorize.glsl">
|
||||
<Filter>shaders</Filter>
|
||||
</None>
|
||||
<None Include="data\shaders\texture-mask.glsl">
|
||||
<Filter>shaders</Filter>
|
||||
</None>
|
||||
<None Include="data\shaders\stroke-dilate.glsl">
|
||||
<Filter>shaders</Filter>
|
||||
</None>
|
||||
<None Include="data\shaders\stroke-pad.glsl">
|
||||
<Filter>shaders</Filter>
|
||||
</None>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Image Include="icon.ico">
|
||||
<Filter>Resource Files</Filter>
|
||||
</Image>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Xsd Include="extra\layout.xsd">
|
||||
<Filter>extras</Filter>
|
||||
</Xsd>
|
||||
</ItemGroup>
|
||||
</Project>
|
||||
@@ -2,7 +2,11 @@
|
||||
# This ensures that a certain set of CMake features is available to
|
||||
# your build.
|
||||
|
||||
cmake_minimum_required(VERSION 3.4.1)
|
||||
cmake_minimum_required(VERSION 3.10)
|
||||
|
||||
project(PanoPainterAndroidNative LANGUAGES C CXX)
|
||||
|
||||
include(../cmake/PanoPainterAndroidLegacyCompat.cmake)
|
||||
|
||||
link_directories(
|
||||
../../libs/curl-android-ios/android/${ANDROID_ABI}
|
||||
@@ -23,11 +27,66 @@ add_library(yuv SHARED IMPORTED)
|
||||
set_target_properties(yuv PROPERTIES IMPORTED_LOCATION
|
||||
${CMAKE_SOURCE_DIR}/../../libs/libyuv/lib/android/${ANDROID_ABI}/libyuv.so)
|
||||
|
||||
# now build app's shared lib
|
||||
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++14")
|
||||
set(PP_MODERN_COMPONENT_SOURCES
|
||||
../../src/app_core/document_export.cpp
|
||||
../../src/app_core/document_route.cpp
|
||||
../../src/app_core/document_session.cpp
|
||||
../../src/assets/brush_package.cpp
|
||||
../../src/assets/image_format.cpp
|
||||
../../src/assets/image_metadata.cpp
|
||||
../../src/assets/image_pixels.cpp
|
||||
../../src/assets/ppi_header.cpp
|
||||
../../src/assets/settings_document.cpp
|
||||
../../src/document/document.cpp
|
||||
../../src/document/ppi_export.cpp
|
||||
../../src/document/ppi_import.cpp
|
||||
../../src/foundation/binary_stream.cpp
|
||||
../../src/foundation/event.cpp
|
||||
../../src/foundation/log.cpp
|
||||
../../src/foundation/parse.cpp
|
||||
../../src/foundation/task_queue.cpp
|
||||
../../src/foundation/trace.cpp
|
||||
../../src/paint/blend.cpp
|
||||
../../src/paint/brush.cpp
|
||||
../../src/paint/stroke.cpp
|
||||
../../src/paint/stroke_script.cpp
|
||||
../../src/paint_renderer/compositor.cpp
|
||||
../../src/platform_api/asset_file_load_policy.cpp
|
||||
../../src/platform_api/network_tls_policy.cpp
|
||||
../../src/platform_api/platform_policy.cpp
|
||||
../../src/platform_api/platform_services.cpp
|
||||
../../src/platform_legacy/legacy_platform_services.cpp
|
||||
../../src/renderer_api/recording_renderer.cpp
|
||||
../../src/renderer_api/renderer_api.cpp
|
||||
../../src/renderer_api/shader_catalog.cpp
|
||||
../../src/renderer_gl/command_plan.cpp
|
||||
../../src/renderer_gl/opengl_capabilities.cpp
|
||||
../../src/renderer_gl/shader_bindings.cpp
|
||||
../../src/legacy_app_dialog_services.cpp
|
||||
../../src/legacy_app_preference_services.cpp
|
||||
../../src/legacy_app_shell_services.cpp
|
||||
../../src/legacy_app_startup_services.cpp
|
||||
../../src/legacy_brush_package_export_services.cpp
|
||||
../../src/legacy_brush_package_import_services.cpp
|
||||
../../src/legacy_brush_ui_services.cpp
|
||||
../../src/legacy_canvas_tool_services.cpp
|
||||
../../src/legacy_canvas_view_services.cpp
|
||||
../../src/legacy_cloud_services.cpp
|
||||
../../src/legacy_document_animation_services.cpp
|
||||
../../src/legacy_document_canvas_services.cpp
|
||||
../../src/legacy_document_export_services.cpp
|
||||
../../src/legacy_document_layer_services.cpp
|
||||
../../src/legacy_document_open_services.cpp
|
||||
../../src/legacy_document_session_services.cpp
|
||||
../../src/legacy_grid_ui_services.cpp
|
||||
../../src/legacy_history_services.cpp
|
||||
../../src/legacy_quick_ui_services.cpp
|
||||
../../src/legacy_recording_services.cpp
|
||||
)
|
||||
|
||||
add_library(
|
||||
native-lib SHARED
|
||||
${PP_MODERN_COMPONENT_SOURCES}
|
||||
../../libs/yoga/yoga/event/event.cpp
|
||||
../../libs/yoga/yoga/internal/experiments.cpp
|
||||
../../libs/yoga/yoga/log.cpp
|
||||
@@ -128,6 +187,10 @@ add_library(
|
||||
../../src/node_metadata.cpp
|
||||
)
|
||||
|
||||
target_compile_features(native-lib PRIVATE cxx_std_23)
|
||||
set_target_properties(native-lib PROPERTIES CXX_EXTENSIONS OFF)
|
||||
pp_configure_legacy_nanort_overlay(native-lib)
|
||||
|
||||
target_include_directories(native-lib PRIVATE
|
||||
src/main/cpp
|
||||
../src/cpp
|
||||
|
||||
19
android/cmake/PanoPainterAndroidLegacyCompat.cmake
Normal file
19
android/cmake/PanoPainterAndroidLegacyCompat.cmake
Normal file
@@ -0,0 +1,19 @@
|
||||
set(PP_ANDROID_LEGACY_COMPAT_DIR "${CMAKE_CURRENT_LIST_DIR}")
|
||||
|
||||
function(pp_configure_legacy_nanort_overlay target_name)
|
||||
set(nanort_source "${PP_ANDROID_LEGACY_COMPAT_DIR}/../../libs/nanort/nanort.h")
|
||||
set(nanort_overlay_dir "${CMAKE_CURRENT_BINARY_DIR}/generated/nanort_compat")
|
||||
set(nanort_overlay_header "${nanort_overlay_dir}/nanort.h")
|
||||
|
||||
file(READ "${nanort_source}" nanort_header)
|
||||
string(REPLACE
|
||||
" const size_t vertex_stride_bytes_;"
|
||||
" size_t vertex_stride_bytes_;"
|
||||
nanort_header
|
||||
"${nanort_header}")
|
||||
|
||||
file(MAKE_DIRECTORY "${nanort_overlay_dir}")
|
||||
file(WRITE "${nanort_overlay_header}" "${nanort_header}")
|
||||
|
||||
target_include_directories(${target_name} BEFORE PRIVATE "${nanort_overlay_dir}")
|
||||
endfunction()
|
||||
@@ -2,7 +2,11 @@
|
||||
# This ensures that a certain set of CMake features is available to
|
||||
# your build.
|
||||
|
||||
cmake_minimum_required(VERSION 3.4.1)
|
||||
cmake_minimum_required(VERSION 3.10)
|
||||
|
||||
project(PanoPainterFocusNative LANGUAGES C CXX)
|
||||
|
||||
include(../cmake/PanoPainterAndroidLegacyCompat.cmake)
|
||||
|
||||
# build native_app_glue as a static lib
|
||||
add_library(
|
||||
@@ -17,23 +21,79 @@ set_target_properties(
|
||||
${CMAKE_SOURCE_DIR}/../../libs/wave_sdk/wvr_client/lib/${ANDROID_ABI}/libwvr_api.so
|
||||
)
|
||||
|
||||
set(PP_MODERN_COMPONENT_SOURCES
|
||||
../../src/app_core/document_export.cpp
|
||||
../../src/app_core/document_route.cpp
|
||||
../../src/app_core/document_session.cpp
|
||||
../../src/assets/brush_package.cpp
|
||||
../../src/assets/image_format.cpp
|
||||
../../src/assets/image_metadata.cpp
|
||||
../../src/assets/image_pixels.cpp
|
||||
../../src/assets/ppi_header.cpp
|
||||
../../src/assets/settings_document.cpp
|
||||
../../src/document/document.cpp
|
||||
../../src/document/ppi_export.cpp
|
||||
../../src/document/ppi_import.cpp
|
||||
../../src/foundation/binary_stream.cpp
|
||||
../../src/foundation/event.cpp
|
||||
../../src/foundation/log.cpp
|
||||
../../src/foundation/parse.cpp
|
||||
../../src/foundation/task_queue.cpp
|
||||
../../src/foundation/trace.cpp
|
||||
../../src/paint/blend.cpp
|
||||
../../src/paint/brush.cpp
|
||||
../../src/paint/stroke.cpp
|
||||
../../src/paint/stroke_script.cpp
|
||||
../../src/paint_renderer/compositor.cpp
|
||||
../../src/platform_api/asset_file_load_policy.cpp
|
||||
../../src/platform_api/network_tls_policy.cpp
|
||||
../../src/platform_api/platform_policy.cpp
|
||||
../../src/platform_api/platform_services.cpp
|
||||
../../src/platform_legacy/legacy_platform_services.cpp
|
||||
../../src/renderer_api/recording_renderer.cpp
|
||||
../../src/renderer_api/renderer_api.cpp
|
||||
../../src/renderer_api/shader_catalog.cpp
|
||||
../../src/renderer_gl/command_plan.cpp
|
||||
../../src/renderer_gl/opengl_capabilities.cpp
|
||||
../../src/renderer_gl/shader_bindings.cpp
|
||||
../../src/legacy_app_dialog_services.cpp
|
||||
../../src/legacy_app_preference_services.cpp
|
||||
../../src/legacy_app_shell_services.cpp
|
||||
../../src/legacy_app_startup_services.cpp
|
||||
../../src/legacy_brush_package_export_services.cpp
|
||||
../../src/legacy_brush_package_import_services.cpp
|
||||
../../src/legacy_brush_ui_services.cpp
|
||||
../../src/legacy_canvas_tool_services.cpp
|
||||
../../src/legacy_canvas_view_services.cpp
|
||||
../../src/legacy_cloud_services.cpp
|
||||
../../src/legacy_document_animation_services.cpp
|
||||
../../src/legacy_document_canvas_services.cpp
|
||||
../../src/legacy_document_export_services.cpp
|
||||
../../src/legacy_document_layer_services.cpp
|
||||
../../src/legacy_document_open_services.cpp
|
||||
../../src/legacy_document_session_services.cpp
|
||||
../../src/legacy_grid_ui_services.cpp
|
||||
../../src/legacy_history_services.cpp
|
||||
../../src/legacy_quick_ui_services.cpp
|
||||
../../src/legacy_recording_services.cpp
|
||||
)
|
||||
|
||||
# Specifies a library name, specifies whether the library is STATIC or
|
||||
# SHARED, and provides relative paths to the source code. You can
|
||||
# define multiple libraries by adding multiple add.library() commands,
|
||||
# and CMake builds them for you. When you build your app, Gradle
|
||||
# automatically packages shared libraries with your APK.
|
||||
|
||||
# now build app's shared lib
|
||||
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++14")
|
||||
|
||||
add_library(
|
||||
native-lib SHARED
|
||||
${PP_MODERN_COMPONENT_SOURCES}
|
||||
../../libs/yoga/yoga/event/event.cpp
|
||||
../../libs/yoga/yoga/internal/experiments.cpp
|
||||
../../libs/yoga/yoga/log.cpp
|
||||
../../libs/yoga/yoga/Utils.cpp
|
||||
../../libs/yoga/yoga/YGConfig.cpp
|
||||
../../libs/yoga/yoga/YGEnums.cpp
|
||||
../../libs/yoga/yoga/YGLayout.cpp
|
||||
../../libs/yoga/yoga/YGMarker.cpp
|
||||
../../libs/yoga/yoga/YGNode.cpp
|
||||
../../libs/yoga/yoga/YGNodePrint.cpp
|
||||
../../libs/yoga/yoga/YGStyle.cpp
|
||||
@@ -121,6 +181,10 @@ add_library(
|
||||
../../src/settings.cpp
|
||||
)
|
||||
|
||||
target_compile_features(native-lib PRIVATE cxx_std_23)
|
||||
set_target_properties(native-lib PROPERTIES CXX_EXTENSIONS OFF)
|
||||
pp_configure_legacy_nanort_overlay(native-lib)
|
||||
|
||||
target_include_directories(native-lib PRIVATE
|
||||
../../libs/wave_sdk/wvr_client/include
|
||||
src/main/cpp
|
||||
|
||||
@@ -2,7 +2,11 @@
|
||||
# This ensures that a certain set of CMake features is available to
|
||||
# your build.
|
||||
|
||||
cmake_minimum_required(VERSION 3.4.1)
|
||||
cmake_minimum_required(VERSION 3.10)
|
||||
|
||||
project(PanoPainterQuestNative LANGUAGES C CXX)
|
||||
|
||||
include(../cmake/PanoPainterAndroidLegacyCompat.cmake)
|
||||
|
||||
# build native_app_glue as a static lib
|
||||
add_library(
|
||||
@@ -25,23 +29,79 @@ set_target_properties(
|
||||
)
|
||||
|
||||
|
||||
set(PP_MODERN_COMPONENT_SOURCES
|
||||
../../src/app_core/document_export.cpp
|
||||
../../src/app_core/document_route.cpp
|
||||
../../src/app_core/document_session.cpp
|
||||
../../src/assets/brush_package.cpp
|
||||
../../src/assets/image_format.cpp
|
||||
../../src/assets/image_metadata.cpp
|
||||
../../src/assets/image_pixels.cpp
|
||||
../../src/assets/ppi_header.cpp
|
||||
../../src/assets/settings_document.cpp
|
||||
../../src/document/document.cpp
|
||||
../../src/document/ppi_export.cpp
|
||||
../../src/document/ppi_import.cpp
|
||||
../../src/foundation/binary_stream.cpp
|
||||
../../src/foundation/event.cpp
|
||||
../../src/foundation/log.cpp
|
||||
../../src/foundation/parse.cpp
|
||||
../../src/foundation/task_queue.cpp
|
||||
../../src/foundation/trace.cpp
|
||||
../../src/paint/blend.cpp
|
||||
../../src/paint/brush.cpp
|
||||
../../src/paint/stroke.cpp
|
||||
../../src/paint/stroke_script.cpp
|
||||
../../src/paint_renderer/compositor.cpp
|
||||
../../src/platform_api/asset_file_load_policy.cpp
|
||||
../../src/platform_api/network_tls_policy.cpp
|
||||
../../src/platform_api/platform_policy.cpp
|
||||
../../src/platform_api/platform_services.cpp
|
||||
../../src/platform_legacy/legacy_platform_services.cpp
|
||||
../../src/renderer_api/recording_renderer.cpp
|
||||
../../src/renderer_api/renderer_api.cpp
|
||||
../../src/renderer_api/shader_catalog.cpp
|
||||
../../src/renderer_gl/command_plan.cpp
|
||||
../../src/renderer_gl/opengl_capabilities.cpp
|
||||
../../src/renderer_gl/shader_bindings.cpp
|
||||
../../src/legacy_app_dialog_services.cpp
|
||||
../../src/legacy_app_preference_services.cpp
|
||||
../../src/legacy_app_shell_services.cpp
|
||||
../../src/legacy_app_startup_services.cpp
|
||||
../../src/legacy_brush_package_export_services.cpp
|
||||
../../src/legacy_brush_package_import_services.cpp
|
||||
../../src/legacy_brush_ui_services.cpp
|
||||
../../src/legacy_canvas_tool_services.cpp
|
||||
../../src/legacy_canvas_view_services.cpp
|
||||
../../src/legacy_cloud_services.cpp
|
||||
../../src/legacy_document_animation_services.cpp
|
||||
../../src/legacy_document_canvas_services.cpp
|
||||
../../src/legacy_document_export_services.cpp
|
||||
../../src/legacy_document_layer_services.cpp
|
||||
../../src/legacy_document_open_services.cpp
|
||||
../../src/legacy_document_session_services.cpp
|
||||
../../src/legacy_grid_ui_services.cpp
|
||||
../../src/legacy_history_services.cpp
|
||||
../../src/legacy_quick_ui_services.cpp
|
||||
../../src/legacy_recording_services.cpp
|
||||
)
|
||||
|
||||
# Specifies a library name, specifies whether the library is STATIC or
|
||||
# SHARED, and provides relative paths to the source code. You can
|
||||
# define multiple libraries by adding multiple add.library() commands,
|
||||
# and CMake builds them for you. When you build your app, Gradle
|
||||
# automatically packages shared libraries with your APK.
|
||||
|
||||
# now build app's shared lib
|
||||
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++14")
|
||||
|
||||
add_library(
|
||||
native-lib SHARED
|
||||
${PP_MODERN_COMPONENT_SOURCES}
|
||||
../../libs/yoga/yoga/event/event.cpp
|
||||
../../libs/yoga/yoga/internal/experiments.cpp
|
||||
../../libs/yoga/yoga/log.cpp
|
||||
../../libs/yoga/yoga/Utils.cpp
|
||||
../../libs/yoga/yoga/YGConfig.cpp
|
||||
../../libs/yoga/yoga/YGEnums.cpp
|
||||
../../libs/yoga/yoga/YGLayout.cpp
|
||||
../../libs/yoga/yoga/YGMarker.cpp
|
||||
../../libs/yoga/yoga/YGNode.cpp
|
||||
../../libs/yoga/yoga/YGNodePrint.cpp
|
||||
../../libs/yoga/yoga/YGStyle.cpp
|
||||
@@ -129,6 +189,10 @@ add_library(
|
||||
../../src/settings.cpp
|
||||
)
|
||||
|
||||
target_compile_features(native-lib PRIVATE cxx_std_23)
|
||||
set_target_properties(native-lib PROPERTIES CXX_EXTENSIONS OFF)
|
||||
pp_configure_legacy_nanort_overlay(native-lib)
|
||||
|
||||
target_include_directories(native-lib PRIVATE
|
||||
../../libs/ovr_mobile/include
|
||||
../../libs/ovr_platform/Include
|
||||
|
||||
@@ -35,7 +35,9 @@
|
||||
#include "keymap.h"
|
||||
#include "main.h"
|
||||
#include "settings.h"
|
||||
#if __has_include("com_omixlab_panopainter_MainActivity.h")
|
||||
#include "com_omixlab_panopainter_MainActivity.h"
|
||||
#endif
|
||||
|
||||
#ifdef __QUEST__
|
||||
#include "oculus_vr.h"
|
||||
@@ -241,7 +243,7 @@ extern "C"
|
||||
#ifdef __FOCUS__
|
||||
JNIEXPORT void JNICALL Java_com_omixlab_panopainter_MainActivity_init_vr(JNIEnv *env, jobject activity, jobject am)
|
||||
{
|
||||
Asset::m_am = AAssetManager_fromJava(env, am);
|
||||
Asset::set_android_asset_manager(AAssetManager_fromJava(env, am));
|
||||
wave_init(0, 0, 0);
|
||||
}
|
||||
#endif
|
||||
@@ -700,7 +702,7 @@ static int engine_init_display(struct engine* engine) {
|
||||
LOG("PROP Maker: %s", os_props["ro.product.manufacturer"].c_str());
|
||||
LOG("PROP Mode: %s", os_props["ro.product.model"].c_str());
|
||||
|
||||
Asset::m_am = engine->app->activity->assetManager;
|
||||
Asset::set_android_asset_manager(engine->app->activity->assetManager);
|
||||
App::I->and_app = engine->app;
|
||||
App::I->and_engine = engine;
|
||||
|
||||
|
||||
23
cmake/PanoPainterAutomation.cmake
Normal file
23
cmake/PanoPainterAutomation.cmake
Normal file
@@ -0,0 +1,23 @@
|
||||
find_program(PP_POWERSHELL_COMMAND NAMES pwsh powershell)
|
||||
|
||||
function(pp_add_powershell_automation_target target_name)
|
||||
cmake_parse_arguments(PP_AUTOMATION_TARGET "" "COMMENT" "ARGUMENTS" ${ARGN})
|
||||
|
||||
if(PP_POWERSHELL_COMMAND)
|
||||
add_custom_target(${target_name}
|
||||
COMMAND "${PP_POWERSHELL_COMMAND}"
|
||||
-NoProfile
|
||||
-ExecutionPolicy Bypass
|
||||
${PP_AUTOMATION_TARGET_ARGUMENTS}
|
||||
WORKING_DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}"
|
||||
COMMENT "${PP_AUTOMATION_TARGET_COMMENT}"
|
||||
USES_TERMINAL
|
||||
VERBATIM)
|
||||
else()
|
||||
add_custom_target(${target_name}
|
||||
COMMAND "${CMAKE_COMMAND}" -E echo "PowerShell was not found; cannot run ${target_name}."
|
||||
COMMAND "${CMAKE_COMMAND}" -E false
|
||||
COMMENT "${PP_AUTOMATION_TARGET_COMMENT}"
|
||||
VERBATIM)
|
||||
endif()
|
||||
endfunction()
|
||||
20
cmake/PanoPainterOptions.cmake
Normal file
20
cmake/PanoPainterOptions.cmake
Normal file
@@ -0,0 +1,20 @@
|
||||
option(PP_BUILD_APP "Build the PanoPainter application target from root CMake." ON)
|
||||
option(PP_BUILD_TESTS "Build PanoPainter tests." ON)
|
||||
option(PP_BUILD_TOOLS "Build PanoPainter automation tools." ON)
|
||||
|
||||
option(PP_ENABLE_OPENGL "Enable the OpenGL renderer backend." ON)
|
||||
option(PP_ENABLE_VULKAN_EXPERIMENTAL "Enable non-production Vulkan experiments." OFF)
|
||||
option(PP_ENABLE_VR "Enable VR support." ON)
|
||||
option(PP_ENABLE_CLOUD "Enable cloud/network features." ON)
|
||||
option(PP_ENABLE_VIDEO "Enable MP4/timelapse video features." ON)
|
||||
|
||||
option(PP_ENABLE_ASAN "Enable AddressSanitizer where supported." OFF)
|
||||
option(PP_ENABLE_UBSAN "Enable UndefinedBehaviorSanitizer where supported." OFF)
|
||||
option(PP_ENABLE_TSAN "Enable ThreadSanitizer for headless targets where supported." OFF)
|
||||
option(PP_ENABLE_MSVC_ANALYZE "Enable MSVC static analysis." OFF)
|
||||
option(PP_ENABLE_CLANG_TIDY "Enable clang-tidy integration." OFF)
|
||||
option(PP_ENABLE_CPPCHECK "Enable cppcheck integration." OFF)
|
||||
option(PP_USE_VCPKG_TINYXML2 "Use the vcpkg tinyxml2 package for component targets." OFF)
|
||||
|
||||
set(PP_ANDROID_FLAVOR "standard" CACHE STRING "Android package flavor: standard, quest, or focus.")
|
||||
set_property(CACHE PP_ANDROID_FLAVOR PROPERTY STRINGS standard quest focus)
|
||||
44
cmake/PanoPainterPackageTargets.cmake
Normal file
44
cmake/PanoPainterPackageTargets.cmake
Normal file
@@ -0,0 +1,44 @@
|
||||
include(PanoPainterAutomation)
|
||||
|
||||
pp_add_powershell_automation_target(panopainter_package_readiness
|
||||
COMMENT "Report package readiness blockers."
|
||||
ARGUMENTS
|
||||
-File "${CMAKE_CURRENT_SOURCE_DIR}/scripts/automation/package-smoke.ps1"
|
||||
-ReadinessOnly)
|
||||
|
||||
pp_add_powershell_automation_target(panopainter_windows_app_package_smoke
|
||||
COMMENT "Build the Windows app artifact and report package readiness blockers."
|
||||
ARGUMENTS
|
||||
-File "${CMAKE_CURRENT_SOURCE_DIR}/scripts/automation/package-smoke.ps1"
|
||||
-Preset windows-msvc-default
|
||||
-Configuration Debug
|
||||
-Target PanoPainter
|
||||
-CMakeCommand "${CMAKE_COMMAND}")
|
||||
|
||||
pp_add_powershell_automation_target(panopainter_android_standard_native_package
|
||||
COMMENT "Build retained Android standard native package library."
|
||||
ARGUMENTS
|
||||
-File "${CMAKE_CURRENT_SOURCE_DIR}/scripts/automation/android-legacy-package-build.ps1"
|
||||
-Packages standard)
|
||||
|
||||
pp_add_powershell_automation_target(panopainter_android_vr_native_package_configure
|
||||
COMMENT "Configure retained Android Quest/Focus native package paths."
|
||||
ARGUMENTS
|
||||
-File "${CMAKE_CURRENT_SOURCE_DIR}/scripts/automation/android-legacy-package-build.ps1"
|
||||
-Packages quest,focus
|
||||
-ConfigureOnly)
|
||||
|
||||
pp_add_powershell_automation_target(panopainter_android_native_package_smoke
|
||||
COMMENT "Run retained Android native package checks through package-smoke."
|
||||
ARGUMENTS
|
||||
-File "${CMAKE_CURRENT_SOURCE_DIR}/scripts/automation/package-smoke.ps1"
|
||||
-ReadinessOnly
|
||||
-AndroidNativeChecks
|
||||
-PackageKinds android-standard-apk,android-quest-apk,android-focus-apk)
|
||||
|
||||
pp_add_powershell_automation_target(panopainter_linux_webgl_package_readiness
|
||||
COMMENT "Report retained Linux and WebGL package readiness blockers."
|
||||
ARGUMENTS
|
||||
-File "${CMAKE_CURRENT_SOURCE_DIR}/scripts/automation/package-smoke.ps1"
|
||||
-ReadinessOnly
|
||||
-PackageKinds linux-app,webgl)
|
||||
26
cmake/PanoPainterPlatformTargets.cmake
Normal file
26
cmake/PanoPainterPlatformTargets.cmake
Normal file
@@ -0,0 +1,26 @@
|
||||
include(PanoPainterAutomation)
|
||||
|
||||
pp_add_powershell_automation_target(panopainter_platform_build_headless
|
||||
COMMENT "Run the default headless platform build matrix."
|
||||
ARGUMENTS
|
||||
-File "${CMAKE_CURRENT_SOURCE_DIR}/scripts/automation/platform-build.ps1")
|
||||
|
||||
pp_add_powershell_automation_target(panopainter_platform_build_android_assets
|
||||
COMMENT "Build Android root CMake asset component across standard/VR presets."
|
||||
ARGUMENTS
|
||||
-File "${CMAKE_CURRENT_SOURCE_DIR}/scripts/automation/platform-build.ps1"
|
||||
-Presets android-arm64,android-x64,android-quest-arm64,android-focus-arm64
|
||||
-Targets pp_assets)
|
||||
|
||||
pp_add_powershell_automation_target(panopainter_platform_build_vcpkg_ui_core
|
||||
COMMENT "Build the Windows vcpkg-backed UI core dependency boundary."
|
||||
ARGUMENTS
|
||||
-File "${CMAKE_CURRENT_SOURCE_DIR}/scripts/automation/platform-build.ps1"
|
||||
-Presets windows-msvc-vcpkg-headless
|
||||
-Targets pp_ui_core,pp_ui_core_layout_xml_tests)
|
||||
|
||||
pp_add_powershell_automation_target(panopainter_platform_build_apple_remote
|
||||
COMMENT "Run remote Apple compile validation for macOS, iOS simulator, and iOS device."
|
||||
ARGUMENTS
|
||||
-File "${CMAKE_CURRENT_SOURCE_DIR}/scripts/automation/apple-remote-build.ps1"
|
||||
-Presets macos,ios-simulator,ios-device)
|
||||
29
cmake/PanoPainterRuntime.cmake
Normal file
29
cmake/PanoPainterRuntime.cmake
Normal file
@@ -0,0 +1,29 @@
|
||||
function(pp_configure_windows_runtime_payloads target_name)
|
||||
if(NOT TARGET "${target_name}")
|
||||
message(FATAL_ERROR "pp_configure_windows_runtime_payloads target does not exist: ${target_name}")
|
||||
endif()
|
||||
|
||||
add_custom_command(TARGET "${target_name}" POST_BUILD
|
||||
COMMAND "${CMAKE_COMMAND}" -E copy_directory
|
||||
"${CMAKE_CURRENT_SOURCE_DIR}/data"
|
||||
"$<TARGET_FILE_DIR:${target_name}>/data"
|
||||
COMMAND "${CMAKE_COMMAND}" -E copy_if_different
|
||||
"${CMAKE_CURRENT_SOURCE_DIR}/libs/bugtrap-client/lib/BugTrapU-x64.dll"
|
||||
"$<TARGET_FILE_DIR:${target_name}>/BugTrapU-x64.dll"
|
||||
COMMAND "${CMAKE_COMMAND}" -E copy_if_different
|
||||
"$<$<CONFIG:Debug>:${CMAKE_CURRENT_SOURCE_DIR}/libs/curl-win/lib/dll-debug-x64/libcurl_debug.dll>$<$<NOT:$<CONFIG:Debug>>:${CMAKE_CURRENT_SOURCE_DIR}/libs/curl-win/lib/dll-release-x64/libcurl.dll>"
|
||||
"$<TARGET_FILE_DIR:${target_name}>/"
|
||||
COMMAND "${CMAKE_COMMAND}" -E copy_if_different
|
||||
"${CMAKE_CURRENT_SOURCE_DIR}/libs/libyuv/lib/win/libyuv.dll"
|
||||
"$<TARGET_FILE_DIR:${target_name}>/libyuv.dll"
|
||||
COMMAND "${CMAKE_COMMAND}" -E copy_if_different
|
||||
"${CMAKE_CURRENT_SOURCE_DIR}/libs/mp4v2/lib/win/libmp4v2.dll"
|
||||
"$<TARGET_FILE_DIR:${target_name}>/libmp4v2.dll"
|
||||
COMMAND "${CMAKE_COMMAND}" -E copy_if_different
|
||||
"${CMAKE_CURRENT_SOURCE_DIR}/libs/openh264/lib/openh264-2.0.0-win64.dll"
|
||||
"$<TARGET_FILE_DIR:${target_name}>/openh264-2.0.0-win64.dll"
|
||||
COMMAND "${CMAKE_COMMAND}" -E copy_if_different
|
||||
"${CMAKE_CURRENT_SOURCE_DIR}/libs/openvr/bin/win64/openvr_api.dll"
|
||||
"$<TARGET_FILE_DIR:${target_name}>/openvr_api.dll"
|
||||
VERBATIM)
|
||||
endfunction()
|
||||
212
cmake/PanoPainterSources.cmake
Normal file
212
cmake/PanoPainterSources.cmake
Normal file
@@ -0,0 +1,212 @@
|
||||
set(PP_LEGACY_ENGINE_SOURCES
|
||||
src/hmd.cpp
|
||||
src/log.cpp
|
||||
src/mp4enc.cpp
|
||||
src/util.cpp
|
||||
src/wacom.cpp
|
||||
)
|
||||
|
||||
set(PP_LEGACY_ASSETS_IO_SOURCES
|
||||
src/abr.cpp
|
||||
src/asset.cpp
|
||||
src/binary_stream.cpp
|
||||
src/image.cpp
|
||||
src/serializer.cpp
|
||||
src/settings.cpp
|
||||
)
|
||||
|
||||
set(PP_LEGACY_PAINT_DOCUMENT_SOURCES
|
||||
src/action.cpp
|
||||
src/bezier.cpp
|
||||
src/brush.cpp
|
||||
src/canvas.cpp
|
||||
src/canvas_actions.cpp
|
||||
src/canvas_layer.cpp
|
||||
src/event.cpp
|
||||
)
|
||||
|
||||
set(PP_LEGACY_RENDERER_GL_SOURCES
|
||||
src/font.cpp
|
||||
src/rtt.cpp
|
||||
src/shader.cpp
|
||||
src/shape.cpp
|
||||
src/texture.cpp
|
||||
)
|
||||
|
||||
set(PP_LEGACY_UI_CORE_SOURCES
|
||||
src/layout.cpp
|
||||
src/node.cpp
|
||||
src/node_border.cpp
|
||||
src/node_button.cpp
|
||||
src/node_button_custom.cpp
|
||||
src/node_checkbox.cpp
|
||||
src/node_combobox.cpp
|
||||
src/node_icon.cpp
|
||||
src/node_image.cpp
|
||||
src/node_image_texture.cpp
|
||||
src/node_input_box.cpp
|
||||
src/node_popup_menu.cpp
|
||||
src/node_progress_bar.cpp
|
||||
src/node_remote_page.cpp
|
||||
src/node_scroll.cpp
|
||||
src/node_settings.cpp
|
||||
src/node_shorcuts.cpp
|
||||
src/node_slider.cpp
|
||||
src/node_text.cpp
|
||||
src/node_text_input.cpp
|
||||
)
|
||||
|
||||
set(PP_LEGACY_APP_SOURCES
|
||||
src/canvas_modes.cpp
|
||||
src/legacy_app_shell_services.cpp
|
||||
src/legacy_app_shell_services.h
|
||||
src/legacy_canvas_tool_services.cpp
|
||||
src/legacy_canvas_tool_services.h
|
||||
src/legacy_canvas_view_services.cpp
|
||||
src/legacy_canvas_view_services.h
|
||||
src/legacy_document_canvas_services.cpp
|
||||
src/legacy_document_canvas_services.h
|
||||
src/legacy_document_layer_services.cpp
|
||||
src/legacy_document_layer_services.h
|
||||
src/legacy_history_services.cpp
|
||||
src/legacy_history_services.h
|
||||
src/legacy_recording_services.cpp
|
||||
src/legacy_recording_services.h
|
||||
src/legacy_ui_overlay_services.cpp
|
||||
src/legacy_ui_overlay_services.h
|
||||
src/pch.cpp
|
||||
)
|
||||
|
||||
set(PP_PANOPAINTER_APP_SOURCES
|
||||
src/app.cpp
|
||||
src/app_cloud.cpp
|
||||
src/app_commands.cpp
|
||||
src/app_dialogs.cpp
|
||||
src/app_events.cpp
|
||||
src/app_layout.cpp
|
||||
src/app_shaders.cpp
|
||||
src/app_vr.cpp
|
||||
src/legacy_app_dialog_services.cpp
|
||||
src/legacy_app_dialog_services.h
|
||||
src/legacy_app_preference_services.cpp
|
||||
src/legacy_app_preference_services.h
|
||||
src/legacy_app_startup_services.cpp
|
||||
src/legacy_app_startup_services.h
|
||||
src/legacy_brush_package_import_services.cpp
|
||||
src/legacy_brush_package_import_services.h
|
||||
src/legacy_brush_package_export_services.cpp
|
||||
src/legacy_brush_package_export_services.h
|
||||
src/legacy_cloud_services.cpp
|
||||
src/legacy_cloud_services.h
|
||||
src/legacy_document_export_services.cpp
|
||||
src/legacy_document_export_services.h
|
||||
src/legacy_document_open_services.cpp
|
||||
src/legacy_document_open_services.h
|
||||
src/legacy_document_session_services.cpp
|
||||
src/legacy_document_session_services.h
|
||||
src/platform_legacy/legacy_platform_services.cpp
|
||||
src/platform_legacy/legacy_platform_services.h
|
||||
src/version.cpp
|
||||
)
|
||||
|
||||
set(PP_PANOPAINTER_UI_SOURCES
|
||||
src/legacy_brush_ui_services.cpp
|
||||
src/legacy_brush_ui_services.h
|
||||
src/legacy_document_animation_services.cpp
|
||||
src/legacy_document_animation_services.h
|
||||
src/legacy_grid_ui_services.cpp
|
||||
src/legacy_grid_ui_services.h
|
||||
src/legacy_quick_ui_services.cpp
|
||||
src/legacy_quick_ui_services.h
|
||||
src/node_about.cpp
|
||||
src/node_canvas.cpp
|
||||
src/node_changelog.cpp
|
||||
src/node_color_quad.cpp
|
||||
src/node_colorwheel.cpp
|
||||
src/node_dialog_browse.cpp
|
||||
src/node_dialog_cloud.cpp
|
||||
src/node_dialog_export_ppbr.cpp
|
||||
src/node_dialog_layer_rename.cpp
|
||||
src/node_dialog_open.cpp
|
||||
src/node_dialog_picker.cpp
|
||||
src/node_dialog_resize.cpp
|
||||
src/node_message_box.cpp
|
||||
src/node_metadata.cpp
|
||||
src/node_panel_animation.cpp
|
||||
src/node_panel_brush.cpp
|
||||
src/node_panel_color.cpp
|
||||
src/node_panel_floating.cpp
|
||||
src/node_panel_grid.cpp
|
||||
src/node_panel_layer.cpp
|
||||
src/node_panel_quick.cpp
|
||||
src/node_panel_stroke.cpp
|
||||
src/node_stroke_preview.cpp
|
||||
src/node_tool_bucket.cpp
|
||||
src/node_usermanual.cpp
|
||||
src/node_viewport.cpp
|
||||
)
|
||||
|
||||
set(PP_WINDOWS_PLATFORM_SOURCES
|
||||
src/main.cpp
|
||||
src/platform_windows/windows_platform_services.cpp
|
||||
src/platform_windows/windows_platform_services.h
|
||||
)
|
||||
|
||||
set(PP_WINDOWS_APP_SOURCES
|
||||
PanoPainter.rc
|
||||
)
|
||||
|
||||
set(PP_VENDOR_SOURCES
|
||||
libs/fmt/src/format.cc
|
||||
libs/fmt/src/posix.cc
|
||||
libs/glad/src/glad.c
|
||||
libs/glad/src/glad_wgl.c
|
||||
libs/hash-library/md5.cpp
|
||||
libs/jpeg/jpgd.cpp
|
||||
libs/jpeg/jpge.cpp
|
||||
libs/nanort/nanort.cc
|
||||
libs/poly2tri/poly2tri/common/shapes.cc
|
||||
libs/poly2tri/poly2tri/sweep/advancing_front.cc
|
||||
libs/poly2tri/poly2tri/sweep/cdt.cc
|
||||
libs/poly2tri/poly2tri/sweep/sweep.cc
|
||||
libs/poly2tri/poly2tri/sweep/sweep_context.cc
|
||||
libs/sqlite3/sqlite3.c
|
||||
libs/tinyxml2/tinyxml2.cpp
|
||||
libs/wacom/WinTab/Utils.cpp
|
||||
libs/yoga/yoga/event/event.cpp
|
||||
libs/yoga/yoga/internal/experiments.cpp
|
||||
libs/yoga/yoga/log.cpp
|
||||
libs/yoga/yoga/Utils.cpp
|
||||
libs/yoga/yoga/YGConfig.cpp
|
||||
libs/yoga/yoga/YGEnums.cpp
|
||||
libs/yoga/yoga/YGLayout.cpp
|
||||
libs/yoga/yoga/YGNode.cpp
|
||||
libs/yoga/yoga/YGNodePrint.cpp
|
||||
libs/yoga/yoga/YGStyle.cpp
|
||||
libs/yoga/yoga/YGValue.cpp
|
||||
libs/yoga/yoga/Yoga.cpp
|
||||
)
|
||||
|
||||
set(PP_LEGACY_INCLUDE_DIRS
|
||||
"${CMAKE_CURRENT_SOURCE_DIR}/src"
|
||||
"${CMAKE_CURRENT_SOURCE_DIR}"
|
||||
"${CMAKE_CURRENT_SOURCE_DIR}/libs/base64"
|
||||
"${CMAKE_CURRENT_SOURCE_DIR}/libs/bugtrap-client/include"
|
||||
"${CMAKE_CURRENT_SOURCE_DIR}/libs/curl-win/include"
|
||||
"${CMAKE_CURRENT_SOURCE_DIR}/libs/fmt/include"
|
||||
"${CMAKE_CURRENT_SOURCE_DIR}/libs/glad/include"
|
||||
"${CMAKE_CURRENT_SOURCE_DIR}/libs/glm"
|
||||
"${CMAKE_CURRENT_SOURCE_DIR}/libs/hash-library"
|
||||
"${CMAKE_CURRENT_SOURCE_DIR}/libs/jpeg"
|
||||
"${CMAKE_CURRENT_SOURCE_DIR}/libs/libyuv/include"
|
||||
"${CMAKE_CURRENT_SOURCE_DIR}/libs/mp4v2/include"
|
||||
"${CMAKE_CURRENT_SOURCE_DIR}/libs/nanort"
|
||||
"${CMAKE_CURRENT_SOURCE_DIR}/libs/openh264/include"
|
||||
"${CMAKE_CURRENT_SOURCE_DIR}/libs/openvr/headers"
|
||||
"${CMAKE_CURRENT_SOURCE_DIR}/libs/poly2tri/poly2tri"
|
||||
"${CMAKE_CURRENT_SOURCE_DIR}/libs/sqlite3"
|
||||
"${CMAKE_CURRENT_SOURCE_DIR}/libs/stb"
|
||||
"${CMAKE_CURRENT_SOURCE_DIR}/libs/tinyxml2"
|
||||
"${CMAKE_CURRENT_SOURCE_DIR}/libs/wacom"
|
||||
"${CMAKE_CURRENT_SOURCE_DIR}/libs/yoga"
|
||||
)
|
||||
17
cmake/PanoPainterVersion.cmake
Normal file
17
cmake/PanoPainterVersion.cmake
Normal file
@@ -0,0 +1,17 @@
|
||||
function(pp_add_version_generation target config_name)
|
||||
find_package(Python3 COMPONENTS Interpreter REQUIRED)
|
||||
|
||||
add_custom_command(
|
||||
OUTPUT "${CMAKE_CURRENT_SOURCE_DIR}/src/version.gen.h"
|
||||
COMMAND "${Python3_EXECUTABLE}" "${CMAKE_CURRENT_SOURCE_DIR}/scripts/pre-build.py" "${config_name}"
|
||||
WORKING_DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}"
|
||||
DEPENDS "${CMAKE_CURRENT_SOURCE_DIR}/scripts/pre-build.py"
|
||||
COMMENT "Generating src/version.gen.h"
|
||||
VERBATIM)
|
||||
|
||||
add_custom_target(pp_generate_version
|
||||
DEPENDS "${CMAKE_CURRENT_SOURCE_DIR}/src/version.gen.h")
|
||||
|
||||
add_dependencies(${target} pp_generate_version)
|
||||
endfunction()
|
||||
|
||||
44
cmake/PanoPainterWarnings.cmake
Normal file
44
cmake/PanoPainterWarnings.cmake
Normal file
@@ -0,0 +1,44 @@
|
||||
function(pp_configure_project_warnings target)
|
||||
if(MSVC)
|
||||
target_compile_options(${target} INTERFACE
|
||||
/W4
|
||||
/permissive-
|
||||
/Zc:__cplusplus
|
||||
/Zc:preprocessor
|
||||
# DEBT-0019: remove once legacy callback/interface parameters are either named intentionally or consumed.
|
||||
/wd4100)
|
||||
if(PP_ENABLE_MSVC_ANALYZE)
|
||||
target_compile_options(${target} INTERFACE /analyze)
|
||||
endif()
|
||||
else()
|
||||
target_compile_options(${target} INTERFACE
|
||||
-Wall
|
||||
-Wextra
|
||||
-Wpedantic
|
||||
-Wconversion
|
||||
-Wshadow
|
||||
-Wnull-dereference
|
||||
# DEBT-0019: remove once legacy callback/interface parameters are either named intentionally or consumed.
|
||||
-Wno-unused-parameter)
|
||||
endif()
|
||||
|
||||
if(PP_ENABLE_ASAN)
|
||||
if(MSVC)
|
||||
target_compile_options(${target} INTERFACE /fsanitize=address)
|
||||
target_link_options(${target} INTERFACE /fsanitize=address)
|
||||
else()
|
||||
target_compile_options(${target} INTERFACE -fsanitize=address)
|
||||
target_link_options(${target} INTERFACE -fsanitize=address)
|
||||
endif()
|
||||
endif()
|
||||
|
||||
if(PP_ENABLE_UBSAN AND NOT MSVC)
|
||||
target_compile_options(${target} INTERFACE -fsanitize=undefined)
|
||||
target_link_options(${target} INTERFACE -fsanitize=undefined)
|
||||
endif()
|
||||
|
||||
if(PP_ENABLE_TSAN AND NOT MSVC)
|
||||
target_compile_options(${target} INTERFACE -fsanitize=thread)
|
||||
target_link_options(${target} INTERFACE -fsanitize=thread)
|
||||
endif()
|
||||
endfunction()
|
||||
75
cmake/ValidatePanoPainterShaders.cmake
Normal file
75
cmake/ValidatePanoPainterShaders.cmake
Normal file
@@ -0,0 +1,75 @@
|
||||
if(NOT DEFINED PP_SHADER_DIR)
|
||||
message(FATAL_ERROR "PP_SHADER_DIR is required")
|
||||
endif()
|
||||
|
||||
file(REAL_PATH "${PP_SHADER_DIR}" pp_shader_dir)
|
||||
if(NOT IS_DIRECTORY "${pp_shader_dir}")
|
||||
message(FATAL_ERROR "Shader directory does not exist: ${pp_shader_dir}")
|
||||
endif()
|
||||
|
||||
file(GLOB_RECURSE pp_shader_files
|
||||
"${pp_shader_dir}/*.glsl")
|
||||
|
||||
if(NOT pp_shader_files)
|
||||
message(FATAL_ERROR "No shader files found under: ${pp_shader_dir}")
|
||||
endif()
|
||||
|
||||
set(pp_shader_errors "")
|
||||
set(pp_top_level_count 0)
|
||||
set(pp_include_count 0)
|
||||
|
||||
foreach(pp_shader_file IN LISTS pp_shader_files)
|
||||
file(RELATIVE_PATH pp_shader_rel "${pp_shader_dir}" "${pp_shader_file}")
|
||||
file(READ "${pp_shader_file}" pp_shader_contents)
|
||||
|
||||
string(REGEX MATCHALL "#[ \t]*include[ \t]+\"[^\"]+\"" pp_include_lines "${pp_shader_contents}")
|
||||
foreach(pp_include_line IN LISTS pp_include_lines)
|
||||
string(REGEX REPLACE ".*\"([^\"]+)\".*" "\\1" pp_include_path "${pp_include_line}")
|
||||
if(pp_include_path MATCHES "^/")
|
||||
list(APPEND pp_shader_errors "${pp_shader_rel}: include path must be relative: ${pp_include_path}")
|
||||
endif()
|
||||
if(pp_include_path MATCHES "^[A-Za-z]:")
|
||||
list(APPEND pp_shader_errors "${pp_shader_rel}: include path must not be drive-absolute: ${pp_include_path}")
|
||||
endif()
|
||||
if(pp_include_path MATCHES "\\.\\.")
|
||||
list(APPEND pp_shader_errors "${pp_shader_rel}: include path must not traverse parent directories: ${pp_include_path}")
|
||||
endif()
|
||||
if(NOT EXISTS "${pp_shader_dir}/${pp_include_path}")
|
||||
list(APPEND pp_shader_errors "${pp_shader_rel}: missing include: ${pp_include_path}")
|
||||
endif()
|
||||
endforeach()
|
||||
|
||||
if(pp_shader_rel MATCHES "^include/")
|
||||
math(EXPR pp_include_count "${pp_include_count} + 1")
|
||||
if(pp_shader_contents MATCHES "\\[\\[(vertex|fragment)\\]\\]")
|
||||
list(APPEND pp_shader_errors "${pp_shader_rel}: include shaders must not declare stage markers")
|
||||
endif()
|
||||
else()
|
||||
math(EXPR pp_top_level_count "${pp_top_level_count} + 1")
|
||||
|
||||
string(REGEX MATCHALL "\\[\\[vertex\\]\\]" pp_vertex_markers "${pp_shader_contents}")
|
||||
string(REGEX MATCHALL "\\[\\[fragment\\]\\]" pp_fragment_markers "${pp_shader_contents}")
|
||||
list(LENGTH pp_vertex_markers pp_vertex_count)
|
||||
list(LENGTH pp_fragment_markers pp_fragment_count)
|
||||
|
||||
if(NOT pp_vertex_count EQUAL 1)
|
||||
list(APPEND pp_shader_errors "${pp_shader_rel}: expected exactly one [[vertex]] marker")
|
||||
endif()
|
||||
if(NOT pp_fragment_count EQUAL 1)
|
||||
list(APPEND pp_shader_errors "${pp_shader_rel}: expected exactly one [[fragment]] marker")
|
||||
endif()
|
||||
|
||||
string(FIND "${pp_shader_contents}" "[[vertex]]" pp_vertex_pos)
|
||||
string(FIND "${pp_shader_contents}" "[[fragment]]" pp_fragment_pos)
|
||||
if(pp_vertex_pos GREATER_EQUAL 0 AND pp_fragment_pos GREATER_EQUAL 0 AND NOT pp_vertex_pos LESS pp_fragment_pos)
|
||||
list(APPEND pp_shader_errors "${pp_shader_rel}: [[vertex]] marker must appear before [[fragment]]")
|
||||
endif()
|
||||
endif()
|
||||
endforeach()
|
||||
|
||||
if(pp_shader_errors)
|
||||
list(JOIN pp_shader_errors "\n" pp_shader_error_text)
|
||||
message(FATAL_ERROR "Shader validation failed:\n${pp_shader_error_text}")
|
||||
endif()
|
||||
|
||||
message(STATUS "Validated ${pp_top_level_count} shader programs and ${pp_include_count} shader includes under ${pp_shader_dir}")
|
||||
49
docs/adr/0001-modernization-boundaries.md
Normal file
49
docs/adr/0001-modernization-boundaries.md
Normal file
@@ -0,0 +1,49 @@
|
||||
# ADR 0001: Incremental Component Boundaries
|
||||
|
||||
Status: accepted
|
||||
Date: 2026-05-31
|
||||
|
||||
## Context
|
||||
|
||||
PanoPainter currently has a flat `src/` layout with broad dependencies through
|
||||
`pch.h`, global singletons such as `App::I` and `Canvas::I`, OpenGL types in
|
||||
high-level painting/document headers, and duplicated platform source lists.
|
||||
The modernization work must retain existing behavior across Windows desktop
|
||||
and AppX, macOS, iOS, Android standard, Quest, Focus/Wave, Linux, and WebGL.
|
||||
|
||||
## Decision
|
||||
|
||||
Modernization will proceed incrementally. OpenGL remains the production
|
||||
renderer while component boundaries and tests are introduced. Vulkan, Metal,
|
||||
and WebGPU-related work must stay out of the production path until OpenGL
|
||||
parity tests exist.
|
||||
|
||||
The target dependency direction is:
|
||||
|
||||
```text
|
||||
pp_foundation
|
||||
-> pp_assets
|
||||
-> pp_paint
|
||||
-> pp_document
|
||||
-> pp_renderer_api
|
||||
-> pp_renderer_gl
|
||||
-> pp_paint_renderer
|
||||
-> pp_ui_core
|
||||
-> pp_panopainter_ui
|
||||
-> pp_platform_*
|
||||
-> panopainter_app
|
||||
```
|
||||
|
||||
Pure component headers must not include platform SDK headers or graphics API
|
||||
headers. Temporary shims are allowed only when recorded in
|
||||
`docs/modernization/debt.md`.
|
||||
|
||||
## Consequences
|
||||
|
||||
- The first implementation steps are documentation, inventory, CMake skeleton,
|
||||
diagnostics, and tests, not a renderer rewrite.
|
||||
- Existing project files remain until the shared CMake targets are validated.
|
||||
- Refactors should prefer additive compatibility layers before moving behavior.
|
||||
- Every extracted component must gain its own tests before the next component
|
||||
boundary is extracted.
|
||||
|
||||
1338
docs/modernization/build-inventory.md
Normal file
1338
docs/modernization/build-inventory.md
Normal file
File diff suppressed because it is too large
Load Diff
85
docs/modernization/capability-map.md
Normal file
85
docs/modernization/capability-map.md
Normal file
@@ -0,0 +1,85 @@
|
||||
# PanoPainter Capability Map
|
||||
|
||||
Status: live
|
||||
Last updated: 2026-06-05
|
||||
|
||||
This map is the preservation checklist for the modernization. When a component
|
||||
is extracted, update the relevant rows with the owning component, test label,
|
||||
and validation command.
|
||||
|
||||
## Project And Documents
|
||||
|
||||
| Capability | Current Area | Target Owner | Required Tests |
|
||||
| --- | --- | --- | --- |
|
||||
| PPI open/save | `Canvas`, serializer, dialogs | `pp_document`, `pp_assets`, `pano_cli` | Round-trip tiny project, old-version fixture, corrupt/truncated fixture, live-canvas-to-`pp_document` snapshot projection with captured RGBA8 payloads, pending renderer-readback counts, save-readiness reporting before retained live saves, pure PPI export from payload-complete snapshots, and shared paint-renderer export-readiness reporting from the same snapshot |
|
||||
| Open-document routing | `App::open_document` | `pp_app_core`, `pano_cli`, `pp_panopainter_ui`, `pp_document`, `pp_assets` | Project/ABR/PPBR route tests, malformed path tests, open-action plan tests, CLI route/action smoke, app open smoke |
|
||||
| Document session decisions | `App::open_document`, `App::request_close`, save hotkeys, file menu, dialogs | `pp_app_core`, `pano_cli`, `pp_panopainter_ui` | Clean/dirty/prompt-open/save/save-as/save-version/save-before-workflow/name/new-document resolution/overwrite/version-target decision tests, CLI session, new-document, document-file, and document-version smoke, app close/open/save/new/browse smoke |
|
||||
| Version metadata | `scripts/pre-build.py`, `version.*` | build system, `pp_foundation` | Generated header smoke test, missing-tag behavior |
|
||||
| Thumbnail generation/read | `Canvas`, `Image` | `pp_assets`, `pp_paint_renderer` | Golden thumbnail, corrupt input, destination-feedback copy/fetch gate |
|
||||
| Save-as, overwrite prompts | App/dialogs | `pp_app_core`, `pp_panopainter_ui`, `pp_platform_*` | Decision tests, UI automation, and platform smoke |
|
||||
| App status and renderer diagnostics | App title/status widgets, extension indicators | `pp_app_core`, `pp_renderer_api`, `pp_panopainter_ui` | Title/status text tests, renderer diagnostic indicator tests, CLI status smoke, live layout adapter smoke |
|
||||
|
||||
## Image And Export
|
||||
|
||||
| Capability | Current Area | Target Owner | Required Tests |
|
||||
| --- | --- | --- | --- |
|
||||
| PNG/JPEG import | `Image`, `Canvas` import paths | `pp_assets`, `pp_document` | Fixture import, malformed file |
|
||||
| PNG/JPEG export | `Canvas`, `Image`, export dialogs | `pp_assets`, `pp_paint_renderer`, `pp_app_core` | Golden output tolerance, export start/target planning tests, live export-adapter document snapshot readiness through the shared paint-renderer export report, pure cube-face PNG writer, pure equirectangular PNG/JPEG+XMP writers, pure layer/frame collection PNG writers, app-core collection write executor, retained fallback coverage |
|
||||
| Equirectangular import/export | `Canvas`, shaders, RTT, export dialogs | `pp_paint_renderer`, `pp_app_core` | Tiny cube/equirect golden, app-core file target tests, live export-adapter renderer-upload/face-PNG readiness report, pure document-frame equirectangular PNG and JPEG+XMP export with live writer fallback, pure layer/frame equirectangular PNG collection export, exact GPU/golden parity |
|
||||
| Cube face export | `Canvas` fallback | `pp_paint_renderer`, `pp_app_core` | Pure six-face document frame composite, renderer texture-upload bridge, shared export-readiness report, app-core face filename planning and write/publish service execution, payload-complete canvas-snapshot renderer-upload and face-PNG automation, live document/renderer face-PNG writer with retained Canvas fallback, OpenGL command-plan coverage, six-face golden set |
|
||||
| Depth export | `Canvas`, grid tools | `pp_paint_renderer`, `pp_app_core` | Depth target/write planning, document-snapshot renderer-readiness logging, depth render-plan draw/readback counts, retained render/readback parity, and format/golden validation |
|
||||
|
||||
## Brush And Painting
|
||||
|
||||
| Capability | Current Area | Target Owner | Required Tests |
|
||||
| --- | --- | --- | --- |
|
||||
| Brush settings serialization and stroke-panel controls | `Brush`, `Serializer`, `NodePanelStroke` | `pp_paint`, `pp_assets`, `pp_app_core`, `pp_panopainter_ui` | Round-trip and boundary values; stroke slider/toggle/blend/reset planning and invalid setting tests |
|
||||
| ABR import | `ABR`, `Brush` | `pp_assets`, `pp_paint` | Sample ABR and malformed ABR |
|
||||
| PPBR import/export | brush panel/dialog | `pp_assets`, `pp_panopainter_ui` | Round-trip fixture |
|
||||
| Stroke sampling | `Stroke`, `Canvas` | `pp_paint` | Property tests for spacing, pressure, jitter |
|
||||
| Dual brush/pattern behavior | `Brush`, shaders | `pp_paint`, `pp_paint_renderer` | Stroke-alpha CPU reference, dual/pattern feedback planning, GPU golden |
|
||||
| Blend modes | GLSL include files, layer rendering | `pp_paint`, `pp_paint_renderer` | Final RGBA and stroke-alpha CPU reference vectors, pure `pp_document` face and six-face frame compositing plus renderer texture upload/OpenGL command-plan coverage, fixed-function/framebuffer-fetch/ping-pong stroke composite planning, live `Canvas`/`NodeCanvas` blend-gate coverage, live canvas stroke/thumbnail/brush-preview destination-copy coverage, and GPU parity |
|
||||
| Erase/flood fill/masks | `Canvas`, modes, shaders | `pp_document`, `pp_paint_renderer` | Edge masks, alpha lock, dirty rects |
|
||||
|
||||
## Layers And Animation
|
||||
|
||||
| Capability | Current Area | Target Owner | Required Tests |
|
||||
| --- | --- | --- | --- |
|
||||
| Layer rename/add/remove/move/merge | `Canvas`, `Layer`, actions | `pp_document`, `pp_app_core` | Rename and operation planning, service-dispatch, no-op preservation, undo/redo invariant tests |
|
||||
| Blend/opacity/visibility/alpha lock | `Layer`, UI panels, shaders | `pp_document`, `pp_app_core`, `pp_paint_renderer` | Metadata planning, service-dispatch, live-canvas-to-`pp_document` snapshot projection, CPU model and render golden |
|
||||
| Selection mask | `Canvas` mask layer | `pp_document`, `pp_paint_renderer` | Mask apply/clear edge cases |
|
||||
| Animation frames | `LayerFrame`, animation panel | `pp_document`, `pp_panopainter_ui` | Duration, duplicate, remove, seek |
|
||||
| MP4/timelapse export | `MP4Encoder`, recording thread, export dialogs | `pp_assets`, `pp_paint_renderer`, `pp_app_core`, app | Recording lifecycle/progress decision tests, smoke export, cancellation, suggested-name tests |
|
||||
|
||||
## UI And Workflow
|
||||
|
||||
| Capability | Current Area | Target Owner | Required Tests |
|
||||
| --- | --- | --- | --- |
|
||||
| XML layout parsing | `LayoutManager`, `Node` | `pp_ui_core` | Layout fixtures and malformed XML |
|
||||
| Yoga layout | `Node` | `pp_ui_core` | Deterministic geometry fixtures |
|
||||
| Generic controls | `NodeButton`, sliders, text, images | `pp_ui_core` | Event dispatch, layout, ownership-handle, callback-disconnect, and destroy-during-callback tests |
|
||||
| PanoPainter panels/dialogs | `NodePanel*`, `NodeDialog*` | `pp_panopainter_ui`, `pp_ui_core` | UI automation scripts, command-dispatch view models, pure overlay lifetime tests, retained overlay-adapter build coverage, retained popup/dialog lifetime tests |
|
||||
| Canvas viewport UI | `NodeCanvas` | `pp_panopainter_ui`, `pp_paint_renderer` | Input-to-command automation |
|
||||
| Settings UI | `Settings`, `NodeSettings` | `pp_assets`, `pp_panopainter_ui` | Round-trip settings |
|
||||
|
||||
## Input, Platform, And Devices
|
||||
|
||||
| Capability | Current Area | Target Owner | Required Tests |
|
||||
| --- | --- | --- | --- |
|
||||
| Mouse/keyboard/touch/gestures/cursor | `App`, platform entrypoints | `pp_app_core`, `pp_platform_api`, `pp_platform_*`, app | Cursor visibility decision tests, platform service dispatch tests, synthetic event playback |
|
||||
| Wacom pressure | `WacomTablet` | `pp_platform_windows` | Adapter smoke with fallback |
|
||||
| Clipboard/file picker/share/display | `App` platform methods | `pp_app_core`, `pp_platform_api`, `pp_platform_*` | Clipboard read/write, share saved-path, picked-path, and display-file decision tests, platform service display/share/picker dispatch tests, platform smoke or mocked service |
|
||||
| Virtual keyboard | `App`, platform entrypoints | `pp_app_core`, `pp_platform_api`, `pp_platform_*` | Keyboard visibility decision tests, platform service dispatch tests, platform smoke |
|
||||
| Desktop XR | `HMD`, `Vive`, `app_vr`, retained OpenVR bridge | `pp_platform_vr`, app with OpenXR backend | Runtime-selection policy tests, compile gate, and mocked pose tests |
|
||||
| Quest/OVR | Android Quest files | `pp_platform_android_quest` | Compile/package gate |
|
||||
| Focus/Wave | Android Focus files | `pp_platform_android_wave` | Compile/package gate |
|
||||
|
||||
## Cloud, Logging, And Automation
|
||||
|
||||
| Capability | Current Area | Target Owner | Required Tests |
|
||||
| --- | --- | --- | --- |
|
||||
| Upload/download/browse | `app_cloud`, CURL helpers | `pp_app_core`, app service, `pp_platform_*` | Upload prompt/new-doc/no-canvas decision tests, bulk-upload progress decision tests, browse/selection decision tests, mocked HTTP and timeout tests |
|
||||
| License/check flows | app/cloud code | app service | Mocked response tests |
|
||||
| Logging/crash reporting | `log`, BugTrap/AppCenter | `pp_foundation`, platform wrappers | Log formatting and platform compile |
|
||||
| Headless automation | none yet | `tools/pano_cli` | JSON command fixtures |
|
||||
| Tracing | none yet | `pp_foundation` | Span nesting/timing tests |
|
||||
761
docs/modernization/debt.md
Normal file
761
docs/modernization/debt.md
Normal file
File diff suppressed because one or more lines are too long
2936
docs/modernization/roadmap.md
Normal file
2936
docs/modernization/roadmap.md
Normal file
File diff suppressed because it is too large
Load Diff
@@ -62,7 +62,7 @@ BOOL LoadWintab( void )
|
||||
// ghWintab = LoadLibraryA( "C:\\dev\\mainline\\Wacom\\Win\\Win32\\Debug\\Wacom_Tablet.dll" );
|
||||
// ghWintab = LoadLibraryA( "C:\\dev\\mainline\\Wacom\\Win\\Win32\\Debug\\Wintab32.dll" );
|
||||
LOG("calling LoadLibrary");
|
||||
ghWintab = LoadLibrary(L"Wintab32.dll");
|
||||
ghWintab = LoadLibraryW(L"Wintab32.dll");
|
||||
LOG("LoadLibrary called");
|
||||
|
||||
if ( !ghWintab )
|
||||
|
||||
@@ -1,7 +1,5 @@
|
||||
cmake_minimum_required(VERSION 3.4.1)
|
||||
project(panopainter)
|
||||
|
||||
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++14")
|
||||
cmake_minimum_required(VERSION 3.10)
|
||||
project(PanoPainterLinux LANGUAGES C CXX)
|
||||
|
||||
add_executable(panopainter
|
||||
src/main.cpp
|
||||
@@ -120,4 +118,7 @@ target_include_directories(panopainter PRIVATE
|
||||
)
|
||||
|
||||
target_link_libraries(panopainter glfw curl GL dl X11 pthread)
|
||||
target_compile_features(panopainter PRIVATE cxx_std_23)
|
||||
set_target_properties(panopainter PROPERTIES
|
||||
CXX_EXTENSIONS OFF)
|
||||
target_compile_definitions(panopainter PUBLIC "$<$<CONFIG:DEBUG>:_DEBUG>")
|
||||
|
||||
65
scripts/automation/analyze.ps1
Normal file
65
scripts/automation/analyze.ps1
Normal file
@@ -0,0 +1,65 @@
|
||||
[CmdletBinding()]
|
||||
param(
|
||||
[string]$Preset = "windows-msvc-default",
|
||||
[switch]$NoApp
|
||||
)
|
||||
|
||||
$ErrorActionPreference = "Stop"
|
||||
$started = Get-Date
|
||||
$argsList = @(
|
||||
"--preset", $Preset,
|
||||
"-DPP_ENABLE_MSVC_ANALYZE=ON",
|
||||
"-DPP_ENABLE_CLANG_TIDY=ON",
|
||||
"-DPP_ENABLE_CPPCHECK=ON"
|
||||
)
|
||||
if ($NoApp) {
|
||||
$argsList += "-DPP_BUILD_APP=OFF"
|
||||
}
|
||||
|
||||
& cmake @argsList
|
||||
$configureExitCode = $LASTEXITCODE
|
||||
$shaderExitCode = 0
|
||||
$rendererBoundaryExitCode = 0
|
||||
|
||||
if ($configureExitCode -eq 0) {
|
||||
& cmake --build --preset $Preset --target panopainter_validate_shaders
|
||||
$shaderExitCode = $LASTEXITCODE
|
||||
}
|
||||
|
||||
if ($configureExitCode -eq 0) {
|
||||
& powershell -ExecutionPolicy Bypass -File (Join-Path $PSScriptRoot "check-renderer-boundary.ps1")
|
||||
$rendererBoundaryExitCode = $LASTEXITCODE
|
||||
}
|
||||
|
||||
$exitCode = $configureExitCode
|
||||
if ($exitCode -eq 0 -and $shaderExitCode -ne 0) {
|
||||
$exitCode = $shaderExitCode
|
||||
}
|
||||
if ($exitCode -eq 0 -and $rendererBoundaryExitCode -ne 0) {
|
||||
$exitCode = $rendererBoundaryExitCode
|
||||
}
|
||||
|
||||
$elapsed = [int]((Get-Date) - $started).TotalMilliseconds
|
||||
|
||||
[ordered]@{
|
||||
command = "analyze"
|
||||
preset = $Preset
|
||||
exitCode = $exitCode
|
||||
checks = @(
|
||||
[ordered]@{
|
||||
name = "configure"
|
||||
exitCode = $configureExitCode
|
||||
},
|
||||
[ordered]@{
|
||||
name = "shader-validation"
|
||||
exitCode = $shaderExitCode
|
||||
},
|
||||
[ordered]@{
|
||||
name = "renderer-boundary"
|
||||
exitCode = $rendererBoundaryExitCode
|
||||
}
|
||||
)
|
||||
elapsedMs = $elapsed
|
||||
} | ConvertTo-Json -Compress -Depth 4
|
||||
|
||||
exit $exitCode
|
||||
29
scripts/automation/analyze.sh
Normal file
29
scripts/automation/analyze.sh
Normal file
@@ -0,0 +1,29 @@
|
||||
#!/usr/bin/env sh
|
||||
set -u
|
||||
|
||||
script_dir="$(CDPATH= cd -- "$(dirname -- "$0")" && pwd)"
|
||||
preset="${1:-linux-clang}"
|
||||
start="$(date +%s)"
|
||||
cmake --preset "$preset" -DPP_ENABLE_CLANG_TIDY=ON -DPP_ENABLE_CPPCHECK=ON
|
||||
configure_exit_code="$?"
|
||||
shader_exit_code="0"
|
||||
renderer_boundary_exit_code="0"
|
||||
if [ "$configure_exit_code" -eq 0 ]; then
|
||||
cmake --build --preset "$preset" --target panopainter_validate_shaders
|
||||
shader_exit_code="$?"
|
||||
fi
|
||||
if [ "$configure_exit_code" -eq 0 ]; then
|
||||
"$script_dir/check-renderer-boundary.sh"
|
||||
renderer_boundary_exit_code="$?"
|
||||
fi
|
||||
exit_code="$configure_exit_code"
|
||||
if [ "$exit_code" -eq 0 ] && [ "$shader_exit_code" -ne 0 ]; then
|
||||
exit_code="$shader_exit_code"
|
||||
fi
|
||||
if [ "$exit_code" -eq 0 ] && [ "$renderer_boundary_exit_code" -ne 0 ]; then
|
||||
exit_code="$renderer_boundary_exit_code"
|
||||
fi
|
||||
end="$(date +%s)"
|
||||
elapsed_ms="$(( (end - start) * 1000 ))"
|
||||
printf '{"command":"analyze","preset":"%s","exitCode":%s,"checks":[{"name":"configure","exitCode":%s},{"name":"shader-validation","exitCode":%s},{"name":"renderer-boundary","exitCode":%s}],"elapsedMs":%s}\n' "$preset" "$exit_code" "$configure_exit_code" "$shader_exit_code" "$renderer_boundary_exit_code" "$elapsed_ms"
|
||||
exit "$exit_code"
|
||||
103
scripts/automation/android-legacy-package-build.ps1
Normal file
103
scripts/automation/android-legacy-package-build.ps1
Normal file
@@ -0,0 +1,103 @@
|
||||
[CmdletBinding()]
|
||||
param(
|
||||
[string[]]$Packages = @("standard"),
|
||||
[switch]$ConfigureOnly
|
||||
)
|
||||
|
||||
$ErrorActionPreference = "Stop"
|
||||
|
||||
. "$PSScriptRoot\android-sdk-env.ps1"
|
||||
|
||||
function Expand-ArgumentList {
|
||||
param([string[]]$Values)
|
||||
|
||||
$expanded = @()
|
||||
foreach ($value in $Values) {
|
||||
foreach ($part in ($value -split ",")) {
|
||||
$trimmed = $part.Trim()
|
||||
if ($trimmed.Length -gt 0) {
|
||||
$expanded += $trimmed
|
||||
}
|
||||
}
|
||||
}
|
||||
return $expanded
|
||||
}
|
||||
|
||||
$Packages = @(Expand-ArgumentList -Values $Packages)
|
||||
|
||||
$toolchain = Set-AndroidSdkToolchainEnvironment
|
||||
$packageMap = @{
|
||||
standard = "android/android"
|
||||
quest = "android/quest"
|
||||
focus = "android/focus"
|
||||
}
|
||||
|
||||
$started = Get-Date
|
||||
$results = @()
|
||||
$overallExitCode = 0
|
||||
|
||||
foreach ($package in $Packages) {
|
||||
if (!$packageMap.ContainsKey($package)) {
|
||||
throw "Unknown Android package '$package'. Expected one of: standard, quest, focus."
|
||||
}
|
||||
|
||||
$sourceDir = $packageMap[$package]
|
||||
$buildDir = "out/build/android-legacy-$package-arm64"
|
||||
$toolchainFile = Join-Path $toolchain.ndkPath "build\cmake\android.toolchain.cmake"
|
||||
|
||||
$configureArgs = @(
|
||||
"-S", $sourceDir,
|
||||
"-B", $buildDir,
|
||||
"-G", "Ninja",
|
||||
"-DCMAKE_TOOLCHAIN_FILE=$toolchainFile",
|
||||
"-DANDROID_ABI=arm64-v8a",
|
||||
"-DANDROID_PLATFORM=android-23"
|
||||
)
|
||||
|
||||
& $toolchain.cmakeCommand @configureArgs
|
||||
$configureExitCode = $LASTEXITCODE
|
||||
if ($configureExitCode -ne 0) {
|
||||
if ($overallExitCode -eq 0) {
|
||||
$overallExitCode = $configureExitCode
|
||||
}
|
||||
$results += [ordered]@{
|
||||
package = $package
|
||||
stage = "configure"
|
||||
exitCode = $configureExitCode
|
||||
}
|
||||
continue
|
||||
}
|
||||
|
||||
if ($ConfigureOnly) {
|
||||
$results += [ordered]@{
|
||||
package = $package
|
||||
stage = "configure"
|
||||
exitCode = 0
|
||||
}
|
||||
continue
|
||||
}
|
||||
|
||||
& $toolchain.cmakeCommand --build $buildDir --target native-lib
|
||||
$buildExitCode = $LASTEXITCODE
|
||||
if ($buildExitCode -ne 0 -and $overallExitCode -eq 0) {
|
||||
$overallExitCode = $buildExitCode
|
||||
}
|
||||
|
||||
$results += [ordered]@{
|
||||
package = $package
|
||||
stage = "build"
|
||||
target = "native-lib"
|
||||
exitCode = $buildExitCode
|
||||
}
|
||||
}
|
||||
|
||||
$elapsed = [int]((Get-Date) - $started).TotalMilliseconds
|
||||
[ordered]@{
|
||||
command = "android-legacy-package-build"
|
||||
exitCode = $overallExitCode
|
||||
elapsedMs = $elapsed
|
||||
androidToolchain = $toolchain
|
||||
results = $results
|
||||
} | ConvertTo-Json -Compress -Depth 6
|
||||
|
||||
exit $overallExitCode
|
||||
212
scripts/automation/android-sdk-env.ps1
Normal file
212
scripts/automation/android-sdk-env.ps1
Normal file
@@ -0,0 +1,212 @@
|
||||
[CmdletBinding()]
|
||||
param()
|
||||
|
||||
$ErrorActionPreference = "Stop"
|
||||
|
||||
function Get-AndroidSdkRoot {
|
||||
$candidates = @(
|
||||
$env:ANDROID_SDK_ROOT,
|
||||
$env:ANDROID_HOME,
|
||||
(Join-Path $env:LOCALAPPDATA "Android\Sdk")
|
||||
)
|
||||
|
||||
foreach ($candidate in $candidates) {
|
||||
if ($candidate -and (Test-Path -LiteralPath $candidate -PathType Container)) {
|
||||
return (Resolve-Path -LiteralPath $candidate).Path
|
||||
}
|
||||
}
|
||||
|
||||
throw "Android SDK root was not found. Install command-line tools or set ANDROID_SDK_ROOT."
|
||||
}
|
||||
|
||||
function Get-LatestAndroidSdkPackageDirectory {
|
||||
param(
|
||||
[Parameter(Mandatory=$true)][string]$SdkRoot,
|
||||
[Parameter(Mandatory=$true)][string]$PackageName
|
||||
)
|
||||
|
||||
$packageRoot = Join-Path $SdkRoot $PackageName
|
||||
if (!(Test-Path -LiteralPath $packageRoot -PathType Container)) {
|
||||
throw "Android SDK package directory not found: $packageRoot"
|
||||
}
|
||||
|
||||
$packages = @(Get-ChildItem -LiteralPath $packageRoot -Directory |
|
||||
Where-Object { $_.Name -match '^\d+(\.\d+)*$' } |
|
||||
Sort-Object { [version]$_.Name } -Descending)
|
||||
|
||||
if ($packages.Count -eq 0) {
|
||||
throw "No installed Android SDK package versions found under $packageRoot"
|
||||
}
|
||||
|
||||
return $packages[0]
|
||||
}
|
||||
|
||||
function Get-AndroidSdkManagerCommand {
|
||||
param([Parameter(Mandatory=$true)][string]$SdkRoot)
|
||||
|
||||
$candidates = @(
|
||||
(Join-Path $SdkRoot "cmdline-tools\latest\bin\sdkmanager.bat"),
|
||||
(Join-Path $SdkRoot "cmdline-tools\latest\bin\sdkmanager.exe"),
|
||||
(Join-Path $SdkRoot "tools\bin\sdkmanager.bat"),
|
||||
(Join-Path $SdkRoot "tools\bin\sdkmanager.exe")
|
||||
)
|
||||
|
||||
$cmdlineToolsRoot = Join-Path $SdkRoot "cmdline-tools"
|
||||
if (Test-Path -LiteralPath $cmdlineToolsRoot -PathType Container) {
|
||||
$toolVersions = @(Get-ChildItem -LiteralPath $cmdlineToolsRoot -Directory |
|
||||
Where-Object { $_.Name -ne "latest" } |
|
||||
Sort-Object {
|
||||
try { [version]$_.Name } catch { [version]"0.0" }
|
||||
} -Descending)
|
||||
|
||||
foreach ($toolVersion in $toolVersions) {
|
||||
$candidates += (Join-Path $toolVersion.FullName "bin\sdkmanager.bat")
|
||||
$candidates += (Join-Path $toolVersion.FullName "bin\sdkmanager.exe")
|
||||
}
|
||||
}
|
||||
|
||||
foreach ($candidate in $candidates) {
|
||||
if ($candidate -and (Test-Path -LiteralPath $candidate -PathType Leaf)) {
|
||||
return (Resolve-Path -LiteralPath $candidate).Path
|
||||
}
|
||||
}
|
||||
|
||||
$pathCommand = Get-Command "sdkmanager" -ErrorAction SilentlyContinue
|
||||
if ($pathCommand) {
|
||||
return $pathCommand.Source
|
||||
}
|
||||
|
||||
return $null
|
||||
}
|
||||
|
||||
function Get-LatestAvailableAndroidSdkPackageVersion {
|
||||
param(
|
||||
[Parameter(Mandatory=$true)][string]$SdkRoot,
|
||||
[Parameter(Mandatory=$true)][string]$SdkManagerCommand,
|
||||
[Parameter(Mandatory=$true)][string]$PackageName
|
||||
)
|
||||
|
||||
$output = @(& $SdkManagerCommand "--sdk_root=$SdkRoot" "--list" 2>&1)
|
||||
$exitCode = $LASTEXITCODE
|
||||
if ($exitCode -ne 0) {
|
||||
throw "sdkmanager --list failed while checking $PackageName packages: $($output -join [Environment]::NewLine)"
|
||||
}
|
||||
|
||||
$versions = @()
|
||||
$pattern = "^\s*$([regex]::Escape($PackageName));([0-9]+(?:\.[0-9]+)*)\s*\|"
|
||||
foreach ($line in $output) {
|
||||
$text = $line.ToString()
|
||||
if ($text -match $pattern) {
|
||||
$versions += $Matches[1]
|
||||
}
|
||||
}
|
||||
|
||||
if ($versions.Count -eq 0) {
|
||||
return $null
|
||||
}
|
||||
|
||||
return @($versions | Sort-Object { [version]$_ } -Descending | Select-Object -First 1)[0]
|
||||
}
|
||||
|
||||
function Install-AndroidSdkPackage {
|
||||
param(
|
||||
[Parameter(Mandatory=$true)][string]$SdkRoot,
|
||||
[Parameter(Mandatory=$true)][string]$SdkManagerCommand,
|
||||
[Parameter(Mandatory=$true)][string]$PackageId
|
||||
)
|
||||
|
||||
$licenseInput = ("y`n" * 100)
|
||||
$output = @($licenseInput | & $SdkManagerCommand "--sdk_root=$SdkRoot" "--install" $PackageId 2>&1)
|
||||
$exitCode = $LASTEXITCODE
|
||||
if ($exitCode -ne 0) {
|
||||
throw "sdkmanager failed to install $PackageId with exit code ${exitCode}: $($output -join [Environment]::NewLine)"
|
||||
}
|
||||
}
|
||||
|
||||
function Ensure-LatestAndroidSdkPackageDirectory {
|
||||
param(
|
||||
[Parameter(Mandatory=$true)][string]$SdkRoot,
|
||||
[Parameter(Mandatory=$true)][string]$PackageName,
|
||||
[string]$SdkManagerCommand
|
||||
)
|
||||
|
||||
$installedBefore = $null
|
||||
try {
|
||||
$installedBefore = Get-LatestAndroidSdkPackageDirectory -SdkRoot $SdkRoot -PackageName $PackageName
|
||||
} catch {
|
||||
$installedBefore = $null
|
||||
}
|
||||
|
||||
$availableVersion = $null
|
||||
$action = "using-installed"
|
||||
if ($SdkManagerCommand) {
|
||||
$availableVersion = Get-LatestAvailableAndroidSdkPackageVersion `
|
||||
-SdkRoot $SdkRoot `
|
||||
-SdkManagerCommand $SdkManagerCommand `
|
||||
-PackageName $PackageName
|
||||
|
||||
$installedVersion = if ($installedBefore) { [version]$installedBefore.Name } else { $null }
|
||||
$availableParsed = if ($availableVersion) { [version]$availableVersion } else { $null }
|
||||
if ($availableParsed -and (!$installedVersion -or $availableParsed -gt $installedVersion)) {
|
||||
Install-AndroidSdkPackage `
|
||||
-SdkRoot $SdkRoot `
|
||||
-SdkManagerCommand $SdkManagerCommand `
|
||||
-PackageId "$PackageName;$availableVersion"
|
||||
$action = "installed-latest-available"
|
||||
} elseif ($availableParsed) {
|
||||
$action = "already-latest-available"
|
||||
} else {
|
||||
$action = "available-version-not-listed"
|
||||
}
|
||||
} elseif (!$installedBefore) {
|
||||
throw "No installed Android SDK package versions found under $(Join-Path $SdkRoot $PackageName), and sdkmanager was not found."
|
||||
}
|
||||
|
||||
$selected = Get-LatestAndroidSdkPackageDirectory -SdkRoot $SdkRoot -PackageName $PackageName
|
||||
return [ordered]@{
|
||||
directory = $selected
|
||||
update = [ordered]@{
|
||||
package = $PackageName
|
||||
installedVersionBefore = if ($installedBefore) { $installedBefore.Name } else { $null }
|
||||
availableVersion = $availableVersion
|
||||
selectedVersion = $selected.Name
|
||||
action = $action
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function Set-AndroidSdkToolchainEnvironment {
|
||||
$sdkRoot = Get-AndroidSdkRoot
|
||||
$sdkManagerCommand = Get-AndroidSdkManagerCommand -SdkRoot $sdkRoot
|
||||
$ndkSelection = Ensure-LatestAndroidSdkPackageDirectory `
|
||||
-SdkRoot $sdkRoot `
|
||||
-PackageName "ndk" `
|
||||
-SdkManagerCommand $sdkManagerCommand
|
||||
$cmakeSelection = Ensure-LatestAndroidSdkPackageDirectory `
|
||||
-SdkRoot $sdkRoot `
|
||||
-PackageName "cmake" `
|
||||
-SdkManagerCommand $sdkManagerCommand
|
||||
|
||||
$ndk = $ndkSelection.directory
|
||||
$cmake = $cmakeSelection.directory
|
||||
$cmakeCommand = Join-Path $cmake.FullName "bin\cmake.exe"
|
||||
|
||||
if (!(Test-Path -LiteralPath $cmakeCommand -PathType Leaf)) {
|
||||
throw "Android SDK CMake executable not found: $cmakeCommand"
|
||||
}
|
||||
|
||||
$env:ANDROID_HOME = $sdkRoot
|
||||
$env:ANDROID_SDK_ROOT = $sdkRoot
|
||||
$env:ANDROID_NDK_HOME = $ndk.FullName
|
||||
$env:ANDROID_NDK_ROOT = $ndk.FullName
|
||||
|
||||
return [ordered]@{
|
||||
sdkRoot = $sdkRoot
|
||||
sdkManagerCommand = $sdkManagerCommand
|
||||
packageUpdates = @($ndkSelection.update, $cmakeSelection.update)
|
||||
ndkVersion = $ndk.Name
|
||||
ndkPath = $ndk.FullName
|
||||
cmakeVersion = $cmake.Name
|
||||
cmakeCommand = $cmakeCommand
|
||||
}
|
||||
}
|
||||
201
scripts/automation/android-sdk-env.sh
Normal file
201
scripts/automation/android-sdk-env.sh
Normal file
@@ -0,0 +1,201 @@
|
||||
#!/usr/bin/env sh
|
||||
|
||||
android_sdk_root() {
|
||||
if [ -n "${ANDROID_SDK_ROOT:-}" ] && [ -d "$ANDROID_SDK_ROOT" ]; then
|
||||
printf '%s\n' "$ANDROID_SDK_ROOT"
|
||||
return 0
|
||||
fi
|
||||
|
||||
if [ -n "${ANDROID_HOME:-}" ] && [ -d "$ANDROID_HOME" ]; then
|
||||
printf '%s\n' "$ANDROID_HOME"
|
||||
return 0
|
||||
fi
|
||||
|
||||
if [ -n "${LOCALAPPDATA:-}" ]; then
|
||||
local_sdk="$LOCALAPPDATA/Android/Sdk"
|
||||
if command -v cygpath >/dev/null 2>&1; then
|
||||
local_sdk="$(cygpath -u "$local_sdk")"
|
||||
fi
|
||||
if [ -d "$local_sdk" ]; then
|
||||
printf '%s\n' "$local_sdk"
|
||||
return 0
|
||||
fi
|
||||
fi
|
||||
|
||||
if [ -d "$HOME/Android/Sdk" ]; then
|
||||
printf '%s\n' "$HOME/Android/Sdk"
|
||||
return 0
|
||||
fi
|
||||
|
||||
return 1
|
||||
}
|
||||
|
||||
latest_android_package_dir() {
|
||||
root="$1"
|
||||
package="$2"
|
||||
package_root="$root/$package"
|
||||
[ -d "$package_root" ] || return 1
|
||||
latest="$(
|
||||
for dir in "$package_root"/*; do
|
||||
[ -d "$dir" ] || continue
|
||||
version="${dir##*/}"
|
||||
printf '%s\n' "$version"
|
||||
done | grep -E '^[0-9]+(\.[0-9]+)*$' | sort -t . -k 1,1n -k 2,2n -k 3,3n -k 4,4n | tail -n 1
|
||||
)"
|
||||
[ -n "$latest" ] || return 1
|
||||
printf '%s/%s\n' "$package_root" "$latest"
|
||||
}
|
||||
|
||||
android_sdkmanager_command() {
|
||||
root="$1"
|
||||
for candidate in \
|
||||
"$root/cmdline-tools/latest/bin/sdkmanager" \
|
||||
"$root/cmdline-tools/latest/bin/sdkmanager.bat" \
|
||||
"$root/tools/bin/sdkmanager" \
|
||||
"$root/tools/bin/sdkmanager.bat"
|
||||
do
|
||||
if [ -x "$candidate" ] || [ -f "$candidate" ]; then
|
||||
printf '%s\n' "$candidate"
|
||||
return 0
|
||||
fi
|
||||
done
|
||||
|
||||
if [ -d "$root/cmdline-tools" ]; then
|
||||
for version in "$root/cmdline-tools"/*; do
|
||||
[ -d "$version" ] || continue
|
||||
[ "${version##*/}" = "latest" ] && continue
|
||||
for candidate in "$version/bin/sdkmanager" "$version/bin/sdkmanager.bat"; do
|
||||
if [ -x "$candidate" ] || [ -f "$candidate" ]; then
|
||||
printf '%s\n' "$candidate"
|
||||
return 0
|
||||
fi
|
||||
done
|
||||
done
|
||||
fi
|
||||
|
||||
if command -v sdkmanager >/dev/null 2>&1; then
|
||||
command -v sdkmanager
|
||||
return 0
|
||||
fi
|
||||
|
||||
return 1
|
||||
}
|
||||
|
||||
latest_version_from_stdin() {
|
||||
sort -t . -k 1,1n -k 2,2n -k 3,3n -k 4,4n -k 5,5n | tail -n 1
|
||||
}
|
||||
|
||||
latest_available_android_package_version() {
|
||||
root="$1"
|
||||
sdkmanager_cmd="$2"
|
||||
package="$3"
|
||||
output="$("$sdkmanager_cmd" "--sdk_root=$root" --list)" || return 1
|
||||
printf '%s\n' "$output" |
|
||||
sed -n "s/^[[:space:]]*$package;\\([0-9][0-9.]*\\)[[:space:]]*|.*/\\1/p" |
|
||||
latest_version_from_stdin
|
||||
}
|
||||
|
||||
accept_android_sdk_licenses() {
|
||||
i=0
|
||||
while [ "$i" -lt 100 ]; do
|
||||
printf '%s\n' "y"
|
||||
i="$((i + 1))"
|
||||
done
|
||||
}
|
||||
|
||||
install_android_sdk_package() {
|
||||
root="$1"
|
||||
sdkmanager_cmd="$2"
|
||||
package_id="$3"
|
||||
accept_android_sdk_licenses | "$sdkmanager_cmd" "--sdk_root=$root" --install "$package_id" >&2
|
||||
}
|
||||
|
||||
record_android_package_update() {
|
||||
package="$1"
|
||||
installed_before="$2"
|
||||
available="$3"
|
||||
selected="$4"
|
||||
action="$5"
|
||||
|
||||
case "$package" in
|
||||
ndk)
|
||||
ANDROID_NDK_INSTALLED_BEFORE="$installed_before"
|
||||
ANDROID_NDK_AVAILABLE_VERSION="$available"
|
||||
ANDROID_NDK_UPDATE_ACTION="$action"
|
||||
;;
|
||||
cmake)
|
||||
ANDROID_CMAKE_INSTALLED_BEFORE="$installed_before"
|
||||
ANDROID_CMAKE_AVAILABLE_VERSION="$available"
|
||||
ANDROID_CMAKE_UPDATE_ACTION="$action"
|
||||
;;
|
||||
esac
|
||||
export ANDROID_NDK_INSTALLED_BEFORE ANDROID_NDK_AVAILABLE_VERSION ANDROID_NDK_UPDATE_ACTION
|
||||
export ANDROID_CMAKE_INSTALLED_BEFORE ANDROID_CMAKE_AVAILABLE_VERSION ANDROID_CMAKE_UPDATE_ACTION
|
||||
printf '%s\n' "$selected" >/dev/null
|
||||
}
|
||||
|
||||
ensure_latest_android_package_dir() {
|
||||
root="$1"
|
||||
package="$2"
|
||||
sdkmanager_cmd="${3:-}"
|
||||
|
||||
installed_dir="$(latest_android_package_dir "$root" "$package" 2>/dev/null || true)"
|
||||
installed_before="${installed_dir##*/}"
|
||||
[ -n "$installed_dir" ] || installed_before=""
|
||||
available_version=""
|
||||
action="using-installed"
|
||||
|
||||
if [ -n "$sdkmanager_cmd" ]; then
|
||||
available_version="$(latest_available_android_package_version "$root" "$sdkmanager_cmd" "$package")" || return 1
|
||||
if [ -n "$available_version" ]; then
|
||||
if [ -z "$installed_before" ]; then
|
||||
install_android_sdk_package "$root" "$sdkmanager_cmd" "$package;$available_version" || return 1
|
||||
action="installed-latest-available"
|
||||
else
|
||||
newest="$(printf '%s\n%s\n' "$installed_before" "$available_version" | latest_version_from_stdin)"
|
||||
if [ "$newest" = "$available_version" ] && [ "$available_version" != "$installed_before" ]; then
|
||||
install_android_sdk_package "$root" "$sdkmanager_cmd" "$package;$available_version" || return 1
|
||||
action="installed-latest-available"
|
||||
else
|
||||
action="already-latest-available"
|
||||
fi
|
||||
fi
|
||||
else
|
||||
action="available-version-not-listed"
|
||||
fi
|
||||
elif [ -z "$installed_dir" ]; then
|
||||
printf '%s\n' "No installed Android SDK package was found under $root/$package, and sdkmanager was not found." >&2
|
||||
return 1
|
||||
fi
|
||||
|
||||
selected_dir="$(latest_android_package_dir "$root" "$package")" || return 1
|
||||
record_android_package_update "$package" "$installed_before" "$available_version" "${selected_dir##*/}" "$action"
|
||||
printf '%s\n' "$selected_dir"
|
||||
}
|
||||
|
||||
set_android_sdk_toolchain_environment() {
|
||||
sdk_root="$(android_sdk_root)" || {
|
||||
printf '%s\n' "Android SDK root was not found. Install command-line tools or set ANDROID_SDK_ROOT." >&2
|
||||
return 1
|
||||
}
|
||||
|
||||
sdkmanager_cmd="$(android_sdkmanager_command "$sdk_root" || true)"
|
||||
|
||||
ndk_dir="$(ensure_latest_android_package_dir "$sdk_root" ndk "$sdkmanager_cmd")" || return 1
|
||||
cmake_dir="$(ensure_latest_android_package_dir "$sdk_root" cmake "$sdkmanager_cmd")" || return 1
|
||||
|
||||
cmake_command="$cmake_dir/bin/cmake"
|
||||
[ -x "$cmake_command" ] || {
|
||||
printf '%s\n' "Android SDK CMake executable not found: $cmake_command" >&2
|
||||
return 1
|
||||
}
|
||||
|
||||
export ANDROID_HOME="$sdk_root"
|
||||
export ANDROID_SDK_ROOT="$sdk_root"
|
||||
export ANDROID_NDK_HOME="$ndk_dir"
|
||||
export ANDROID_NDK_ROOT="$ndk_dir"
|
||||
export ANDROID_CMAKE_COMMAND="$cmake_command"
|
||||
export ANDROID_NDK_VERSION="${ndk_dir##*/}"
|
||||
export ANDROID_CMAKE_VERSION="${cmake_dir##*/}"
|
||||
export ANDROID_SDKMANAGER_COMMAND="$sdkmanager_cmd"
|
||||
}
|
||||
93
scripts/automation/apple-remote-build.ps1
Normal file
93
scripts/automation/apple-remote-build.ps1
Normal file
@@ -0,0 +1,93 @@
|
||||
[CmdletBinding()]
|
||||
param(
|
||||
[string]$HostName = "panopainter-mac",
|
||||
[string]$RemoteDirectory = "~/Dev/panopainter",
|
||||
[string]$RepositoryUrl = "ssh://git@git.omar.synology.me:3022/omar/panopainter.git",
|
||||
[string]$Branch = "codex/modernization-cmake-foundation",
|
||||
[string[]]$Presets = @("macos", "ios-simulator", "ios-device")
|
||||
)
|
||||
|
||||
$ErrorActionPreference = "Stop"
|
||||
|
||||
function Join-RemoteArgument {
|
||||
param([string[]]$Values)
|
||||
|
||||
$expanded = @()
|
||||
foreach ($value in $Values) {
|
||||
foreach ($part in ($value -split ",")) {
|
||||
$trimmed = $part.Trim()
|
||||
if ($trimmed.Length -gt 0) {
|
||||
$expanded += $trimmed
|
||||
}
|
||||
}
|
||||
}
|
||||
return ($expanded -join " ")
|
||||
}
|
||||
|
||||
function ConvertTo-ShellSingleQuoted {
|
||||
param([string]$Value)
|
||||
|
||||
return "'" + ($Value -replace "'", "'\\''") + "'"
|
||||
}
|
||||
|
||||
$presetArgument = Join-RemoteArgument -Values $Presets
|
||||
$remoteDirectoryLiteral = ConvertTo-ShellSingleQuoted -Value $RemoteDirectory
|
||||
$repositoryLiteral = ConvertTo-ShellSingleQuoted -Value $RepositoryUrl
|
||||
$branchLiteral = ConvertTo-ShellSingleQuoted -Value $Branch
|
||||
$presetLiteral = ConvertTo-ShellSingleQuoted -Value $presetArgument
|
||||
|
||||
$remoteScript = @"
|
||||
set -eu
|
||||
export PATH="/opt/homebrew/bin:/usr/local/bin:`$HOME/tools/bin:`$PATH"
|
||||
export DEVELOPER_DIR="/Applications/Xcode.app/Contents/Developer"
|
||||
|
||||
remote_dir=$remoteDirectoryLiteral
|
||||
repository_url=$repositoryLiteral
|
||||
branch_name=$branchLiteral
|
||||
presets=$presetLiteral
|
||||
|
||||
case "`$remote_dir" in
|
||||
"~/"*) remote_dir="`$HOME/`$(printf '%s' "`$remote_dir" | sed 's|^~/||')" ;;
|
||||
esac
|
||||
|
||||
mkdir -p "`$(dirname "`$remote_dir")"
|
||||
if [ ! -d "`$remote_dir/.git" ]; then
|
||||
git clone "`$repository_url" "`$remote_dir"
|
||||
fi
|
||||
|
||||
cd "`$remote_dir"
|
||||
git fetch origin
|
||||
git checkout "`$branch_name"
|
||||
git pull --ff-only origin "`$branch_name"
|
||||
|
||||
git submodule update --init --recursive \
|
||||
libs/tinyxml2 \
|
||||
libs/glm \
|
||||
libs/stb/stb \
|
||||
libs/yoga \
|
||||
libs/poly2tri \
|
||||
libs/base64 \
|
||||
libs/sqlite3 \
|
||||
libs/nanort \
|
||||
libs/hash-library \
|
||||
libs/fmt \
|
||||
libs/glad \
|
||||
libs/tinyfiledialogs
|
||||
|
||||
mkdir -p out/logs
|
||||
log="out/logs/apple-platform-build-`$(date +%Y%m%d-%H%M%S).log"
|
||||
set +e
|
||||
sh ./scripts/automation/platform-build.sh "`$presets" > "`$log" 2>&1
|
||||
exit_code=`$?
|
||||
set -e
|
||||
|
||||
printf '{"command":"apple-remote-build","host":"%s","branch":"%s","presets":"%s","log":"%s","exitCode":%s}\n' \
|
||||
"`$(hostname)" "`$branch_name" "`$presets" "`$log" "`$exit_code"
|
||||
tail -n 80 "`$log"
|
||||
exit "`$exit_code"
|
||||
"@
|
||||
|
||||
$remoteScript = $remoteScript -replace "`r`n", "`n"
|
||||
$encodedRemoteScript = [Convert]::ToBase64String([System.Text.Encoding]::UTF8.GetBytes($remoteScript))
|
||||
& ssh -o BatchMode=yes $HostName "printf '%s' '$encodedRemoteScript' | base64 -D | sh"
|
||||
exit $LASTEXITCODE
|
||||
28
scripts/automation/build.ps1
Normal file
28
scripts/automation/build.ps1
Normal file
@@ -0,0 +1,28 @@
|
||||
[CmdletBinding()]
|
||||
param(
|
||||
[string]$Preset = "windows-msvc-default",
|
||||
[string]$Configuration = "Debug",
|
||||
[string]$Target = ""
|
||||
)
|
||||
|
||||
$ErrorActionPreference = "Stop"
|
||||
$started = Get-Date
|
||||
$argsList = @("--build", "--preset", $Preset, "--config", $Configuration)
|
||||
if ($Target.Length -gt 0) {
|
||||
$argsList += @("--target", $Target)
|
||||
}
|
||||
|
||||
& cmake @argsList
|
||||
$exitCode = $LASTEXITCODE
|
||||
$elapsed = [int]((Get-Date) - $started).TotalMilliseconds
|
||||
|
||||
[ordered]@{
|
||||
command = "build"
|
||||
preset = $Preset
|
||||
configuration = $Configuration
|
||||
target = $Target
|
||||
exitCode = $exitCode
|
||||
elapsedMs = $elapsed
|
||||
} | ConvertTo-Json -Compress
|
||||
|
||||
exit $exitCode
|
||||
17
scripts/automation/build.sh
Normal file
17
scripts/automation/build.sh
Normal file
@@ -0,0 +1,17 @@
|
||||
#!/usr/bin/env sh
|
||||
set -u
|
||||
|
||||
preset="${1:-linux-clang}"
|
||||
configuration="${2:-Debug}"
|
||||
target="${3:-}"
|
||||
start="$(date +%s)"
|
||||
if [ -n "$target" ]; then
|
||||
cmake --build --preset "$preset" --config "$configuration" --target "$target"
|
||||
else
|
||||
cmake --build --preset "$preset" --config "$configuration"
|
||||
fi
|
||||
exit_code="$?"
|
||||
end="$(date +%s)"
|
||||
elapsed_ms="$(( (end - start) * 1000 ))"
|
||||
printf '{"command":"build","preset":"%s","configuration":"%s","target":"%s","exitCode":%s,"elapsedMs":%s}\n' "$preset" "$configuration" "$target" "$exit_code" "$elapsed_ms"
|
||||
exit "$exit_code"
|
||||
62
scripts/automation/check-renderer-boundary.ps1
Normal file
62
scripts/automation/check-renderer-boundary.ps1
Normal file
@@ -0,0 +1,62 @@
|
||||
[CmdletBinding()]
|
||||
param(
|
||||
[string]$Root = ""
|
||||
)
|
||||
|
||||
$ErrorActionPreference = "Stop"
|
||||
if ([string]::IsNullOrWhiteSpace($Root)) {
|
||||
$Root = (Resolve-Path (Join-Path $PSScriptRoot "..\..")).Path
|
||||
}
|
||||
$started = Get-Date
|
||||
$pattern = '\b(?:GL|WGL)_[A-Z0-9_]+\b'
|
||||
$allowed = @(
|
||||
"src/renderer_gl/",
|
||||
"src/rtt.cpp",
|
||||
"src/texture.cpp"
|
||||
)
|
||||
$violations = @()
|
||||
$files = Get-ChildItem -Path (Join-Path $Root "src") -Recurse -File -Include *.c,*.cc,*.cpp,*.h,*.hpp
|
||||
|
||||
foreach ($file in $files) {
|
||||
$relative = $file.FullName.Substring($Root.Length).TrimStart('\', '/').Replace('\', '/')
|
||||
$isAllowed = $false
|
||||
foreach ($prefix in $allowed) {
|
||||
if ($relative.StartsWith($prefix)) {
|
||||
$isAllowed = $true
|
||||
break
|
||||
}
|
||||
}
|
||||
if ($isAllowed) {
|
||||
continue
|
||||
}
|
||||
|
||||
$lineNumber = 0
|
||||
foreach ($line in Get-Content -LiteralPath $file.FullName) {
|
||||
$lineNumber += 1
|
||||
$trimmed = $line.TrimStart()
|
||||
if ($trimmed.StartsWith("//")) {
|
||||
continue
|
||||
}
|
||||
$matches = [regex]::Matches($line, $pattern)
|
||||
foreach ($match in $matches) {
|
||||
$violations += [ordered]@{
|
||||
file = $relative
|
||||
line = $lineNumber
|
||||
token = $match.Value
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$exitCode = if ($violations.Count -eq 0) { 0 } else { 1 }
|
||||
$elapsed = [int]((Get-Date) - $started).TotalMilliseconds
|
||||
|
||||
[ordered]@{
|
||||
command = "check-renderer-boundary"
|
||||
exitCode = $exitCode
|
||||
violationCount = $violations.Count
|
||||
violations = @($violations | Select-Object -First 50)
|
||||
elapsedMs = $elapsed
|
||||
} | ConvertTo-Json -Compress -Depth 4
|
||||
|
||||
exit $exitCode
|
||||
34
scripts/automation/check-renderer-boundary.sh
Normal file
34
scripts/automation/check-renderer-boundary.sh
Normal file
@@ -0,0 +1,34 @@
|
||||
#!/usr/bin/env sh
|
||||
set -u
|
||||
|
||||
script_dir="$(CDPATH= cd -- "$(dirname -- "$0")" && pwd)"
|
||||
root="${1:-$(CDPATH= cd -- "$script_dir/../.." && pwd)}"
|
||||
start="$(date +%s)"
|
||||
tmp="${TMPDIR:-/tmp}/panopainter-renderer-boundary-$$.txt"
|
||||
|
||||
find "$root/src" -type f \( -name '*.c' -o -name '*.cc' -o -name '*.cpp' -o -name '*.h' -o -name '*.hpp' \) | while IFS= read -r file; do
|
||||
rel="${file#"$root"/}"
|
||||
case "$rel" in
|
||||
src/renderer_gl/*|src/rtt.cpp|src/texture.cpp)
|
||||
continue
|
||||
;;
|
||||
esac
|
||||
awk -v rel="$rel" '
|
||||
/^[[:space:]]*\/\// { next }
|
||||
match($0, /\<(GL|WGL)_[A-Z0-9_]+\>/) {
|
||||
print rel ":" FNR ":" substr($0, RSTART, RLENGTH)
|
||||
}
|
||||
' "$file"
|
||||
done > "$tmp"
|
||||
|
||||
count="$(wc -l < "$tmp" | tr -d '[:space:]')"
|
||||
end="$(date +%s)"
|
||||
elapsed_ms="$(( (end - start) * 1000 ))"
|
||||
exit_code="0"
|
||||
if [ "$count" -ne 0 ]; then
|
||||
exit_code="1"
|
||||
fi
|
||||
|
||||
printf '{"command":"check-renderer-boundary","exitCode":%s,"violationCount":%s,"elapsedMs":%s}\n' "$exit_code" "$count" "$elapsed_ms"
|
||||
rm -f "$tmp"
|
||||
exit "$exit_code"
|
||||
25
scripts/automation/configure.ps1
Normal file
25
scripts/automation/configure.ps1
Normal file
@@ -0,0 +1,25 @@
|
||||
[CmdletBinding()]
|
||||
param(
|
||||
[string]$Preset = "windows-msvc-default",
|
||||
[switch]$NoApp
|
||||
)
|
||||
|
||||
$ErrorActionPreference = "Stop"
|
||||
$started = Get-Date
|
||||
$argsList = @("--preset", $Preset)
|
||||
if ($NoApp) {
|
||||
$argsList += "-DPP_BUILD_APP=OFF"
|
||||
}
|
||||
|
||||
& cmake @argsList
|
||||
$exitCode = $LASTEXITCODE
|
||||
$elapsed = [int]((Get-Date) - $started).TotalMilliseconds
|
||||
|
||||
[ordered]@{
|
||||
command = "configure"
|
||||
preset = $Preset
|
||||
exitCode = $exitCode
|
||||
elapsedMs = $elapsed
|
||||
} | ConvertTo-Json -Compress
|
||||
|
||||
exit $exitCode
|
||||
11
scripts/automation/configure.sh
Normal file
11
scripts/automation/configure.sh
Normal file
@@ -0,0 +1,11 @@
|
||||
#!/usr/bin/env sh
|
||||
set -u
|
||||
|
||||
preset="${1:-linux-clang}"
|
||||
start="$(date +%s)"
|
||||
cmake --preset "$preset"
|
||||
exit_code="$?"
|
||||
end="$(date +%s)"
|
||||
elapsed_ms="$(( (end - start) * 1000 ))"
|
||||
printf '{"command":"configure","preset":"%s","exitCode":%s,"elapsedMs":%s}\n' "$preset" "$exit_code" "$elapsed_ms"
|
||||
exit "$exit_code"
|
||||
413
scripts/automation/package-smoke.ps1
Normal file
413
scripts/automation/package-smoke.ps1
Normal file
@@ -0,0 +1,413 @@
|
||||
[CmdletBinding()]
|
||||
param(
|
||||
[string]$Preset = "windows-msvc-default",
|
||||
[string]$Configuration = "Debug",
|
||||
[string]$Target = "PanoPainter",
|
||||
[string]$CMakeCommand = "cmake",
|
||||
[switch]$ReadinessOnly,
|
||||
[switch]$AndroidNativeChecks,
|
||||
[string[]]$PackageKinds = @(
|
||||
"windows-appx",
|
||||
"android-standard-apk",
|
||||
"android-quest-apk",
|
||||
"android-focus-apk",
|
||||
"apple-bundle",
|
||||
"linux-app",
|
||||
"webgl"
|
||||
)
|
||||
)
|
||||
|
||||
$ErrorActionPreference = "Stop"
|
||||
$started = Get-Date
|
||||
$root = (Get-Location).Path
|
||||
|
||||
function Test-CommandAvailable {
|
||||
param([string]$Name)
|
||||
return [bool](Get-Command $Name -ErrorAction SilentlyContinue)
|
||||
}
|
||||
|
||||
function Expand-ArgumentList {
|
||||
param([string[]]$Values)
|
||||
|
||||
$expanded = @()
|
||||
foreach ($value in $Values) {
|
||||
foreach ($part in ($value -split ",")) {
|
||||
$trimmed = $part.Trim()
|
||||
if ($trimmed.Length -gt 0) {
|
||||
$expanded += $trimmed
|
||||
}
|
||||
}
|
||||
}
|
||||
return $expanded
|
||||
}
|
||||
|
||||
function New-ArtifactCheck {
|
||||
param(
|
||||
[string]$Name,
|
||||
[string]$Path,
|
||||
[string]$PathType = "Any"
|
||||
)
|
||||
|
||||
$exists = if ($PathType -eq "Container") {
|
||||
Test-Path -LiteralPath $Path -PathType Container
|
||||
} elseif ($PathType -eq "Leaf") {
|
||||
Test-Path -LiteralPath $Path -PathType Leaf
|
||||
} else {
|
||||
Test-Path -LiteralPath $Path
|
||||
}
|
||||
|
||||
[ordered]@{
|
||||
name = $Name
|
||||
path = $Path
|
||||
pathType = $PathType
|
||||
exists = $exists
|
||||
}
|
||||
}
|
||||
|
||||
function New-Prerequisite {
|
||||
param(
|
||||
[string]$Name,
|
||||
[bool]$Available,
|
||||
[string]$Detail
|
||||
)
|
||||
|
||||
[ordered]@{
|
||||
name = $Name
|
||||
available = $Available
|
||||
detail = $Detail
|
||||
}
|
||||
}
|
||||
|
||||
function New-PackageReadiness {
|
||||
param(
|
||||
[string]$Kind,
|
||||
[string]$Status,
|
||||
[string]$Reason,
|
||||
[object[]]$Prerequisites,
|
||||
[object[]]$Artifacts,
|
||||
[string]$ValidationCommand
|
||||
)
|
||||
|
||||
[ordered]@{
|
||||
kind = $Kind
|
||||
status = $Status
|
||||
reason = $Reason
|
||||
debt = "DEBT-0011"
|
||||
validationCommand = $ValidationCommand
|
||||
prerequisites = $Prerequisites
|
||||
artifacts = $Artifacts
|
||||
}
|
||||
}
|
||||
|
||||
function Get-AndroidNativeCheckPlan {
|
||||
param([string[]]$Kinds)
|
||||
|
||||
$packages = @()
|
||||
if ($Kinds -contains "android-standard-apk") {
|
||||
$packages += [ordered]@{
|
||||
packages = @("standard")
|
||||
configureOnly = $false
|
||||
}
|
||||
}
|
||||
|
||||
$configureOnlyPackages = @()
|
||||
if ($Kinds -contains "android-quest-apk") {
|
||||
$configureOnlyPackages += "quest"
|
||||
}
|
||||
if ($Kinds -contains "android-focus-apk") {
|
||||
$configureOnlyPackages += "focus"
|
||||
}
|
||||
if ($configureOnlyPackages.Count -gt 0) {
|
||||
$packages += [ordered]@{
|
||||
packages = $configureOnlyPackages
|
||||
configureOnly = $true
|
||||
}
|
||||
}
|
||||
|
||||
return $packages
|
||||
}
|
||||
|
||||
function Invoke-AndroidNativePackageChecks {
|
||||
param([string[]]$Kinds)
|
||||
|
||||
$plans = @(Get-AndroidNativeCheckPlan -Kinds $Kinds)
|
||||
$results = @()
|
||||
$overallExitCode = 0
|
||||
|
||||
foreach ($plan in $plans) {
|
||||
$arguments = @(
|
||||
"-ExecutionPolicy", "Bypass",
|
||||
"-File", (Join-Path $root "scripts/automation/android-legacy-package-build.ps1"),
|
||||
"-Packages", ($plan.packages -join ",")
|
||||
)
|
||||
if ($plan.configureOnly) {
|
||||
$arguments += "-ConfigureOnly"
|
||||
}
|
||||
|
||||
$output = @(& powershell @arguments 2>&1)
|
||||
$exitCode = $LASTEXITCODE
|
||||
if ($exitCode -ne 0 -and $overallExitCode -eq 0) {
|
||||
$overallExitCode = $exitCode
|
||||
}
|
||||
|
||||
$jsonLine = @($output | ForEach-Object { $_.ToString() } | Where-Object { $_.TrimStart().StartsWith("{") } | Select-Object -Last 1)
|
||||
$summary = $null
|
||||
if ($jsonLine.Count -gt 0) {
|
||||
try {
|
||||
$summary = $jsonLine[-1] | ConvertFrom-Json
|
||||
} catch {
|
||||
$summary = $null
|
||||
}
|
||||
}
|
||||
|
||||
$results += [ordered]@{
|
||||
packages = $plan.packages
|
||||
configureOnly = [bool]$plan.configureOnly
|
||||
exitCode = $exitCode
|
||||
command = "powershell -ExecutionPolicy Bypass -File scripts\automation\android-legacy-package-build.ps1 -Packages $($plan.packages -join ',')$(if ($plan.configureOnly) { ' -ConfigureOnly' } else { '' })"
|
||||
summary = $summary
|
||||
}
|
||||
}
|
||||
|
||||
[ordered]@{
|
||||
requested = $plans.Count -gt 0
|
||||
exitCode = $overallExitCode
|
||||
results = $results
|
||||
}
|
||||
}
|
||||
|
||||
$PackageKinds = @(Expand-ArgumentList -Values $PackageKinds)
|
||||
|
||||
function Get-PackageReadiness {
|
||||
param([string[]]$Kinds)
|
||||
|
||||
$readiness = @()
|
||||
foreach ($kind in $Kinds) {
|
||||
switch ($kind) {
|
||||
"windows-appx" {
|
||||
$wapproj = Join-Path $root "PanoPainterPackage/PanoPainterPackage.wapproj"
|
||||
$manifest = Join-Path $root "PanoPainterPackage/Package.appxmanifest"
|
||||
$appPackages = Join-Path $root "PanoPainterPackage/AppPackages"
|
||||
$readiness += New-PackageReadiness `
|
||||
-Kind $kind `
|
||||
-Status "blocked" `
|
||||
-Reason "legacy-wapproj-present-but-root-cmake-package-target-missing" `
|
||||
-ValidationCommand "msbuild PanoPainterPackage/PanoPainterPackage.wapproj /p:Configuration=$Configuration /p:Platform=x64" `
|
||||
-Prerequisites @(
|
||||
(New-Prerequisite -Name "legacy-wapproj" -Available (Test-Path -LiteralPath $wapproj -PathType Leaf) -Detail $wapproj),
|
||||
(New-Prerequisite -Name "appx-manifest" -Available (Test-Path -LiteralPath $manifest -PathType Leaf) -Detail $manifest),
|
||||
(New-Prerequisite -Name "makeappx" -Available (Test-CommandAvailable "makeappx") -Detail "Windows SDK packaging tool"),
|
||||
(New-Prerequisite -Name "signtool" -Available (Test-CommandAvailable "signtool") -Detail "Windows SDK signing tool"),
|
||||
(New-Prerequisite -Name "root-cmake-package-target" -Available $false -Detail "Not migrated yet")
|
||||
) `
|
||||
-Artifacts @(
|
||||
(New-ArtifactCheck -Name "app-packages" -Path $appPackages -PathType "Container")
|
||||
)
|
||||
}
|
||||
"android-standard-apk" {
|
||||
$gradle = Join-Path $root "android/android/build.gradle"
|
||||
$manifest = Join-Path $root "android/android/src/main/AndroidManifest.xml"
|
||||
$apkDir = Join-Path $root "android/android/build/outputs/apk"
|
||||
$readiness += New-PackageReadiness `
|
||||
-Kind $kind `
|
||||
-Status "blocked" `
|
||||
-Reason "legacy-gradle-package-not-consuming-root-cmake-targets" `
|
||||
-ValidationCommand "gradle -p android/android assembleDebug" `
|
||||
-Prerequisites @(
|
||||
(New-Prerequisite -Name "gradle-build" -Available (Test-Path -LiteralPath $gradle -PathType Leaf) -Detail $gradle),
|
||||
(New-Prerequisite -Name "android-manifest" -Available (Test-Path -LiteralPath $manifest -PathType Leaf) -Detail $manifest),
|
||||
(New-Prerequisite -Name "gradle" -Available (Test-CommandAvailable "gradle") -Detail "Android package builder"),
|
||||
(New-Prerequisite -Name "retained-native-cmake-check" -Available $true -Detail "powershell -ExecutionPolicy Bypass -File scripts\automation\android-legacy-package-build.ps1 -Packages standard"),
|
||||
(New-Prerequisite -Name "root-cmake-preset" -Available $true -Detail "android-arm64/android-x64"),
|
||||
(New-Prerequisite -Name "root-cmake-package-target" -Available $false -Detail "Not migrated yet")
|
||||
) `
|
||||
-Artifacts @(
|
||||
(New-ArtifactCheck -Name "apk-output" -Path $apkDir -PathType "Container")
|
||||
)
|
||||
}
|
||||
"android-quest-apk" {
|
||||
$gradle = Join-Path $root "android/quest/build.gradle"
|
||||
$manifest = Join-Path $root "android/quest/src/main/AndroidManifest.xml"
|
||||
$apkDir = Join-Path $root "android/quest/build/outputs/apk"
|
||||
$readiness += New-PackageReadiness `
|
||||
-Kind $kind `
|
||||
-Status "blocked" `
|
||||
-Reason "legacy-gradle-package-not-consuming-root-cmake-targets" `
|
||||
-ValidationCommand "gradle -p android/quest assembleDebug" `
|
||||
-Prerequisites @(
|
||||
(New-Prerequisite -Name "gradle-build" -Available (Test-Path -LiteralPath $gradle -PathType Leaf) -Detail $gradle),
|
||||
(New-Prerequisite -Name "android-manifest" -Available (Test-Path -LiteralPath $manifest -PathType Leaf) -Detail $manifest),
|
||||
(New-Prerequisite -Name "gradle" -Available (Test-CommandAvailable "gradle") -Detail "Android package builder"),
|
||||
(New-Prerequisite -Name "retained-native-cmake-check" -Available $true -Detail "powershell -ExecutionPolicy Bypass -File scripts\automation\android-legacy-package-build.ps1 -Packages quest -ConfigureOnly"),
|
||||
(New-Prerequisite -Name "root-cmake-preset" -Available $true -Detail "android-quest-arm64"),
|
||||
(New-Prerequisite -Name "root-cmake-package-target" -Available $false -Detail "Not migrated yet")
|
||||
) `
|
||||
-Artifacts @(
|
||||
(New-ArtifactCheck -Name "apk-output" -Path $apkDir -PathType "Container")
|
||||
)
|
||||
}
|
||||
"android-focus-apk" {
|
||||
$gradle = Join-Path $root "android/focus/build.gradle"
|
||||
$manifest = Join-Path $root "android/focus/src/main/AndroidManifest.xml"
|
||||
$apkDir = Join-Path $root "android/focus/build/outputs/apk"
|
||||
$readiness += New-PackageReadiness `
|
||||
-Kind $kind `
|
||||
-Status "blocked" `
|
||||
-Reason "legacy-gradle-package-not-consuming-root-cmake-targets" `
|
||||
-ValidationCommand "gradle -p android/focus assembleDebug" `
|
||||
-Prerequisites @(
|
||||
(New-Prerequisite -Name "gradle-build" -Available (Test-Path -LiteralPath $gradle -PathType Leaf) -Detail $gradle),
|
||||
(New-Prerequisite -Name "android-manifest" -Available (Test-Path -LiteralPath $manifest -PathType Leaf) -Detail $manifest),
|
||||
(New-Prerequisite -Name "gradle" -Available (Test-CommandAvailable "gradle") -Detail "Android package builder"),
|
||||
(New-Prerequisite -Name "retained-native-cmake-check" -Available $true -Detail "powershell -ExecutionPolicy Bypass -File scripts\automation\android-legacy-package-build.ps1 -Packages focus -ConfigureOnly"),
|
||||
(New-Prerequisite -Name "root-cmake-preset" -Available $true -Detail "android-focus-arm64"),
|
||||
(New-Prerequisite -Name "root-cmake-package-target" -Available $false -Detail "Not migrated yet")
|
||||
) `
|
||||
-Artifacts @(
|
||||
(New-ArtifactCheck -Name "apk-output" -Path $apkDir -PathType "Container")
|
||||
)
|
||||
}
|
||||
"apple-bundle" {
|
||||
$xcodeProject = Join-Path $root "PanoPainter.xcodeproj/project.pbxproj"
|
||||
$bundleDir = Join-Path $root "out/package/apple"
|
||||
$readiness += New-PackageReadiness `
|
||||
-Kind $kind `
|
||||
-Status "blocked" `
|
||||
-Reason "legacy-xcode-project-and-host-toolchain-not-aligned-with-root-cmake-package-target" `
|
||||
-ValidationCommand "xcodebuild -project PanoPainter.xcodeproj -configuration $Configuration" `
|
||||
-Prerequisites @(
|
||||
(New-Prerequisite -Name "legacy-xcode-project" -Available (Test-Path -LiteralPath $xcodeProject -PathType Leaf) -Detail $xcodeProject),
|
||||
(New-Prerequisite -Name "xcodebuild" -Available (Test-CommandAvailable "xcodebuild") -Detail "Apple package builder"),
|
||||
(New-Prerequisite -Name "root-cmake-preset" -Available $true -Detail "macos/ios-device/ios-simulator"),
|
||||
(New-Prerequisite -Name "root-cmake-package-target" -Available $false -Detail "Not migrated yet")
|
||||
) `
|
||||
-Artifacts @(
|
||||
(New-ArtifactCheck -Name "apple-package-output" -Path $bundleDir -PathType "Container")
|
||||
)
|
||||
}
|
||||
"linux-app" {
|
||||
$linuxCmake = Join-Path $root "linux/CMakeLists.txt"
|
||||
$linuxBinary = Join-Path $root "out/package/linux/panopainter"
|
||||
$readiness += New-PackageReadiness `
|
||||
-Kind $kind `
|
||||
-Status "blocked" `
|
||||
-Reason "retained-linux-cmake-not-consuming-root-cmake-targets" `
|
||||
-ValidationCommand "cmake -S linux -B out/package/linux-retained && cmake --build out/package/linux-retained --target panopainter" `
|
||||
-Prerequisites @(
|
||||
(New-Prerequisite -Name "retained-linux-cmake" -Available (Test-Path -LiteralPath $linuxCmake -PathType Leaf) -Detail $linuxCmake),
|
||||
(New-Prerequisite -Name "cmake" -Available (Test-CommandAvailable "cmake") -Detail "Linux retained app CMake configure/build tool"),
|
||||
(New-Prerequisite -Name "retained-platform-cmake-baseline" -Available $true -Detail "python scripts/dev/check_retained_platform_cmake.py"),
|
||||
(New-Prerequisite -Name "root-cmake-preset" -Available $true -Detail "linux-clang"),
|
||||
(New-Prerequisite -Name "root-cmake-package-target" -Available $false -Detail "Not migrated yet")
|
||||
) `
|
||||
-Artifacts @(
|
||||
(New-ArtifactCheck -Name "linux-app-output" -Path $linuxBinary -PathType "Leaf")
|
||||
)
|
||||
}
|
||||
"webgl" {
|
||||
$webglCmake = Join-Path $root "webgl/CMakeLists.txt"
|
||||
$webDir = Join-Path $root "out/package/webgl"
|
||||
$readiness += New-PackageReadiness `
|
||||
-Kind $kind `
|
||||
-Status "blocked" `
|
||||
-Reason "retained-webgl-cmake-not-consuming-root-cmake-targets" `
|
||||
-ValidationCommand "emcmake cmake -S webgl -B out/package/webgl-retained && cmake --build out/package/webgl-retained --target panopainter" `
|
||||
-Prerequisites @(
|
||||
(New-Prerequisite -Name "retained-webgl-cmake" -Available (Test-Path -LiteralPath $webglCmake -PathType Leaf) -Detail $webglCmake),
|
||||
(New-Prerequisite -Name "emcc" -Available (Test-CommandAvailable "emcc") -Detail "Emscripten compiler"),
|
||||
(New-Prerequisite -Name "emcmake" -Available (Test-CommandAvailable "emcmake") -Detail "Emscripten CMake wrapper"),
|
||||
(New-Prerequisite -Name "retained-platform-cmake-baseline" -Available $true -Detail "python scripts/dev/check_retained_platform_cmake.py"),
|
||||
(New-Prerequisite -Name "root-cmake-preset" -Available $true -Detail "emscripten"),
|
||||
(New-Prerequisite -Name "root-cmake-package-target" -Available $false -Detail "Not migrated yet")
|
||||
) `
|
||||
-Artifacts @(
|
||||
(New-ArtifactCheck -Name "webgl-output" -Path $webDir -PathType "Container")
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return $readiness
|
||||
}
|
||||
|
||||
$androidNativeValidation = if ($AndroidNativeChecks) {
|
||||
Invoke-AndroidNativePackageChecks -Kinds $PackageKinds
|
||||
} else {
|
||||
[ordered]@{
|
||||
requested = $false
|
||||
exitCode = 0
|
||||
results = @()
|
||||
}
|
||||
}
|
||||
|
||||
if ($ReadinessOnly) {
|
||||
$elapsed = [int]((Get-Date) - $started).TotalMilliseconds
|
||||
[ordered]@{
|
||||
command = "package-smoke"
|
||||
preset = $Preset
|
||||
configuration = $Configuration
|
||||
target = $Target
|
||||
stage = "readiness"
|
||||
exitCode = 0
|
||||
elapsedMs = $elapsed
|
||||
androidNativeValidation = $androidNativeValidation
|
||||
packageReadiness = @(Get-PackageReadiness -Kinds $PackageKinds)
|
||||
} | ConvertTo-Json -Compress -Depth 8
|
||||
exit $androidNativeValidation.exitCode
|
||||
}
|
||||
|
||||
& $CMakeCommand --build --preset $Preset --config $Configuration --target $Target
|
||||
$buildExitCode = $LASTEXITCODE
|
||||
if ($buildExitCode -ne 0) {
|
||||
$elapsed = [int]((Get-Date) - $started).TotalMilliseconds
|
||||
[ordered]@{
|
||||
command = "package-smoke"
|
||||
preset = $Preset
|
||||
configuration = $Configuration
|
||||
target = $Target
|
||||
stage = "build"
|
||||
cmakeCommand = $CMakeCommand
|
||||
exitCode = $buildExitCode
|
||||
elapsedMs = $elapsed
|
||||
androidNativeValidation = $androidNativeValidation
|
||||
packageReadiness = @(Get-PackageReadiness -Kinds $PackageKinds)
|
||||
} | ConvertTo-Json -Compress -Depth 8
|
||||
exit $buildExitCode
|
||||
}
|
||||
|
||||
$binaryDir = Join-Path (Join-Path (Join-Path (Get-Location) "out/build/$Preset") $Configuration) "$Target.exe"
|
||||
$targetDir = Split-Path -Parent $binaryDir
|
||||
$dataDir = Join-Path $targetDir "data"
|
||||
$curlDll = if ($Configuration -eq "Debug") { "libcurl_debug.dll" } else { "libcurl.dll" }
|
||||
$checks = @(
|
||||
[ordered]@{ name = "executable"; path = $binaryDir; exists = Test-Path -LiteralPath $binaryDir -PathType Leaf },
|
||||
[ordered]@{ name = "data"; path = $dataDir; exists = Test-Path -LiteralPath $dataDir -PathType Container },
|
||||
[ordered]@{ name = "BugTrapU-x64.dll"; path = (Join-Path $targetDir "BugTrapU-x64.dll"); exists = Test-Path -LiteralPath (Join-Path $targetDir "BugTrapU-x64.dll") -PathType Leaf },
|
||||
[ordered]@{ name = $curlDll; path = (Join-Path $targetDir $curlDll); exists = Test-Path -LiteralPath (Join-Path $targetDir $curlDll) -PathType Leaf },
|
||||
[ordered]@{ name = "libyuv.dll"; path = (Join-Path $targetDir "libyuv.dll"); exists = Test-Path -LiteralPath (Join-Path $targetDir "libyuv.dll") -PathType Leaf },
|
||||
[ordered]@{ name = "libmp4v2.dll"; path = (Join-Path $targetDir "libmp4v2.dll"); exists = Test-Path -LiteralPath (Join-Path $targetDir "libmp4v2.dll") -PathType Leaf },
|
||||
[ordered]@{ name = "openh264-2.0.0-win64.dll"; path = (Join-Path $targetDir "openh264-2.0.0-win64.dll"); exists = Test-Path -LiteralPath (Join-Path $targetDir "openh264-2.0.0-win64.dll") -PathType Leaf },
|
||||
[ordered]@{ name = "openvr_api.dll"; path = (Join-Path $targetDir "openvr_api.dll"); exists = Test-Path -LiteralPath (Join-Path $targetDir "openvr_api.dll") -PathType Leaf }
|
||||
)
|
||||
|
||||
$failed = @($checks | Where-Object { -not $_.exists })
|
||||
$exitCode = if ($failed.Count -eq 0) { 0 } else { 2 }
|
||||
if ($androidNativeValidation.exitCode -ne 0 -and $exitCode -eq 0) {
|
||||
$exitCode = $androidNativeValidation.exitCode
|
||||
}
|
||||
$elapsedMs = [int]((Get-Date) - $started).TotalMilliseconds
|
||||
|
||||
[ordered]@{
|
||||
command = "package-smoke"
|
||||
preset = $Preset
|
||||
configuration = $Configuration
|
||||
target = $Target
|
||||
cmakeCommand = $CMakeCommand
|
||||
exitCode = $exitCode
|
||||
elapsedMs = $elapsedMs
|
||||
checks = $checks
|
||||
androidNativeValidation = $androidNativeValidation
|
||||
packageReadiness = @(Get-PackageReadiness -Kinds $PackageKinds)
|
||||
} | ConvertTo-Json -Compress -Depth 8
|
||||
|
||||
exit $exitCode
|
||||
132
scripts/automation/package-smoke.sh
Normal file
132
scripts/automation/package-smoke.sh
Normal file
@@ -0,0 +1,132 @@
|
||||
#!/usr/bin/env sh
|
||||
set -u
|
||||
|
||||
preset="${1:-linux-clang}"
|
||||
configuration="${2:-Debug}"
|
||||
target="${3:-PanoPainter}"
|
||||
artifact="${4:-out/build/$preset/$target}"
|
||||
readiness_only=0
|
||||
if [ "${1:-}" = "--readiness-only" ]; then
|
||||
readiness_only=1
|
||||
preset="${2:-linux-clang}"
|
||||
configuration="${3:-Debug}"
|
||||
target="${4:-PanoPainter}"
|
||||
artifact="${5:-out/build/$preset/$target}"
|
||||
fi
|
||||
start="$(date +%s)"
|
||||
root="$(pwd)"
|
||||
|
||||
json_string() {
|
||||
printf '"%s"' "$(printf '%s' "$1" | sed 's/\\/\\\\/g; s/"/\\"/g')"
|
||||
}
|
||||
|
||||
json_bool() {
|
||||
if [ "$1" = "1" ]; then
|
||||
printf true
|
||||
else
|
||||
printf false
|
||||
fi
|
||||
}
|
||||
|
||||
command_available() {
|
||||
command -v "$1" >/dev/null 2>&1
|
||||
}
|
||||
|
||||
file_available() {
|
||||
[ -f "$1" ]
|
||||
}
|
||||
|
||||
dir_available() {
|
||||
[ -d "$1" ]
|
||||
}
|
||||
|
||||
package_readiness_json() {
|
||||
windows_wapproj="$root/PanoPainterPackage/PanoPainterPackage.wapproj"
|
||||
windows_manifest="$root/PanoPainterPackage/Package.appxmanifest"
|
||||
windows_output="$root/PanoPainterPackage/AppPackages"
|
||||
android_standard_gradle="$root/android/android/build.gradle"
|
||||
android_standard_manifest="$root/android/android/src/main/AndroidManifest.xml"
|
||||
android_standard_output="$root/android/android/build/outputs/apk"
|
||||
android_quest_gradle="$root/android/quest/build.gradle"
|
||||
android_quest_manifest="$root/android/quest/src/main/AndroidManifest.xml"
|
||||
android_quest_output="$root/android/quest/build/outputs/apk"
|
||||
android_focus_gradle="$root/android/focus/build.gradle"
|
||||
android_focus_manifest="$root/android/focus/src/main/AndroidManifest.xml"
|
||||
android_focus_output="$root/android/focus/build/outputs/apk"
|
||||
apple_project="$root/PanoPainter.xcodeproj/project.pbxproj"
|
||||
apple_output="$root/out/package/apple"
|
||||
linux_cmake="$root/linux/CMakeLists.txt"
|
||||
linux_output="$root/out/package/linux/panopainter"
|
||||
webgl_cmake="$root/webgl/CMakeLists.txt"
|
||||
webgl_output="$root/out/package/webgl"
|
||||
|
||||
file_available "$windows_wapproj"; windows_wapproj_exists="$([ "$?" -eq 0 ] && printf 1 || printf 0)"
|
||||
file_available "$windows_manifest"; windows_manifest_exists="$([ "$?" -eq 0 ] && printf 1 || printf 0)"
|
||||
command_available makeappx; makeappx_exists="$([ "$?" -eq 0 ] && printf 1 || printf 0)"
|
||||
command_available signtool; signtool_exists="$([ "$?" -eq 0 ] && printf 1 || printf 0)"
|
||||
dir_available "$windows_output"; windows_output_exists="$([ "$?" -eq 0 ] && printf 1 || printf 0)"
|
||||
|
||||
file_available "$android_standard_gradle"; android_standard_gradle_exists="$([ "$?" -eq 0 ] && printf 1 || printf 0)"
|
||||
file_available "$android_standard_manifest"; android_standard_manifest_exists="$([ "$?" -eq 0 ] && printf 1 || printf 0)"
|
||||
file_available "$android_quest_gradle"; android_quest_gradle_exists="$([ "$?" -eq 0 ] && printf 1 || printf 0)"
|
||||
file_available "$android_quest_manifest"; android_quest_manifest_exists="$([ "$?" -eq 0 ] && printf 1 || printf 0)"
|
||||
file_available "$android_focus_gradle"; android_focus_gradle_exists="$([ "$?" -eq 0 ] && printf 1 || printf 0)"
|
||||
file_available "$android_focus_manifest"; android_focus_manifest_exists="$([ "$?" -eq 0 ] && printf 1 || printf 0)"
|
||||
command_available gradle; gradle_exists="$([ "$?" -eq 0 ] && printf 1 || printf 0)"
|
||||
dir_available "$android_standard_output"; android_standard_output_exists="$([ "$?" -eq 0 ] && printf 1 || printf 0)"
|
||||
dir_available "$android_quest_output"; android_quest_output_exists="$([ "$?" -eq 0 ] && printf 1 || printf 0)"
|
||||
dir_available "$android_focus_output"; android_focus_output_exists="$([ "$?" -eq 0 ] && printf 1 || printf 0)"
|
||||
|
||||
file_available "$apple_project"; apple_project_exists="$([ "$?" -eq 0 ] && printf 1 || printf 0)"
|
||||
command_available xcodebuild; xcodebuild_exists="$([ "$?" -eq 0 ] && printf 1 || printf 0)"
|
||||
dir_available "$apple_output"; apple_output_exists="$([ "$?" -eq 0 ] && printf 1 || printf 0)"
|
||||
|
||||
file_available "$linux_cmake"; linux_cmake_exists="$([ "$?" -eq 0 ] && printf 1 || printf 0)"
|
||||
command_available cmake; cmake_exists="$([ "$?" -eq 0 ] && printf 1 || printf 0)"
|
||||
file_available "$linux_output"; linux_output_exists="$([ "$?" -eq 0 ] && printf 1 || printf 0)"
|
||||
|
||||
file_available "$webgl_cmake"; webgl_cmake_exists="$([ "$?" -eq 0 ] && printf 1 || printf 0)"
|
||||
command_available emcc; emcc_exists="$([ "$?" -eq 0 ] && printf 1 || printf 0)"
|
||||
command_available emcmake; emcmake_exists="$([ "$?" -eq 0 ] && printf 1 || printf 0)"
|
||||
dir_available "$webgl_output"; webgl_output_exists="$([ "$?" -eq 0 ] && printf 1 || printf 0)"
|
||||
|
||||
printf '['
|
||||
printf '{"kind":"windows-appx","status":"blocked","reason":"legacy-wapproj-present-but-root-cmake-package-target-missing","debt":"DEBT-0011","validationCommand":"msbuild PanoPainterPackage/PanoPainterPackage.wapproj /p:Configuration=%s /p:Platform=x64","prerequisites":[{"name":"legacy-wapproj","available":%s,"detail":%s},{"name":"appx-manifest","available":%s,"detail":%s},{"name":"makeappx","available":%s,"detail":"Windows SDK packaging tool"},{"name":"signtool","available":%s,"detail":"Windows SDK signing tool"},{"name":"root-cmake-package-target","available":false,"detail":"Not migrated yet"}],"artifacts":[{"name":"app-packages","path":%s,"pathType":"Container","exists":%s}]}' "$configuration" "$(json_bool "$windows_wapproj_exists")" "$(json_string "$windows_wapproj")" "$(json_bool "$windows_manifest_exists")" "$(json_string "$windows_manifest")" "$(json_bool "$makeappx_exists")" "$(json_bool "$signtool_exists")" "$(json_string "$windows_output")" "$(json_bool "$windows_output_exists")"
|
||||
printf ',{"kind":"android-standard-apk","status":"blocked","reason":"legacy-gradle-package-not-consuming-root-cmake-targets","debt":"DEBT-0011","validationCommand":"gradle -p android/android assembleDebug","prerequisites":[{"name":"gradle-build","available":%s,"detail":%s},{"name":"android-manifest","available":%s,"detail":%s},{"name":"gradle","available":%s,"detail":"Android package builder"},{"name":"retained-native-cmake-check","available":true,"detail":"powershell -ExecutionPolicy Bypass -File scripts/automation/android-legacy-package-build.ps1 -Packages standard"},{"name":"root-cmake-preset","available":true,"detail":"android-arm64/android-x64"},{"name":"root-cmake-package-target","available":false,"detail":"Not migrated yet"}],"artifacts":[{"name":"apk-output","path":%s,"pathType":"Container","exists":%s}]}' "$(json_bool "$android_standard_gradle_exists")" "$(json_string "$android_standard_gradle")" "$(json_bool "$android_standard_manifest_exists")" "$(json_string "$android_standard_manifest")" "$(json_bool "$gradle_exists")" "$(json_string "$android_standard_output")" "$(json_bool "$android_standard_output_exists")"
|
||||
printf ',{"kind":"android-quest-apk","status":"blocked","reason":"legacy-gradle-package-not-consuming-root-cmake-targets","debt":"DEBT-0011","validationCommand":"gradle -p android/quest assembleDebug","prerequisites":[{"name":"gradle-build","available":%s,"detail":%s},{"name":"android-manifest","available":%s,"detail":%s},{"name":"gradle","available":%s,"detail":"Android package builder"},{"name":"retained-native-cmake-check","available":true,"detail":"powershell -ExecutionPolicy Bypass -File scripts/automation/android-legacy-package-build.ps1 -Packages quest -ConfigureOnly"},{"name":"root-cmake-preset","available":true,"detail":"android-quest-arm64"},{"name":"root-cmake-package-target","available":false,"detail":"Not migrated yet"}],"artifacts":[{"name":"apk-output","path":%s,"pathType":"Container","exists":%s}]}' "$(json_bool "$android_quest_gradle_exists")" "$(json_string "$android_quest_gradle")" "$(json_bool "$android_quest_manifest_exists")" "$(json_string "$android_quest_manifest")" "$(json_bool "$gradle_exists")" "$(json_string "$android_quest_output")" "$(json_bool "$android_quest_output_exists")"
|
||||
printf ',{"kind":"android-focus-apk","status":"blocked","reason":"legacy-gradle-package-not-consuming-root-cmake-targets","debt":"DEBT-0011","validationCommand":"gradle -p android/focus assembleDebug","prerequisites":[{"name":"gradle-build","available":%s,"detail":%s},{"name":"android-manifest","available":%s,"detail":%s},{"name":"gradle","available":%s,"detail":"Android package builder"},{"name":"retained-native-cmake-check","available":true,"detail":"powershell -ExecutionPolicy Bypass -File scripts/automation/android-legacy-package-build.ps1 -Packages focus -ConfigureOnly"},{"name":"root-cmake-preset","available":true,"detail":"android-focus-arm64"},{"name":"root-cmake-package-target","available":false,"detail":"Not migrated yet"}],"artifacts":[{"name":"apk-output","path":%s,"pathType":"Container","exists":%s}]}' "$(json_bool "$android_focus_gradle_exists")" "$(json_string "$android_focus_gradle")" "$(json_bool "$android_focus_manifest_exists")" "$(json_string "$android_focus_manifest")" "$(json_bool "$gradle_exists")" "$(json_string "$android_focus_output")" "$(json_bool "$android_focus_output_exists")"
|
||||
printf ',{"kind":"apple-bundle","status":"blocked","reason":"legacy-xcode-project-and-host-toolchain-not-aligned-with-root-cmake-package-target","debt":"DEBT-0011","validationCommand":"xcodebuild -project PanoPainter.xcodeproj -configuration %s","prerequisites":[{"name":"legacy-xcode-project","available":%s,"detail":%s},{"name":"xcodebuild","available":%s,"detail":"Apple package builder"},{"name":"root-cmake-preset","available":true,"detail":"macos/ios-device/ios-simulator"},{"name":"root-cmake-package-target","available":false,"detail":"Not migrated yet"}],"artifacts":[{"name":"apple-package-output","path":%s,"pathType":"Container","exists":%s}]}' "$configuration" "$(json_bool "$apple_project_exists")" "$(json_string "$apple_project")" "$(json_bool "$xcodebuild_exists")" "$(json_string "$apple_output")" "$(json_bool "$apple_output_exists")"
|
||||
printf ',{"kind":"linux-app","status":"blocked","reason":"retained-linux-cmake-not-consuming-root-cmake-targets","debt":"DEBT-0011","validationCommand":"cmake -S linux -B out/package/linux-retained && cmake --build out/package/linux-retained --target panopainter","prerequisites":[{"name":"retained-linux-cmake","available":%s,"detail":%s},{"name":"cmake","available":%s,"detail":"Linux retained app CMake configure/build tool"},{"name":"retained-platform-cmake-baseline","available":true,"detail":"python scripts/dev/check_retained_platform_cmake.py"},{"name":"root-cmake-preset","available":true,"detail":"linux-clang"},{"name":"root-cmake-package-target","available":false,"detail":"Not migrated yet"}],"artifacts":[{"name":"linux-app-output","path":%s,"pathType":"Leaf","exists":%s}]}' "$(json_bool "$linux_cmake_exists")" "$(json_string "$linux_cmake")" "$(json_bool "$cmake_exists")" "$(json_string "$linux_output")" "$(json_bool "$linux_output_exists")"
|
||||
printf ',{"kind":"webgl","status":"blocked","reason":"retained-webgl-cmake-not-consuming-root-cmake-targets","debt":"DEBT-0011","validationCommand":"emcmake cmake -S webgl -B out/package/webgl-retained && cmake --build out/package/webgl-retained --target panopainter","prerequisites":[{"name":"retained-webgl-cmake","available":%s,"detail":%s},{"name":"emcc","available":%s,"detail":"Emscripten compiler"},{"name":"emcmake","available":%s,"detail":"Emscripten CMake wrapper"},{"name":"retained-platform-cmake-baseline","available":true,"detail":"python scripts/dev/check_retained_platform_cmake.py"},{"name":"root-cmake-preset","available":true,"detail":"emscripten"},{"name":"root-cmake-package-target","available":false,"detail":"Not migrated yet"}],"artifacts":[{"name":"webgl-output","path":%s,"pathType":"Container","exists":%s}]}' "$(json_bool "$webgl_cmake_exists")" "$(json_string "$webgl_cmake")" "$(json_bool "$emcc_exists")" "$(json_bool "$emcmake_exists")" "$(json_string "$webgl_output")" "$(json_bool "$webgl_output_exists")"
|
||||
printf ']'
|
||||
}
|
||||
|
||||
if [ "$readiness_only" -eq 1 ]; then
|
||||
end="$(date +%s)"
|
||||
elapsed_ms="$(( (end - start) * 1000 ))"
|
||||
readiness="$(package_readiness_json)"
|
||||
printf '{"command":"package-smoke","preset":"%s","configuration":"%s","target":"%s","stage":"readiness","exitCode":0,"elapsedMs":%s,"packageReadiness":%s}\n' "$preset" "$configuration" "$target" "$elapsed_ms" "$readiness"
|
||||
exit 0
|
||||
fi
|
||||
|
||||
cmake --build --preset "$preset" --config "$configuration" --target "$target"
|
||||
build_exit="$?"
|
||||
if [ "$build_exit" -ne 0 ]; then
|
||||
end="$(date +%s)"
|
||||
elapsed_ms="$(( (end - start) * 1000 ))"
|
||||
readiness="$(package_readiness_json)"
|
||||
printf '{"command":"package-smoke","preset":"%s","configuration":"%s","target":"%s","stage":"build","exitCode":%s,"elapsedMs":%s,"packageReadiness":%s}\n' "$preset" "$configuration" "$target" "$build_exit" "$elapsed_ms" "$readiness"
|
||||
exit "$build_exit"
|
||||
fi
|
||||
|
||||
if [ -e "$artifact" ]; then
|
||||
exit_code=0
|
||||
else
|
||||
exit_code=2
|
||||
fi
|
||||
|
||||
end="$(date +%s)"
|
||||
elapsed_ms="$(( (end - start) * 1000 ))"
|
||||
readiness="$(package_readiness_json)"
|
||||
printf '{"command":"package-smoke","preset":"%s","configuration":"%s","target":"%s","artifact":"%s","exists":%s,"exitCode":%s,"elapsedMs":%s,"packageReadiness":%s}\n' "$preset" "$configuration" "$target" "$artifact" "$([ "$exit_code" -eq 0 ] && printf true || printf false)" "$exit_code" "$elapsed_ms" "$readiness"
|
||||
exit "$exit_code"
|
||||
200
scripts/automation/platform-build.ps1
Normal file
200
scripts/automation/platform-build.ps1
Normal file
@@ -0,0 +1,200 @@
|
||||
[CmdletBinding()]
|
||||
param(
|
||||
[string[]]$Presets = @("android-arm64", "android-x64", "android-quest-arm64", "android-focus-arm64"),
|
||||
[string[]]$Targets = @(
|
||||
"pp_foundation",
|
||||
"pp_assets",
|
||||
"pp_paint",
|
||||
"pp_document",
|
||||
"pp_renderer_api",
|
||||
"pp_renderer_gl",
|
||||
"pp_paint_renderer",
|
||||
"pp_ui_core",
|
||||
"pp_platform_api",
|
||||
"pp_app_core",
|
||||
"pano_cli",
|
||||
"pp_foundation_binary_stream_tests",
|
||||
"pp_foundation_event_tests",
|
||||
"pp_foundation_log_tests",
|
||||
"pp_foundation_parse_tests",
|
||||
"pp_foundation_task_queue_tests",
|
||||
"pp_foundation_trace_tests",
|
||||
"pp_assets_brush_package_tests",
|
||||
"pp_assets_image_format_tests",
|
||||
"pp_assets_image_metadata_tests",
|
||||
"pp_assets_image_pixels_tests",
|
||||
"pp_assets_ppi_header_tests",
|
||||
"pp_assets_settings_document_tests",
|
||||
"pp_paint_brush_tests",
|
||||
"pp_paint_blend_tests",
|
||||
"pp_paint_stroke_tests",
|
||||
"pp_paint_stroke_script_tests",
|
||||
"pp_document_tests",
|
||||
"pp_document_ppi_import_tests",
|
||||
"pp_document_ppi_export_tests",
|
||||
"pp_renderer_api_tests",
|
||||
"pp_renderer_gl_capabilities_tests",
|
||||
"pp_renderer_gl_command_plan_tests",
|
||||
"pp_paint_renderer_compositor_tests",
|
||||
"pp_platform_api_tests",
|
||||
"pp_ui_core_color_tests",
|
||||
"pp_ui_core_layout_value_tests",
|
||||
"pp_ui_core_layout_xml_tests",
|
||||
"pp_ui_core_node_lifetime_tests",
|
||||
"pp_ui_core_overlay_lifetime_tests",
|
||||
"pp_app_core_about_menu_tests",
|
||||
"pp_app_core_app_dialog_tests",
|
||||
"pp_app_core_app_preferences_tests",
|
||||
"pp_app_core_app_frame_tests",
|
||||
"pp_app_core_app_thread_tests",
|
||||
"pp_app_core_app_input_tests",
|
||||
"pp_app_core_app_shutdown_tests",
|
||||
"pp_app_core_app_startup_tests",
|
||||
"pp_app_core_app_status_tests",
|
||||
"pp_app_core_command_convert_tests",
|
||||
"pp_app_core_brush_package_export_tests",
|
||||
"pp_app_core_brush_package_import_tests",
|
||||
"pp_app_core_brush_ui_tests",
|
||||
"pp_app_core_canvas_hotkey_tests",
|
||||
"pp_app_core_canvas_tool_ui_tests",
|
||||
"pp_app_core_canvas_view_tests",
|
||||
"pp_app_core_document_animation_tests",
|
||||
"pp_app_core_document_canvas_tests",
|
||||
"pp_app_core_document_cloud_tests",
|
||||
"pp_app_core_document_export_tests",
|
||||
"pp_app_core_document_import_tests",
|
||||
"pp_app_core_document_layer_tests",
|
||||
"pp_app_core_document_platform_io_tests",
|
||||
"pp_app_core_document_recording_tests",
|
||||
"pp_app_core_document_resize_tests",
|
||||
"pp_app_core_document_route_tests",
|
||||
"pp_app_core_document_sharing_tests",
|
||||
"pp_app_core_document_session_tests",
|
||||
"pp_app_core_file_menu_tests",
|
||||
"pp_app_core_grid_ui_tests",
|
||||
"pp_app_core_history_ui_tests",
|
||||
"pp_app_core_main_toolbar_tests",
|
||||
"pp_app_core_quick_ui_tests",
|
||||
"pp_app_core_tools_menu_tests"
|
||||
)
|
||||
)
|
||||
|
||||
$ErrorActionPreference = "Stop"
|
||||
|
||||
function Expand-ArgumentList {
|
||||
param([string[]]$Values)
|
||||
|
||||
$expanded = @()
|
||||
foreach ($value in $Values) {
|
||||
foreach ($part in ($value -split ",")) {
|
||||
$trimmed = $part.Trim()
|
||||
if ($trimmed.Length -gt 0) {
|
||||
$expanded += $trimmed
|
||||
}
|
||||
}
|
||||
}
|
||||
return $expanded
|
||||
}
|
||||
|
||||
function Get-VcpkgRoot {
|
||||
$candidates = @()
|
||||
if ($env:VCPKG_ROOT) {
|
||||
$candidates += $env:VCPKG_ROOT
|
||||
}
|
||||
|
||||
$programFiles = @($env:ProgramFiles, ${env:ProgramFiles(x86)}) | Where-Object { $_ }
|
||||
$vsYears = @("2026", "2022")
|
||||
$vsEditions = @("Community", "Professional", "Enterprise", "BuildTools", "Preview")
|
||||
foreach ($root in $programFiles) {
|
||||
foreach ($year in $vsYears) {
|
||||
foreach ($edition in $vsEditions) {
|
||||
$candidates += (Join-Path $root "Microsoft Visual Studio\$year\$edition\VC\vcpkg")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
foreach ($candidate in $candidates) {
|
||||
if ($candidate -and (Test-Path -LiteralPath (Join-Path $candidate "vcpkg.exe") -PathType Leaf)) {
|
||||
return (Resolve-Path -LiteralPath $candidate).Path
|
||||
}
|
||||
}
|
||||
|
||||
throw "VCPKG_ROOT was not set and no Visual Studio bundled vcpkg root was found."
|
||||
}
|
||||
|
||||
function Set-VcpkgRootEnvironment {
|
||||
$vcpkgRoot = Get-VcpkgRoot
|
||||
$env:VCPKG_ROOT = $vcpkgRoot
|
||||
return [ordered]@{
|
||||
vcpkgRoot = $vcpkgRoot
|
||||
vcpkgCommand = Join-Path $vcpkgRoot "vcpkg.exe"
|
||||
}
|
||||
}
|
||||
|
||||
$Presets = @(Expand-ArgumentList -Values $Presets)
|
||||
$Targets = @(Expand-ArgumentList -Values $Targets)
|
||||
|
||||
$cmakeCommand = "cmake"
|
||||
$androidToolchain = $null
|
||||
$vcpkgToolchain = $null
|
||||
if ($Presets | Where-Object { $_ -like "*vcpkg*" }) {
|
||||
$vcpkgToolchain = Set-VcpkgRootEnvironment
|
||||
}
|
||||
if ($Presets | Where-Object { $_ -like "android-*" }) {
|
||||
. "$PSScriptRoot\android-sdk-env.ps1"
|
||||
$androidToolchain = Set-AndroidSdkToolchainEnvironment
|
||||
$cmakeCommand = $androidToolchain.cmakeCommand
|
||||
}
|
||||
|
||||
$started = Get-Date
|
||||
$results = @()
|
||||
$overallExitCode = 0
|
||||
|
||||
foreach ($preset in $Presets) {
|
||||
$presetCmakeCommand = $cmakeCommand
|
||||
if ($androidToolchain -and $preset -notlike "android-*") {
|
||||
$presetCmakeCommand = "cmake"
|
||||
}
|
||||
|
||||
& $presetCmakeCommand --preset $preset
|
||||
$configureExitCode = $LASTEXITCODE
|
||||
if ($configureExitCode -ne 0) {
|
||||
$overallExitCode = $configureExitCode
|
||||
$results += [ordered]@{
|
||||
preset = $preset
|
||||
stage = "configure"
|
||||
exitCode = $configureExitCode
|
||||
}
|
||||
continue
|
||||
}
|
||||
|
||||
$buildArgs = @("--build", "--preset", $preset)
|
||||
foreach ($target in $Targets) {
|
||||
$buildArgs += @("--target", $target)
|
||||
}
|
||||
|
||||
& $presetCmakeCommand @buildArgs
|
||||
$buildExitCode = $LASTEXITCODE
|
||||
if ($buildExitCode -ne 0 -and $overallExitCode -eq 0) {
|
||||
$overallExitCode = $buildExitCode
|
||||
}
|
||||
|
||||
$results += [ordered]@{
|
||||
preset = $preset
|
||||
stage = "build"
|
||||
targets = $Targets
|
||||
exitCode = $buildExitCode
|
||||
}
|
||||
}
|
||||
|
||||
$elapsed = [int]((Get-Date) - $started).TotalMilliseconds
|
||||
[ordered]@{
|
||||
command = "platform-build"
|
||||
exitCode = $overallExitCode
|
||||
elapsedMs = $elapsed
|
||||
androidToolchain = $androidToolchain
|
||||
vcpkgToolchain = $vcpkgToolchain
|
||||
results = $results
|
||||
} | ConvertTo-Json -Compress -Depth 6
|
||||
|
||||
exit $overallExitCode
|
||||
65
scripts/automation/platform-build.sh
Normal file
65
scripts/automation/platform-build.sh
Normal file
@@ -0,0 +1,65 @@
|
||||
#!/usr/bin/env sh
|
||||
set -u
|
||||
|
||||
presets="${1:-android-arm64 android-x64 android-quest-arm64 android-focus-arm64}"
|
||||
shift || true
|
||||
targets="${*:-pp_foundation pp_assets pp_paint pp_document pp_renderer_api pp_renderer_gl pp_paint_renderer pp_ui_core pp_platform_api pp_app_core pano_cli pp_foundation_binary_stream_tests pp_foundation_event_tests pp_foundation_log_tests pp_foundation_parse_tests pp_foundation_task_queue_tests pp_foundation_trace_tests pp_assets_brush_package_tests pp_assets_image_format_tests pp_assets_image_metadata_tests pp_assets_image_pixels_tests pp_assets_ppi_header_tests pp_assets_settings_document_tests pp_paint_brush_tests pp_paint_blend_tests pp_paint_stroke_tests pp_paint_stroke_script_tests pp_document_tests pp_document_ppi_import_tests pp_document_ppi_export_tests pp_renderer_api_tests pp_renderer_gl_capabilities_tests pp_renderer_gl_command_plan_tests pp_paint_renderer_compositor_tests pp_platform_api_tests pp_ui_core_color_tests pp_ui_core_layout_value_tests pp_ui_core_layout_xml_tests pp_ui_core_node_lifetime_tests pp_ui_core_overlay_lifetime_tests pp_app_core_about_menu_tests pp_app_core_app_dialog_tests pp_app_core_app_preferences_tests pp_app_core_app_frame_tests pp_app_core_app_thread_tests pp_app_core_app_input_tests pp_app_core_app_shutdown_tests pp_app_core_app_startup_tests pp_app_core_app_status_tests pp_app_core_command_convert_tests pp_app_core_brush_package_export_tests pp_app_core_brush_package_import_tests pp_app_core_brush_ui_tests pp_app_core_canvas_hotkey_tests pp_app_core_canvas_tool_ui_tests pp_app_core_canvas_view_tests pp_app_core_document_animation_tests pp_app_core_document_canvas_tests pp_app_core_document_cloud_tests pp_app_core_document_export_tests pp_app_core_document_import_tests pp_app_core_document_layer_tests pp_app_core_document_platform_io_tests pp_app_core_document_recording_tests pp_app_core_document_resize_tests pp_app_core_document_route_tests pp_app_core_document_sharing_tests pp_app_core_document_session_tests pp_app_core_file_menu_tests pp_app_core_grid_ui_tests pp_app_core_history_ui_tests pp_app_core_main_toolbar_tests pp_app_core_quick_ui_tests pp_app_core_tools_menu_tests}"
|
||||
start="$(date +%s)"
|
||||
|
||||
android_cmake_cmd=""
|
||||
case " $presets " in
|
||||
*" android-"*)
|
||||
# shellcheck disable=SC1091
|
||||
. "$(dirname "$0")/android-sdk-env.sh"
|
||||
set_android_sdk_toolchain_environment || exit 1
|
||||
android_cmake_cmd="$ANDROID_CMAKE_COMMAND"
|
||||
;;
|
||||
esac
|
||||
|
||||
overall_exit=0
|
||||
results=""
|
||||
first_result=1
|
||||
|
||||
build_args=""
|
||||
for target in $targets; do
|
||||
build_args="$build_args --target $target"
|
||||
done
|
||||
|
||||
normalized_presets="$(printf '%s' "$presets" | tr ',' ' ')"
|
||||
for preset in $normalized_presets; do
|
||||
cmake_cmd="cmake"
|
||||
case "$preset" in
|
||||
android-*)
|
||||
cmake_cmd="$android_cmake_cmd"
|
||||
;;
|
||||
esac
|
||||
|
||||
"$cmake_cmd" --preset "$preset"
|
||||
configure_exit="$?"
|
||||
if [ "$configure_exit" -ne 0 ]; then
|
||||
[ "$overall_exit" -eq 0 ] && overall_exit="$configure_exit"
|
||||
result="$(printf '{"preset":"%s","stage":"configure","exitCode":%s}' "$preset" "$configure_exit")"
|
||||
else
|
||||
# shellcheck disable=SC2086
|
||||
"$cmake_cmd" --build --preset "$preset" $build_args
|
||||
build_exit="$?"
|
||||
[ "$build_exit" -ne 0 ] && [ "$overall_exit" -eq 0 ] && overall_exit="$build_exit"
|
||||
result="$(printf '{"preset":"%s","stage":"build","targets":"%s","exitCode":%s}' "$preset" "$targets" "$build_exit")"
|
||||
fi
|
||||
|
||||
if [ "$first_result" -eq 1 ]; then
|
||||
results="$result"
|
||||
first_result=0
|
||||
else
|
||||
results="$results,$result"
|
||||
fi
|
||||
done
|
||||
|
||||
end="$(date +%s)"
|
||||
elapsed_ms="$(( (end - start) * 1000 ))"
|
||||
if [ -n "${ANDROID_NDK_HOME:-}" ] && [ -n "${ANDROID_CMAKE_COMMAND:-}" ]; then
|
||||
printf '{"command":"platform-build","exitCode":%s,"elapsedMs":%s,"androidToolchain":{"sdkRoot":"%s","sdkManagerCommand":"%s","packageUpdates":[{"package":"ndk","installedVersionBefore":"%s","availableVersion":"%s","selectedVersion":"%s","action":"%s"},{"package":"cmake","installedVersionBefore":"%s","availableVersion":"%s","selectedVersion":"%s","action":"%s"}],"ndkVersion":"%s","ndkPath":"%s","cmakeVersion":"%s","cmakeCommand":"%s"},"results":[%s]}\n' "$overall_exit" "$elapsed_ms" "$ANDROID_SDK_ROOT" "$ANDROID_SDKMANAGER_COMMAND" "$ANDROID_NDK_INSTALLED_BEFORE" "$ANDROID_NDK_AVAILABLE_VERSION" "$ANDROID_NDK_VERSION" "$ANDROID_NDK_UPDATE_ACTION" "$ANDROID_CMAKE_INSTALLED_BEFORE" "$ANDROID_CMAKE_AVAILABLE_VERSION" "$ANDROID_CMAKE_VERSION" "$ANDROID_CMAKE_UPDATE_ACTION" "$ANDROID_NDK_VERSION" "$ANDROID_NDK_HOME" "$ANDROID_CMAKE_VERSION" "$ANDROID_CMAKE_COMMAND" "$results"
|
||||
else
|
||||
printf '{"command":"platform-build","exitCode":%s,"elapsedMs":%s,"results":[%s]}\n' "$overall_exit" "$elapsed_ms" "$results"
|
||||
fi
|
||||
exit "$overall_exit"
|
||||
304
scripts/automation/quiet-validate.ps1
Normal file
304
scripts/automation/quiet-validate.ps1
Normal file
@@ -0,0 +1,304 @@
|
||||
[CmdletBinding()]
|
||||
param(
|
||||
[string]$BuildPreset = "windows-msvc-default",
|
||||
[string]$Configuration = "Debug",
|
||||
[string[]]$BuildTargets = @("PanoPainter", "pano_cli"),
|
||||
[string]$TestPreset = "desktop-fast",
|
||||
[string]$TestRegex = "",
|
||||
[switch]$Configure,
|
||||
[switch]$SkipBuild,
|
||||
[switch]$SkipTests,
|
||||
[string]$CMakeCommand = "",
|
||||
[string]$CTestCommand = "",
|
||||
[string]$LogDir = "out/logs/quiet-validation",
|
||||
[string]$IgnoreFilterFile = "",
|
||||
[string[]]$IgnorePattern = @(),
|
||||
[int]$FailureTailLines = 0
|
||||
)
|
||||
|
||||
$ErrorActionPreference = "Stop"
|
||||
|
||||
function Resolve-CMakeCommand {
|
||||
param([string]$Requested)
|
||||
|
||||
if ($Requested.Length -gt 0) {
|
||||
return $Requested
|
||||
}
|
||||
|
||||
$vsCmake = "C:\Program Files\Microsoft Visual Studio\18\Community\Common7\IDE\CommonExtensions\Microsoft\CMake\CMake\bin\cmake.exe"
|
||||
if (Test-Path -LiteralPath $vsCmake) {
|
||||
return $vsCmake
|
||||
}
|
||||
|
||||
return "cmake"
|
||||
}
|
||||
|
||||
function Resolve-CTestCommand {
|
||||
param(
|
||||
[string]$Requested,
|
||||
[string]$ResolvedCMake
|
||||
)
|
||||
|
||||
if ($Requested.Length -gt 0) {
|
||||
return $Requested
|
||||
}
|
||||
|
||||
if ($ResolvedCMake.EndsWith("cmake.exe", [System.StringComparison]::OrdinalIgnoreCase)) {
|
||||
$candidate = Join-Path -Path (Split-Path -Parent $ResolvedCMake) -ChildPath "ctest.exe"
|
||||
if (Test-Path -LiteralPath $candidate) {
|
||||
return $candidate
|
||||
}
|
||||
}
|
||||
|
||||
return "ctest"
|
||||
}
|
||||
|
||||
function Read-IgnorePatterns {
|
||||
param(
|
||||
[string]$FilterFile,
|
||||
[string[]]$InlinePatterns
|
||||
)
|
||||
|
||||
$patterns = @()
|
||||
if ($FilterFile.Length -eq 0) {
|
||||
$defaultFile = Join-Path -Path $PSScriptRoot -ChildPath "quiet-validation-ignore.txt"
|
||||
if (Test-Path -LiteralPath $defaultFile) {
|
||||
$FilterFile = $defaultFile
|
||||
}
|
||||
}
|
||||
|
||||
if ($FilterFile.Length -gt 0 -and (Test-Path -LiteralPath $FilterFile)) {
|
||||
$patterns += Get-Content -LiteralPath $FilterFile |
|
||||
Where-Object { $_.Trim().Length -gt 0 -and -not $_.TrimStart().StartsWith("#") }
|
||||
}
|
||||
|
||||
$patterns += $InlinePatterns
|
||||
return @($patterns | Where-Object { $_ -and $_.Length -gt 0 })
|
||||
}
|
||||
|
||||
function Expand-ArgumentList {
|
||||
param([string[]]$Values)
|
||||
|
||||
$expanded = @()
|
||||
foreach ($value in $Values) {
|
||||
if ($null -eq $value) {
|
||||
continue
|
||||
}
|
||||
$expanded += $value -split "[,\s]+" | Where-Object { $_.Length -gt 0 }
|
||||
}
|
||||
return @($expanded)
|
||||
}
|
||||
|
||||
function Limit-LogSlug {
|
||||
param(
|
||||
[string]$Value,
|
||||
[int]$MaxLength = 96
|
||||
)
|
||||
|
||||
if ($Value.Length -le $MaxLength) {
|
||||
return $Value
|
||||
}
|
||||
return $Value.Substring(0, $MaxLength)
|
||||
}
|
||||
|
||||
function Test-IgnoredLine {
|
||||
param(
|
||||
[string]$Line,
|
||||
[string[]]$Patterns
|
||||
)
|
||||
|
||||
foreach ($pattern in $Patterns) {
|
||||
if ($Line -match $pattern) {
|
||||
return $true
|
||||
}
|
||||
}
|
||||
return $false
|
||||
}
|
||||
|
||||
function Measure-Log {
|
||||
param(
|
||||
[string]$Path,
|
||||
[string[]]$IgnorePatterns
|
||||
)
|
||||
|
||||
$errorPattern = "(?i)(:\s*(fatal\s+)?error\s+[A-Z0-9]+:|^LINK\s*:\s*fatal error|^CMake Error|Errors while running CTest|Unable to find executable|\*\*\*Failed)"
|
||||
$warningPattern = "(?i)(:\s*warning\s+[A-Z0-9]+:|^LINK\s*:\s*warning\s+[A-Z0-9]+:|warning:)"
|
||||
$ctestSummaryPattern = "(\d+)% tests passed, (\d+) tests failed out of (\d+)"
|
||||
|
||||
$lineCount = 0
|
||||
$rawErrors = 0
|
||||
$rawWarnings = 0
|
||||
$visibleErrors = 0
|
||||
$visibleWarnings = 0
|
||||
$ignoredErrors = 0
|
||||
$ignoredWarnings = 0
|
||||
$testsFailed = $null
|
||||
$testsTotal = $null
|
||||
|
||||
if (Test-Path -LiteralPath $Path) {
|
||||
foreach ($line in Get-Content -LiteralPath $Path) {
|
||||
++$lineCount
|
||||
$ignored = Test-IgnoredLine -Line $line -Patterns $IgnorePatterns
|
||||
if ($line -match $ctestSummaryPattern) {
|
||||
$testsFailed = [int]$Matches[2]
|
||||
$testsTotal = [int]$Matches[3]
|
||||
}
|
||||
if ($line -match $errorPattern) {
|
||||
++$rawErrors
|
||||
if ($ignored) { ++$ignoredErrors } else { ++$visibleErrors }
|
||||
}
|
||||
if ($line -match $warningPattern) {
|
||||
++$rawWarnings
|
||||
if ($ignored) { ++$ignoredWarnings } else { ++$visibleWarnings }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return [ordered]@{
|
||||
lineCount = $lineCount
|
||||
errors = $visibleErrors
|
||||
warnings = $visibleWarnings
|
||||
rawErrors = $rawErrors
|
||||
rawWarnings = $rawWarnings
|
||||
ignoredErrors = $ignoredErrors
|
||||
ignoredWarnings = $ignoredWarnings
|
||||
testsFailed = $testsFailed
|
||||
testsTotal = $testsTotal
|
||||
}
|
||||
}
|
||||
|
||||
function Invoke-QuietStep {
|
||||
param(
|
||||
[string]$Name,
|
||||
[string]$Command,
|
||||
[string[]]$Arguments,
|
||||
[string]$LogPath,
|
||||
[string[]]$IgnorePatterns,
|
||||
[int]$FailureTailLines
|
||||
)
|
||||
|
||||
$started = Get-Date
|
||||
$exitCode = 0
|
||||
try {
|
||||
& $Command @Arguments *> $LogPath
|
||||
$exitCode = $LASTEXITCODE
|
||||
if ($null -eq $exitCode) {
|
||||
$exitCode = 0
|
||||
}
|
||||
}
|
||||
catch {
|
||||
$_ | Out-File -LiteralPath $LogPath -Append -Encoding utf8
|
||||
$exitCode = 1
|
||||
}
|
||||
|
||||
$elapsed = [int]((Get-Date) - $started).TotalMilliseconds
|
||||
$summary = Measure-Log -Path $LogPath -IgnorePatterns $IgnorePatterns
|
||||
$result = [ordered]@{
|
||||
name = $Name
|
||||
exitCode = $exitCode
|
||||
elapsedMs = $elapsed
|
||||
log = $LogPath
|
||||
summary = $summary
|
||||
}
|
||||
|
||||
if ($exitCode -ne 0 -and $FailureTailLines -gt 0 -and (Test-Path -LiteralPath $LogPath)) {
|
||||
$result.failureTail = @(Get-Content -LiteralPath $LogPath -Tail $FailureTailLines | ForEach-Object { [string]$_ })
|
||||
}
|
||||
|
||||
return $result
|
||||
}
|
||||
|
||||
$resolvedCMake = Resolve-CMakeCommand -Requested $CMakeCommand
|
||||
$resolvedCTest = Resolve-CTestCommand -Requested $CTestCommand -ResolvedCMake $resolvedCMake
|
||||
$BuildTargets = @(Expand-ArgumentList -Values $BuildTargets)
|
||||
$IgnorePattern = @(Expand-ArgumentList -Values $IgnorePattern)
|
||||
$ignorePatterns = Read-IgnorePatterns -FilterFile $IgnoreFilterFile -InlinePatterns $IgnorePattern
|
||||
|
||||
New-Item -ItemType Directory -Force -Path $LogDir | Out-Null
|
||||
$runId = Get-Date -Format "yyyyMMdd-HHmmss"
|
||||
$started = Get-Date
|
||||
$results = @()
|
||||
$overallExitCode = 0
|
||||
|
||||
if ($Configure) {
|
||||
$log = Join-Path -Path $LogDir -ChildPath "$runId-configure-$BuildPreset.log"
|
||||
$result = Invoke-QuietStep `
|
||||
-Name "configure:$BuildPreset" `
|
||||
-Command $resolvedCMake `
|
||||
-Arguments @("--preset", $BuildPreset) `
|
||||
-LogPath $log `
|
||||
-IgnorePatterns $ignorePatterns `
|
||||
-FailureTailLines $FailureTailLines
|
||||
$results += $result
|
||||
if ($result.exitCode -ne 0 -and $overallExitCode -eq 0) {
|
||||
$overallExitCode = $result.exitCode
|
||||
}
|
||||
}
|
||||
|
||||
if (-not $SkipBuild) {
|
||||
$targets = @($BuildTargets | Where-Object { $_ -and $_.Length -gt 0 })
|
||||
if ($targets.Count -gt 0) {
|
||||
$safeTargets = Limit-LogSlug -Value (($targets -join "_") -replace "[^A-Za-z0-9_.-]", "_")
|
||||
$log = Join-Path -Path $LogDir -ChildPath "$runId-build-$BuildPreset-$Configuration-$safeTargets.log"
|
||||
$buildArgs = @("--build", "--preset", $BuildPreset, "--config", $Configuration, "--target") + $targets
|
||||
$result = Invoke-QuietStep `
|
||||
-Name ("build:{0}:{1}" -f $BuildPreset, $Configuration) `
|
||||
-Command $resolvedCMake `
|
||||
-Arguments $buildArgs `
|
||||
-LogPath $log `
|
||||
-IgnorePatterns $ignorePatterns `
|
||||
-FailureTailLines $FailureTailLines
|
||||
$result.targets = $targets
|
||||
$results += $result
|
||||
if ($result.exitCode -ne 0 -and $overallExitCode -eq 0) {
|
||||
$overallExitCode = $result.exitCode
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (-not $SkipTests) {
|
||||
$safeRegex = if ($TestRegex.Length -gt 0) {
|
||||
Limit-LogSlug -Value ($TestRegex -replace "[^A-Za-z0-9_.-]", "_")
|
||||
} else {
|
||||
"all"
|
||||
}
|
||||
$log = Join-Path -Path $LogDir -ChildPath "$runId-test-$TestPreset-$Configuration-$safeRegex.log"
|
||||
$testArgs = @("--preset", $TestPreset, "--build-config", $Configuration, "--output-on-failure")
|
||||
if ($TestRegex.Length -gt 0) {
|
||||
$testArgs += @("-R", $TestRegex)
|
||||
}
|
||||
$result = Invoke-QuietStep `
|
||||
-Name ("test:{0}:{1}" -f $TestPreset, $Configuration) `
|
||||
-Command $resolvedCTest `
|
||||
-Arguments $testArgs `
|
||||
-LogPath $log `
|
||||
-IgnorePatterns $ignorePatterns `
|
||||
-FailureTailLines $FailureTailLines
|
||||
if ($TestRegex.Length -gt 0) {
|
||||
$result.testRegex = $TestRegex
|
||||
}
|
||||
$results += $result
|
||||
if ($result.exitCode -ne 0 -and $overallExitCode -eq 0) {
|
||||
$overallExitCode = $result.exitCode
|
||||
}
|
||||
}
|
||||
|
||||
$elapsed = [int]((Get-Date) - $started).TotalMilliseconds
|
||||
$summaryPath = Join-Path -Path $LogDir -ChildPath "$runId-summary.json"
|
||||
$payload = [ordered]@{
|
||||
command = "quiet-validate"
|
||||
exitCode = $overallExitCode
|
||||
elapsedMs = $elapsed
|
||||
buildPreset = $BuildPreset
|
||||
configuration = $Configuration
|
||||
testPreset = $TestPreset
|
||||
logDir = $LogDir
|
||||
summary = $summaryPath
|
||||
ignoreFilterFile = $IgnoreFilterFile
|
||||
ignorePatternCount = $ignorePatterns.Count
|
||||
results = $results
|
||||
}
|
||||
|
||||
$payload | ConvertTo-Json -Depth 8 | Out-File -LiteralPath $summaryPath -Encoding utf8
|
||||
$payload | ConvertTo-Json -Compress -Depth 8
|
||||
exit $overallExitCode
|
||||
13
scripts/automation/quiet-validation-ignore.txt
Normal file
13
scripts/automation/quiet-validation-ignore.txt
Normal file
@@ -0,0 +1,13 @@
|
||||
# Regex patterns for warnings/noise hidden from quiet validation summaries.
|
||||
# The full logs still contain these lines; this file only affects visible counts.
|
||||
The vcpkg manifest was disabled
|
||||
warning C4201:
|
||||
warning C4267:
|
||||
warning C5311:
|
||||
warning C4018:
|
||||
warning C4244:
|
||||
warning C4189:
|
||||
warning C4305:
|
||||
warning C4099:
|
||||
warning LNK4099: PDB 'yuv\.pdb'
|
||||
warning LNK4098: defaultlib 'MSVCRT' conflicts
|
||||
22
scripts/automation/test.ps1
Normal file
22
scripts/automation/test.ps1
Normal file
@@ -0,0 +1,22 @@
|
||||
[CmdletBinding()]
|
||||
param(
|
||||
[string]$Preset = "desktop-fast",
|
||||
[string]$Configuration = "Debug"
|
||||
)
|
||||
|
||||
$ErrorActionPreference = "Stop"
|
||||
$started = Get-Date
|
||||
|
||||
& ctest --preset $Preset --build-config $Configuration
|
||||
$exitCode = $LASTEXITCODE
|
||||
$elapsed = [int]((Get-Date) - $started).TotalMilliseconds
|
||||
|
||||
[ordered]@{
|
||||
command = "test"
|
||||
preset = $Preset
|
||||
configuration = $Configuration
|
||||
exitCode = $exitCode
|
||||
elapsedMs = $elapsed
|
||||
} | ConvertTo-Json -Compress
|
||||
|
||||
exit $exitCode
|
||||
12
scripts/automation/test.sh
Normal file
12
scripts/automation/test.sh
Normal file
@@ -0,0 +1,12 @@
|
||||
#!/usr/bin/env sh
|
||||
set -u
|
||||
|
||||
preset="${1:-desktop-fast}"
|
||||
configuration="${2:-Debug}"
|
||||
start="$(date +%s)"
|
||||
ctest --preset "$preset" --build-config "$configuration"
|
||||
exit_code="$?"
|
||||
end="$(date +%s)"
|
||||
elapsed_ms="$(( (end - start) * 1000 ))"
|
||||
printf '{"command":"test","preset":"%s","configuration":"%s","exitCode":%s,"elapsedMs":%s}\n' "$preset" "$configuration" "$exit_code" "$elapsed_ms"
|
||||
exit "$exit_code"
|
||||
144
scripts/dev/check_package_smoke_readiness.py
Normal file
144
scripts/dev/check_package_smoke_readiness.py
Normal file
@@ -0,0 +1,144 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Verify package-smoke wrappers report the expected package readiness matrix."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import json
|
||||
import re
|
||||
import sys
|
||||
from pathlib import Path
|
||||
|
||||
|
||||
EXPECTED_PACKAGE_KINDS = [
|
||||
"windows-appx",
|
||||
"android-standard-apk",
|
||||
"android-quest-apk",
|
||||
"android-focus-apk",
|
||||
"apple-bundle",
|
||||
"linux-app",
|
||||
"webgl",
|
||||
]
|
||||
|
||||
EXPECTED_CMAKE_PACKAGE_TARGETS = [
|
||||
"panopainter_package_readiness",
|
||||
"panopainter_windows_app_package_smoke",
|
||||
"panopainter_android_standard_native_package",
|
||||
"panopainter_android_vr_native_package_configure",
|
||||
"panopainter_android_native_package_smoke",
|
||||
"panopainter_linux_webgl_package_readiness",
|
||||
]
|
||||
|
||||
|
||||
def repo_root() -> Path:
|
||||
return Path(__file__).resolve().parents[2]
|
||||
|
||||
|
||||
def powershell_package_kinds(root: Path) -> list[str]:
|
||||
script = (root / "scripts" / "automation" / "package-smoke.ps1").read_text(encoding="utf-8")
|
||||
match = re.search(r"\[string\[\]\]\$PackageKinds\s*=\s*@\((.*?)\n\s*\)", script, re.S)
|
||||
if not match:
|
||||
raise RuntimeError("Could not find PackageKinds default in package-smoke.ps1")
|
||||
return sorted(re.findall(r'"([^"]+)"', match.group(1)))
|
||||
|
||||
|
||||
def shell_package_kinds(root: Path) -> list[str]:
|
||||
script = (root / "scripts" / "automation" / "package-smoke.sh").read_text(encoding="utf-8")
|
||||
return sorted(set(re.findall(r'"kind":"([^"]+)"', script)))
|
||||
|
||||
|
||||
def count_regex(root: Path, patterns: dict[str, str]) -> dict[str, int]:
|
||||
counts: dict[str, int] = {}
|
||||
for script_name, pattern in patterns.items():
|
||||
text = (root / "scripts" / "automation" / script_name).read_text(encoding="utf-8")
|
||||
counts[script_name] = len(re.findall(pattern, text))
|
||||
return counts
|
||||
|
||||
|
||||
def main() -> int:
|
||||
root = repo_root()
|
||||
expected = sorted(EXPECTED_PACKAGE_KINDS)
|
||||
ps_kinds = powershell_package_kinds(root)
|
||||
sh_kinds = shell_package_kinds(root)
|
||||
debt_counts = count_regex(root, {
|
||||
"package-smoke.ps1": r'debt\s*=\s*"DEBT-0011"',
|
||||
"package-smoke.sh": r'"debt":"DEBT-0011"',
|
||||
})
|
||||
blocked_counts = count_regex(root, {
|
||||
"package-smoke.ps1": r'-Status\s+"blocked"',
|
||||
"package-smoke.sh": r'"status":"blocked"',
|
||||
})
|
||||
readiness_mode_counts = {
|
||||
"package-smoke.ps1": (root / "scripts" / "automation" / "package-smoke.ps1").read_text(encoding="utf-8").count("ReadinessOnly"),
|
||||
"package-smoke.sh": (root / "scripts" / "automation" / "package-smoke.sh").read_text(encoding="utf-8").count("readiness_only"),
|
||||
}
|
||||
retained_android_native_counts = count_regex(root, {
|
||||
"package-smoke.ps1": r"retained-native-cmake-check",
|
||||
"package-smoke.sh": r"retained-native-cmake-check",
|
||||
})
|
||||
retained_platform_cmake_counts = count_regex(root, {
|
||||
"package-smoke.ps1": r"retained-(linux|webgl)-cmake",
|
||||
"package-smoke.sh": r"retained-(linux|webgl)-cmake",
|
||||
})
|
||||
cmake_package_module = (root / "cmake" / "PanoPainterPackageTargets.cmake").read_text(encoding="utf-8")
|
||||
root_cmake = (root / "CMakeLists.txt").read_text(encoding="utf-8")
|
||||
|
||||
missing = {
|
||||
"package-smoke.ps1": [kind for kind in expected if kind not in ps_kinds],
|
||||
"package-smoke.sh": [kind for kind in expected if kind not in sh_kinds],
|
||||
}
|
||||
unexpected = {
|
||||
"package-smoke.ps1": [kind for kind in ps_kinds if kind not in expected],
|
||||
"package-smoke.sh": [kind for kind in sh_kinds if kind not in expected],
|
||||
}
|
||||
debt_thresholds = {
|
||||
"package-smoke.ps1": 1,
|
||||
"package-smoke.sh": len(expected),
|
||||
}
|
||||
debt_complete = {name: count >= debt_thresholds[name] for name, count in debt_counts.items()}
|
||||
blocked_complete = {name: count >= len(expected) for name, count in blocked_counts.items()}
|
||||
readiness_mode_present = {name: count > 0 for name, count in readiness_mode_counts.items()}
|
||||
retained_android_native_complete = {
|
||||
name: count >= 3 for name, count in retained_android_native_counts.items()
|
||||
}
|
||||
retained_platform_cmake_complete = {
|
||||
name: count >= 2 for name, count in retained_platform_cmake_counts.items()
|
||||
}
|
||||
cmake_package_targets_present = {
|
||||
target: target in cmake_package_module for target in EXPECTED_CMAKE_PACKAGE_TARGETS
|
||||
}
|
||||
cmake_package_module_included = "include(PanoPainterPackageTargets)" in root_cmake
|
||||
|
||||
ok = (
|
||||
all(not values for values in missing.values())
|
||||
and all(not values for values in unexpected.values())
|
||||
and all(debt_complete.values())
|
||||
and all(blocked_complete.values())
|
||||
and all(readiness_mode_present.values())
|
||||
and all(retained_android_native_complete.values())
|
||||
and all(retained_platform_cmake_complete.values())
|
||||
and all(cmake_package_targets_present.values())
|
||||
and cmake_package_module_included
|
||||
)
|
||||
|
||||
print(json.dumps({
|
||||
"ok": ok,
|
||||
"expectedPackageKinds": expected,
|
||||
"packageKinds": {
|
||||
"package-smoke.ps1": ps_kinds,
|
||||
"package-smoke.sh": sh_kinds,
|
||||
},
|
||||
"missing": missing,
|
||||
"unexpected": unexpected,
|
||||
"debtComplete": debt_complete,
|
||||
"blockedComplete": blocked_complete,
|
||||
"readinessModePresent": readiness_mode_present,
|
||||
"retainedAndroidNativeComplete": retained_android_native_complete,
|
||||
"retainedPlatformCmakeComplete": retained_platform_cmake_complete,
|
||||
"cmakePackageTargetsPresent": cmake_package_targets_present,
|
||||
"cmakePackageModuleIncluded": cmake_package_module_included,
|
||||
}, separators=(",", ":")))
|
||||
return 0 if ok else 1
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
sys.exit(main())
|
||||
153
scripts/dev/check_platform_build_targets.py
Normal file
153
scripts/dev/check_platform_build_targets.py
Normal file
@@ -0,0 +1,153 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Verify platform-build wrappers include the current headless target matrix."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import json
|
||||
import re
|
||||
import sys
|
||||
from pathlib import Path
|
||||
|
||||
|
||||
REQUIRED_COMPONENT_TARGETS = [
|
||||
"pp_foundation",
|
||||
"pp_assets",
|
||||
"pp_paint",
|
||||
"pp_document",
|
||||
"pp_renderer_api",
|
||||
"pp_renderer_gl",
|
||||
"pp_paint_renderer",
|
||||
"pp_ui_core",
|
||||
"pp_platform_api",
|
||||
"pp_app_core",
|
||||
"pano_cli",
|
||||
]
|
||||
|
||||
REQUIRED_ANDROID_PRESETS = [
|
||||
"android-arm64",
|
||||
"android-x64",
|
||||
"android-quest-arm64",
|
||||
"android-focus-arm64",
|
||||
]
|
||||
|
||||
EXPECTED_CMAKE_PLATFORM_TARGETS = [
|
||||
"panopainter_platform_build_headless",
|
||||
"panopainter_platform_build_android_assets",
|
||||
"panopainter_platform_build_vcpkg_ui_core",
|
||||
"panopainter_platform_build_apple_remote",
|
||||
]
|
||||
|
||||
|
||||
def repo_root() -> Path:
|
||||
return Path(__file__).resolve().parents[2]
|
||||
|
||||
|
||||
def cmake_test_targets(root: Path) -> list[str]:
|
||||
cmake_lists = root / "tests" / "CMakeLists.txt"
|
||||
text = cmake_lists.read_text(encoding="utf-8")
|
||||
return sorted(set(re.findall(r"^\s*add_executable\(([^\s\)]+)", text, re.MULTILINE)))
|
||||
|
||||
|
||||
def powershell_default_targets(root: Path) -> list[str]:
|
||||
script = root / "scripts" / "automation" / "platform-build.ps1"
|
||||
targets: list[str] = []
|
||||
in_targets = False
|
||||
|
||||
for line in script.read_text(encoding="utf-8").splitlines():
|
||||
if "[string[]]$Targets" in line:
|
||||
in_targets = True
|
||||
if not in_targets:
|
||||
continue
|
||||
|
||||
targets.extend(re.findall(r'"([^"]+)"', line))
|
||||
if in_targets and line.strip() == ")":
|
||||
break
|
||||
|
||||
return sorted(set(targets))
|
||||
|
||||
|
||||
def powershell_default_presets(root: Path) -> list[str]:
|
||||
script = (root / "scripts" / "automation" / "platform-build.ps1").read_text(encoding="utf-8")
|
||||
match = re.search(r"\[string\[\]\]\$Presets\s*=\s*@\((.*?)\)", script, re.S)
|
||||
if not match:
|
||||
raise RuntimeError("Could not find default presets in platform-build.ps1")
|
||||
return sorted(set(re.findall(r'"([^"]+)"', match.group(1))))
|
||||
|
||||
|
||||
def shell_default_targets(root: Path) -> list[str]:
|
||||
script = root / "scripts" / "automation" / "platform-build.sh"
|
||||
text = script.read_text(encoding="utf-8")
|
||||
match = re.search(r'targets="\$\{[^:]+:-(.*)\}"', text)
|
||||
if not match:
|
||||
raise RuntimeError("Could not find default targets in platform-build.sh")
|
||||
return sorted(set(match.group(1).split()))
|
||||
|
||||
|
||||
def shell_default_presets(root: Path) -> list[str]:
|
||||
script = (root / "scripts" / "automation" / "platform-build.sh").read_text(encoding="utf-8")
|
||||
match = re.search(r'presets="\$\{[^:]+:-(.*)\}"', script)
|
||||
if not match:
|
||||
raise RuntimeError("Could not find default presets in platform-build.sh")
|
||||
return sorted(set(match.group(1).split()))
|
||||
|
||||
|
||||
def missing(expected: list[str], actual: list[str]) -> list[str]:
|
||||
actual_set = set(actual)
|
||||
return [target for target in expected if target not in actual_set]
|
||||
|
||||
|
||||
def main() -> int:
|
||||
root = repo_root()
|
||||
expected = sorted(set(REQUIRED_COMPONENT_TARGETS + cmake_test_targets(root)))
|
||||
ps_targets = powershell_default_targets(root)
|
||||
sh_targets = shell_default_targets(root)
|
||||
ps_presets = powershell_default_presets(root)
|
||||
sh_presets = shell_default_presets(root)
|
||||
cmake_platform_module = (root / "cmake" / "PanoPainterPlatformTargets.cmake").read_text(encoding="utf-8")
|
||||
root_cmake = (root / "CMakeLists.txt").read_text(encoding="utf-8")
|
||||
cmake_platform_targets_present = {
|
||||
target: target in cmake_platform_module for target in EXPECTED_CMAKE_PLATFORM_TARGETS
|
||||
}
|
||||
cmake_platform_module_included = "include(PanoPainterPlatformTargets)" in root_cmake
|
||||
android_sdk_env = {
|
||||
"android-sdk-env.ps1": (root / "scripts" / "automation" / "android-sdk-env.ps1").read_text(encoding="utf-8"),
|
||||
"android-sdk-env.sh": (root / "scripts" / "automation" / "android-sdk-env.sh").read_text(encoding="utf-8"),
|
||||
}
|
||||
android_sdkmanager_update_support = {
|
||||
name: all(token in text for token in ("sdkmanager", "--list", "--install", "ndk", "cmake"))
|
||||
for name, text in android_sdk_env.items()
|
||||
}
|
||||
|
||||
result = {
|
||||
"ok": True,
|
||||
"expectedTargetCount": len(expected),
|
||||
"powershellTargetCount": len(ps_targets),
|
||||
"shellTargetCount": len(sh_targets),
|
||||
"expectedAndroidPresets": REQUIRED_ANDROID_PRESETS,
|
||||
"defaultPresets": {
|
||||
"platform-build.ps1": ps_presets,
|
||||
"platform-build.sh": sh_presets,
|
||||
},
|
||||
"cmakePlatformTargetsPresent": cmake_platform_targets_present,
|
||||
"cmakePlatformModuleIncluded": cmake_platform_module_included,
|
||||
"androidSdkmanagerUpdateSupport": android_sdkmanager_update_support,
|
||||
"missing": {
|
||||
"platform-build.ps1.targets": missing(expected, ps_targets),
|
||||
"platform-build.sh.targets": missing(expected, sh_targets),
|
||||
"platform-build.ps1.presets": missing(REQUIRED_ANDROID_PRESETS, ps_presets),
|
||||
"platform-build.sh.presets": missing(REQUIRED_ANDROID_PRESETS, sh_presets),
|
||||
},
|
||||
}
|
||||
result["ok"] = (
|
||||
all(not values for values in result["missing"].values())
|
||||
and all(cmake_platform_targets_present.values())
|
||||
and cmake_platform_module_included
|
||||
and all(android_sdkmanager_update_support.values())
|
||||
)
|
||||
|
||||
print(json.dumps(result, separators=(",", ":")))
|
||||
return 0 if result["ok"] else 1
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
sys.exit(main())
|
||||
61
scripts/dev/check_retained_platform_cmake.py
Normal file
61
scripts/dev/check_retained_platform_cmake.py
Normal file
@@ -0,0 +1,61 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Guard retained non-root platform CMake files during Phase 6 migration."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import json
|
||||
import re
|
||||
import sys
|
||||
from pathlib import Path
|
||||
|
||||
|
||||
RETAINED_PLATFORM_CMAKE = [
|
||||
Path("linux/CMakeLists.txt"),
|
||||
Path("webgl/CMakeLists.txt"),
|
||||
]
|
||||
|
||||
|
||||
def repo_root() -> Path:
|
||||
return Path(__file__).resolve().parents[2]
|
||||
|
||||
|
||||
def cmake_minimum_version(text: str) -> tuple[int, ...] | None:
|
||||
match = re.search(r"cmake_minimum_required\s*\(\s*VERSION\s+([0-9.]+)", text, re.I)
|
||||
if not match:
|
||||
return None
|
||||
return tuple(int(part) for part in match.group(1).split("."))
|
||||
|
||||
|
||||
def main() -> int:
|
||||
root = repo_root()
|
||||
results: dict[str, object] = {}
|
||||
ok = True
|
||||
|
||||
for path in RETAINED_PLATFORM_CMAKE:
|
||||
text = (root / path).read_text(encoding="utf-8")
|
||||
minimum_version = cmake_minimum_version(text)
|
||||
has_target_cxx23 = "target_compile_features(panopainter PRIVATE cxx_std_23)" in text
|
||||
has_cxx_extensions_off = "CXX_EXTENSIONS OFF" in text
|
||||
uses_global_cxx_standard_flag = bool(re.search(r"-std=c\+\+14|-std=c\+\+17|-std=c\+\+20", text))
|
||||
platform_ok = (
|
||||
minimum_version is not None
|
||||
and minimum_version >= (3, 10)
|
||||
and has_target_cxx23
|
||||
and has_cxx_extensions_off
|
||||
and not uses_global_cxx_standard_flag
|
||||
)
|
||||
ok = ok and platform_ok
|
||||
results[str(path)] = {
|
||||
"ok": platform_ok,
|
||||
"minimumVersion": ".".join(str(part) for part in minimum_version) if minimum_version else None,
|
||||
"hasTargetCxx23": has_target_cxx23,
|
||||
"hasCxxExtensionsOff": has_cxx_extensions_off,
|
||||
"usesGlobalCxxStandardFlag": uses_global_cxx_standard_flag,
|
||||
}
|
||||
|
||||
print(json.dumps({"ok": ok, "platforms": results}, separators=(",", ":")))
|
||||
return 0 if ok else 1
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
sys.exit(main())
|
||||
608
scripts/dev/clangd_nav.py
Normal file
608
scripts/dev/clangd_nav.py
Normal file
@@ -0,0 +1,608 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Small clangd navigation helper for agent-friendly C++ code lookup.
|
||||
|
||||
Examples:
|
||||
python scripts/dev/clangd_nav.py symbols --file src/app_core/brush_ui.h
|
||||
python scripts/dev/clangd_nav.py symbols --file src/app_core/brush_ui.h --name-regex "execute_.*preset"
|
||||
python scripts/dev/clangd_nav.py symbols --file src/app_core/document_export.h --detail-regex "Export.*Plan"
|
||||
python scripts/dev/clangd_nav.py definition --file src/node_panel_brush.cpp --line 410 --column 30
|
||||
python scripts/dev/clangd_nav.py references --file src/app_core/brush_ui.h --line 192 --column 43 --path-regex "src[\\\\/]app_core"
|
||||
python scripts/dev/clangd_nav.py self-test
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import argparse
|
||||
import json
|
||||
import os
|
||||
from pathlib import Path
|
||||
import queue
|
||||
import re
|
||||
import subprocess
|
||||
import sys
|
||||
import threading
|
||||
import time
|
||||
from typing import Any, Pattern
|
||||
|
||||
|
||||
DEFAULT_BUILD_DIRS = (
|
||||
"out/build/windows-clangcl-asan",
|
||||
"out/build/android-arm64",
|
||||
)
|
||||
|
||||
|
||||
def _repo_root() -> Path:
|
||||
return Path(__file__).resolve().parents[2]
|
||||
|
||||
|
||||
def _find_compile_commands_dir(repo_root: Path, requested: str | None) -> Path:
|
||||
if requested:
|
||||
path = Path(requested).expanduser()
|
||||
if not path.is_absolute():
|
||||
path = repo_root / path
|
||||
if path.is_file():
|
||||
path = path.parent
|
||||
if not (path / "compile_commands.json").exists():
|
||||
raise SystemExit(f"compile_commands.json not found in {path}")
|
||||
return path.resolve()
|
||||
|
||||
env_dir = os.environ.get("PP_CLANGD_COMPILE_COMMANDS_DIR")
|
||||
if env_dir:
|
||||
return _find_compile_commands_dir(repo_root, env_dir)
|
||||
|
||||
for candidate in DEFAULT_BUILD_DIRS:
|
||||
path = repo_root / candidate
|
||||
if (path / "compile_commands.json").exists():
|
||||
return path.resolve()
|
||||
|
||||
matches = sorted((repo_root / "out" / "build").glob("*/compile_commands.json"))
|
||||
if matches:
|
||||
return matches[0].parent.resolve()
|
||||
|
||||
raise SystemExit(
|
||||
"No compile_commands.json found. Configure a Ninja CMake preset first, "
|
||||
"or pass --compile-commands-dir."
|
||||
)
|
||||
|
||||
|
||||
def _resolve_file(repo_root: Path, file_arg: str) -> Path:
|
||||
path = Path(file_arg).expanduser()
|
||||
if not path.is_absolute():
|
||||
path = repo_root / path
|
||||
if not path.exists():
|
||||
raise SystemExit(f"file not found: {path}")
|
||||
return path.resolve()
|
||||
|
||||
|
||||
def _read_lsp_message(stream: Any) -> dict[str, Any] | None:
|
||||
content_length: int | None = None
|
||||
while True:
|
||||
line = stream.readline()
|
||||
if not line:
|
||||
return None
|
||||
if line in (b"\r\n", b"\n"):
|
||||
break
|
||||
name, _, value = line.decode("ascii", errors="replace").partition(":")
|
||||
if name.lower() == "content-length":
|
||||
content_length = int(value.strip())
|
||||
|
||||
if content_length is None:
|
||||
return None
|
||||
|
||||
payload = stream.read(content_length)
|
||||
if not payload:
|
||||
return None
|
||||
return json.loads(payload.decode("utf-8"))
|
||||
|
||||
|
||||
def _write_lsp_message(stream: Any, message: dict[str, Any]) -> None:
|
||||
payload = json.dumps(message, separators=(",", ":")).encode("utf-8")
|
||||
header = f"Content-Length: {len(payload)}\r\n\r\n".encode("ascii")
|
||||
stream.write(header + payload)
|
||||
stream.flush()
|
||||
|
||||
|
||||
class ClangdClient:
|
||||
def __init__(
|
||||
self,
|
||||
clangd: str,
|
||||
compile_commands_dir: Path,
|
||||
timeout_seconds: float,
|
||||
background_index: bool) -> None:
|
||||
self._timeout_seconds = timeout_seconds
|
||||
self._next_id = 1
|
||||
self._responses: dict[int, dict[str, Any]] = {}
|
||||
self._condition = threading.Condition()
|
||||
self._messages: "queue.Queue[dict[str, Any]]" = queue.Queue()
|
||||
clangd_args = [
|
||||
clangd,
|
||||
f"--compile-commands-dir={compile_commands_dir}",
|
||||
"--log=error",
|
||||
]
|
||||
if not background_index:
|
||||
clangd_args.append("--background-index=false")
|
||||
|
||||
self._process = subprocess.Popen(
|
||||
clangd_args,
|
||||
stdin=subprocess.PIPE,
|
||||
stdout=subprocess.PIPE,
|
||||
stderr=subprocess.DEVNULL,
|
||||
)
|
||||
if self._process.stdin is None or self._process.stdout is None:
|
||||
raise RuntimeError("failed to open clangd stdio pipes")
|
||||
|
||||
self._stdin = self._process.stdin
|
||||
self._stdout = self._process.stdout
|
||||
self._reader = threading.Thread(target=self._reader_loop, daemon=True)
|
||||
self._reader.start()
|
||||
|
||||
def close(self) -> None:
|
||||
try:
|
||||
self.notify("exit", {})
|
||||
except Exception:
|
||||
pass
|
||||
try:
|
||||
self._process.terminate()
|
||||
self._process.wait(timeout=2)
|
||||
except Exception:
|
||||
self._process.kill()
|
||||
|
||||
def _reader_loop(self) -> None:
|
||||
while True:
|
||||
try:
|
||||
message = _read_lsp_message(self._stdout)
|
||||
except Exception as exc:
|
||||
message = {"error": {"message": f"failed to read clangd response: {exc}"}}
|
||||
if message is None:
|
||||
return
|
||||
if "id" in message:
|
||||
with self._condition:
|
||||
self._responses[int(message["id"])] = message
|
||||
self._condition.notify_all()
|
||||
else:
|
||||
self._messages.put(message)
|
||||
|
||||
def request(self, method: str, params: dict[str, Any]) -> Any:
|
||||
request_id = self._next_id
|
||||
self._next_id += 1
|
||||
_write_lsp_message(
|
||||
self._stdin,
|
||||
{
|
||||
"jsonrpc": "2.0",
|
||||
"id": request_id,
|
||||
"method": method,
|
||||
"params": params,
|
||||
},
|
||||
)
|
||||
deadline = time.monotonic() + self._timeout_seconds
|
||||
with self._condition:
|
||||
while request_id not in self._responses:
|
||||
remaining = deadline - time.monotonic()
|
||||
if remaining <= 0:
|
||||
raise TimeoutError(f"clangd request timed out: {method}")
|
||||
self._condition.wait(remaining)
|
||||
response = self._responses.pop(request_id)
|
||||
|
||||
if "error" in response:
|
||||
raise RuntimeError(response["error"].get("message", "clangd request failed"))
|
||||
return response.get("result")
|
||||
|
||||
def notify(self, method: str, params: dict[str, Any]) -> None:
|
||||
_write_lsp_message(
|
||||
self._stdin,
|
||||
{
|
||||
"jsonrpc": "2.0",
|
||||
"method": method,
|
||||
"params": params,
|
||||
},
|
||||
)
|
||||
|
||||
|
||||
def _position_params(file_path: Path, line: int, column: int) -> dict[str, Any]:
|
||||
if line < 1 or column < 1:
|
||||
raise SystemExit("--line and --column are 1-based and must be positive")
|
||||
return {
|
||||
"textDocument": { "uri": file_path.as_uri() },
|
||||
"position": { "line": line - 1, "character": column - 1 },
|
||||
}
|
||||
|
||||
|
||||
def _range_to_json(range_value: dict[str, Any]) -> dict[str, Any]:
|
||||
start = range_value["start"]
|
||||
end = range_value["end"]
|
||||
return {
|
||||
"start": { "line": start["line"] + 1, "column": start["character"] + 1 },
|
||||
"end": { "line": end["line"] + 1, "column": end["character"] + 1 },
|
||||
}
|
||||
|
||||
|
||||
def _location_to_json(value: dict[str, Any]) -> dict[str, Any]:
|
||||
if "targetUri" in value:
|
||||
uri = value["targetUri"]
|
||||
range_value = value.get("targetRange", value.get("targetSelectionRange"))
|
||||
else:
|
||||
uri = value["uri"]
|
||||
range_value = value["range"]
|
||||
return {
|
||||
"uri": uri,
|
||||
"path": _uri_to_path(uri),
|
||||
"range": _range_to_json(range_value),
|
||||
}
|
||||
|
||||
|
||||
def _uri_to_path(uri: str) -> str:
|
||||
if uri.startswith("file:///"):
|
||||
raw = uri[8:]
|
||||
if len(raw) >= 3 and raw[1] == ":":
|
||||
return raw.replace("/", "\\")
|
||||
return "/" + raw
|
||||
return uri
|
||||
|
||||
|
||||
def _locations_to_json(result: Any) -> list[dict[str, Any]]:
|
||||
if result is None:
|
||||
return []
|
||||
if isinstance(result, dict):
|
||||
return [_location_to_json(result)]
|
||||
return [_location_to_json(item) for item in result]
|
||||
|
||||
|
||||
def _symbols_to_json(symbols: list[dict[str, Any]]) -> list[dict[str, Any]]:
|
||||
def convert(symbol: dict[str, Any]) -> dict[str, Any]:
|
||||
item = {
|
||||
"name": symbol.get("name", ""),
|
||||
"detail": symbol.get("detail", ""),
|
||||
"kind": symbol.get("kind", 0),
|
||||
"range": _range_to_json(symbol["range"]),
|
||||
"selectionRange": _range_to_json(symbol.get("selectionRange", symbol["range"])),
|
||||
}
|
||||
children = symbol.get("children")
|
||||
if children:
|
||||
item["children"] = [convert(child) for child in children]
|
||||
return item
|
||||
|
||||
return [convert(symbol) for symbol in symbols or []]
|
||||
|
||||
|
||||
def _flatten_symbols(symbols: list[dict[str, Any]], parent: str = "") -> list[dict[str, Any]]:
|
||||
flattened: list[dict[str, Any]] = []
|
||||
for symbol in symbols:
|
||||
qualified_name = f"{parent}::{symbol['name']}" if parent else symbol["name"]
|
||||
item = {
|
||||
"name": symbol["name"],
|
||||
"qualifiedName": qualified_name,
|
||||
"detail": symbol.get("detail", ""),
|
||||
"kind": symbol.get("kind", 0),
|
||||
"range": symbol["range"],
|
||||
"selectionRange": symbol["selectionRange"],
|
||||
}
|
||||
flattened.append(item)
|
||||
flattened.extend(_flatten_symbols(symbol.get("children", []), qualified_name))
|
||||
return flattened
|
||||
|
||||
|
||||
def _limit_results(values: list[dict[str, Any]], max_results: int) -> tuple[list[dict[str, Any]], bool]:
|
||||
if max_results < 1:
|
||||
return values, False
|
||||
return values[:max_results], len(values) > max_results
|
||||
|
||||
|
||||
def _hover_to_json(result: Any) -> dict[str, Any] | None:
|
||||
if not result:
|
||||
return None
|
||||
contents = result.get("contents")
|
||||
if isinstance(contents, dict):
|
||||
value = contents.get("value", "")
|
||||
elif isinstance(contents, list):
|
||||
value = "\n".join(str(item.get("value", item)) if isinstance(item, dict) else str(item) for item in contents)
|
||||
else:
|
||||
value = str(contents)
|
||||
output = { "contents": value }
|
||||
if "range" in result:
|
||||
output["range"] = _range_to_json(result["range"])
|
||||
return output
|
||||
|
||||
|
||||
def _compile_optional_regex(pattern: str | None, option_name: str, ignore_case: bool) -> Pattern[str] | None:
|
||||
if not pattern:
|
||||
return None
|
||||
flags = re.IGNORECASE if ignore_case else 0
|
||||
try:
|
||||
return re.compile(pattern, flags)
|
||||
except re.error as exc:
|
||||
raise SystemExit(f"invalid {option_name}: {exc}") from exc
|
||||
|
||||
|
||||
def _regex_matches(regex: Pattern[str] | None, value: str) -> bool:
|
||||
return regex is None or regex.search(value) is not None
|
||||
|
||||
|
||||
def _filter_flat_symbols(
|
||||
symbols: list[dict[str, Any]],
|
||||
name_substring: str | None,
|
||||
name_regex: Pattern[str] | None,
|
||||
detail_regex: Pattern[str] | None,
|
||||
) -> list[dict[str, Any]]:
|
||||
filtered = symbols
|
||||
if name_substring:
|
||||
needle = name_substring.lower()
|
||||
filtered = [
|
||||
symbol for symbol in filtered
|
||||
if needle in symbol["qualifiedName"].lower()
|
||||
]
|
||||
if name_regex:
|
||||
filtered = [
|
||||
symbol for symbol in filtered
|
||||
if name_regex.search(symbol["qualifiedName"])
|
||||
]
|
||||
if detail_regex:
|
||||
filtered = [
|
||||
symbol for symbol in filtered
|
||||
if detail_regex.search(symbol.get("detail", ""))
|
||||
]
|
||||
return filtered
|
||||
|
||||
|
||||
def _filter_locations(
|
||||
locations: list[dict[str, Any]],
|
||||
path_regex: Pattern[str] | None,
|
||||
) -> list[dict[str, Any]]:
|
||||
if not path_regex:
|
||||
return locations
|
||||
return [
|
||||
location for location in locations
|
||||
if _regex_matches(path_regex, location.get("path", ""))
|
||||
or _regex_matches(path_regex, location.get("uri", ""))
|
||||
]
|
||||
|
||||
|
||||
def _run_self_test() -> int:
|
||||
name_regex = _compile_optional_regex(r"node(panel|dialog)::open_.*", "--name-regex", True)
|
||||
detail_regex = _compile_optional_regex(r"export.*plan", "--detail-regex", True)
|
||||
path_regex = _compile_optional_regex(r"src[\\/]app(_dialogs)?\.cpp$", "--path-regex", True)
|
||||
case_sensitive_regex = _compile_optional_regex(r"Brush", "--name-regex", False)
|
||||
|
||||
symbols = [
|
||||
{
|
||||
"qualifiedName": "NodePanel::open_project",
|
||||
"detail": "void()",
|
||||
},
|
||||
{
|
||||
"qualifiedName": "NodeDialog::open_export",
|
||||
"detail": "ExportTargetPlan()",
|
||||
},
|
||||
{
|
||||
"qualifiedName": "Brush::open_project",
|
||||
"detail": "BrushPlan()",
|
||||
},
|
||||
]
|
||||
name_matches = _filter_flat_symbols(symbols, None, name_regex, None)
|
||||
detail_matches = _filter_flat_symbols(symbols, None, None, detail_regex)
|
||||
case_sensitive_matches = _filter_flat_symbols(symbols, None, case_sensitive_regex, None)
|
||||
|
||||
locations = [
|
||||
{
|
||||
"path": r"D:\Dev\panopainter\src\app.cpp",
|
||||
"uri": "file:///D:/Dev/panopainter/src/app.cpp",
|
||||
},
|
||||
{
|
||||
"path": r"D:\Dev\panopainter\src\app_dialogs.cpp",
|
||||
"uri": "file:///D:/Dev/panopainter/src/app_dialogs.cpp",
|
||||
},
|
||||
{
|
||||
"path": r"D:\Dev\panopainter\docs\modernization\roadmap.md",
|
||||
"uri": "file:///D:/Dev/panopainter/docs/modernization/roadmap.md",
|
||||
},
|
||||
]
|
||||
path_matches = _filter_locations(locations, path_regex)
|
||||
|
||||
checks = {
|
||||
"nameRegexMatchesAlternationAndWildcard": len(name_matches) == 2,
|
||||
"detailRegexMatchesSymbolDetail": len(detail_matches) == 1
|
||||
and detail_matches[0]["qualifiedName"] == "NodeDialog::open_export",
|
||||
"caseSensitiveRegexHonorsNoIgnoreCase": len(case_sensitive_matches) == 1
|
||||
and case_sensitive_matches[0]["qualifiedName"] == "Brush::open_project",
|
||||
"pathRegexFiltersLocations": len(path_matches) == 2
|
||||
and all("src" in location["path"] for location in path_matches),
|
||||
}
|
||||
ok = all(checks.values())
|
||||
print(json.dumps(
|
||||
{
|
||||
"ok": ok,
|
||||
"command": "self-test",
|
||||
"checks": checks,
|
||||
},
|
||||
indent=2,
|
||||
))
|
||||
return 0 if ok else 1
|
||||
|
||||
|
||||
def _open_document(client: ClangdClient, file_path: Path) -> None:
|
||||
language_id = "cpp"
|
||||
if file_path.suffix.lower() in { ".h", ".hpp", ".hh", ".hxx" }:
|
||||
language_id = "cpp"
|
||||
elif file_path.suffix.lower() == ".c":
|
||||
language_id = "c"
|
||||
|
||||
client.notify(
|
||||
"textDocument/didOpen",
|
||||
{
|
||||
"textDocument": {
|
||||
"uri": file_path.as_uri(),
|
||||
"languageId": language_id,
|
||||
"version": 1,
|
||||
"text": file_path.read_text(encoding="utf-8", errors="replace"),
|
||||
}
|
||||
},
|
||||
)
|
||||
|
||||
|
||||
def run(args: argparse.Namespace) -> int:
|
||||
if args.command == "self-test":
|
||||
return _run_self_test()
|
||||
|
||||
if not args.file:
|
||||
raise SystemExit("--file is required for clangd navigation commands")
|
||||
|
||||
symbol_filters = [args.name, args.name_regex, args.detail_regex]
|
||||
if any(symbol_filters) and args.command != "symbols":
|
||||
raise SystemExit("--name, --name-regex, and --detail-regex are only supported by the symbols command")
|
||||
if args.path_regex and args.command not in { "definition", "declaration", "implementation", "references" }:
|
||||
raise SystemExit("--path-regex is only supported by location commands")
|
||||
if args.hierarchical and any(symbol_filters):
|
||||
raise SystemExit("--name, --name-regex, and --detail-regex require flat symbols; omit --hierarchical")
|
||||
|
||||
repo_root = _repo_root()
|
||||
compile_commands_dir = _find_compile_commands_dir(repo_root, args.compile_commands_dir)
|
||||
file_path = _resolve_file(repo_root, args.file)
|
||||
|
||||
name_regex = _compile_optional_regex(args.name_regex, "--name-regex", args.ignore_case)
|
||||
detail_regex = _compile_optional_regex(args.detail_regex, "--detail-regex", args.ignore_case)
|
||||
path_regex = _compile_optional_regex(args.path_regex, "--path-regex", args.ignore_case)
|
||||
|
||||
if args.command == "references" and not args.background_index and not args.allow_incomplete_references:
|
||||
raise SystemExit(
|
||||
"references may be incomplete without clangd background indexing. "
|
||||
"Pass --background-index for a broader best-effort query or "
|
||||
"--allow-incomplete-references for current-translation-unit lookup."
|
||||
)
|
||||
|
||||
client = ClangdClient(args.clangd, compile_commands_dir, args.timeout, args.background_index)
|
||||
try:
|
||||
client.request(
|
||||
"initialize",
|
||||
{
|
||||
"processId": None,
|
||||
"rootUri": repo_root.as_uri(),
|
||||
"capabilities": {
|
||||
"textDocument": {
|
||||
"definition": { "linkSupport": True },
|
||||
"declaration": { "linkSupport": True },
|
||||
"implementation": { "linkSupport": True },
|
||||
"references": {},
|
||||
"hover": {},
|
||||
"documentSymbol": { "hierarchicalDocumentSymbolSupport": True },
|
||||
}
|
||||
},
|
||||
},
|
||||
)
|
||||
client.notify("initialized", {})
|
||||
_open_document(client, file_path)
|
||||
|
||||
command = args.command
|
||||
result: Any
|
||||
result_count: int | None = None
|
||||
truncated = False
|
||||
if command == "symbols":
|
||||
symbols = _symbols_to_json(
|
||||
client.request("textDocument/documentSymbol", { "textDocument": { "uri": file_path.as_uri() } })
|
||||
)
|
||||
if args.hierarchical:
|
||||
result = symbols
|
||||
result_count = len(symbols)
|
||||
else:
|
||||
flattened = _flatten_symbols(symbols)
|
||||
flattened = _filter_flat_symbols(flattened, args.name, name_regex, detail_regex)
|
||||
result_count = len(flattened)
|
||||
result, truncated = _limit_results(flattened, args.max_results)
|
||||
elif command == "hover":
|
||||
result = _hover_to_json(client.request("textDocument/hover", _position_params(file_path, args.line, args.column)))
|
||||
elif command == "references":
|
||||
params = _position_params(file_path, args.line, args.column)
|
||||
params["context"] = { "includeDeclaration": args.include_declaration }
|
||||
locations = _locations_to_json(client.request("textDocument/references", params))
|
||||
locations = _filter_locations(locations, path_regex)
|
||||
result_count = len(locations)
|
||||
result, truncated = _limit_results(locations, args.max_results)
|
||||
else:
|
||||
method = {
|
||||
"definition": "textDocument/definition",
|
||||
"declaration": "textDocument/declaration",
|
||||
"implementation": "textDocument/implementation",
|
||||
}[command]
|
||||
locations = _locations_to_json(client.request(method, _position_params(file_path, args.line, args.column)))
|
||||
locations = _filter_locations(locations, path_regex)
|
||||
result_count = len(locations)
|
||||
result, truncated = _limit_results(locations, args.max_results)
|
||||
|
||||
print(json.dumps(
|
||||
{
|
||||
"ok": True,
|
||||
"command": command,
|
||||
"file": str(file_path),
|
||||
"compileCommandsDir": str(compile_commands_dir),
|
||||
"backgroundIndex": args.background_index,
|
||||
"referenceCompleteness": (
|
||||
"not-applicable" if command != "references"
|
||||
else ("best-effort-background-index" if args.background_index else "current-translation-unit-only")
|
||||
),
|
||||
"filters": {
|
||||
"name": args.name,
|
||||
"nameRegex": args.name_regex,
|
||||
"detailRegex": args.detail_regex,
|
||||
"pathRegex": args.path_regex,
|
||||
"ignoreCase": args.ignore_case,
|
||||
},
|
||||
"resultCount": result_count,
|
||||
"truncated": truncated,
|
||||
"result": result,
|
||||
},
|
||||
indent=2,
|
||||
))
|
||||
return 0
|
||||
finally:
|
||||
client.close()
|
||||
|
||||
|
||||
def main(argv: list[str]) -> int:
|
||||
parser = argparse.ArgumentParser(description="Navigate C++ symbols through clangd JSON-RPC.")
|
||||
parser.add_argument(
|
||||
"command",
|
||||
choices=("definition", "declaration", "implementation", "references", "hover", "symbols", "self-test"),
|
||||
)
|
||||
parser.add_argument("--file", help="Source/header file to open.")
|
||||
parser.add_argument("--line", type=int, default=1, help="1-based line for position commands.")
|
||||
parser.add_argument("--column", type=int, default=1, help="1-based column for position commands.")
|
||||
parser.add_argument(
|
||||
"--compile-commands-dir",
|
||||
help="Directory containing compile_commands.json. Defaults to PP_CLANGD_COMPILE_COMMANDS_DIR or known build dirs.",
|
||||
)
|
||||
parser.add_argument("--clangd", default="clangd", help="clangd executable path.")
|
||||
parser.add_argument("--timeout", type=float, default=20.0, help="Request timeout in seconds.")
|
||||
parser.add_argument("--name", help="Case-insensitive symbol-name filter for symbols command.")
|
||||
parser.add_argument("--name-regex", help="Regex filter for symbols command, matched against qualifiedName.")
|
||||
parser.add_argument("--detail-regex", help="Regex filter for symbols command, matched against detail.")
|
||||
parser.add_argument("--path-regex", help="Regex filter for definition/declaration/implementation/references paths.")
|
||||
parser.add_argument(
|
||||
"--ignore-case",
|
||||
action=argparse.BooleanOptionalAction,
|
||||
default=True,
|
||||
help="Use case-insensitive regex matching for --name-regex, --detail-regex, and --path-regex. Enabled by default.",
|
||||
)
|
||||
parser.add_argument("--max-results", type=int, default=100, help="Maximum locations/symbols to print; <=0 disables.")
|
||||
parser.add_argument(
|
||||
"--background-index",
|
||||
action="store_true",
|
||||
help="Allow clangd to build/use its background index for broader cross-translation-unit references.",
|
||||
)
|
||||
parser.add_argument(
|
||||
"--allow-incomplete-references",
|
||||
action="store_true",
|
||||
help="Permit current-translation-unit-only references when --background-index is not enabled.",
|
||||
)
|
||||
parser.add_argument(
|
||||
"--hierarchical",
|
||||
action="store_true",
|
||||
help="Print nested document symbols instead of the compact flat symbol list.",
|
||||
)
|
||||
parser.add_argument(
|
||||
"--include-declaration",
|
||||
action=argparse.BooleanOptionalAction,
|
||||
default=True,
|
||||
help="Include declaration in references results.",
|
||||
)
|
||||
return run(parser.parse_args(argv))
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
raise SystemExit(main(sys.argv[1:]))
|
||||
1063
src/app.cpp
1063
src/app.cpp
File diff suppressed because it is too large
Load Diff
154
src/app.h
154
src/app.h
@@ -19,30 +19,38 @@
|
||||
#include "node_canvas.h"
|
||||
#include "node_dialog_layer_rename.h"
|
||||
#include "node_progress_bar.h"
|
||||
#include "node_panel_grid.h"
|
||||
#include "node_panel_quick.h"
|
||||
#include "node_input_box.h"
|
||||
#include "node_panel_animation.h"
|
||||
#include "layout.h"
|
||||
#include "app_core/document_session.h"
|
||||
#include "app_core/app_thread.h"
|
||||
|
||||
namespace pp::platform {
|
||||
class PlatformServices;
|
||||
struct PlatformStoragePaths;
|
||||
}
|
||||
|
||||
class NodePanelGrid;
|
||||
|
||||
#if defined(__OBJC__) && defined(__IOS__)
|
||||
#import <Foundation/Foundation.h>
|
||||
#import "GameViewController.h"
|
||||
#import "AppDelegate.h"
|
||||
@class GameViewController;
|
||||
@class AppDelegate;
|
||||
#endif
|
||||
|
||||
#if defined(__OBJC__) && defined(__OSX__)
|
||||
#import "main.h"
|
||||
@class View;
|
||||
@class AppOSX;
|
||||
#endif
|
||||
|
||||
#ifdef __ANDROID__
|
||||
#include "main.h"
|
||||
struct android_app;
|
||||
struct engine;
|
||||
#endif
|
||||
|
||||
#ifdef __LINUX__
|
||||
#include <GLFW/glfw3.h>
|
||||
#if __LINUX__ || __WEB__
|
||||
struct GLFWwindow;
|
||||
#endif
|
||||
|
||||
struct VRController
|
||||
{
|
||||
enum class kButton : uint8_t
|
||||
@@ -155,6 +163,7 @@ public:
|
||||
float display_density = 1.f;
|
||||
float zoom = 1.f;
|
||||
int idle_ms = 100;
|
||||
pp::platform::PlatformServices* platform_services_ = nullptr;
|
||||
|
||||
#if defined(__IOS__) && defined(__OBJC__)
|
||||
GameViewController* ios_view;
|
||||
@@ -174,15 +183,53 @@ public:
|
||||
bool clipboard_set_text(const std::string& s);
|
||||
void pick_image(std::function<void(std::string path)> callback);
|
||||
void pick_file(std::vector<std::string> types, std::function<void(std::string path)> callback);
|
||||
#if __IOS__ || __WEB__
|
||||
void pick_file_save(const std::string& type, const std::string& default_name,
|
||||
std::function<void(std::string path)> writer, std::function<void(const std::string& path, bool saved)> callback);
|
||||
#else
|
||||
void pick_file_save(std::vector<std::string> types, std::function<void(std::string path)> callback);
|
||||
#endif
|
||||
[[nodiscard]] bool supports_working_directory_picker() const;
|
||||
[[nodiscard]] std::string format_working_directory_path(std::string_view path) const;
|
||||
[[nodiscard]] bool uses_prepared_file_writes() const;
|
||||
[[nodiscard]] bool uses_work_directory_document_export_collections() const;
|
||||
[[nodiscard]] bool disables_network_tls_verification() const;
|
||||
[[nodiscard]] bool uses_ppbr_export_data_directory_override() const;
|
||||
[[nodiscard]] bool platform_supports_sonarpen() const;
|
||||
void start_platform_sonarpen();
|
||||
[[nodiscard]] int default_canvas_resolution() const;
|
||||
[[nodiscard]] bool draws_canvas_tip_for_input(kEventSource source, kEventType type) const;
|
||||
[[nodiscard]] float adjust_canvas_input_pressure(float pressure) const;
|
||||
void pick_dir(std::function<void(std::string path)> callback);
|
||||
void display_file(std::string path);
|
||||
void share_file(std::string path);
|
||||
void request_app_close();
|
||||
[[nodiscard]] bool start_platform_vr_mode();
|
||||
void stop_platform_vr_mode();
|
||||
void attach_ui_thread();
|
||||
void detach_ui_thread();
|
||||
void acquire_render_context();
|
||||
void release_render_context();
|
||||
void present_render_context();
|
||||
void bind_default_render_target();
|
||||
void bind_main_render_target();
|
||||
void apply_render_platform_hints();
|
||||
void install_render_debug_callback();
|
||||
void begin_render_capture_frame();
|
||||
void end_render_capture_frame();
|
||||
[[nodiscard]] bool platform_deletes_recorded_files_on_clear();
|
||||
void clear_platform_recorded_files(std::string path);
|
||||
void publish_exported_image(std::string path);
|
||||
void flush_platform_storage();
|
||||
[[nodiscard]] std::vector<std::string> document_browse_roots() const;
|
||||
void save_platform_ui_state();
|
||||
[[nodiscard]] bool platform_enables_live_asset_reloading();
|
||||
void update_platform_frame(float delta_time_seconds);
|
||||
void report_rendered_frames(int frames);
|
||||
void save_prepared_file(
|
||||
std::string path,
|
||||
std::string suggested_name,
|
||||
std::function<void(const std::string& path, bool saved)> callback);
|
||||
void set_platform_services(pp::platform::PlatformServices* services) noexcept;
|
||||
[[nodiscard]] pp::platform::PlatformServices* platform_services() const noexcept;
|
||||
[[nodiscard]] pp::platform::PlatformStoragePaths prepare_storage_paths();
|
||||
void showKeyboard();
|
||||
void hideKeyboard();
|
||||
void initLog();
|
||||
@@ -248,6 +295,8 @@ public:
|
||||
void dialog_usermanual();
|
||||
void dialog_changelog();
|
||||
void dialog_about();
|
||||
void save_document(pp::app::DocumentSaveIntent intent);
|
||||
void continue_document_workflow_after_optional_save(std::function<void()> action);
|
||||
void dialog_newdoc();
|
||||
void dialog_save();
|
||||
void dialog_save_ver();
|
||||
@@ -327,21 +376,36 @@ public:
|
||||
{
|
||||
AppTask pt(task);
|
||||
auto f = pt.get_future();
|
||||
if (is_render_thread())
|
||||
const auto dispatch = pp::app::plan_app_task_dispatch(
|
||||
is_render_thread(),
|
||||
unique,
|
||||
0U,
|
||||
render_running,
|
||||
false,
|
||||
false);
|
||||
if (dispatch.execute_immediately)
|
||||
{
|
||||
pt();
|
||||
}
|
||||
else
|
||||
else if (dispatch.queue_task)
|
||||
{
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(render_task_mutex);
|
||||
const auto queue_dispatch = pp::app::plan_app_task_dispatch(
|
||||
false,
|
||||
unique,
|
||||
render_tasklist.size(),
|
||||
render_running,
|
||||
false,
|
||||
false);
|
||||
// remove any previously queued task from the same lambda
|
||||
if (unique && !render_tasklist.empty())
|
||||
if (queue_dispatch.remove_matching_unique_task)
|
||||
render_tasklist.erase(std::remove_if(render_tasklist.begin(), render_tasklist.end(),
|
||||
[id = pt.task_id](AppTask const& t){ return t.task_id == id; }), render_tasklist.end());
|
||||
render_tasklist.push_back(std::move(pt));
|
||||
}
|
||||
render_cv.notify_all();
|
||||
if (dispatch.notify_worker)
|
||||
render_cv.notify_all();
|
||||
}
|
||||
return f;
|
||||
}
|
||||
@@ -351,19 +415,27 @@ public:
|
||||
{
|
||||
AppTask pt(task);
|
||||
auto f = pt.get_future();
|
||||
if (is_render_thread())
|
||||
const auto dispatch = pp::app::plan_app_task_dispatch(
|
||||
is_render_thread(),
|
||||
false,
|
||||
0U,
|
||||
render_running,
|
||||
true,
|
||||
false);
|
||||
if (dispatch.execute_immediately)
|
||||
{
|
||||
pt();
|
||||
}
|
||||
else
|
||||
else if (dispatch.queue_task)
|
||||
{
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(render_task_mutex);
|
||||
render_tasklist.push_back(std::move(pt));
|
||||
}
|
||||
render_cv.notify_all();
|
||||
if (dispatch.notify_worker)
|
||||
render_cv.notify_all();
|
||||
}
|
||||
if (render_running)
|
||||
if (dispatch.wait_for_completion)
|
||||
f.get();
|
||||
}
|
||||
|
||||
@@ -399,21 +471,36 @@ public:
|
||||
{
|
||||
AppTask pt(task);
|
||||
auto f = pt.get_future();
|
||||
if (is_ui_thread())
|
||||
const auto dispatch = pp::app::plan_app_task_dispatch(
|
||||
is_ui_thread(),
|
||||
unique,
|
||||
0U,
|
||||
ui_running,
|
||||
false,
|
||||
false);
|
||||
if (dispatch.execute_immediately)
|
||||
{
|
||||
pt();
|
||||
}
|
||||
else
|
||||
else if (dispatch.queue_task)
|
||||
{
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(ui_task_mutex);
|
||||
const auto queue_dispatch = pp::app::plan_app_task_dispatch(
|
||||
false,
|
||||
unique,
|
||||
ui_tasklist.size(),
|
||||
ui_running,
|
||||
false,
|
||||
false);
|
||||
// remove any previously queued task from the same lambda
|
||||
if (unique && !ui_tasklist.empty())
|
||||
if (queue_dispatch.remove_matching_unique_task)
|
||||
ui_tasklist.erase(std::remove_if(ui_tasklist.begin(), ui_tasklist.end(),
|
||||
[id = pt.task_id](AppTask const& t){ return t.task_id == id; }), ui_tasklist.end());
|
||||
ui_tasklist.push_back(std::move(pt));
|
||||
}
|
||||
ui_cv.notify_all();
|
||||
if (dispatch.notify_worker)
|
||||
ui_cv.notify_all();
|
||||
}
|
||||
return f;
|
||||
}
|
||||
@@ -423,21 +510,30 @@ public:
|
||||
{
|
||||
AppTask pt(task);
|
||||
auto f = pt.get_future();
|
||||
if (is_ui_thread())
|
||||
const auto dispatch = pp::app::plan_app_task_dispatch(
|
||||
is_ui_thread(),
|
||||
false,
|
||||
0U,
|
||||
ui_running,
|
||||
true,
|
||||
true);
|
||||
if (dispatch.execute_immediately)
|
||||
{
|
||||
pt();
|
||||
}
|
||||
else
|
||||
else if (dispatch.queue_task)
|
||||
{
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(ui_task_mutex);
|
||||
ui_tasklist.push_back(std::move(pt));
|
||||
}
|
||||
ui_cv.notify_all();
|
||||
if (dispatch.notify_worker)
|
||||
ui_cv.notify_all();
|
||||
}
|
||||
if (ui_running)
|
||||
if (dispatch.wait_for_completion)
|
||||
f.get();
|
||||
redraw = true;
|
||||
if (dispatch.request_redraw)
|
||||
redraw = true;
|
||||
}
|
||||
|
||||
void ui_sync()
|
||||
|
||||
@@ -1,48 +1,20 @@
|
||||
#include "pch.h"
|
||||
#include "app.h"
|
||||
#include "app_core/document_cloud.h"
|
||||
#include "legacy_cloud_services.h"
|
||||
#include "util.h"
|
||||
#include "node_progress_bar.h"
|
||||
#include "node_dialog_cloud.h"
|
||||
|
||||
void App::cloud_upload()
|
||||
{
|
||||
if (!canvas)
|
||||
return;
|
||||
if (Canvas::I->m_newdoc)
|
||||
{
|
||||
message_box("Warning", "This document needs to be saved before upload.");
|
||||
}
|
||||
else
|
||||
{
|
||||
auto upload_thread = [this] {
|
||||
BT_SetTerminate();
|
||||
const bool has_canvas = canvas != nullptr;
|
||||
const auto plan = pp::app::plan_cloud_upload(
|
||||
has_canvas,
|
||||
has_canvas && Canvas::I->m_newdoc,
|
||||
has_canvas && Canvas::I->m_unsaved);
|
||||
|
||||
if (Canvas::I->m_unsaved)
|
||||
{
|
||||
Canvas::I->project_save_thread(doc_path, true);
|
||||
}
|
||||
|
||||
auto pb = show_progress("Uploading");
|
||||
|
||||
upload(doc_path, doc_filename, [this,pb](float p){
|
||||
pb->set_progress(p);
|
||||
});
|
||||
|
||||
pb->destroy();
|
||||
message_box("Success", "This document has been succesfully uploaded.");
|
||||
};
|
||||
|
||||
auto m = message_box("Publish document", "Would you like to upload to the public domain?");
|
||||
m->btn_ok->m_text->set_text("Yes");
|
||||
m->btn_cancel->m_text->set_text("No");
|
||||
m->btn_ok->on_click = [this, m, upload_thread](Node*) {
|
||||
std::thread(upload_thread).detach();
|
||||
m->destroy();
|
||||
};
|
||||
m->btn_cancel->on_click = [this, m, upload_thread](Node*) {
|
||||
m->destroy();
|
||||
};
|
||||
}
|
||||
const auto status = pp::panopainter::execute_legacy_cloud_upload_plan(*this, plan);
|
||||
if (!status.ok())
|
||||
LOG("Cloud upload action failed: %s", status.message);
|
||||
}
|
||||
|
||||
void App::cloud_upload_all()
|
||||
@@ -51,70 +23,18 @@ void App::cloud_upload_all()
|
||||
BT_SetTerminate();
|
||||
|
||||
auto names = Asset::list_files(data_path, ".*\\.ppi");
|
||||
const auto plan = pp::app::plan_cloud_bulk_upload(names.size(), layout.m_loaded);
|
||||
|
||||
gl_state gl;
|
||||
std::shared_ptr<NodeProgressBar> pb;
|
||||
if (layout.m_loaded)
|
||||
pb = show_progress("Export Pano Image", names.size());
|
||||
|
||||
for (const auto& n : names)
|
||||
{
|
||||
std::string path = data_path + "/" + n;
|
||||
upload(path);
|
||||
|
||||
if (layout.m_loaded)
|
||||
pb->increment();
|
||||
}
|
||||
|
||||
if (layout.m_loaded)
|
||||
pb->destroy();
|
||||
|
||||
const auto status = pp::panopainter::execute_legacy_cloud_bulk_upload_plan(*this, plan);
|
||||
if (!status.ok())
|
||||
LOG("Cloud bulk upload action failed: %s", status.message);
|
||||
}).detach();
|
||||
}
|
||||
|
||||
void App::cloud_browse()
|
||||
{
|
||||
if (!canvas)
|
||||
return;
|
||||
|
||||
// load thumbnail test
|
||||
auto dialog = std::make_shared<NodeDialogCloud>();
|
||||
dialog->set_manager(&layout);
|
||||
dialog->init();
|
||||
dialog->create();
|
||||
dialog->loaded();
|
||||
|
||||
layout[main_id]->add_child(dialog);
|
||||
|
||||
dialog->btn_ok->on_click = [this, dialog](Node*)
|
||||
{
|
||||
if (dialog->selected_file.empty())
|
||||
return;
|
||||
dialog->destroy();
|
||||
std::thread([this, dialog] {
|
||||
BT_SetTerminate();
|
||||
|
||||
auto* m = layout[main_id]->add_child<NodeMessageBox>();
|
||||
m->m_title->set_text("Downloading");
|
||||
m->m_message->set_text("Download in progress");
|
||||
std::string url = "https://panopainter.com/cloud/cloud-dwl.php?file=" + dialog->selected_file;
|
||||
download(url, dialog->selected_path, [this,m](float p){
|
||||
static char progress[256];
|
||||
sprintf(progress, "Download in progress %.2f%%", p * 100.f);
|
||||
m->m_message->set_text(progress);
|
||||
});
|
||||
|
||||
canvas->reset_camera();
|
||||
layers->clear();
|
||||
|
||||
canvas->m_canvas->project_open_thread(dialog->selected_path);
|
||||
|
||||
doc_name = dialog->selected_name;
|
||||
title_update();
|
||||
for (auto& l : canvas->m_canvas->m_layers)
|
||||
layers->add_layer(l->m_name.c_str(), false);
|
||||
ActionManager::clear();
|
||||
m->destroy();
|
||||
}).detach();
|
||||
};
|
||||
const auto browse_plan = pp::app::plan_cloud_browse(canvas != nullptr);
|
||||
const auto status = pp::panopainter::execute_legacy_cloud_browse_action(*this, browse_plan);
|
||||
if (!status.ok())
|
||||
LOG("Cloud browse action failed: %s", status.message);
|
||||
}
|
||||
|
||||
@@ -1,16 +1,70 @@
|
||||
#include "pch.h"
|
||||
#include "app_core/command_convert.h"
|
||||
#include "app.h"
|
||||
#include "canvas.h"
|
||||
#include "legacy_ui_gl_dispatch.h"
|
||||
#include "log.h"
|
||||
#include "renderer_gl/opengl_capabilities.h"
|
||||
|
||||
namespace {
|
||||
|
||||
void apply_convert_command_state()
|
||||
{
|
||||
const auto status = pp::renderer::gl::apply_panopainter_convert_command_state(
|
||||
pp::renderer::gl::OpenGlConvertCommandStateDispatch {
|
||||
.enable = pp::legacy::ui_gl::enable_opengl_state,
|
||||
.disable = pp::legacy::ui_gl::disable_opengl_state,
|
||||
.blend_func = pp::legacy::ui_gl::set_opengl_blend_func,
|
||||
.blend_equation = pp::legacy::ui_gl::set_opengl_blend_equation,
|
||||
});
|
||||
if (!status.ok())
|
||||
LOG("OpenGL convert command state failed: %s", status.message);
|
||||
}
|
||||
|
||||
class LegacyCommandConvertServices final : public pp::app::CommandConvertServices {
|
||||
public:
|
||||
void apply_renderer_state() override
|
||||
{
|
||||
apply_convert_command_state();
|
||||
}
|
||||
|
||||
void create_canvas(int canvas_resolution) override
|
||||
{
|
||||
command_canvas = new Canvas;
|
||||
command_canvas->create(canvas_resolution, canvas_resolution);
|
||||
}
|
||||
|
||||
void open_project(std::string_view project_path) override
|
||||
{
|
||||
if (command_canvas)
|
||||
command_canvas->project_open_thread(std::string(project_path));
|
||||
}
|
||||
|
||||
void export_equirectangular(std::string_view output_path) override
|
||||
{
|
||||
if (command_canvas)
|
||||
command_canvas->export_equirectangular_thread(std::string(output_path));
|
||||
}
|
||||
|
||||
private:
|
||||
Canvas* command_canvas = nullptr;
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
void App::cmd_convert(std::string pano_path, std::string out_path)
|
||||
{
|
||||
glDisable(GL_DEPTH_TEST);
|
||||
glEnable(GL_PROGRAM_POINT_SIZE);
|
||||
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
|
||||
glBlendEquation(GL_FUNC_ADD);
|
||||
const auto plan = pp::app::plan_command_convert(
|
||||
pano_path,
|
||||
out_path,
|
||||
default_canvas_resolution());
|
||||
if (!plan) {
|
||||
LOG("Convert command rejected: %s", plan.status().message);
|
||||
return;
|
||||
}
|
||||
|
||||
Canvas* canvas = new Canvas;
|
||||
canvas->create(CANVAS_RES, CANVAS_RES);
|
||||
canvas->project_open_thread(pano_path);
|
||||
canvas->export_equirectangular_thread(out_path);
|
||||
LegacyCommandConvertServices services;
|
||||
const auto status = pp::app::execute_command_convert_plan(plan.value(), services);
|
||||
if (!status.ok())
|
||||
LOG("Convert command failed: %s", status.message);
|
||||
}
|
||||
|
||||
126
src/app_core/about_menu.h
Normal file
126
src/app_core/about_menu.h
Normal file
@@ -0,0 +1,126 @@
|
||||
#pragma once
|
||||
|
||||
#include "foundation/result.h"
|
||||
|
||||
#include <string>
|
||||
|
||||
namespace pp::app {
|
||||
|
||||
enum class AboutMenuCommand {
|
||||
help_guide,
|
||||
about_app,
|
||||
whats_new,
|
||||
induce_crash,
|
||||
performance_test,
|
||||
};
|
||||
|
||||
enum class AboutMenuAction {
|
||||
show_user_manual,
|
||||
show_about_dialog,
|
||||
show_whats_new_dialog,
|
||||
trigger_crash_test,
|
||||
run_performance_test,
|
||||
no_op_unavailable,
|
||||
};
|
||||
|
||||
struct AboutMenuPlan {
|
||||
AboutMenuCommand command = AboutMenuCommand::help_guide;
|
||||
AboutMenuAction action = AboutMenuAction::show_user_manual;
|
||||
std::string label;
|
||||
bool closes_root_popup = true;
|
||||
bool requires_canvas = false;
|
||||
int performance_iterations = 0;
|
||||
int performance_updates_per_iteration = 0;
|
||||
};
|
||||
|
||||
class AboutMenuServices {
|
||||
public:
|
||||
virtual ~AboutMenuServices() = default;
|
||||
|
||||
virtual void show_user_manual() = 0;
|
||||
virtual void show_about_dialog() = 0;
|
||||
virtual void show_whats_new_dialog() = 0;
|
||||
virtual void trigger_crash_test() = 0;
|
||||
virtual void run_performance_test(const AboutMenuPlan& plan) = 0;
|
||||
};
|
||||
|
||||
[[nodiscard]] inline AboutMenuPlan plan_about_menu_command(
|
||||
AboutMenuCommand command,
|
||||
int version_major,
|
||||
int version_minor,
|
||||
int version_fix,
|
||||
bool diagnostics_available = true,
|
||||
bool has_canvas = true)
|
||||
{
|
||||
AboutMenuPlan plan;
|
||||
plan.command = command;
|
||||
|
||||
switch (command) {
|
||||
case AboutMenuCommand::help_guide:
|
||||
plan.action = AboutMenuAction::show_user_manual;
|
||||
plan.label = "Help Guide";
|
||||
break;
|
||||
case AboutMenuCommand::about_app:
|
||||
plan.action = AboutMenuAction::show_about_dialog;
|
||||
plan.label = "About PanoPainter";
|
||||
break;
|
||||
case AboutMenuCommand::whats_new:
|
||||
plan.action = AboutMenuAction::show_whats_new_dialog;
|
||||
plan.label = "What's new in "
|
||||
+ std::to_string(version_major)
|
||||
+ "."
|
||||
+ std::to_string(version_minor)
|
||||
+ "."
|
||||
+ std::to_string(version_fix)
|
||||
+ "?";
|
||||
break;
|
||||
case AboutMenuCommand::induce_crash:
|
||||
plan.label = "Induce crash";
|
||||
plan.action = diagnostics_available
|
||||
? AboutMenuAction::trigger_crash_test
|
||||
: AboutMenuAction::no_op_unavailable;
|
||||
plan.closes_root_popup = diagnostics_available;
|
||||
break;
|
||||
case AboutMenuCommand::performance_test:
|
||||
plan.label = has_canvas ? "Performance test" : "Performance test (No canvas)";
|
||||
plan.requires_canvas = true;
|
||||
plan.performance_iterations = 100;
|
||||
plan.performance_updates_per_iteration = 10;
|
||||
plan.action = has_canvas
|
||||
? AboutMenuAction::run_performance_test
|
||||
: AboutMenuAction::no_op_unavailable;
|
||||
plan.closes_root_popup = has_canvas;
|
||||
break;
|
||||
}
|
||||
|
||||
return plan;
|
||||
}
|
||||
|
||||
[[nodiscard]] inline pp::foundation::Status execute_about_menu_plan(
|
||||
const AboutMenuPlan& plan,
|
||||
AboutMenuServices& services)
|
||||
{
|
||||
switch (plan.action) {
|
||||
case AboutMenuAction::show_user_manual:
|
||||
services.show_user_manual();
|
||||
return pp::foundation::Status::success();
|
||||
case AboutMenuAction::show_about_dialog:
|
||||
services.show_about_dialog();
|
||||
return pp::foundation::Status::success();
|
||||
case AboutMenuAction::show_whats_new_dialog:
|
||||
services.show_whats_new_dialog();
|
||||
return pp::foundation::Status::success();
|
||||
case AboutMenuAction::trigger_crash_test:
|
||||
services.trigger_crash_test();
|
||||
return pp::foundation::Status::success();
|
||||
case AboutMenuAction::run_performance_test:
|
||||
services.run_performance_test(plan);
|
||||
return pp::foundation::Status::success();
|
||||
case AboutMenuAction::no_op_unavailable:
|
||||
return pp::foundation::Status::success();
|
||||
}
|
||||
|
||||
return pp::foundation::Status::invalid_argument("unknown about menu action");
|
||||
}
|
||||
|
||||
}
|
||||
82
src/app_core/app_dialog.h
Normal file
82
src/app_core/app_dialog.h
Normal file
@@ -0,0 +1,82 @@
|
||||
#pragma once
|
||||
|
||||
#include "foundation/result.h"
|
||||
|
||||
#include <string>
|
||||
#include <string_view>
|
||||
|
||||
namespace pp::app {
|
||||
|
||||
enum class AppDialogKind {
|
||||
progress,
|
||||
message,
|
||||
input,
|
||||
};
|
||||
|
||||
struct AppProgressDialogPlan {
|
||||
std::string title;
|
||||
int total = 0;
|
||||
int count = 0;
|
||||
float progress_fraction = 0.0F;
|
||||
};
|
||||
|
||||
struct AppMessageDialogPlan {
|
||||
std::string title;
|
||||
std::string message;
|
||||
std::string ok_caption = "Ok";
|
||||
std::string cancel_caption = "Cancel";
|
||||
bool show_cancel = false;
|
||||
};
|
||||
|
||||
struct AppInputDialogPlan {
|
||||
std::string title;
|
||||
std::string field_name;
|
||||
std::string ok_caption = "Ok";
|
||||
};
|
||||
|
||||
[[nodiscard]] inline AppProgressDialogPlan plan_app_progress_dialog(
|
||||
std::string_view title,
|
||||
int total) noexcept
|
||||
{
|
||||
return {
|
||||
std::string(title),
|
||||
total < 0 ? 0 : total,
|
||||
0,
|
||||
0.0F,
|
||||
};
|
||||
}
|
||||
|
||||
[[nodiscard]] inline AppMessageDialogPlan plan_app_message_dialog(
|
||||
std::string_view title,
|
||||
std::string_view message,
|
||||
bool show_cancel,
|
||||
std::string_view ok_caption = "Ok",
|
||||
std::string_view cancel_caption = "Cancel")
|
||||
{
|
||||
return {
|
||||
std::string(title),
|
||||
std::string(message),
|
||||
std::string(ok_caption),
|
||||
std::string(cancel_caption),
|
||||
show_cancel,
|
||||
};
|
||||
}
|
||||
|
||||
[[nodiscard]] inline pp::foundation::Result<AppInputDialogPlan> plan_app_input_dialog(
|
||||
std::string_view title,
|
||||
std::string_view field_name,
|
||||
std::string_view ok_caption)
|
||||
{
|
||||
if (ok_caption.empty()) {
|
||||
return pp::foundation::Result<AppInputDialogPlan>::failure(
|
||||
pp::foundation::Status::invalid_argument("input dialog ok caption must not be empty"));
|
||||
}
|
||||
|
||||
return pp::foundation::Result<AppInputDialogPlan>::success({
|
||||
std::string(title),
|
||||
std::string(field_name),
|
||||
std::string(ok_caption),
|
||||
});
|
||||
}
|
||||
|
||||
} // namespace pp::app
|
||||
240
src/app_core/app_frame.h
Normal file
240
src/app_core/app_frame.h
Normal file
@@ -0,0 +1,240 @@
|
||||
#pragma once
|
||||
|
||||
#include "foundation/result.h"
|
||||
|
||||
#include <cmath>
|
||||
#include <cstdint>
|
||||
#include <limits>
|
||||
#include <span>
|
||||
|
||||
namespace pp::app {
|
||||
|
||||
struct AppInitialSurfacePlan {
|
||||
float width = 960.0F;
|
||||
float height = 540.0F;
|
||||
};
|
||||
|
||||
struct AppFrameUpdatePlan {
|
||||
bool update_frame = false;
|
||||
bool update_layouts = false;
|
||||
bool refresh_canvas_toolbar = false;
|
||||
};
|
||||
|
||||
struct AppFrameDrawPlan {
|
||||
bool draw_canvas_stroke = false;
|
||||
bool draw_vr_ui = false;
|
||||
bool draw_main_ui = true;
|
||||
bool reset_redraw = true;
|
||||
};
|
||||
|
||||
struct AppFrameTickPlan {
|
||||
bool tick_designer_layout = false;
|
||||
bool tick_main_layout = false;
|
||||
};
|
||||
|
||||
struct AppResizePlan {
|
||||
float width = 0.0F;
|
||||
float height = 0.0F;
|
||||
int render_target_width = 0;
|
||||
int render_target_height = 0;
|
||||
bool recreate_ui_render_target = true;
|
||||
bool request_redraw = true;
|
||||
};
|
||||
|
||||
struct AppUiObserverRect {
|
||||
float x = 0.0F;
|
||||
float y = 0.0F;
|
||||
float width = 0.0F;
|
||||
float height = 0.0F;
|
||||
};
|
||||
|
||||
struct AppUiObserverParentClip {
|
||||
AppUiObserverRect clip;
|
||||
float padding_top = 0.0F;
|
||||
float padding_right = 0.0F;
|
||||
float padding_bottom = 0.0F;
|
||||
float padding_left = 0.0F;
|
||||
};
|
||||
|
||||
struct AppUiObserverPlan {
|
||||
bool draw_node = false;
|
||||
bool notify_enter_screen = false;
|
||||
bool notify_leave_screen = false;
|
||||
bool next_on_screen = false;
|
||||
AppUiObserverRect visible_clip;
|
||||
std::int32_t scissor_x = 0;
|
||||
std::int32_t scissor_y = 0;
|
||||
std::int32_t scissor_width = 0;
|
||||
std::int32_t scissor_height = 0;
|
||||
};
|
||||
|
||||
[[nodiscard]] constexpr AppInitialSurfacePlan plan_app_initial_surface() noexcept
|
||||
{
|
||||
return AppInitialSurfacePlan {
|
||||
.width = 1920.0F / 2.0F,
|
||||
.height = 1080.0F / 2.0F,
|
||||
};
|
||||
}
|
||||
|
||||
[[nodiscard]] constexpr AppFrameUpdatePlan plan_app_frame_update(bool redraw, bool animate) noexcept
|
||||
{
|
||||
const bool update_frame = redraw || animate;
|
||||
return AppFrameUpdatePlan {
|
||||
.update_frame = update_frame,
|
||||
.update_layouts = update_frame,
|
||||
.refresh_canvas_toolbar = update_frame,
|
||||
};
|
||||
}
|
||||
|
||||
[[nodiscard]] constexpr AppFrameDrawPlan plan_app_frame_draw(
|
||||
bool has_canvas_node,
|
||||
bool has_canvas_document,
|
||||
bool vr_active,
|
||||
bool ui_visible,
|
||||
bool vr_only) noexcept
|
||||
{
|
||||
return AppFrameDrawPlan {
|
||||
.draw_canvas_stroke = has_canvas_node && has_canvas_document,
|
||||
.draw_vr_ui = vr_active && ui_visible,
|
||||
.draw_main_ui = !vr_only,
|
||||
.reset_redraw = true,
|
||||
};
|
||||
}
|
||||
|
||||
[[nodiscard]] constexpr AppFrameTickPlan plan_app_frame_tick(
|
||||
bool has_designer_layout,
|
||||
bool has_main_layout) noexcept
|
||||
{
|
||||
return AppFrameTickPlan {
|
||||
.tick_designer_layout = has_designer_layout,
|
||||
.tick_main_layout = has_main_layout,
|
||||
};
|
||||
}
|
||||
|
||||
[[nodiscard]] inline pp::foundation::Result<AppResizePlan> plan_app_resize(float width, float height)
|
||||
{
|
||||
if (!std::isfinite(width) || !std::isfinite(height)) {
|
||||
return pp::foundation::Result<AppResizePlan>::failure(
|
||||
pp::foundation::Status::invalid_argument("resize dimensions must be finite"));
|
||||
}
|
||||
|
||||
if (width < 1.0F || height < 1.0F) {
|
||||
return pp::foundation::Result<AppResizePlan>::failure(
|
||||
pp::foundation::Status::invalid_argument("resize dimensions must be positive"));
|
||||
}
|
||||
|
||||
if (width > static_cast<float>(std::numeric_limits<int>::max())
|
||||
|| height > static_cast<float>(std::numeric_limits<int>::max())) {
|
||||
return pp::foundation::Result<AppResizePlan>::failure(
|
||||
pp::foundation::Status::out_of_range("resize dimensions exceed integer range"));
|
||||
}
|
||||
|
||||
return pp::foundation::Result<AppResizePlan>::success(AppResizePlan {
|
||||
.width = width,
|
||||
.height = height,
|
||||
.render_target_width = static_cast<int>(width),
|
||||
.render_target_height = static_cast<int>(height),
|
||||
.recreate_ui_render_target = true,
|
||||
.request_redraw = true,
|
||||
});
|
||||
}
|
||||
|
||||
[[nodiscard]] constexpr AppUiObserverRect intersect_app_ui_observer_rect(
|
||||
AppUiObserverRect a,
|
||||
AppUiObserverRect b) noexcept
|
||||
{
|
||||
const float x0 = a.x > b.x ? a.x : b.x;
|
||||
const float y0 = a.y > b.y ? a.y : b.y;
|
||||
const float x1 = (a.x + a.width) < (b.x + b.width) ? (a.x + a.width) : (b.x + b.width);
|
||||
const float y1 = (a.y + a.height) < (b.y + b.height) ? (a.y + a.height) : (b.y + b.height);
|
||||
return AppUiObserverRect {
|
||||
.x = x0,
|
||||
.y = y0,
|
||||
.width = x1 - x0,
|
||||
.height = y1 - y0,
|
||||
};
|
||||
}
|
||||
|
||||
[[nodiscard]] inline pp::foundation::Result<AppUiObserverPlan> plan_app_ui_observer(
|
||||
bool has_node,
|
||||
bool display,
|
||||
bool was_on_screen,
|
||||
AppUiObserverRect node_clip,
|
||||
std::span<const AppUiObserverParentClip> parent_clips,
|
||||
float surface_height,
|
||||
float zoom,
|
||||
float offset_x,
|
||||
float offset_y)
|
||||
{
|
||||
if (!has_node || !display) {
|
||||
return pp::foundation::Result<AppUiObserverPlan>::success(AppUiObserverPlan {
|
||||
.draw_node = false,
|
||||
.next_on_screen = was_on_screen,
|
||||
.visible_clip = node_clip,
|
||||
});
|
||||
}
|
||||
|
||||
const auto finite_rect = [](AppUiObserverRect rect) noexcept {
|
||||
return std::isfinite(rect.x) && std::isfinite(rect.y)
|
||||
&& std::isfinite(rect.width) && std::isfinite(rect.height);
|
||||
};
|
||||
|
||||
if (!finite_rect(node_clip) || !std::isfinite(surface_height)
|
||||
|| !std::isfinite(zoom) || !std::isfinite(offset_x) || !std::isfinite(offset_y)) {
|
||||
return pp::foundation::Result<AppUiObserverPlan>::failure(
|
||||
pp::foundation::Status::invalid_argument("UI observer geometry must be finite"));
|
||||
}
|
||||
|
||||
if (surface_height < 1.0F || zoom <= 0.0F) {
|
||||
return pp::foundation::Result<AppUiObserverPlan>::failure(
|
||||
pp::foundation::Status::invalid_argument("UI observer surface height and zoom must be positive"));
|
||||
}
|
||||
|
||||
AppUiObserverRect visible = node_clip;
|
||||
for (const auto& parent : parent_clips) {
|
||||
if (!finite_rect(parent.clip)
|
||||
|| !std::isfinite(parent.padding_top)
|
||||
|| !std::isfinite(parent.padding_right)
|
||||
|| !std::isfinite(parent.padding_bottom)
|
||||
|| !std::isfinite(parent.padding_left)) {
|
||||
return pp::foundation::Result<AppUiObserverPlan>::failure(
|
||||
pp::foundation::Status::invalid_argument("UI observer parent geometry must be finite"));
|
||||
}
|
||||
|
||||
const AppUiObserverRect padded {
|
||||
.x = parent.clip.x + parent.padding_left,
|
||||
.y = parent.clip.y + parent.padding_top,
|
||||
.width = parent.clip.width - parent.padding_right - parent.padding_left,
|
||||
.height = parent.clip.height - parent.padding_bottom - parent.padding_top,
|
||||
};
|
||||
visible = intersect_app_ui_observer_rect(visible, padded);
|
||||
}
|
||||
|
||||
if (visible.width <= 0.0F || visible.height <= 0.0F) {
|
||||
return pp::foundation::Result<AppUiObserverPlan>::success(AppUiObserverPlan {
|
||||
.draw_node = false,
|
||||
.notify_leave_screen = was_on_screen,
|
||||
.next_on_screen = false,
|
||||
.visible_clip = visible,
|
||||
});
|
||||
}
|
||||
|
||||
const float projected_x = (visible.x - 1.0F) * zoom;
|
||||
const float projected_y = (surface_height / zoom - visible.y - visible.height - 1.0F) * zoom;
|
||||
const float projected_width = (visible.width + 2.0F) * zoom;
|
||||
const float projected_height = (visible.height + 2.0F) * zoom;
|
||||
|
||||
return pp::foundation::Result<AppUiObserverPlan>::success(AppUiObserverPlan {
|
||||
.draw_node = true,
|
||||
.notify_enter_screen = !was_on_screen,
|
||||
.notify_leave_screen = false,
|
||||
.next_on_screen = true,
|
||||
.visible_clip = visible,
|
||||
.scissor_x = static_cast<std::int32_t>(std::floor(projected_x + offset_x)),
|
||||
.scissor_y = static_cast<std::int32_t>(std::floor(projected_y + offset_y)),
|
||||
.scissor_width = static_cast<std::int32_t>(std::ceil(projected_width)),
|
||||
.scissor_height = static_cast<std::int32_t>(std::ceil(projected_height)),
|
||||
});
|
||||
}
|
||||
|
||||
} // namespace pp::app
|
||||
209
src/app_core/app_input.h
Normal file
209
src/app_core/app_input.h
Normal file
@@ -0,0 +1,209 @@
|
||||
#pragma once
|
||||
|
||||
#include "foundation/result.h"
|
||||
|
||||
#include <cmath>
|
||||
#include <cstddef>
|
||||
|
||||
namespace pp::app {
|
||||
|
||||
struct AppPointerDispatchPlan {
|
||||
bool request_redraw = true;
|
||||
bool dispatch_designer_first = false;
|
||||
bool dispatch_main_if_not_consumed = false;
|
||||
float normalized_x = 0.0F;
|
||||
float normalized_y = 0.0F;
|
||||
};
|
||||
|
||||
struct AppInputDispatchPlan {
|
||||
bool request_redraw = true;
|
||||
bool dispatch_main = false;
|
||||
};
|
||||
|
||||
struct AppGestureDispatchPlan {
|
||||
bool request_redraw = true;
|
||||
bool dispatch_main = false;
|
||||
float normalized_x = 0.0F;
|
||||
float normalized_y = 0.0F;
|
||||
float distance = 0.0F;
|
||||
float distance_delta = 0.0F;
|
||||
float position_delta_x = 0.0F;
|
||||
float position_delta_y = 0.0F;
|
||||
};
|
||||
|
||||
struct AppKeyDispatchPlan {
|
||||
bool request_redraw = true;
|
||||
bool dispatch_main = false;
|
||||
bool set_key_down = false;
|
||||
bool sync_vr_camera_rotation = false;
|
||||
};
|
||||
|
||||
struct AppUiVisibilityTogglePlan {
|
||||
bool next_ui_visible = true;
|
||||
std::size_t first_panel_child_index = 1;
|
||||
std::size_t panel_child_count = 0;
|
||||
};
|
||||
|
||||
struct AppStylusAttachPlan {
|
||||
bool set_has_stylus = true;
|
||||
bool enable_canvas_touch_lock = false;
|
||||
};
|
||||
|
||||
[[nodiscard]] inline pp::foundation::Status validate_input_zoom(float zoom)
|
||||
{
|
||||
if (!std::isfinite(zoom) || zoom <= 0.0F) {
|
||||
return pp::foundation::Status::invalid_argument("input zoom must be finite and positive");
|
||||
}
|
||||
|
||||
return pp::foundation::Status::success();
|
||||
}
|
||||
|
||||
[[nodiscard]] inline pp::foundation::Result<AppPointerDispatchPlan> plan_app_pointer_dispatch(
|
||||
float x,
|
||||
float y,
|
||||
float zoom,
|
||||
bool has_designer_layout,
|
||||
bool has_main_layout)
|
||||
{
|
||||
const auto zoom_status = validate_input_zoom(zoom);
|
||||
if (!zoom_status.ok()) {
|
||||
return pp::foundation::Result<AppPointerDispatchPlan>::failure(zoom_status);
|
||||
}
|
||||
|
||||
if (!std::isfinite(x) || !std::isfinite(y)) {
|
||||
return pp::foundation::Result<AppPointerDispatchPlan>::failure(
|
||||
pp::foundation::Status::invalid_argument("input coordinates must be finite"));
|
||||
}
|
||||
|
||||
return pp::foundation::Result<AppPointerDispatchPlan>::success(AppPointerDispatchPlan {
|
||||
.request_redraw = true,
|
||||
.dispatch_designer_first = has_designer_layout,
|
||||
.dispatch_main_if_not_consumed = has_main_layout,
|
||||
.normalized_x = x / zoom,
|
||||
.normalized_y = y / zoom,
|
||||
});
|
||||
}
|
||||
|
||||
[[nodiscard]] constexpr AppPointerDispatchPlan plan_app_mouse_cancel_dispatch(
|
||||
bool has_designer_layout,
|
||||
bool has_main_layout) noexcept
|
||||
{
|
||||
return AppPointerDispatchPlan {
|
||||
.request_redraw = true,
|
||||
.dispatch_designer_first = has_designer_layout,
|
||||
.dispatch_main_if_not_consumed = has_main_layout,
|
||||
.normalized_x = 0.0F,
|
||||
.normalized_y = 0.0F,
|
||||
};
|
||||
}
|
||||
|
||||
[[nodiscard]] constexpr AppInputDispatchPlan plan_app_main_input_dispatch(bool has_main_layout) noexcept
|
||||
{
|
||||
return AppInputDispatchPlan {
|
||||
.request_redraw = true,
|
||||
.dispatch_main = has_main_layout,
|
||||
};
|
||||
}
|
||||
|
||||
[[nodiscard]] inline pp::foundation::Result<AppGestureDispatchPlan> plan_app_gesture_dispatch(
|
||||
float x0,
|
||||
float y0,
|
||||
float x1,
|
||||
float y1,
|
||||
float previous_x0,
|
||||
float previous_y0,
|
||||
float previous_x1,
|
||||
float previous_y1,
|
||||
float zoom,
|
||||
bool has_main_layout)
|
||||
{
|
||||
const auto zoom_status = validate_input_zoom(zoom);
|
||||
if (!zoom_status.ok()) {
|
||||
return pp::foundation::Result<AppGestureDispatchPlan>::failure(zoom_status);
|
||||
}
|
||||
|
||||
if (!std::isfinite(x0) || !std::isfinite(y0) || !std::isfinite(x1) || !std::isfinite(y1)
|
||||
|| !std::isfinite(previous_x0) || !std::isfinite(previous_y0)
|
||||
|| !std::isfinite(previous_x1) || !std::isfinite(previous_y1)) {
|
||||
return pp::foundation::Result<AppGestureDispatchPlan>::failure(
|
||||
pp::foundation::Status::invalid_argument("gesture coordinates must be finite"));
|
||||
}
|
||||
|
||||
const float midpoint_x = (x0 + x1) * 0.5F;
|
||||
const float midpoint_y = (y0 + y1) * 0.5F;
|
||||
const float previous_midpoint_x = (previous_x0 + previous_x1) * 0.5F;
|
||||
const float previous_midpoint_y = (previous_y0 + previous_y1) * 0.5F;
|
||||
const float dx = x1 - x0;
|
||||
const float dy = y1 - y0;
|
||||
const float previous_dx = previous_x1 - previous_x0;
|
||||
const float previous_dy = previous_y1 - previous_y0;
|
||||
const float distance = std::sqrt(dx * dx + dy * dy);
|
||||
const float previous_distance = std::sqrt(previous_dx * previous_dx + previous_dy * previous_dy);
|
||||
|
||||
return pp::foundation::Result<AppGestureDispatchPlan>::success(AppGestureDispatchPlan {
|
||||
.request_redraw = true,
|
||||
.dispatch_main = has_main_layout,
|
||||
.normalized_x = midpoint_x / zoom,
|
||||
.normalized_y = midpoint_y / zoom,
|
||||
.distance = distance,
|
||||
.distance_delta = distance - previous_distance,
|
||||
.position_delta_x = midpoint_x - previous_midpoint_x,
|
||||
.position_delta_y = midpoint_y - previous_midpoint_y,
|
||||
});
|
||||
}
|
||||
|
||||
[[nodiscard]] constexpr AppKeyDispatchPlan plan_app_key_down_dispatch(
|
||||
bool has_main_layout,
|
||||
bool is_spacebar,
|
||||
bool vr_active) noexcept
|
||||
{
|
||||
return AppKeyDispatchPlan {
|
||||
.request_redraw = true,
|
||||
.dispatch_main = has_main_layout,
|
||||
.set_key_down = true,
|
||||
.sync_vr_camera_rotation = is_spacebar && vr_active,
|
||||
};
|
||||
}
|
||||
|
||||
[[nodiscard]] constexpr AppKeyDispatchPlan plan_app_key_up_dispatch(bool has_main_layout) noexcept
|
||||
{
|
||||
return AppKeyDispatchPlan {
|
||||
.request_redraw = true,
|
||||
.dispatch_main = has_main_layout,
|
||||
.set_key_down = false,
|
||||
.sync_vr_camera_rotation = false,
|
||||
};
|
||||
}
|
||||
|
||||
[[nodiscard]] inline pp::foundation::Result<AppUiVisibilityTogglePlan> plan_app_ui_visibility_toggle(
|
||||
bool current_ui_visible,
|
||||
bool has_main_layout,
|
||||
std::size_t main_child_count,
|
||||
std::size_t panel_child_count)
|
||||
{
|
||||
if (!has_main_layout) {
|
||||
return pp::foundation::Result<AppUiVisibilityTogglePlan>::failure(
|
||||
pp::foundation::Status::invalid_argument("UI toggle requires a main layout"));
|
||||
}
|
||||
|
||||
if (main_child_count <= 1U) {
|
||||
return pp::foundation::Result<AppUiVisibilityTogglePlan>::failure(
|
||||
pp::foundation::Status::invalid_argument("UI toggle requires a panel container child"));
|
||||
}
|
||||
|
||||
return pp::foundation::Result<AppUiVisibilityTogglePlan>::success(AppUiVisibilityTogglePlan {
|
||||
.next_ui_visible = !current_ui_visible,
|
||||
.first_panel_child_index = 1U,
|
||||
.panel_child_count = panel_child_count,
|
||||
});
|
||||
}
|
||||
|
||||
[[nodiscard]] constexpr AppStylusAttachPlan plan_app_stylus_attach(bool has_canvas) noexcept
|
||||
{
|
||||
return AppStylusAttachPlan {
|
||||
.set_has_stylus = true,
|
||||
.enable_canvas_touch_lock = has_canvas,
|
||||
};
|
||||
}
|
||||
|
||||
} // namespace pp::app
|
||||
195
src/app_core/app_preferences.h
Normal file
195
src/app_core/app_preferences.h
Normal file
@@ -0,0 +1,195 @@
|
||||
#pragma once
|
||||
|
||||
#include "foundation/result.h"
|
||||
|
||||
#include <cstddef>
|
||||
#include <span>
|
||||
|
||||
namespace pp::app {
|
||||
|
||||
enum class InterfaceDirection {
|
||||
left_to_right,
|
||||
right_to_left,
|
||||
};
|
||||
|
||||
enum class TimelapseRecordingAction {
|
||||
no_op,
|
||||
start_recording,
|
||||
stop_recording,
|
||||
};
|
||||
|
||||
struct ScaleApplicationPlan {
|
||||
float scale = 1.0F;
|
||||
float display_density = 1.0F;
|
||||
float font_scale = 1.0F;
|
||||
};
|
||||
|
||||
struct ScaleOptionSelection {
|
||||
bool has_selection = false;
|
||||
std::size_t index = 0;
|
||||
};
|
||||
|
||||
struct InterfaceDirectionPlan {
|
||||
InterfaceDirection direction = InterfaceDirection::left_to_right;
|
||||
};
|
||||
|
||||
struct TimelapsePreferencePlan {
|
||||
bool enabled = true;
|
||||
TimelapseRecordingAction recording_action = TimelapseRecordingAction::no_op;
|
||||
};
|
||||
|
||||
struct StoredIntegerPreferencePlan {
|
||||
int value = 0;
|
||||
};
|
||||
|
||||
struct StoredBooleanPreferencePlan {
|
||||
bool value = false;
|
||||
};
|
||||
|
||||
class AppPreferenceServices {
|
||||
public:
|
||||
virtual ~AppPreferenceServices() = default;
|
||||
|
||||
virtual void apply_ui_scale(const ScaleApplicationPlan& plan) = 0;
|
||||
virtual void apply_viewport_scale(const ScaleApplicationPlan& plan) = 0;
|
||||
virtual void apply_interface_direction(const InterfaceDirectionPlan& plan) = 0;
|
||||
virtual bool apply_vr_mode_preference(const StoredBooleanPreferencePlan& plan) = 0;
|
||||
virtual void apply_vr_controllers_preference(const StoredBooleanPreferencePlan& plan) = 0;
|
||||
virtual void apply_timelapse_preference(const TimelapsePreferencePlan& plan) = 0;
|
||||
virtual void apply_canvas_cursor_mode(const StoredIntegerPreferencePlan& plan) = 0;
|
||||
};
|
||||
|
||||
[[nodiscard]] constexpr ScaleApplicationPlan plan_ui_scale(
|
||||
float requested_scale,
|
||||
float display_density) noexcept
|
||||
{
|
||||
return {
|
||||
requested_scale,
|
||||
display_density,
|
||||
requested_scale * display_density,
|
||||
};
|
||||
}
|
||||
|
||||
[[nodiscard]] constexpr ScaleApplicationPlan plan_viewport_scale(
|
||||
float requested_scale,
|
||||
float display_density = 1.0F) noexcept
|
||||
{
|
||||
return {
|
||||
requested_scale,
|
||||
display_density,
|
||||
requested_scale * display_density,
|
||||
};
|
||||
}
|
||||
|
||||
[[nodiscard]] constexpr ScaleOptionSelection plan_scale_option_selection(
|
||||
float current_scale,
|
||||
std::span<const float> options) noexcept
|
||||
{
|
||||
ScaleOptionSelection selection;
|
||||
for (std::size_t index = 0; index < options.size(); ++index) {
|
||||
if (current_scale >= options[index]) {
|
||||
selection.has_selection = true;
|
||||
selection.index = index;
|
||||
}
|
||||
}
|
||||
return selection;
|
||||
}
|
||||
|
||||
[[nodiscard]] constexpr InterfaceDirectionPlan plan_interface_direction(bool right_to_left) noexcept
|
||||
{
|
||||
return {
|
||||
right_to_left ? InterfaceDirection::right_to_left : InterfaceDirection::left_to_right,
|
||||
};
|
||||
}
|
||||
|
||||
[[nodiscard]] constexpr TimelapsePreferencePlan plan_timelapse_preference(
|
||||
bool enabled,
|
||||
bool recording_running) noexcept
|
||||
{
|
||||
if (enabled && !recording_running) {
|
||||
return { enabled, TimelapseRecordingAction::start_recording };
|
||||
}
|
||||
if (!enabled && recording_running) {
|
||||
return { enabled, TimelapseRecordingAction::stop_recording };
|
||||
}
|
||||
return { enabled, TimelapseRecordingAction::no_op };
|
||||
}
|
||||
|
||||
[[nodiscard]] constexpr StoredBooleanPreferencePlan plan_vr_controllers_preference(
|
||||
bool enabled) noexcept
|
||||
{
|
||||
return { enabled };
|
||||
}
|
||||
|
||||
[[nodiscard]] constexpr StoredBooleanPreferencePlan plan_vr_mode_preference(bool enabled) noexcept
|
||||
{
|
||||
return { enabled };
|
||||
}
|
||||
|
||||
[[nodiscard]] constexpr StoredIntegerPreferencePlan plan_canvas_cursor_mode(int mode) noexcept
|
||||
{
|
||||
return { mode };
|
||||
}
|
||||
|
||||
[[nodiscard]] inline pp::foundation::Status execute_ui_scale_preference(
|
||||
float requested_scale,
|
||||
float display_density,
|
||||
AppPreferenceServices& services)
|
||||
{
|
||||
services.apply_ui_scale(plan_ui_scale(requested_scale, display_density));
|
||||
return pp::foundation::Status::success();
|
||||
}
|
||||
|
||||
[[nodiscard]] inline pp::foundation::Status execute_viewport_scale_preference(
|
||||
float requested_scale,
|
||||
float display_density,
|
||||
AppPreferenceServices& services)
|
||||
{
|
||||
services.apply_viewport_scale(plan_viewport_scale(requested_scale, display_density));
|
||||
return pp::foundation::Status::success();
|
||||
}
|
||||
|
||||
[[nodiscard]] inline pp::foundation::Status execute_interface_direction_preference(
|
||||
bool right_to_left,
|
||||
AppPreferenceServices& services)
|
||||
{
|
||||
services.apply_interface_direction(plan_interface_direction(right_to_left));
|
||||
return pp::foundation::Status::success();
|
||||
}
|
||||
|
||||
[[nodiscard]] inline pp::foundation::Status execute_vr_mode_preference(
|
||||
bool enabled,
|
||||
AppPreferenceServices& services)
|
||||
{
|
||||
if (!services.apply_vr_mode_preference(plan_vr_mode_preference(enabled))) {
|
||||
return pp::foundation::Status::invalid_argument("VR mode could not start");
|
||||
}
|
||||
return pp::foundation::Status::success();
|
||||
}
|
||||
|
||||
[[nodiscard]] inline pp::foundation::Status execute_vr_controllers_preference(
|
||||
bool enabled,
|
||||
AppPreferenceServices& services)
|
||||
{
|
||||
services.apply_vr_controllers_preference(plan_vr_controllers_preference(enabled));
|
||||
return pp::foundation::Status::success();
|
||||
}
|
||||
|
||||
[[nodiscard]] inline pp::foundation::Status execute_timelapse_preference(
|
||||
bool enabled,
|
||||
bool recording_running,
|
||||
AppPreferenceServices& services)
|
||||
{
|
||||
services.apply_timelapse_preference(plan_timelapse_preference(enabled, recording_running));
|
||||
return pp::foundation::Status::success();
|
||||
}
|
||||
|
||||
[[nodiscard]] inline pp::foundation::Status execute_canvas_cursor_mode_preference(
|
||||
int mode,
|
||||
AppPreferenceServices& services)
|
||||
{
|
||||
services.apply_canvas_cursor_mode(plan_canvas_cursor_mode(mode));
|
||||
return pp::foundation::Status::success();
|
||||
}
|
||||
|
||||
}
|
||||
23
src/app_core/app_shutdown.h
Normal file
23
src/app_core/app_shutdown.h
Normal file
@@ -0,0 +1,23 @@
|
||||
#pragma once
|
||||
|
||||
namespace pp::app {
|
||||
|
||||
struct AppShutdownPlan {
|
||||
bool save_ui_state = true;
|
||||
bool terminate_stroke_preview_renderer = true;
|
||||
bool stop_recording = true;
|
||||
bool invalidate_textures = true;
|
||||
bool invalidate_shaders = true;
|
||||
bool unload_layouts = true;
|
||||
bool destroy_ui_render_target = true;
|
||||
bool destroy_face_plane = true;
|
||||
bool release_panel_nodes = true;
|
||||
bool clear_quick_mode_state = true;
|
||||
};
|
||||
|
||||
[[nodiscard]] constexpr AppShutdownPlan plan_app_shutdown() noexcept
|
||||
{
|
||||
return AppShutdownPlan {};
|
||||
}
|
||||
|
||||
} // namespace pp::app
|
||||
188
src/app_core/app_startup.h
Normal file
188
src/app_core/app_startup.h
Normal file
@@ -0,0 +1,188 @@
|
||||
#pragma once
|
||||
|
||||
#include "foundation/result.h"
|
||||
|
||||
#include <cmath>
|
||||
#include <limits>
|
||||
|
||||
namespace pp::app {
|
||||
|
||||
struct AppStartupPlan {
|
||||
int previous_run_counter = 0;
|
||||
int next_run_counter = 1;
|
||||
bool save_preferences = true;
|
||||
bool start_timelapse = false;
|
||||
bool vr_controllers_enabled = true;
|
||||
bool show_license_warning = false;
|
||||
};
|
||||
|
||||
struct AppStartupResourcePlan {
|
||||
int ui_render_target_width = 0;
|
||||
int ui_render_target_height = 0;
|
||||
bool initialize_shaders = true;
|
||||
bool initialize_assets = true;
|
||||
bool initialize_layout = true;
|
||||
bool update_title = true;
|
||||
bool create_ui_render_target = true;
|
||||
};
|
||||
|
||||
class AppStartupServices {
|
||||
public:
|
||||
virtual ~AppStartupServices() = default;
|
||||
|
||||
virtual void store_run_counter(int value) = 0;
|
||||
virtual void save_preferences() = 0;
|
||||
virtual void start_timelapse_recording() = 0;
|
||||
virtual void apply_vr_controllers_enabled(bool enabled) = 0;
|
||||
virtual void show_license_warning() = 0;
|
||||
};
|
||||
|
||||
class AppStartupResourceServices {
|
||||
public:
|
||||
virtual ~AppStartupResourceServices() = default;
|
||||
|
||||
virtual void initialize_shaders() = 0;
|
||||
virtual void initialize_assets() = 0;
|
||||
virtual void initialize_layout() = 0;
|
||||
virtual void update_title() = 0;
|
||||
virtual void create_ui_render_target(int width, int height) = 0;
|
||||
};
|
||||
|
||||
[[nodiscard]] inline pp::foundation::Result<AppStartupPlan> plan_app_startup(
|
||||
int current_run_counter,
|
||||
bool auto_timelapse_enabled,
|
||||
bool stored_vr_controllers_enabled,
|
||||
bool license_valid)
|
||||
{
|
||||
if (current_run_counter < 0) {
|
||||
return pp::foundation::Result<AppStartupPlan>::failure(
|
||||
pp::foundation::Status::invalid_argument("run counter must not be negative"));
|
||||
}
|
||||
|
||||
if (current_run_counter == std::numeric_limits<int>::max()) {
|
||||
return pp::foundation::Result<AppStartupPlan>::failure(
|
||||
pp::foundation::Status::out_of_range("run counter would overflow"));
|
||||
}
|
||||
|
||||
AppStartupPlan plan;
|
||||
plan.previous_run_counter = current_run_counter;
|
||||
plan.next_run_counter = current_run_counter + 1;
|
||||
plan.start_timelapse = auto_timelapse_enabled;
|
||||
plan.vr_controllers_enabled = stored_vr_controllers_enabled;
|
||||
plan.show_license_warning = !license_valid;
|
||||
return pp::foundation::Result<AppStartupPlan>::success(plan);
|
||||
}
|
||||
|
||||
[[nodiscard]] inline pp::foundation::Result<AppStartupResourcePlan> plan_app_startup_resources(
|
||||
float ui_width,
|
||||
float ui_height)
|
||||
{
|
||||
if (!std::isfinite(ui_width) || !std::isfinite(ui_height)) {
|
||||
return pp::foundation::Result<AppStartupResourcePlan>::failure(
|
||||
pp::foundation::Status::invalid_argument("startup resource dimensions must be finite"));
|
||||
}
|
||||
|
||||
if (ui_width < 1.0F || ui_height < 1.0F) {
|
||||
return pp::foundation::Result<AppStartupResourcePlan>::failure(
|
||||
pp::foundation::Status::invalid_argument("startup resource dimensions must be positive"));
|
||||
}
|
||||
|
||||
if (ui_width > static_cast<float>(std::numeric_limits<int>::max())
|
||||
|| ui_height > static_cast<float>(std::numeric_limits<int>::max())) {
|
||||
return pp::foundation::Result<AppStartupResourcePlan>::failure(
|
||||
pp::foundation::Status::out_of_range("startup resource dimensions exceed integer range"));
|
||||
}
|
||||
|
||||
AppStartupResourcePlan plan;
|
||||
plan.ui_render_target_width = static_cast<int>(ui_width);
|
||||
plan.ui_render_target_height = static_cast<int>(ui_height);
|
||||
return pp::foundation::Result<AppStartupResourcePlan>::success(plan);
|
||||
}
|
||||
|
||||
[[nodiscard]] inline pp::foundation::Status execute_app_startup_plan(
|
||||
const AppStartupPlan& plan,
|
||||
AppStartupServices& services)
|
||||
{
|
||||
if (plan.previous_run_counter < 0 || plan.next_run_counter <= plan.previous_run_counter) {
|
||||
return pp::foundation::Status::invalid_argument("startup plan has invalid run counter state");
|
||||
}
|
||||
|
||||
services.store_run_counter(plan.next_run_counter);
|
||||
if (plan.save_preferences) {
|
||||
services.save_preferences();
|
||||
}
|
||||
if (plan.start_timelapse) {
|
||||
services.start_timelapse_recording();
|
||||
}
|
||||
services.apply_vr_controllers_enabled(plan.vr_controllers_enabled);
|
||||
if (plan.show_license_warning) {
|
||||
services.show_license_warning();
|
||||
}
|
||||
|
||||
return pp::foundation::Status::success();
|
||||
}
|
||||
|
||||
[[nodiscard]] inline pp::foundation::Status execute_app_startup_persistence_plan(
|
||||
const AppStartupPlan& plan,
|
||||
AppStartupServices& services)
|
||||
{
|
||||
if (plan.previous_run_counter < 0 || plan.next_run_counter <= plan.previous_run_counter) {
|
||||
return pp::foundation::Status::invalid_argument("startup plan has invalid run counter state");
|
||||
}
|
||||
|
||||
services.store_run_counter(plan.next_run_counter);
|
||||
if (plan.save_preferences) {
|
||||
services.save_preferences();
|
||||
}
|
||||
|
||||
return pp::foundation::Status::success();
|
||||
}
|
||||
|
||||
[[nodiscard]] inline pp::foundation::Status execute_app_startup_runtime_plan(
|
||||
const AppStartupPlan& plan,
|
||||
AppStartupServices& services)
|
||||
{
|
||||
if (plan.previous_run_counter < 0 || plan.next_run_counter <= plan.previous_run_counter) {
|
||||
return pp::foundation::Status::invalid_argument("startup plan has invalid run counter state");
|
||||
}
|
||||
|
||||
if (plan.start_timelapse) {
|
||||
services.start_timelapse_recording();
|
||||
}
|
||||
services.apply_vr_controllers_enabled(plan.vr_controllers_enabled);
|
||||
if (plan.show_license_warning) {
|
||||
services.show_license_warning();
|
||||
}
|
||||
|
||||
return pp::foundation::Status::success();
|
||||
}
|
||||
|
||||
[[nodiscard]] inline pp::foundation::Status execute_app_startup_resources(
|
||||
const AppStartupResourcePlan& plan,
|
||||
AppStartupResourceServices& services)
|
||||
{
|
||||
if (plan.create_ui_render_target
|
||||
&& (plan.ui_render_target_width <= 0 || plan.ui_render_target_height <= 0)) {
|
||||
return pp::foundation::Status::invalid_argument("startup resource plan has invalid UI render target size");
|
||||
}
|
||||
|
||||
if (plan.initialize_shaders) {
|
||||
services.initialize_shaders();
|
||||
}
|
||||
if (plan.initialize_assets) {
|
||||
services.initialize_assets();
|
||||
}
|
||||
if (plan.initialize_layout) {
|
||||
services.initialize_layout();
|
||||
}
|
||||
if (plan.update_title) {
|
||||
services.update_title();
|
||||
}
|
||||
if (plan.create_ui_render_target) {
|
||||
services.create_ui_render_target(plan.ui_render_target_width, plan.ui_render_target_height);
|
||||
}
|
||||
|
||||
return pp::foundation::Status::success();
|
||||
}
|
||||
|
||||
} // namespace pp::app
|
||||
170
src/app_core/app_status.h
Normal file
170
src/app_core/app_status.h
Normal file
@@ -0,0 +1,170 @@
|
||||
#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;
|
||||
};
|
||||
|
||||
struct RendererDiagnosticsInput {
|
||||
bool framebuffer_fetch = false;
|
||||
bool float32_render_targets = false;
|
||||
bool float32_linear_filtering = false;
|
||||
bool float16_render_targets = false;
|
||||
};
|
||||
|
||||
struct RendererDiagnosticIndicator {
|
||||
bool supported = false;
|
||||
std::string_view label;
|
||||
};
|
||||
|
||||
struct RendererDiagnosticsPlan {
|
||||
RendererDiagnosticIndicator framebuffer_fetch;
|
||||
RendererDiagnosticIndicator floating_point_targets;
|
||||
};
|
||||
|
||||
[[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,
|
||||
};
|
||||
}
|
||||
|
||||
[[nodiscard]] inline RendererDiagnosticsPlan plan_renderer_diagnostics(
|
||||
RendererDiagnosticsInput input) noexcept
|
||||
{
|
||||
RendererDiagnosticsPlan plan;
|
||||
plan.framebuffer_fetch = {
|
||||
input.framebuffer_fetch,
|
||||
"FBF",
|
||||
};
|
||||
|
||||
if (input.float32_linear_filtering) {
|
||||
plan.floating_point_targets = {
|
||||
true,
|
||||
"F32L",
|
||||
};
|
||||
} else if (input.float32_render_targets) {
|
||||
plan.floating_point_targets = {
|
||||
true,
|
||||
"F32",
|
||||
};
|
||||
} else if (input.float16_render_targets) {
|
||||
plan.floating_point_targets = {
|
||||
true,
|
||||
"F16",
|
||||
};
|
||||
} else {
|
||||
plan.floating_point_targets = {
|
||||
false,
|
||||
"",
|
||||
};
|
||||
}
|
||||
|
||||
return plan;
|
||||
}
|
||||
|
||||
}
|
||||
200
src/app_core/app_thread.h
Normal file
200
src/app_core/app_thread.h
Normal file
@@ -0,0 +1,200 @@
|
||||
#pragma once
|
||||
|
||||
#include "foundation/result.h"
|
||||
|
||||
#include <cmath>
|
||||
#include <cstddef>
|
||||
|
||||
namespace pp::app {
|
||||
|
||||
struct AppTaskDispatchPlan {
|
||||
bool execute_immediately = false;
|
||||
bool queue_task = false;
|
||||
bool remove_matching_unique_task = false;
|
||||
bool notify_worker = false;
|
||||
bool wait_for_completion = false;
|
||||
bool request_redraw = false;
|
||||
};
|
||||
|
||||
struct AppAsyncRedrawPlan {
|
||||
bool set_redraw = true;
|
||||
bool notify_ui = true;
|
||||
};
|
||||
|
||||
struct AppQueueDrainPlan {
|
||||
bool mark_running = true;
|
||||
bool drain_tasks = false;
|
||||
bool wrap_in_render_context = false;
|
||||
std::size_t task_count = 0;
|
||||
};
|
||||
|
||||
struct AppUiTickPlan {
|
||||
bool mark_running = true;
|
||||
bool execute_tasks = false;
|
||||
bool tick_app = true;
|
||||
bool update_before_render = false;
|
||||
bool enqueue_render_frame = false;
|
||||
std::size_t task_count = 0;
|
||||
};
|
||||
|
||||
struct AppUiLoopTimerPlan {
|
||||
bool update_platform_frame = true;
|
||||
float frame_accumulator = 0.0F;
|
||||
float fps_accumulator = 0.0F;
|
||||
float reload_accumulator = 0.0F;
|
||||
bool report_rendered_frames = false;
|
||||
int reported_frame_count = 0;
|
||||
int rendered_frames_after_report = 0;
|
||||
bool check_live_asset_reload = false;
|
||||
};
|
||||
|
||||
struct AppUiLoopRedrawPlan {
|
||||
bool tick_app = true;
|
||||
bool update_before_render = false;
|
||||
bool enqueue_render_frame = false;
|
||||
bool reset_frame_accumulator = false;
|
||||
int rendered_frames = 0;
|
||||
};
|
||||
|
||||
struct AppThreadStartPlan {
|
||||
bool start_thread = true;
|
||||
bool mark_running = true;
|
||||
};
|
||||
|
||||
struct AppThreadStopPlan {
|
||||
bool mark_not_running = true;
|
||||
bool notify_worker = true;
|
||||
bool join_thread = false;
|
||||
};
|
||||
|
||||
[[nodiscard]] constexpr AppTaskDispatchPlan plan_app_task_dispatch(
|
||||
bool already_on_target_thread,
|
||||
bool unique,
|
||||
std::size_t queued_task_count,
|
||||
bool worker_running,
|
||||
bool wait_for_completion,
|
||||
bool request_redraw_after_dispatch) noexcept
|
||||
{
|
||||
const bool queue_task = !already_on_target_thread;
|
||||
return AppTaskDispatchPlan {
|
||||
.execute_immediately = already_on_target_thread,
|
||||
.queue_task = queue_task,
|
||||
.remove_matching_unique_task = queue_task && unique && queued_task_count > 0U,
|
||||
.notify_worker = queue_task,
|
||||
.wait_for_completion = queue_task && worker_running && wait_for_completion,
|
||||
.request_redraw = request_redraw_after_dispatch,
|
||||
};
|
||||
}
|
||||
|
||||
[[nodiscard]] constexpr AppAsyncRedrawPlan plan_app_async_redraw() noexcept
|
||||
{
|
||||
return AppAsyncRedrawPlan {};
|
||||
}
|
||||
|
||||
[[nodiscard]] constexpr AppQueueDrainPlan plan_app_render_queue_drain(std::size_t queued_task_count) noexcept
|
||||
{
|
||||
const bool drain = queued_task_count > 0U;
|
||||
return AppQueueDrainPlan {
|
||||
.mark_running = true,
|
||||
.drain_tasks = drain,
|
||||
.wrap_in_render_context = drain,
|
||||
.task_count = queued_task_count,
|
||||
};
|
||||
}
|
||||
|
||||
[[nodiscard]] constexpr AppQueueDrainPlan plan_app_ui_queue_drain(std::size_t queued_task_count) noexcept
|
||||
{
|
||||
return AppQueueDrainPlan {
|
||||
.mark_running = true,
|
||||
.drain_tasks = queued_task_count > 0U,
|
||||
.wrap_in_render_context = false,
|
||||
.task_count = queued_task_count,
|
||||
};
|
||||
}
|
||||
|
||||
[[nodiscard]] constexpr AppUiTickPlan plan_app_ui_thread_tick(
|
||||
std::size_t queued_task_count,
|
||||
bool redraw) noexcept
|
||||
{
|
||||
return AppUiTickPlan {
|
||||
.mark_running = true,
|
||||
.execute_tasks = queued_task_count > 0U,
|
||||
.tick_app = true,
|
||||
.update_before_render = redraw,
|
||||
.enqueue_render_frame = redraw,
|
||||
.task_count = queued_task_count,
|
||||
};
|
||||
}
|
||||
|
||||
[[nodiscard]] inline pp::foundation::Result<AppUiLoopTimerPlan> plan_app_ui_loop_timers(
|
||||
float delta_time_seconds,
|
||||
float frame_accumulator,
|
||||
float fps_accumulator,
|
||||
float reload_accumulator,
|
||||
int rendered_frames,
|
||||
bool live_asset_reloading_enabled)
|
||||
{
|
||||
if (!std::isfinite(delta_time_seconds) || !std::isfinite(frame_accumulator)
|
||||
|| !std::isfinite(fps_accumulator) || !std::isfinite(reload_accumulator)) {
|
||||
return pp::foundation::Result<AppUiLoopTimerPlan>::failure(
|
||||
pp::foundation::Status::invalid_argument("UI loop timer values must be finite"));
|
||||
}
|
||||
|
||||
if (delta_time_seconds < 0.0F || frame_accumulator < 0.0F
|
||||
|| fps_accumulator < 0.0F || reload_accumulator < 0.0F || rendered_frames < 0) {
|
||||
return pp::foundation::Result<AppUiLoopTimerPlan>::failure(
|
||||
pp::foundation::Status::invalid_argument("UI loop timer values must not be negative"));
|
||||
}
|
||||
|
||||
AppUiLoopTimerPlan plan;
|
||||
plan.frame_accumulator = frame_accumulator + delta_time_seconds;
|
||||
plan.fps_accumulator = fps_accumulator + delta_time_seconds;
|
||||
plan.reload_accumulator = reload_accumulator;
|
||||
plan.rendered_frames_after_report = rendered_frames;
|
||||
|
||||
if (plan.fps_accumulator > 1.0F) {
|
||||
plan.report_rendered_frames = true;
|
||||
plan.reported_frame_count = rendered_frames;
|
||||
plan.fps_accumulator = 0.0F;
|
||||
plan.rendered_frames_after_report = 0;
|
||||
}
|
||||
|
||||
if (live_asset_reloading_enabled) {
|
||||
plan.reload_accumulator += delta_time_seconds;
|
||||
if (plan.reload_accumulator > 1.0F) {
|
||||
plan.reload_accumulator = 0.0F;
|
||||
plan.check_live_asset_reload = true;
|
||||
}
|
||||
}
|
||||
|
||||
return pp::foundation::Result<AppUiLoopTimerPlan>::success(plan);
|
||||
}
|
||||
|
||||
[[nodiscard]] constexpr AppUiLoopRedrawPlan plan_app_ui_loop_redraw(
|
||||
bool redraw,
|
||||
int rendered_frames) noexcept
|
||||
{
|
||||
return AppUiLoopRedrawPlan {
|
||||
.tick_app = true,
|
||||
.update_before_render = redraw,
|
||||
.enqueue_render_frame = redraw,
|
||||
.reset_frame_accumulator = redraw,
|
||||
.rendered_frames = rendered_frames + (redraw ? 1 : 0),
|
||||
};
|
||||
}
|
||||
|
||||
[[nodiscard]] constexpr AppThreadStartPlan plan_app_thread_start() noexcept
|
||||
{
|
||||
return AppThreadStartPlan {};
|
||||
}
|
||||
|
||||
[[nodiscard]] constexpr AppThreadStopPlan plan_app_thread_stop(bool thread_joinable) noexcept
|
||||
{
|
||||
return AppThreadStopPlan {
|
||||
.mark_not_running = true,
|
||||
.notify_worker = true,
|
||||
.join_thread = thread_joinable,
|
||||
};
|
||||
}
|
||||
|
||||
} // namespace pp::app
|
||||
71
src/app_core/brush_package_export.h
Normal file
71
src/app_core/brush_package_export.h
Normal file
@@ -0,0 +1,71 @@
|
||||
#pragma once
|
||||
|
||||
#include "app_core/app_dialog.h"
|
||||
#include "foundation/result.h"
|
||||
|
||||
#include <string>
|
||||
#include <string_view>
|
||||
|
||||
namespace pp::app {
|
||||
|
||||
struct BrushPackageExportRequest {
|
||||
std::string author;
|
||||
std::string email;
|
||||
std::string url;
|
||||
std::string description;
|
||||
std::string destination_path;
|
||||
bool export_data = false;
|
||||
bool has_header_image = false;
|
||||
};
|
||||
|
||||
class BrushPackageExportServices {
|
||||
public:
|
||||
virtual ~BrushPackageExportServices() = default;
|
||||
|
||||
virtual void export_brush_package(std::string_view path, const BrushPackageExportRequest& request) = 0;
|
||||
};
|
||||
|
||||
[[nodiscard]] inline pp::foundation::Status validate_brush_package_export_path(std::string_view path) noexcept
|
||||
{
|
||||
if (path.empty()) {
|
||||
return pp::foundation::Status::invalid_argument("brush package export path must not be empty");
|
||||
}
|
||||
|
||||
return pp::foundation::Status::success();
|
||||
}
|
||||
|
||||
[[nodiscard]] inline AppMessageDialogPlan plan_brush_package_export_success_dialog(std::string_view path)
|
||||
{
|
||||
std::string message = "Brushes exported to:\n";
|
||||
message += path;
|
||||
return plan_app_message_dialog("Export PPBR", message, false);
|
||||
}
|
||||
|
||||
[[nodiscard]] inline pp::foundation::Status validate_brush_package_export_request(
|
||||
std::string_view path,
|
||||
const BrushPackageExportRequest& request) noexcept
|
||||
{
|
||||
(void)request;
|
||||
const auto path_status = validate_brush_package_export_path(path);
|
||||
if (!path_status.ok()) {
|
||||
return path_status;
|
||||
}
|
||||
|
||||
return pp::foundation::Status::success();
|
||||
}
|
||||
|
||||
[[nodiscard]] inline pp::foundation::Status execute_brush_package_export(
|
||||
std::string_view path,
|
||||
const BrushPackageExportRequest& request,
|
||||
BrushPackageExportServices& services)
|
||||
{
|
||||
const auto status = validate_brush_package_export_request(path, request);
|
||||
if (!status.ok()) {
|
||||
return status;
|
||||
}
|
||||
|
||||
services.export_brush_package(path, request);
|
||||
return pp::foundation::Status::success();
|
||||
}
|
||||
|
||||
} // namespace pp::app
|
||||
56
src/app_core/brush_package_import.h
Normal file
56
src/app_core/brush_package_import.h
Normal file
@@ -0,0 +1,56 @@
|
||||
#pragma once
|
||||
|
||||
#include "foundation/result.h"
|
||||
|
||||
#include <string_view>
|
||||
|
||||
namespace pp::app {
|
||||
|
||||
enum class BrushPackageImportKind {
|
||||
abr,
|
||||
ppbr,
|
||||
};
|
||||
|
||||
class BrushPackageImportServices {
|
||||
public:
|
||||
virtual ~BrushPackageImportServices() = default;
|
||||
|
||||
virtual void import_brush_package(BrushPackageImportKind kind, std::string_view path) = 0;
|
||||
};
|
||||
|
||||
[[nodiscard]] inline const char* brush_package_import_kind_name(BrushPackageImportKind kind) noexcept
|
||||
{
|
||||
switch (kind) {
|
||||
case BrushPackageImportKind::abr:
|
||||
return "abr";
|
||||
case BrushPackageImportKind::ppbr:
|
||||
return "ppbr";
|
||||
}
|
||||
|
||||
return "abr";
|
||||
}
|
||||
|
||||
[[nodiscard]] inline pp::foundation::Status validate_brush_package_import_path(std::string_view path) noexcept
|
||||
{
|
||||
if (path.empty()) {
|
||||
return pp::foundation::Status::invalid_argument("brush package import path must not be empty");
|
||||
}
|
||||
|
||||
return pp::foundation::Status::success();
|
||||
}
|
||||
|
||||
[[nodiscard]] inline pp::foundation::Status execute_brush_package_import(
|
||||
BrushPackageImportKind kind,
|
||||
std::string_view path,
|
||||
BrushPackageImportServices& services)
|
||||
{
|
||||
const auto status = validate_brush_package_import_path(path);
|
||||
if (!status.ok()) {
|
||||
return status;
|
||||
}
|
||||
|
||||
services.import_brush_package(kind, path);
|
||||
return pp::foundation::Status::success();
|
||||
}
|
||||
|
||||
} // namespace pp::app
|
||||
1008
src/app_core/brush_ui.h
Normal file
1008
src/app_core/brush_ui.h
Normal file
File diff suppressed because it is too large
Load Diff
225
src/app_core/canvas_hotkey.h
Normal file
225
src/app_core/canvas_hotkey.h
Normal file
@@ -0,0 +1,225 @@
|
||||
#pragma once
|
||||
|
||||
#include "app_core/canvas_tool_ui.h"
|
||||
#include "app_core/document_session.h"
|
||||
#include "app_core/history_ui.h"
|
||||
#include "foundation/result.h"
|
||||
|
||||
namespace pp::app {
|
||||
|
||||
enum class CanvasHotkeyEvent {
|
||||
key_down,
|
||||
key_up,
|
||||
touch_tap,
|
||||
};
|
||||
|
||||
enum class CanvasHotkeyKey {
|
||||
other,
|
||||
android_back,
|
||||
alt,
|
||||
e,
|
||||
s,
|
||||
tab,
|
||||
z,
|
||||
bracket_left,
|
||||
bracket_right,
|
||||
};
|
||||
|
||||
enum class CanvasHotkeyAction {
|
||||
none,
|
||||
select_tool,
|
||||
history,
|
||||
save_document,
|
||||
toggle_ui,
|
||||
adjust_brush_size,
|
||||
show_cursor,
|
||||
};
|
||||
|
||||
struct CanvasHotkeyState {
|
||||
bool ctrl_down = false;
|
||||
bool shift_down = false;
|
||||
bool mouse_focused = false;
|
||||
int undo_count = 0;
|
||||
int redo_count = 0;
|
||||
int touch_finger_count = 0;
|
||||
};
|
||||
|
||||
struct CanvasHotkeyPlan {
|
||||
CanvasHotkeyAction action = CanvasHotkeyAction::none;
|
||||
CanvasHotkeyEvent event = CanvasHotkeyEvent::key_up;
|
||||
CanvasHotkeyKey key = CanvasHotkeyKey::other;
|
||||
CanvasToolPlan tool;
|
||||
HistoryUiPlan history;
|
||||
DocumentSaveIntent save_intent = DocumentSaveIntent::save;
|
||||
float brush_size_delta = 0.0F;
|
||||
bool no_op = true;
|
||||
};
|
||||
|
||||
class CanvasHotkeyServices {
|
||||
public:
|
||||
virtual ~CanvasHotkeyServices() = default;
|
||||
|
||||
virtual pp::foundation::Status execute_tool(const CanvasToolPlan& plan) = 0;
|
||||
virtual pp::foundation::Status execute_history(const HistoryUiPlan& plan) = 0;
|
||||
virtual void save_document(DocumentSaveIntent intent) = 0;
|
||||
virtual void toggle_ui() = 0;
|
||||
virtual void adjust_brush_size(float delta) = 0;
|
||||
virtual void show_cursor() = 0;
|
||||
};
|
||||
|
||||
[[nodiscard]] inline pp::foundation::Status validate_canvas_hotkey_state(
|
||||
const CanvasHotkeyState& state) noexcept
|
||||
{
|
||||
if (state.undo_count < 0) {
|
||||
return pp::foundation::Status::out_of_range("undo action count must not be negative");
|
||||
}
|
||||
if (state.redo_count < 0) {
|
||||
return pp::foundation::Status::out_of_range("redo action count must not be negative");
|
||||
}
|
||||
if (state.touch_finger_count < 0) {
|
||||
return pp::foundation::Status::out_of_range("touch finger count must not be negative");
|
||||
}
|
||||
return pp::foundation::Status::success();
|
||||
}
|
||||
|
||||
[[nodiscard]] inline pp::foundation::Result<CanvasHotkeyPlan> plan_canvas_hotkey(
|
||||
CanvasHotkeyEvent event,
|
||||
CanvasHotkeyKey key,
|
||||
const CanvasHotkeyState& state)
|
||||
{
|
||||
const auto state_status = validate_canvas_hotkey_state(state);
|
||||
if (!state_status.ok()) {
|
||||
return pp::foundation::Result<CanvasHotkeyPlan>::failure(state_status);
|
||||
}
|
||||
|
||||
CanvasHotkeyPlan plan;
|
||||
plan.event = event;
|
||||
plan.key = key;
|
||||
|
||||
if (event == CanvasHotkeyEvent::touch_tap) {
|
||||
if (state.touch_finger_count == 2) {
|
||||
auto history = plan_history_undo(state.undo_count);
|
||||
if (!history) {
|
||||
return pp::foundation::Result<CanvasHotkeyPlan>::failure(history.status());
|
||||
}
|
||||
plan.action = CanvasHotkeyAction::history;
|
||||
plan.history = history.value();
|
||||
plan.no_op = plan.history.no_op;
|
||||
}
|
||||
return pp::foundation::Result<CanvasHotkeyPlan>::success(plan);
|
||||
}
|
||||
|
||||
if (event == CanvasHotkeyEvent::key_down) {
|
||||
switch (key) {
|
||||
case CanvasHotkeyKey::e:
|
||||
plan.action = CanvasHotkeyAction::select_tool;
|
||||
plan.tool = plan_canvas_tool_select(CanvasToolMode::erase);
|
||||
plan.no_op = false;
|
||||
break;
|
||||
case CanvasHotkeyKey::android_back: {
|
||||
auto history = plan_history_undo(state.undo_count);
|
||||
if (!history) {
|
||||
return pp::foundation::Result<CanvasHotkeyPlan>::failure(history.status());
|
||||
}
|
||||
plan.action = CanvasHotkeyAction::history;
|
||||
plan.history = history.value();
|
||||
plan.no_op = plan.history.no_op;
|
||||
break;
|
||||
}
|
||||
case CanvasHotkeyKey::alt:
|
||||
if (state.mouse_focused) {
|
||||
plan.action = CanvasHotkeyAction::show_cursor;
|
||||
plan.no_op = false;
|
||||
}
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
return pp::foundation::Result<CanvasHotkeyPlan>::success(plan);
|
||||
}
|
||||
|
||||
switch (key) {
|
||||
case CanvasHotkeyKey::e:
|
||||
plan.action = CanvasHotkeyAction::select_tool;
|
||||
plan.tool = plan_canvas_tool_select(CanvasToolMode::draw);
|
||||
plan.no_op = false;
|
||||
break;
|
||||
case CanvasHotkeyKey::tab:
|
||||
plan.action = CanvasHotkeyAction::toggle_ui;
|
||||
plan.no_op = false;
|
||||
break;
|
||||
case CanvasHotkeyKey::z:
|
||||
if (state.ctrl_down) {
|
||||
auto history = state.shift_down
|
||||
? plan_history_redo(state.redo_count)
|
||||
: plan_history_undo(state.undo_count);
|
||||
if (!history) {
|
||||
return pp::foundation::Result<CanvasHotkeyPlan>::failure(history.status());
|
||||
}
|
||||
plan.action = CanvasHotkeyAction::history;
|
||||
plan.history = history.value();
|
||||
plan.no_op = plan.history.no_op;
|
||||
}
|
||||
break;
|
||||
case CanvasHotkeyKey::s:
|
||||
if (state.ctrl_down) {
|
||||
plan.action = CanvasHotkeyAction::save_document;
|
||||
plan.save_intent = state.shift_down
|
||||
? DocumentSaveIntent::save_dirty_version
|
||||
: DocumentSaveIntent::save;
|
||||
plan.no_op = false;
|
||||
}
|
||||
break;
|
||||
case CanvasHotkeyKey::bracket_left:
|
||||
plan.action = CanvasHotkeyAction::adjust_brush_size;
|
||||
plan.brush_size_delta = -0.05F;
|
||||
plan.no_op = false;
|
||||
break;
|
||||
case CanvasHotkeyKey::bracket_right:
|
||||
plan.action = CanvasHotkeyAction::adjust_brush_size;
|
||||
plan.brush_size_delta = 0.05F;
|
||||
plan.no_op = false;
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
return pp::foundation::Result<CanvasHotkeyPlan>::success(plan);
|
||||
}
|
||||
|
||||
[[nodiscard]] inline pp::foundation::Status execute_canvas_hotkey_plan(
|
||||
const CanvasHotkeyPlan& plan,
|
||||
CanvasHotkeyServices& services)
|
||||
{
|
||||
if (plan.no_op || plan.action == CanvasHotkeyAction::none) {
|
||||
return pp::foundation::Status::success();
|
||||
}
|
||||
|
||||
switch (plan.action) {
|
||||
case CanvasHotkeyAction::select_tool:
|
||||
return services.execute_tool(plan.tool);
|
||||
case CanvasHotkeyAction::history:
|
||||
return services.execute_history(plan.history);
|
||||
case CanvasHotkeyAction::save_document:
|
||||
services.save_document(plan.save_intent);
|
||||
return pp::foundation::Status::success();
|
||||
case CanvasHotkeyAction::toggle_ui:
|
||||
services.toggle_ui();
|
||||
return pp::foundation::Status::success();
|
||||
case CanvasHotkeyAction::adjust_brush_size:
|
||||
if (plan.brush_size_delta == 0.0F) {
|
||||
return pp::foundation::Status::invalid_argument("brush-size hotkey plan must include a delta");
|
||||
}
|
||||
services.adjust_brush_size(plan.brush_size_delta);
|
||||
return pp::foundation::Status::success();
|
||||
case CanvasHotkeyAction::show_cursor:
|
||||
services.show_cursor();
|
||||
return pp::foundation::Status::success();
|
||||
case CanvasHotkeyAction::none:
|
||||
return pp::foundation::Status::success();
|
||||
}
|
||||
|
||||
return pp::foundation::Status::invalid_argument("unknown canvas hotkey action");
|
||||
}
|
||||
|
||||
} // namespace pp::app
|
||||
315
src/app_core/canvas_tool_ui.h
Normal file
315
src/app_core/canvas_tool_ui.h
Normal file
@@ -0,0 +1,315 @@
|
||||
#pragma once
|
||||
|
||||
#include "foundation/result.h"
|
||||
|
||||
#include <array>
|
||||
#include <cmath>
|
||||
#include <string_view>
|
||||
|
||||
namespace pp::app {
|
||||
|
||||
enum class CanvasToolOperation {
|
||||
select_mode,
|
||||
toggle_picking,
|
||||
toggle_touch_lock,
|
||||
};
|
||||
|
||||
enum class CanvasToolMode {
|
||||
draw,
|
||||
erase,
|
||||
line,
|
||||
camera,
|
||||
grid,
|
||||
copy,
|
||||
cut,
|
||||
fill,
|
||||
mask_free,
|
||||
mask_line,
|
||||
flood_fill,
|
||||
};
|
||||
|
||||
enum class CanvasToolTransformAction {
|
||||
none,
|
||||
copy,
|
||||
cut,
|
||||
};
|
||||
|
||||
enum class CanvasToolToolbarAction {
|
||||
select_mode,
|
||||
toggle_picking,
|
||||
toggle_touch_lock,
|
||||
};
|
||||
|
||||
enum class CanvasCursorVisibilityMode {
|
||||
never,
|
||||
small_brush,
|
||||
not_painting,
|
||||
always,
|
||||
};
|
||||
|
||||
struct CanvasToolPlan {
|
||||
CanvasToolOperation operation = CanvasToolOperation::select_mode;
|
||||
CanvasToolMode mode = CanvasToolMode::draw;
|
||||
CanvasToolTransformAction transform_action = CanvasToolTransformAction::none;
|
||||
bool selects_toolbar_button = false;
|
||||
bool updates_canvas_mode = false;
|
||||
bool toggles_picking = false;
|
||||
bool toggles_touch_lock = false;
|
||||
bool requires_draw_mode = false;
|
||||
bool no_op = false;
|
||||
};
|
||||
|
||||
struct CanvasToolButtonState {
|
||||
CanvasToolMode mode = CanvasToolMode::draw;
|
||||
bool pick_active = false;
|
||||
bool touch_lock_active = false;
|
||||
bool pen_active = false;
|
||||
bool erase_active = false;
|
||||
bool line_active = false;
|
||||
bool camera_active = false;
|
||||
bool grid_active = false;
|
||||
bool copy_active = false;
|
||||
bool cut_active = false;
|
||||
bool fill_active = false;
|
||||
bool mask_free_active = false;
|
||||
bool mask_line_active = false;
|
||||
bool flood_fill_active = false;
|
||||
};
|
||||
|
||||
struct CanvasToolToolbarBinding {
|
||||
std::string_view button_id;
|
||||
CanvasToolToolbarAction action = CanvasToolToolbarAction::select_mode;
|
||||
CanvasToolMode mode = CanvasToolMode::draw;
|
||||
bool custom_button = true;
|
||||
bool applies_default_on_init = false;
|
||||
};
|
||||
|
||||
struct CanvasToolToolbarPlan {
|
||||
std::array<CanvasToolToolbarBinding, 13> bindings {};
|
||||
CanvasToolMode default_mode = CanvasToolMode::draw;
|
||||
};
|
||||
|
||||
struct CanvasCursorVisibilityInput {
|
||||
CanvasToolMode mode = CanvasToolMode::draw;
|
||||
CanvasCursorVisibilityMode visibility_mode = CanvasCursorVisibilityMode::never;
|
||||
bool has_current_brush = true;
|
||||
float brush_tip_size = 0.0F;
|
||||
bool pen_is_drawing = false;
|
||||
bool alt_down = false;
|
||||
bool pen_is_resizing = false;
|
||||
bool pen_is_picking = false;
|
||||
};
|
||||
|
||||
struct CanvasCursorVisibilityPlan {
|
||||
bool visible = true;
|
||||
bool paint_mode = false;
|
||||
bool uses_brush_size = false;
|
||||
bool uses_pen_state = false;
|
||||
bool forced_visible_by_modifier_or_tool = false;
|
||||
};
|
||||
|
||||
class CanvasToolServices {
|
||||
public:
|
||||
virtual ~CanvasToolServices() = default;
|
||||
|
||||
virtual void select_toolbar_button(CanvasToolMode mode) = 0;
|
||||
virtual void set_transform_action(CanvasToolTransformAction action) = 0;
|
||||
virtual void set_canvas_mode(CanvasToolMode mode) = 0;
|
||||
virtual void toggle_picking() = 0;
|
||||
virtual void toggle_touch_lock() = 0;
|
||||
};
|
||||
|
||||
[[nodiscard]] inline constexpr CanvasToolTransformAction transform_action_for_mode(CanvasToolMode mode) noexcept
|
||||
{
|
||||
if (mode == CanvasToolMode::copy) {
|
||||
return CanvasToolTransformAction::copy;
|
||||
}
|
||||
if (mode == CanvasToolMode::cut) {
|
||||
return CanvasToolTransformAction::cut;
|
||||
}
|
||||
return CanvasToolTransformAction::none;
|
||||
}
|
||||
|
||||
[[nodiscard]] inline constexpr CanvasToolPlan plan_canvas_tool_select(CanvasToolMode mode) noexcept
|
||||
{
|
||||
CanvasToolPlan plan;
|
||||
plan.operation = CanvasToolOperation::select_mode;
|
||||
plan.mode = mode;
|
||||
plan.transform_action = transform_action_for_mode(mode);
|
||||
plan.selects_toolbar_button = true;
|
||||
plan.updates_canvas_mode = true;
|
||||
return plan;
|
||||
}
|
||||
|
||||
[[nodiscard]] inline constexpr CanvasToolPlan plan_canvas_tool_pick_toggle(bool current_mode_is_draw) noexcept
|
||||
{
|
||||
CanvasToolPlan plan;
|
||||
plan.operation = CanvasToolOperation::toggle_picking;
|
||||
plan.mode = CanvasToolMode::draw;
|
||||
plan.requires_draw_mode = true;
|
||||
plan.toggles_picking = current_mode_is_draw;
|
||||
plan.no_op = !current_mode_is_draw;
|
||||
return plan;
|
||||
}
|
||||
|
||||
[[nodiscard]] inline constexpr CanvasToolPlan plan_canvas_tool_touch_lock_toggle() noexcept
|
||||
{
|
||||
CanvasToolPlan plan;
|
||||
plan.operation = CanvasToolOperation::toggle_touch_lock;
|
||||
plan.toggles_touch_lock = true;
|
||||
return plan;
|
||||
}
|
||||
|
||||
[[nodiscard]] inline constexpr CanvasToolToolbarPlan plan_canvas_tool_toolbar() noexcept
|
||||
{
|
||||
return {
|
||||
std::array<CanvasToolToolbarBinding, 13> {
|
||||
CanvasToolToolbarBinding { "btn-pen", CanvasToolToolbarAction::select_mode, CanvasToolMode::draw, true, true },
|
||||
CanvasToolToolbarBinding { "btn-pick", CanvasToolToolbarAction::toggle_picking, CanvasToolMode::draw, true, false },
|
||||
CanvasToolToolbarBinding { "btn-touchlock", CanvasToolToolbarAction::toggle_touch_lock, CanvasToolMode::draw, true, false },
|
||||
CanvasToolToolbarBinding { "btn-erase", CanvasToolToolbarAction::select_mode, CanvasToolMode::erase, true, false },
|
||||
CanvasToolToolbarBinding { "btn-line", CanvasToolToolbarAction::select_mode, CanvasToolMode::line, true, false },
|
||||
CanvasToolToolbarBinding { "btn-cam", CanvasToolToolbarAction::select_mode, CanvasToolMode::camera, false, false },
|
||||
CanvasToolToolbarBinding { "btn-grid", CanvasToolToolbarAction::select_mode, CanvasToolMode::grid, false, false },
|
||||
CanvasToolToolbarBinding { "btn-copy", CanvasToolToolbarAction::select_mode, CanvasToolMode::copy, false, false },
|
||||
CanvasToolToolbarBinding { "btn-cut", CanvasToolToolbarAction::select_mode, CanvasToolMode::cut, false, false },
|
||||
CanvasToolToolbarBinding { "btn-fill", CanvasToolToolbarAction::select_mode, CanvasToolMode::fill, false, false },
|
||||
CanvasToolToolbarBinding { "btn-mask-free", CanvasToolToolbarAction::select_mode, CanvasToolMode::mask_free, true, false },
|
||||
CanvasToolToolbarBinding { "btn-mask-line", CanvasToolToolbarAction::select_mode, CanvasToolMode::mask_line, true, false },
|
||||
CanvasToolToolbarBinding { "btn-bucket", CanvasToolToolbarAction::select_mode, CanvasToolMode::flood_fill, true, false },
|
||||
},
|
||||
CanvasToolMode::draw,
|
||||
};
|
||||
}
|
||||
|
||||
[[nodiscard]] inline constexpr CanvasToolPlan plan_canvas_tool_toolbar_binding_action(
|
||||
const CanvasToolToolbarBinding& binding,
|
||||
bool current_mode_is_draw) noexcept
|
||||
{
|
||||
switch (binding.action) {
|
||||
case CanvasToolToolbarAction::select_mode:
|
||||
return plan_canvas_tool_select(binding.mode);
|
||||
case CanvasToolToolbarAction::toggle_picking:
|
||||
return plan_canvas_tool_pick_toggle(current_mode_is_draw);
|
||||
case CanvasToolToolbarAction::toggle_touch_lock:
|
||||
return plan_canvas_tool_touch_lock_toggle();
|
||||
}
|
||||
|
||||
return plan_canvas_tool_select(CanvasToolMode::draw);
|
||||
}
|
||||
|
||||
[[nodiscard]] inline constexpr CanvasToolButtonState plan_canvas_tool_button_state(
|
||||
CanvasToolMode mode,
|
||||
bool picking,
|
||||
bool touch_lock) noexcept
|
||||
{
|
||||
CanvasToolButtonState state;
|
||||
state.mode = mode;
|
||||
state.pick_active = mode == CanvasToolMode::draw && picking;
|
||||
state.touch_lock_active = touch_lock;
|
||||
state.pen_active = mode == CanvasToolMode::draw;
|
||||
state.erase_active = mode == CanvasToolMode::erase;
|
||||
state.line_active = mode == CanvasToolMode::line;
|
||||
state.camera_active = mode == CanvasToolMode::camera;
|
||||
state.grid_active = mode == CanvasToolMode::grid;
|
||||
state.copy_active = mode == CanvasToolMode::copy;
|
||||
state.cut_active = mode == CanvasToolMode::cut;
|
||||
state.fill_active = mode == CanvasToolMode::fill;
|
||||
state.mask_free_active = mode == CanvasToolMode::mask_free;
|
||||
state.mask_line_active = mode == CanvasToolMode::mask_line;
|
||||
state.flood_fill_active = mode == CanvasToolMode::flood_fill;
|
||||
return state;
|
||||
}
|
||||
|
||||
[[nodiscard]] inline constexpr bool canvas_tool_mode_is_paint(CanvasToolMode mode) noexcept
|
||||
{
|
||||
return mode == CanvasToolMode::draw || mode == CanvasToolMode::erase;
|
||||
}
|
||||
|
||||
[[nodiscard]] inline pp::foundation::Result<CanvasCursorVisibilityPlan> plan_canvas_cursor_visibility(
|
||||
const CanvasCursorVisibilityInput& input)
|
||||
{
|
||||
CanvasCursorVisibilityPlan plan;
|
||||
plan.paint_mode = canvas_tool_mode_is_paint(input.mode);
|
||||
if (!plan.paint_mode) {
|
||||
plan.visible = true;
|
||||
return pp::foundation::Result<CanvasCursorVisibilityPlan>::success(plan);
|
||||
}
|
||||
|
||||
switch (input.visibility_mode) {
|
||||
case CanvasCursorVisibilityMode::always:
|
||||
plan.visible = true;
|
||||
break;
|
||||
case CanvasCursorVisibilityMode::never:
|
||||
plan.visible = false;
|
||||
break;
|
||||
case CanvasCursorVisibilityMode::small_brush:
|
||||
if (!input.has_current_brush) {
|
||||
return pp::foundation::Result<CanvasCursorVisibilityPlan>::failure(
|
||||
pp::foundation::Status::invalid_argument("canvas cursor small-brush mode requires a current brush"));
|
||||
}
|
||||
if (!std::isfinite(input.brush_tip_size) || input.brush_tip_size < 0.0F) {
|
||||
return pp::foundation::Result<CanvasCursorVisibilityPlan>::failure(
|
||||
pp::foundation::Status::invalid_argument("canvas cursor brush size must be finite and non-negative"));
|
||||
}
|
||||
plan.visible = input.brush_tip_size < 10.0F;
|
||||
plan.uses_brush_size = true;
|
||||
break;
|
||||
case CanvasCursorVisibilityMode::not_painting:
|
||||
plan.visible = !input.pen_is_drawing;
|
||||
plan.uses_pen_state = true;
|
||||
break;
|
||||
}
|
||||
|
||||
if (input.alt_down || input.pen_is_resizing || input.pen_is_picking) {
|
||||
plan.visible = true;
|
||||
plan.forced_visible_by_modifier_or_tool = true;
|
||||
}
|
||||
|
||||
return pp::foundation::Result<CanvasCursorVisibilityPlan>::success(plan);
|
||||
}
|
||||
|
||||
[[nodiscard]] inline pp::foundation::Status execute_canvas_tool_plan(
|
||||
const CanvasToolPlan& plan,
|
||||
CanvasToolServices& services)
|
||||
{
|
||||
switch (plan.operation) {
|
||||
case CanvasToolOperation::select_mode:
|
||||
if (!plan.selects_toolbar_button || !plan.updates_canvas_mode) {
|
||||
return pp::foundation::Status::invalid_argument("canvas tool select plan must select toolbar and update mode");
|
||||
}
|
||||
if (plan.transform_action != transform_action_for_mode(plan.mode)) {
|
||||
return pp::foundation::Status::invalid_argument("canvas tool select plan has mismatched transform action");
|
||||
}
|
||||
services.select_toolbar_button(plan.mode);
|
||||
if (plan.transform_action != CanvasToolTransformAction::none) {
|
||||
services.set_transform_action(plan.transform_action);
|
||||
}
|
||||
services.set_canvas_mode(plan.mode);
|
||||
return pp::foundation::Status::success();
|
||||
|
||||
case CanvasToolOperation::toggle_picking:
|
||||
if (!plan.requires_draw_mode) {
|
||||
return pp::foundation::Status::invalid_argument("canvas pick plan must require draw mode");
|
||||
}
|
||||
if (plan.no_op) {
|
||||
return pp::foundation::Status::success();
|
||||
}
|
||||
if (!plan.toggles_picking) {
|
||||
return pp::foundation::Status::invalid_argument("canvas pick plan must toggle picking or be a no-op");
|
||||
}
|
||||
services.toggle_picking();
|
||||
return pp::foundation::Status::success();
|
||||
|
||||
case CanvasToolOperation::toggle_touch_lock:
|
||||
if (!plan.toggles_touch_lock || plan.no_op) {
|
||||
return pp::foundation::Status::invalid_argument("canvas touch-lock plan must toggle touch lock");
|
||||
}
|
||||
services.toggle_touch_lock();
|
||||
return pp::foundation::Status::success();
|
||||
}
|
||||
|
||||
return pp::foundation::Status::invalid_argument("unknown canvas tool operation");
|
||||
}
|
||||
|
||||
} // namespace pp::app
|
||||
115
src/app_core/canvas_view.h
Normal file
115
src/app_core/canvas_view.h
Normal file
@@ -0,0 +1,115 @@
|
||||
#pragma once
|
||||
|
||||
#include "foundation/result.h"
|
||||
|
||||
#include <array>
|
||||
#include <cmath>
|
||||
|
||||
namespace pp::app {
|
||||
|
||||
enum class CanvasViewCursorMode {
|
||||
never = 0,
|
||||
small_brush = 1,
|
||||
not_painting = 2,
|
||||
always = 3,
|
||||
};
|
||||
|
||||
struct CanvasCameraState {
|
||||
std::array<float, 16> rotation {};
|
||||
std::array<float, 3> position {};
|
||||
float field_of_view_degrees = 85.0F;
|
||||
std::array<float, 2> pan {};
|
||||
};
|
||||
|
||||
struct CanvasViewDensityPlan {
|
||||
float density = 1.0F;
|
||||
bool recreates_buffers = true;
|
||||
};
|
||||
|
||||
struct CanvasViewCursorModePlan {
|
||||
CanvasViewCursorMode mode = CanvasViewCursorMode::never;
|
||||
};
|
||||
|
||||
class CanvasViewServices {
|
||||
public:
|
||||
virtual ~CanvasViewServices() = default;
|
||||
|
||||
virtual void reset_camera(const CanvasCameraState& state) = 0;
|
||||
virtual void set_density(const CanvasViewDensityPlan& plan) = 0;
|
||||
virtual void set_cursor_mode(const CanvasViewCursorModePlan& plan) = 0;
|
||||
};
|
||||
|
||||
[[nodiscard]] constexpr CanvasCameraState plan_canvas_camera_reset() noexcept
|
||||
{
|
||||
CanvasCameraState state;
|
||||
state.rotation = {
|
||||
1.0F, 0.0F, 0.0F, 0.0F,
|
||||
0.0F, 1.0F, 0.0F, 0.0F,
|
||||
0.0F, 0.0F, 1.0F, 0.0F,
|
||||
0.0F, 0.0F, 0.0F, 1.0F,
|
||||
};
|
||||
state.position = { 0.0F, 0.0F, 0.0F };
|
||||
state.field_of_view_degrees = 85.0F;
|
||||
state.pan = { 0.0F, 0.0F };
|
||||
return state;
|
||||
}
|
||||
|
||||
[[nodiscard]] inline pp::foundation::Result<CanvasViewDensityPlan> plan_canvas_view_density(float density)
|
||||
{
|
||||
if (!std::isfinite(density) || density <= 0.0F) {
|
||||
return pp::foundation::Result<CanvasViewDensityPlan>::failure(
|
||||
pp::foundation::Status::invalid_argument("canvas view density must be finite and positive"));
|
||||
}
|
||||
|
||||
return pp::foundation::Result<CanvasViewDensityPlan>::success(CanvasViewDensityPlan {
|
||||
.density = density,
|
||||
.recreates_buffers = true,
|
||||
});
|
||||
}
|
||||
|
||||
[[nodiscard]] inline pp::foundation::Result<CanvasViewCursorModePlan> plan_canvas_view_cursor_mode(int mode)
|
||||
{
|
||||
if (mode < static_cast<int>(CanvasViewCursorMode::never)
|
||||
|| mode > static_cast<int>(CanvasViewCursorMode::always)) {
|
||||
return pp::foundation::Result<CanvasViewCursorModePlan>::failure(
|
||||
pp::foundation::Status::out_of_range("canvas cursor mode is out of range"));
|
||||
}
|
||||
|
||||
return pp::foundation::Result<CanvasViewCursorModePlan>::success(CanvasViewCursorModePlan {
|
||||
.mode = static_cast<CanvasViewCursorMode>(mode),
|
||||
});
|
||||
}
|
||||
|
||||
[[nodiscard]] inline pp::foundation::Status execute_canvas_camera_reset(CanvasViewServices& services)
|
||||
{
|
||||
services.reset_camera(plan_canvas_camera_reset());
|
||||
return pp::foundation::Status::success();
|
||||
}
|
||||
|
||||
[[nodiscard]] inline pp::foundation::Status execute_canvas_view_density(
|
||||
float density,
|
||||
CanvasViewServices& services)
|
||||
{
|
||||
const auto plan = plan_canvas_view_density(density);
|
||||
if (!plan) {
|
||||
return plan.status();
|
||||
}
|
||||
|
||||
services.set_density(plan.value());
|
||||
return pp::foundation::Status::success();
|
||||
}
|
||||
|
||||
[[nodiscard]] inline pp::foundation::Status execute_canvas_view_cursor_mode(
|
||||
int mode,
|
||||
CanvasViewServices& services)
|
||||
{
|
||||
const auto plan = plan_canvas_view_cursor_mode(mode);
|
||||
if (!plan) {
|
||||
return plan.status();
|
||||
}
|
||||
|
||||
services.set_cursor_mode(plan.value());
|
||||
return pp::foundation::Status::success();
|
||||
}
|
||||
|
||||
} // namespace pp::app
|
||||
97
src/app_core/command_convert.h
Normal file
97
src/app_core/command_convert.h
Normal file
@@ -0,0 +1,97 @@
|
||||
#pragma once
|
||||
|
||||
#include "foundation/result.h"
|
||||
|
||||
#include <string>
|
||||
#include <string_view>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
namespace pp::app {
|
||||
|
||||
enum class CommandConvertStep {
|
||||
apply_renderer_state,
|
||||
create_canvas,
|
||||
open_project,
|
||||
export_equirectangular,
|
||||
};
|
||||
|
||||
struct CommandConvertPlan {
|
||||
std::string project_path;
|
||||
std::string output_path;
|
||||
int canvas_resolution = 0;
|
||||
std::vector<CommandConvertStep> steps;
|
||||
};
|
||||
|
||||
class CommandConvertServices {
|
||||
public:
|
||||
virtual ~CommandConvertServices() = default;
|
||||
|
||||
virtual void apply_renderer_state() = 0;
|
||||
virtual void create_canvas(int canvas_resolution) = 0;
|
||||
virtual void open_project(std::string_view project_path) = 0;
|
||||
virtual void export_equirectangular(std::string_view output_path) = 0;
|
||||
};
|
||||
|
||||
[[nodiscard]] inline pp::foundation::Result<CommandConvertPlan> plan_command_convert(
|
||||
std::string_view project_path,
|
||||
std::string_view output_path,
|
||||
int canvas_resolution)
|
||||
{
|
||||
if (project_path.empty()) {
|
||||
return pp::foundation::Result<CommandConvertPlan>::failure(
|
||||
pp::foundation::Status::invalid_argument("convert project path must not be empty"));
|
||||
}
|
||||
|
||||
if (output_path.empty()) {
|
||||
return pp::foundation::Result<CommandConvertPlan>::failure(
|
||||
pp::foundation::Status::invalid_argument("convert output path must not be empty"));
|
||||
}
|
||||
|
||||
if (canvas_resolution < 1) {
|
||||
return pp::foundation::Result<CommandConvertPlan>::failure(
|
||||
pp::foundation::Status::invalid_argument("convert canvas resolution must be positive"));
|
||||
}
|
||||
|
||||
CommandConvertPlan plan;
|
||||
plan.project_path = std::string(project_path);
|
||||
plan.output_path = std::string(output_path);
|
||||
plan.canvas_resolution = canvas_resolution;
|
||||
plan.steps = {
|
||||
CommandConvertStep::apply_renderer_state,
|
||||
CommandConvertStep::create_canvas,
|
||||
CommandConvertStep::open_project,
|
||||
CommandConvertStep::export_equirectangular,
|
||||
};
|
||||
return pp::foundation::Result<CommandConvertPlan>::success(std::move(plan));
|
||||
}
|
||||
|
||||
[[nodiscard]] inline pp::foundation::Status execute_command_convert_plan(
|
||||
const CommandConvertPlan& plan,
|
||||
CommandConvertServices& services)
|
||||
{
|
||||
if (plan.project_path.empty() || plan.output_path.empty() || plan.canvas_resolution < 1) {
|
||||
return pp::foundation::Status::invalid_argument("convert plan is malformed");
|
||||
}
|
||||
|
||||
for (const auto step : plan.steps) {
|
||||
switch (step) {
|
||||
case CommandConvertStep::apply_renderer_state:
|
||||
services.apply_renderer_state();
|
||||
break;
|
||||
case CommandConvertStep::create_canvas:
|
||||
services.create_canvas(plan.canvas_resolution);
|
||||
break;
|
||||
case CommandConvertStep::open_project:
|
||||
services.open_project(plan.project_path);
|
||||
break;
|
||||
case CommandConvertStep::export_equirectangular:
|
||||
services.export_equirectangular(plan.output_path);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return pp::foundation::Status::success();
|
||||
}
|
||||
|
||||
} // namespace pp::app
|
||||
836
src/app_core/document_animation.h
Normal file
836
src/app_core/document_animation.h
Normal file
@@ -0,0 +1,836 @@
|
||||
#pragma once
|
||||
|
||||
#include "foundation/result.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <cmath>
|
||||
#include <cstdint>
|
||||
#include <limits>
|
||||
#include <string>
|
||||
#include <string_view>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
namespace pp::app {
|
||||
|
||||
inline constexpr int document_animation_default_frame_duration = 1;
|
||||
|
||||
enum class DocumentAnimationOperation {
|
||||
add_frame,
|
||||
duplicate_frame,
|
||||
remove_frame,
|
||||
adjust_duration,
|
||||
move_frame,
|
||||
select_frame,
|
||||
goto_frame,
|
||||
goto_next,
|
||||
goto_previous,
|
||||
playback_step,
|
||||
toggle_playback,
|
||||
set_onion_size,
|
||||
};
|
||||
|
||||
enum class DocumentAnimationPanelAction {
|
||||
goto_frame,
|
||||
next_frame,
|
||||
previous_frame,
|
||||
playback_step,
|
||||
toggle_playback,
|
||||
};
|
||||
|
||||
struct DocumentAnimationPanelState {
|
||||
int total_duration = 1;
|
||||
int current_frame = 0;
|
||||
bool playback_active = false;
|
||||
};
|
||||
|
||||
struct DocumentAnimationOperationPlan {
|
||||
DocumentAnimationOperation operation = DocumentAnimationOperation::goto_frame;
|
||||
int frame_count = 1;
|
||||
int current_frame = 0;
|
||||
int selected_frame = 0;
|
||||
int target_frame = 0;
|
||||
int frame_duration = document_animation_default_frame_duration;
|
||||
int duration_delta = 0;
|
||||
int move_offset = 0;
|
||||
int onion_size = 1;
|
||||
int layer_index = 0;
|
||||
std::uint32_t layer_id = 0;
|
||||
int playback_idle_ms = 100;
|
||||
bool requires_selected_frame = false;
|
||||
bool mutates_document = false;
|
||||
bool reloads_animation_layers = false;
|
||||
bool updates_canvas_animation = false;
|
||||
bool marks_unsaved = false;
|
||||
bool playback_was_active = false;
|
||||
bool playback_active = false;
|
||||
bool resets_playback_timer = false;
|
||||
};
|
||||
|
||||
struct DocumentAnimationOnionFrameRange {
|
||||
int frame_count = 1;
|
||||
int current_frame = 0;
|
||||
int onion_size = 0;
|
||||
int first_frame = 0;
|
||||
int last_frame = 0;
|
||||
};
|
||||
|
||||
inline constexpr float document_animation_timeline_frame_width = 35.0F;
|
||||
|
||||
struct DocumentAnimationTimelineScrubPlan {
|
||||
int total_duration = 1;
|
||||
float cursor_x = 0.0F;
|
||||
float frame_width = document_animation_timeline_frame_width;
|
||||
int target_frame = 0;
|
||||
};
|
||||
|
||||
struct DocumentAnimationLayerInput {
|
||||
int layer_index = 0;
|
||||
std::uint32_t layer_id = 0;
|
||||
std::string name;
|
||||
bool visible = true;
|
||||
std::vector<int> frame_durations;
|
||||
};
|
||||
|
||||
struct DocumentAnimationFrameView {
|
||||
int frame_index = 0;
|
||||
int duration = document_animation_default_frame_duration;
|
||||
bool selected = false;
|
||||
};
|
||||
|
||||
struct DocumentAnimationLayerView {
|
||||
int layer_index = 0;
|
||||
std::uint32_t layer_id = 0;
|
||||
std::string name;
|
||||
bool visible = true;
|
||||
bool current = false;
|
||||
std::vector<DocumentAnimationFrameView> frames;
|
||||
};
|
||||
|
||||
struct DocumentAnimationPanelView {
|
||||
int total_duration = 1;
|
||||
int current_frame = 0;
|
||||
int onion_size = 0;
|
||||
std::uint32_t selected_layer_id = 0;
|
||||
int selected_frame = -1;
|
||||
bool has_selected_frame = false;
|
||||
std::vector<DocumentAnimationLayerView> layers;
|
||||
};
|
||||
|
||||
class DocumentAnimationServices {
|
||||
public:
|
||||
virtual ~DocumentAnimationServices() = default;
|
||||
|
||||
virtual void add_frame() = 0;
|
||||
virtual void duplicate_frame(int selected_frame) = 0;
|
||||
virtual void remove_frame(int selected_frame, int target_frame) = 0;
|
||||
virtual void set_frame_duration(int selected_frame, int duration) = 0;
|
||||
virtual int move_frame(int selected_frame, int move_offset) = 0;
|
||||
virtual void select_frame(std::uint32_t layer_id, int layer_index, int selected_frame) = 0;
|
||||
virtual void select_layer(int layer_index) = 0;
|
||||
virtual void goto_frame(int target_frame) = 0;
|
||||
virtual void set_timeline_frame(int target_frame) = 0;
|
||||
virtual void set_onion_size(int onion_size) = 0;
|
||||
virtual void capture_playback_restore_mode() = 0;
|
||||
virtual void enter_playback_camera_mode() = 0;
|
||||
virtual void restore_playback_canvas_mode() = 0;
|
||||
virtual void set_playback_active(bool active) = 0;
|
||||
virtual void reset_playback_timer() = 0;
|
||||
virtual void set_playback_idle_ms(int idle_ms) = 0;
|
||||
virtual void update_canvas_animation() = 0;
|
||||
virtual void update_frame_status() = 0;
|
||||
virtual void reload_animation_layers() = 0;
|
||||
virtual void mark_unsaved() = 0;
|
||||
};
|
||||
|
||||
[[nodiscard]] inline pp::foundation::Status validate_animation_frame_count(int frame_count) noexcept
|
||||
{
|
||||
if (frame_count <= 0) {
|
||||
return pp::foundation::Status::invalid_argument("animation layer must contain at least one frame");
|
||||
}
|
||||
|
||||
return pp::foundation::Status::success();
|
||||
}
|
||||
|
||||
[[nodiscard]] inline pp::foundation::Status validate_animation_frame_index(
|
||||
int frame_count,
|
||||
int index) noexcept
|
||||
{
|
||||
const auto count_status = validate_animation_frame_count(frame_count);
|
||||
if (!count_status.ok()) {
|
||||
return count_status;
|
||||
}
|
||||
|
||||
if (index < 0 || index >= frame_count) {
|
||||
return pp::foundation::Status::out_of_range("animation frame index is outside the layer");
|
||||
}
|
||||
|
||||
return pp::foundation::Status::success();
|
||||
}
|
||||
|
||||
[[nodiscard]] inline pp::foundation::Status validate_animation_frame_duration(int duration) noexcept
|
||||
{
|
||||
if (duration < 1) {
|
||||
return pp::foundation::Status::invalid_argument("animation frame duration must be at least 1");
|
||||
}
|
||||
|
||||
return pp::foundation::Status::success();
|
||||
}
|
||||
|
||||
[[nodiscard]] inline pp::foundation::Result<DocumentAnimationPanelView> plan_animation_panel_view(
|
||||
const std::vector<DocumentAnimationLayerInput>& layers,
|
||||
int total_duration,
|
||||
int current_layer_index,
|
||||
int current_frame,
|
||||
std::uint32_t selected_layer_id,
|
||||
int selected_frame,
|
||||
int onion_size)
|
||||
{
|
||||
if (layers.empty()) {
|
||||
return pp::foundation::Result<DocumentAnimationPanelView>::failure(
|
||||
pp::foundation::Status::invalid_argument("animation panel requires at least one layer"));
|
||||
}
|
||||
|
||||
const auto timeline_status = validate_animation_frame_index(total_duration, current_frame);
|
||||
if (!timeline_status.ok()) {
|
||||
return pp::foundation::Result<DocumentAnimationPanelView>::failure(timeline_status);
|
||||
}
|
||||
|
||||
if (current_layer_index < 0 || current_layer_index >= static_cast<int>(layers.size())) {
|
||||
return pp::foundation::Result<DocumentAnimationPanelView>::failure(
|
||||
pp::foundation::Status::out_of_range("current animation layer index is outside the document"));
|
||||
}
|
||||
|
||||
if (onion_size < 0) {
|
||||
return pp::foundation::Result<DocumentAnimationPanelView>::failure(
|
||||
pp::foundation::Status::invalid_argument("animation onion size must not be negative"));
|
||||
}
|
||||
|
||||
DocumentAnimationPanelView view;
|
||||
view.total_duration = total_duration;
|
||||
view.current_frame = current_frame;
|
||||
view.onion_size = onion_size;
|
||||
view.selected_layer_id = selected_layer_id;
|
||||
view.selected_frame = selected_frame;
|
||||
view.layers.reserve(layers.size());
|
||||
|
||||
for (std::size_t i = 0; i < layers.size(); ++i) {
|
||||
const auto& input = layers[i];
|
||||
if (input.layer_index < 0) {
|
||||
return pp::foundation::Result<DocumentAnimationPanelView>::failure(
|
||||
pp::foundation::Status::out_of_range("animation layer index must not be negative"));
|
||||
}
|
||||
if (input.frame_durations.empty()) {
|
||||
return pp::foundation::Result<DocumentAnimationPanelView>::failure(
|
||||
pp::foundation::Status::invalid_argument("animation layer must contain at least one frame"));
|
||||
}
|
||||
|
||||
DocumentAnimationLayerView layer;
|
||||
layer.layer_index = input.layer_index;
|
||||
layer.layer_id = input.layer_id;
|
||||
layer.name = input.name;
|
||||
layer.visible = input.visible;
|
||||
layer.current = input.layer_index == current_layer_index;
|
||||
layer.frames.reserve(input.frame_durations.size());
|
||||
|
||||
for (std::size_t frame_index = 0; frame_index < input.frame_durations.size(); ++frame_index) {
|
||||
const int duration = input.frame_durations[frame_index];
|
||||
const auto duration_status = validate_animation_frame_duration(duration);
|
||||
if (!duration_status.ok()) {
|
||||
return pp::foundation::Result<DocumentAnimationPanelView>::failure(duration_status);
|
||||
}
|
||||
|
||||
const bool selected = selected_frame >= 0
|
||||
&& input.layer_id == selected_layer_id
|
||||
&& static_cast<int>(frame_index) == selected_frame;
|
||||
view.has_selected_frame = view.has_selected_frame || selected;
|
||||
layer.frames.push_back(DocumentAnimationFrameView {
|
||||
.frame_index = static_cast<int>(frame_index),
|
||||
.duration = duration,
|
||||
.selected = selected,
|
||||
});
|
||||
}
|
||||
|
||||
view.layers.push_back(std::move(layer));
|
||||
}
|
||||
|
||||
return pp::foundation::Result<DocumentAnimationPanelView>::success(std::move(view));
|
||||
}
|
||||
|
||||
[[nodiscard]] inline pp::foundation::Result<DocumentAnimationOnionFrameRange> plan_animation_onion_frame_range(
|
||||
int frame_count,
|
||||
int current_frame,
|
||||
int onion_size)
|
||||
{
|
||||
const auto index_status = validate_animation_frame_index(frame_count, current_frame);
|
||||
if (!index_status.ok()) {
|
||||
return pp::foundation::Result<DocumentAnimationOnionFrameRange>::failure(index_status);
|
||||
}
|
||||
|
||||
if (onion_size < 0) {
|
||||
return pp::foundation::Result<DocumentAnimationOnionFrameRange>::failure(
|
||||
pp::foundation::Status::invalid_argument("animation onion size must not be negative"));
|
||||
}
|
||||
|
||||
const auto first = std::max<std::int64_t>(
|
||||
static_cast<std::int64_t>(current_frame) - static_cast<std::int64_t>(onion_size),
|
||||
0);
|
||||
const auto last = std::min<std::int64_t>(
|
||||
static_cast<std::int64_t>(current_frame) + static_cast<std::int64_t>(onion_size),
|
||||
static_cast<std::int64_t>(frame_count) - 1);
|
||||
|
||||
return pp::foundation::Result<DocumentAnimationOnionFrameRange>::success(
|
||||
DocumentAnimationOnionFrameRange {
|
||||
.frame_count = frame_count,
|
||||
.current_frame = current_frame,
|
||||
.onion_size = onion_size,
|
||||
.first_frame = static_cast<int>(first),
|
||||
.last_frame = static_cast<int>(last),
|
||||
});
|
||||
}
|
||||
|
||||
[[nodiscard]] inline pp::foundation::Result<DocumentAnimationTimelineScrubPlan> plan_animation_timeline_scrub(
|
||||
int total_duration,
|
||||
float cursor_x,
|
||||
float frame_width = document_animation_timeline_frame_width)
|
||||
{
|
||||
if (total_duration <= 0) {
|
||||
return pp::foundation::Result<DocumentAnimationTimelineScrubPlan>::failure(
|
||||
pp::foundation::Status::invalid_argument("animation timeline duration must be greater than zero"));
|
||||
}
|
||||
|
||||
if (!std::isfinite(cursor_x)) {
|
||||
return pp::foundation::Result<DocumentAnimationTimelineScrubPlan>::failure(
|
||||
pp::foundation::Status::invalid_argument("animation timeline cursor position must be finite"));
|
||||
}
|
||||
|
||||
if (!std::isfinite(frame_width) || frame_width <= 0.0F) {
|
||||
return pp::foundation::Result<DocumentAnimationTimelineScrubPlan>::failure(
|
||||
pp::foundation::Status::invalid_argument("animation timeline frame width must be positive and finite"));
|
||||
}
|
||||
|
||||
const auto raw_frame = static_cast<std::int64_t>(std::floor(cursor_x / frame_width));
|
||||
const auto target_frame = std::clamp<std::int64_t>(raw_frame, 0, total_duration - 1);
|
||||
return pp::foundation::Result<DocumentAnimationTimelineScrubPlan>::success(
|
||||
DocumentAnimationTimelineScrubPlan {
|
||||
.total_duration = total_duration,
|
||||
.cursor_x = cursor_x,
|
||||
.frame_width = frame_width,
|
||||
.target_frame = static_cast<int>(target_frame),
|
||||
});
|
||||
}
|
||||
|
||||
[[nodiscard]] inline float animation_onion_frame_alpha(
|
||||
const DocumentAnimationOnionFrameRange& range,
|
||||
int frame) noexcept
|
||||
{
|
||||
if (frame < range.first_frame || frame > range.last_frame) {
|
||||
return 0.0f;
|
||||
}
|
||||
|
||||
const int distance = frame >= range.current_frame
|
||||
? frame - range.current_frame
|
||||
: range.current_frame - frame;
|
||||
if (distance > range.onion_size) {
|
||||
return 0.0f;
|
||||
}
|
||||
|
||||
return 1.0f - static_cast<float>(distance) / static_cast<float>(range.onion_size + 1);
|
||||
}
|
||||
|
||||
[[nodiscard]] inline pp::foundation::Result<DocumentAnimationOperationPlan> plan_animation_add_frame(
|
||||
int frame_count,
|
||||
int current_frame)
|
||||
{
|
||||
const auto count_status = validate_animation_frame_count(frame_count);
|
||||
if (!count_status.ok()) {
|
||||
return pp::foundation::Result<DocumentAnimationOperationPlan>::failure(count_status);
|
||||
}
|
||||
|
||||
if (current_frame < 0) {
|
||||
return pp::foundation::Result<DocumentAnimationOperationPlan>::failure(
|
||||
pp::foundation::Status::out_of_range("current animation frame must not be negative"));
|
||||
}
|
||||
|
||||
DocumentAnimationOperationPlan plan;
|
||||
plan.operation = DocumentAnimationOperation::add_frame;
|
||||
plan.frame_count = frame_count;
|
||||
plan.current_frame = current_frame;
|
||||
plan.selected_frame = frame_count;
|
||||
plan.target_frame = current_frame;
|
||||
plan.mutates_document = true;
|
||||
plan.reloads_animation_layers = true;
|
||||
plan.updates_canvas_animation = true;
|
||||
plan.marks_unsaved = true;
|
||||
return pp::foundation::Result<DocumentAnimationOperationPlan>::success(plan);
|
||||
}
|
||||
|
||||
[[nodiscard]] inline pp::foundation::Result<DocumentAnimationOperationPlan> plan_animation_duplicate_frame(
|
||||
int frame_count,
|
||||
int selected_frame)
|
||||
{
|
||||
const auto index_status = validate_animation_frame_index(frame_count, selected_frame);
|
||||
if (!index_status.ok()) {
|
||||
return pp::foundation::Result<DocumentAnimationOperationPlan>::failure(index_status);
|
||||
}
|
||||
|
||||
DocumentAnimationOperationPlan plan;
|
||||
plan.operation = DocumentAnimationOperation::duplicate_frame;
|
||||
plan.frame_count = frame_count;
|
||||
plan.selected_frame = selected_frame;
|
||||
plan.target_frame = selected_frame + 1;
|
||||
plan.requires_selected_frame = true;
|
||||
plan.mutates_document = true;
|
||||
plan.reloads_animation_layers = true;
|
||||
plan.updates_canvas_animation = true;
|
||||
plan.marks_unsaved = true;
|
||||
return pp::foundation::Result<DocumentAnimationOperationPlan>::success(plan);
|
||||
}
|
||||
|
||||
[[nodiscard]] inline pp::foundation::Result<DocumentAnimationOperationPlan> plan_animation_remove_frame(
|
||||
int frame_count,
|
||||
int selected_frame)
|
||||
{
|
||||
const auto index_status = validate_animation_frame_index(frame_count, selected_frame);
|
||||
if (!index_status.ok()) {
|
||||
return pp::foundation::Result<DocumentAnimationOperationPlan>::failure(index_status);
|
||||
}
|
||||
|
||||
if (frame_count <= 1) {
|
||||
return pp::foundation::Result<DocumentAnimationOperationPlan>::failure(
|
||||
pp::foundation::Status::invalid_argument("animation layer must keep at least one frame"));
|
||||
}
|
||||
|
||||
DocumentAnimationOperationPlan plan;
|
||||
plan.operation = DocumentAnimationOperation::remove_frame;
|
||||
plan.frame_count = frame_count;
|
||||
plan.selected_frame = selected_frame;
|
||||
plan.target_frame = std::min(selected_frame, frame_count - 2);
|
||||
plan.requires_selected_frame = true;
|
||||
plan.mutates_document = true;
|
||||
plan.reloads_animation_layers = true;
|
||||
plan.updates_canvas_animation = true;
|
||||
plan.marks_unsaved = true;
|
||||
return pp::foundation::Result<DocumentAnimationOperationPlan>::success(plan);
|
||||
}
|
||||
|
||||
[[nodiscard]] inline pp::foundation::Result<DocumentAnimationOperationPlan> plan_animation_adjust_duration(
|
||||
int frame_count,
|
||||
int selected_frame,
|
||||
int current_duration,
|
||||
int delta)
|
||||
{
|
||||
const auto index_status = validate_animation_frame_index(frame_count, selected_frame);
|
||||
if (!index_status.ok()) {
|
||||
return pp::foundation::Result<DocumentAnimationOperationPlan>::failure(index_status);
|
||||
}
|
||||
|
||||
const auto duration_status = validate_animation_frame_duration(current_duration);
|
||||
if (!duration_status.ok()) {
|
||||
return pp::foundation::Result<DocumentAnimationOperationPlan>::failure(duration_status);
|
||||
}
|
||||
|
||||
if (delta == 0) {
|
||||
return pp::foundation::Result<DocumentAnimationOperationPlan>::failure(
|
||||
pp::foundation::Status::invalid_argument("animation frame duration delta must not be zero"));
|
||||
}
|
||||
|
||||
if (delta > 0 && current_duration > std::numeric_limits<int>::max() - delta) {
|
||||
return pp::foundation::Result<DocumentAnimationOperationPlan>::failure(
|
||||
pp::foundation::Status::out_of_range("animation frame duration would overflow"));
|
||||
}
|
||||
|
||||
DocumentAnimationOperationPlan plan;
|
||||
plan.operation = DocumentAnimationOperation::adjust_duration;
|
||||
plan.frame_count = frame_count;
|
||||
plan.selected_frame = selected_frame;
|
||||
plan.target_frame = selected_frame;
|
||||
plan.frame_duration = std::max(current_duration + delta, 1);
|
||||
plan.duration_delta = delta;
|
||||
plan.requires_selected_frame = true;
|
||||
plan.mutates_document = plan.frame_duration != current_duration;
|
||||
plan.reloads_animation_layers = plan.mutates_document;
|
||||
plan.updates_canvas_animation = plan.mutates_document;
|
||||
plan.marks_unsaved = plan.mutates_document;
|
||||
return pp::foundation::Result<DocumentAnimationOperationPlan>::success(plan);
|
||||
}
|
||||
|
||||
[[nodiscard]] inline pp::foundation::Result<DocumentAnimationOperationPlan> plan_animation_move_frame(
|
||||
int frame_count,
|
||||
int selected_frame,
|
||||
int offset)
|
||||
{
|
||||
const auto index_status = validate_animation_frame_index(frame_count, selected_frame);
|
||||
if (!index_status.ok()) {
|
||||
return pp::foundation::Result<DocumentAnimationOperationPlan>::failure(index_status);
|
||||
}
|
||||
|
||||
if (offset == 0) {
|
||||
return pp::foundation::Result<DocumentAnimationOperationPlan>::failure(
|
||||
pp::foundation::Status::invalid_argument("animation frame move offset must not be zero"));
|
||||
}
|
||||
|
||||
DocumentAnimationOperationPlan plan;
|
||||
plan.operation = DocumentAnimationOperation::move_frame;
|
||||
plan.frame_count = frame_count;
|
||||
plan.selected_frame = selected_frame;
|
||||
const auto unclamped_target = static_cast<std::int64_t>(selected_frame) + static_cast<std::int64_t>(offset);
|
||||
plan.target_frame = static_cast<int>(std::clamp<std::int64_t>(unclamped_target, 0, frame_count - 1));
|
||||
plan.move_offset = offset;
|
||||
plan.requires_selected_frame = true;
|
||||
plan.mutates_document = plan.target_frame != selected_frame;
|
||||
plan.reloads_animation_layers = true;
|
||||
plan.updates_canvas_animation = true;
|
||||
plan.marks_unsaved = plan.mutates_document;
|
||||
return pp::foundation::Result<DocumentAnimationOperationPlan>::success(plan);
|
||||
}
|
||||
|
||||
[[nodiscard]] inline pp::foundation::Result<DocumentAnimationOperationPlan> plan_animation_select_frame(
|
||||
int frame_count,
|
||||
int layer_index,
|
||||
std::uint32_t layer_id,
|
||||
int selected_frame)
|
||||
{
|
||||
const auto index_status = validate_animation_frame_index(frame_count, selected_frame);
|
||||
if (!index_status.ok()) {
|
||||
return pp::foundation::Result<DocumentAnimationOperationPlan>::failure(index_status);
|
||||
}
|
||||
|
||||
if (layer_index < 0) {
|
||||
return pp::foundation::Result<DocumentAnimationOperationPlan>::failure(
|
||||
pp::foundation::Status::out_of_range("animation layer index must not be negative"));
|
||||
}
|
||||
|
||||
DocumentAnimationOperationPlan plan;
|
||||
plan.operation = DocumentAnimationOperation::select_frame;
|
||||
plan.frame_count = frame_count;
|
||||
plan.selected_frame = selected_frame;
|
||||
plan.target_frame = selected_frame;
|
||||
plan.layer_index = layer_index;
|
||||
plan.layer_id = layer_id;
|
||||
plan.requires_selected_frame = true;
|
||||
plan.updates_canvas_animation = true;
|
||||
return pp::foundation::Result<DocumentAnimationOperationPlan>::success(plan);
|
||||
}
|
||||
|
||||
[[nodiscard]] inline pp::foundation::Result<DocumentAnimationOperationPlan> plan_animation_goto_frame(
|
||||
int total_duration,
|
||||
int frame)
|
||||
{
|
||||
if (total_duration <= 0) {
|
||||
return pp::foundation::Result<DocumentAnimationOperationPlan>::failure(
|
||||
pp::foundation::Status::invalid_argument("animation duration must be greater than zero"));
|
||||
}
|
||||
|
||||
if (frame < 0 || frame >= total_duration) {
|
||||
return pp::foundation::Result<DocumentAnimationOperationPlan>::failure(
|
||||
pp::foundation::Status::out_of_range("animation timeline frame is outside the document"));
|
||||
}
|
||||
|
||||
DocumentAnimationOperationPlan plan;
|
||||
plan.operation = DocumentAnimationOperation::goto_frame;
|
||||
plan.frame_count = total_duration;
|
||||
plan.current_frame = frame;
|
||||
plan.target_frame = frame;
|
||||
plan.reloads_animation_layers = true;
|
||||
plan.updates_canvas_animation = true;
|
||||
return pp::foundation::Result<DocumentAnimationOperationPlan>::success(plan);
|
||||
}
|
||||
|
||||
[[nodiscard]] inline pp::foundation::Result<DocumentAnimationOperationPlan> plan_animation_step_frame(
|
||||
int total_duration,
|
||||
int current_frame,
|
||||
int offset)
|
||||
{
|
||||
const auto current_status = plan_animation_goto_frame(total_duration, current_frame);
|
||||
if (!current_status) {
|
||||
return pp::foundation::Result<DocumentAnimationOperationPlan>::failure(current_status.status());
|
||||
}
|
||||
|
||||
if (offset == 0) {
|
||||
return pp::foundation::Result<DocumentAnimationOperationPlan>::failure(
|
||||
pp::foundation::Status::invalid_argument("animation frame step offset must not be zero"));
|
||||
}
|
||||
|
||||
DocumentAnimationOperationPlan plan;
|
||||
plan.operation = offset > 0 ? DocumentAnimationOperation::goto_next : DocumentAnimationOperation::goto_previous;
|
||||
plan.frame_count = total_duration;
|
||||
plan.current_frame = current_frame;
|
||||
auto target = (static_cast<std::int64_t>(current_frame) + static_cast<std::int64_t>(offset))
|
||||
% static_cast<std::int64_t>(total_duration);
|
||||
if (target < 0) {
|
||||
target += total_duration;
|
||||
}
|
||||
plan.target_frame = static_cast<int>(target);
|
||||
plan.updates_canvas_animation = true;
|
||||
plan.reloads_animation_layers = true;
|
||||
return pp::foundation::Result<DocumentAnimationOperationPlan>::success(plan);
|
||||
}
|
||||
|
||||
[[nodiscard]] inline pp::foundation::Result<DocumentAnimationOperationPlan> plan_animation_playback_step(
|
||||
int total_duration,
|
||||
int current_frame,
|
||||
int offset)
|
||||
{
|
||||
const auto step = plan_animation_step_frame(total_duration, current_frame, offset);
|
||||
if (!step) {
|
||||
return pp::foundation::Result<DocumentAnimationOperationPlan>::failure(step.status());
|
||||
}
|
||||
|
||||
auto plan = step.value();
|
||||
plan.operation = DocumentAnimationOperation::playback_step;
|
||||
plan.move_offset = offset;
|
||||
plan.reloads_animation_layers = false;
|
||||
return pp::foundation::Result<DocumentAnimationOperationPlan>::success(plan);
|
||||
}
|
||||
|
||||
[[nodiscard]] inline pp::foundation::Result<DocumentAnimationOperationPlan> plan_animation_playback_toggle(
|
||||
bool playback_active)
|
||||
{
|
||||
DocumentAnimationOperationPlan plan;
|
||||
plan.operation = DocumentAnimationOperation::toggle_playback;
|
||||
plan.playback_was_active = playback_active;
|
||||
plan.playback_active = !playback_active;
|
||||
plan.playback_idle_ms = playback_active ? 100 : 10;
|
||||
plan.resets_playback_timer = !playback_active;
|
||||
return pp::foundation::Result<DocumentAnimationOperationPlan>::success(plan);
|
||||
}
|
||||
|
||||
[[nodiscard]] inline pp::foundation::Result<DocumentAnimationOperationPlan> plan_animation_onion_size(int onion_size)
|
||||
{
|
||||
if (onion_size < 0) {
|
||||
return pp::foundation::Result<DocumentAnimationOperationPlan>::failure(
|
||||
pp::foundation::Status::invalid_argument("animation onion size must not be negative"));
|
||||
}
|
||||
|
||||
DocumentAnimationOperationPlan plan;
|
||||
plan.operation = DocumentAnimationOperation::set_onion_size;
|
||||
plan.onion_size = onion_size;
|
||||
plan.updates_canvas_animation = true;
|
||||
return pp::foundation::Result<DocumentAnimationOperationPlan>::success(plan);
|
||||
}
|
||||
|
||||
[[nodiscard]] inline pp::foundation::Result<DocumentAnimationOperationPlan> plan_animation_panel_action(
|
||||
DocumentAnimationPanelAction action,
|
||||
const DocumentAnimationPanelState& state,
|
||||
int target_frame = 0)
|
||||
{
|
||||
switch (action) {
|
||||
case DocumentAnimationPanelAction::goto_frame:
|
||||
return plan_animation_goto_frame(state.total_duration, target_frame);
|
||||
|
||||
case DocumentAnimationPanelAction::next_frame:
|
||||
return plan_animation_step_frame(state.total_duration, state.current_frame, 1);
|
||||
|
||||
case DocumentAnimationPanelAction::previous_frame:
|
||||
return plan_animation_step_frame(state.total_duration, state.current_frame, -1);
|
||||
|
||||
case DocumentAnimationPanelAction::playback_step:
|
||||
return plan_animation_playback_step(state.total_duration, state.current_frame, 1);
|
||||
|
||||
case DocumentAnimationPanelAction::toggle_playback:
|
||||
return plan_animation_playback_toggle(state.playback_active);
|
||||
}
|
||||
|
||||
return pp::foundation::Result<DocumentAnimationOperationPlan>::failure(
|
||||
pp::foundation::Status::invalid_argument("unknown animation panel action"));
|
||||
}
|
||||
|
||||
[[nodiscard]] inline pp::foundation::Status validate_animation_operation_plan(
|
||||
const DocumentAnimationOperationPlan& plan) noexcept
|
||||
{
|
||||
switch (plan.operation) {
|
||||
case DocumentAnimationOperation::add_frame:
|
||||
if (!plan.mutates_document || !plan.marks_unsaved) {
|
||||
return pp::foundation::Status::invalid_argument("animation add plan must mutate the document");
|
||||
}
|
||||
return validate_animation_frame_count(plan.frame_count);
|
||||
|
||||
case DocumentAnimationOperation::duplicate_frame:
|
||||
case DocumentAnimationOperation::remove_frame:
|
||||
if (!plan.requires_selected_frame || !plan.mutates_document || !plan.marks_unsaved) {
|
||||
return pp::foundation::Status::invalid_argument("animation selected-frame plan must mutate the document");
|
||||
}
|
||||
if (plan.operation == DocumentAnimationOperation::remove_frame && plan.frame_count <= 1) {
|
||||
return pp::foundation::Status::invalid_argument("animation layer must keep at least one frame");
|
||||
}
|
||||
return validate_animation_frame_index(plan.frame_count, plan.selected_frame);
|
||||
|
||||
case DocumentAnimationOperation::adjust_duration:
|
||||
if (!plan.requires_selected_frame) {
|
||||
return pp::foundation::Status::invalid_argument("animation duration plan must require a selected frame");
|
||||
}
|
||||
{
|
||||
const auto index_status = validate_animation_frame_index(plan.frame_count, plan.selected_frame);
|
||||
if (!index_status.ok()) {
|
||||
return index_status;
|
||||
}
|
||||
}
|
||||
return validate_animation_frame_duration(plan.frame_duration);
|
||||
|
||||
case DocumentAnimationOperation::move_frame:
|
||||
if (!plan.requires_selected_frame || plan.move_offset == 0) {
|
||||
return pp::foundation::Status::invalid_argument("animation move plan must require selected frame and non-zero offset");
|
||||
}
|
||||
return validate_animation_frame_index(plan.frame_count, plan.selected_frame);
|
||||
|
||||
case DocumentAnimationOperation::select_frame:
|
||||
if (!plan.requires_selected_frame || !plan.updates_canvas_animation || plan.layer_index < 0) {
|
||||
return pp::foundation::Status::invalid_argument("animation frame select plan has invalid state");
|
||||
}
|
||||
{
|
||||
const auto index_status = validate_animation_frame_index(plan.frame_count, plan.selected_frame);
|
||||
if (!index_status.ok()) {
|
||||
return index_status;
|
||||
}
|
||||
}
|
||||
return validate_animation_frame_index(plan.frame_count, plan.target_frame);
|
||||
|
||||
case DocumentAnimationOperation::goto_frame:
|
||||
case DocumentAnimationOperation::goto_next:
|
||||
case DocumentAnimationOperation::goto_previous:
|
||||
case DocumentAnimationOperation::playback_step:
|
||||
if (!plan.updates_canvas_animation) {
|
||||
return pp::foundation::Status::invalid_argument("animation goto plan must update canvas animation");
|
||||
}
|
||||
if (plan.operation == DocumentAnimationOperation::playback_step && plan.move_offset == 0) {
|
||||
return pp::foundation::Status::invalid_argument("animation playback step offset must not be zero");
|
||||
}
|
||||
return validate_animation_frame_index(plan.frame_count, plan.target_frame);
|
||||
|
||||
case DocumentAnimationOperation::toggle_playback:
|
||||
if (plan.playback_active == plan.playback_was_active) {
|
||||
return pp::foundation::Status::invalid_argument("animation playback toggle must change state");
|
||||
}
|
||||
if (plan.playback_idle_ms <= 0) {
|
||||
return pp::foundation::Status::invalid_argument("animation playback idle interval must be positive");
|
||||
}
|
||||
if (plan.playback_active && !plan.resets_playback_timer) {
|
||||
return pp::foundation::Status::invalid_argument("animation playback start must reset timer");
|
||||
}
|
||||
return pp::foundation::Status::success();
|
||||
|
||||
case DocumentAnimationOperation::set_onion_size:
|
||||
if (plan.onion_size < 0) {
|
||||
return pp::foundation::Status::invalid_argument("animation onion size must not be negative");
|
||||
}
|
||||
if (!plan.updates_canvas_animation) {
|
||||
return pp::foundation::Status::invalid_argument("animation onion plan must update canvas animation");
|
||||
}
|
||||
return pp::foundation::Status::success();
|
||||
}
|
||||
|
||||
return pp::foundation::Status::invalid_argument("unknown animation operation");
|
||||
}
|
||||
|
||||
[[nodiscard]] inline pp::foundation::Status execute_animation_operation_plan(
|
||||
const DocumentAnimationOperationPlan& plan,
|
||||
DocumentAnimationServices& services)
|
||||
{
|
||||
const auto validation = validate_animation_operation_plan(plan);
|
||||
if (!validation.ok()) {
|
||||
return validation;
|
||||
}
|
||||
|
||||
switch (plan.operation) {
|
||||
case DocumentAnimationOperation::add_frame:
|
||||
services.add_frame();
|
||||
services.mark_unsaved();
|
||||
services.update_canvas_animation();
|
||||
services.reload_animation_layers();
|
||||
return pp::foundation::Status::success();
|
||||
|
||||
case DocumentAnimationOperation::duplicate_frame:
|
||||
services.duplicate_frame(plan.selected_frame);
|
||||
services.mark_unsaved();
|
||||
services.update_canvas_animation();
|
||||
services.reload_animation_layers();
|
||||
return pp::foundation::Status::success();
|
||||
|
||||
case DocumentAnimationOperation::remove_frame:
|
||||
services.remove_frame(plan.selected_frame, plan.target_frame);
|
||||
services.mark_unsaved();
|
||||
if (plan.updates_canvas_animation) {
|
||||
services.goto_frame(plan.target_frame);
|
||||
}
|
||||
if (plan.reloads_animation_layers) {
|
||||
services.reload_animation_layers();
|
||||
}
|
||||
return pp::foundation::Status::success();
|
||||
|
||||
case DocumentAnimationOperation::adjust_duration:
|
||||
if (plan.mutates_document) {
|
||||
services.set_frame_duration(plan.selected_frame, plan.frame_duration);
|
||||
services.mark_unsaved();
|
||||
if (plan.updates_canvas_animation) {
|
||||
services.update_canvas_animation();
|
||||
}
|
||||
if (plan.reloads_animation_layers) {
|
||||
services.reload_animation_layers();
|
||||
}
|
||||
}
|
||||
return pp::foundation::Status::success();
|
||||
|
||||
case DocumentAnimationOperation::move_frame:
|
||||
{
|
||||
const auto actual_target_frame = services.move_frame(plan.selected_frame, plan.move_offset);
|
||||
if (plan.marks_unsaved) {
|
||||
services.mark_unsaved();
|
||||
}
|
||||
if (plan.updates_canvas_animation) {
|
||||
services.goto_frame(actual_target_frame);
|
||||
}
|
||||
if (plan.reloads_animation_layers) {
|
||||
services.reload_animation_layers();
|
||||
}
|
||||
return pp::foundation::Status::success();
|
||||
}
|
||||
case DocumentAnimationOperation::goto_frame:
|
||||
case DocumentAnimationOperation::goto_next:
|
||||
case DocumentAnimationOperation::goto_previous:
|
||||
services.goto_frame(plan.target_frame);
|
||||
if (plan.reloads_animation_layers) {
|
||||
services.reload_animation_layers();
|
||||
}
|
||||
return pp::foundation::Status::success();
|
||||
|
||||
case DocumentAnimationOperation::select_frame:
|
||||
services.select_frame(plan.layer_id, plan.layer_index, plan.selected_frame);
|
||||
if (plan.updates_canvas_animation) {
|
||||
services.goto_frame(plan.target_frame);
|
||||
}
|
||||
services.select_layer(plan.layer_index);
|
||||
return pp::foundation::Status::success();
|
||||
|
||||
case DocumentAnimationOperation::playback_step:
|
||||
services.goto_frame(plan.target_frame);
|
||||
services.set_timeline_frame(plan.target_frame);
|
||||
services.update_frame_status();
|
||||
return pp::foundation::Status::success();
|
||||
|
||||
case DocumentAnimationOperation::toggle_playback:
|
||||
if (plan.playback_active) {
|
||||
services.capture_playback_restore_mode();
|
||||
services.enter_playback_camera_mode();
|
||||
if (plan.resets_playback_timer) {
|
||||
services.reset_playback_timer();
|
||||
}
|
||||
} else {
|
||||
services.restore_playback_canvas_mode();
|
||||
}
|
||||
services.set_playback_active(plan.playback_active);
|
||||
services.set_playback_idle_ms(plan.playback_idle_ms);
|
||||
return pp::foundation::Status::success();
|
||||
|
||||
case DocumentAnimationOperation::set_onion_size:
|
||||
services.set_onion_size(plan.onion_size);
|
||||
if (plan.updates_canvas_animation) {
|
||||
services.update_canvas_animation();
|
||||
}
|
||||
return pp::foundation::Status::success();
|
||||
}
|
||||
|
||||
return pp::foundation::Status::invalid_argument("unknown animation operation");
|
||||
}
|
||||
|
||||
} // namespace pp::app
|
||||
526
src/app_core/document_canvas.h
Normal file
526
src/app_core/document_canvas.h
Normal file
@@ -0,0 +1,526 @@
|
||||
#pragma once
|
||||
|
||||
#include "document/document.h"
|
||||
#include "document/ppi_export.h"
|
||||
#include "foundation/result.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <cmath>
|
||||
#include <cstddef>
|
||||
#include <cstdint>
|
||||
#include <span>
|
||||
#include <string>
|
||||
#include <string_view>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
namespace pp::app {
|
||||
|
||||
struct DocumentCanvasClearPlan {
|
||||
float r = 0.0F;
|
||||
float g = 0.0F;
|
||||
float b = 0.0F;
|
||||
float a = 0.0F;
|
||||
bool clears_canvas = false;
|
||||
bool records_undo = false;
|
||||
bool marks_unsaved = false;
|
||||
bool no_op = true;
|
||||
};
|
||||
|
||||
struct DocumentCanvasFacePayloadInput {
|
||||
std::uint32_t frame_index = 0;
|
||||
std::uint32_t face_index = 0;
|
||||
std::uint32_t x = 0;
|
||||
std::uint32_t y = 0;
|
||||
std::uint32_t width = 0;
|
||||
std::uint32_t height = 0;
|
||||
std::span<const std::uint8_t> rgba8;
|
||||
};
|
||||
|
||||
struct DocumentCanvasLayerSnapshotInput {
|
||||
std::string_view name;
|
||||
bool visible = true;
|
||||
bool alpha_locked = false;
|
||||
float opacity = 1.0F;
|
||||
int blend_mode = 0;
|
||||
std::span<const std::uint32_t> frame_durations_ms;
|
||||
std::size_t pending_face_payloads = 0;
|
||||
std::span<const DocumentCanvasFacePayloadInput> captured_face_payloads;
|
||||
};
|
||||
|
||||
struct DocumentCanvasSnapshotInput {
|
||||
bool has_canvas = true;
|
||||
std::uint32_t width = 0;
|
||||
std::uint32_t height = 0;
|
||||
std::size_t active_layer_index = 0;
|
||||
std::size_t active_frame_index = 0;
|
||||
std::span<const DocumentCanvasLayerSnapshotInput> layers;
|
||||
};
|
||||
|
||||
struct DocumentCanvasSnapshotResult {
|
||||
pp::document::CanvasDocument document;
|
||||
std::size_t layer_count = 0;
|
||||
std::size_t frame_count = 0;
|
||||
std::size_t pending_face_payloads = 0;
|
||||
std::size_t captured_face_payloads = 0;
|
||||
bool metadata_only = false;
|
||||
bool requires_renderer_payload_readback = false;
|
||||
};
|
||||
|
||||
struct DocumentCanvasSaveSnapshotReport {
|
||||
std::uint32_t width = 0;
|
||||
std::uint32_t height = 0;
|
||||
std::size_t layer_count = 0;
|
||||
std::size_t frame_count = 0;
|
||||
std::size_t captured_face_payloads = 0;
|
||||
std::size_t pending_face_payloads = 0;
|
||||
bool payload_complete = false;
|
||||
bool can_export_ppi = false;
|
||||
};
|
||||
|
||||
enum class DocumentCanvasSaveWriterAction {
|
||||
use_document_ppi_writer,
|
||||
use_legacy_project_save,
|
||||
};
|
||||
|
||||
struct DocumentCanvasSaveWriterRoutePlan {
|
||||
DocumentCanvasSaveWriterAction action = DocumentCanvasSaveWriterAction::use_legacy_project_save;
|
||||
bool payload_complete = false;
|
||||
bool can_export_ppi = false;
|
||||
bool uses_document_ppi_writer = false;
|
||||
std::string_view fallback_reason;
|
||||
};
|
||||
|
||||
struct DocumentCanvasPpiExportResult {
|
||||
DocumentCanvasSaveSnapshotReport report;
|
||||
std::vector<std::byte> bytes;
|
||||
};
|
||||
|
||||
struct DocumentCanvasProjectSaveTargetPlan {
|
||||
std::string target_path;
|
||||
std::string file_name;
|
||||
std::string temporary_path;
|
||||
std::string timelapse_path;
|
||||
};
|
||||
|
||||
enum class DocumentCanvasProjectSaveWriteAction {
|
||||
write_direct_to_target,
|
||||
write_temporary_then_swap,
|
||||
};
|
||||
|
||||
struct DocumentCanvasProjectSaveWritePlan {
|
||||
DocumentCanvasProjectSaveWriteAction action = DocumentCanvasProjectSaveWriteAction::write_direct_to_target;
|
||||
std::string write_path;
|
||||
std::string target_path;
|
||||
std::string temporary_path;
|
||||
bool target_exists = false;
|
||||
bool uses_temporary = false;
|
||||
bool falls_back_to_direct_on_temporary_open_failure = false;
|
||||
};
|
||||
|
||||
struct DocumentCanvasProjectSaveCommitInput {
|
||||
bool used_temporary = false;
|
||||
bool target_remove_attempted = false;
|
||||
bool target_remove_succeeded = false;
|
||||
bool temporary_rename_attempted = false;
|
||||
bool temporary_rename_succeeded = false;
|
||||
};
|
||||
|
||||
struct DocumentCanvasProjectSaveCommitPlan {
|
||||
bool saved = false;
|
||||
bool used_temporary = false;
|
||||
bool target_removed = false;
|
||||
bool temporary_renamed = false;
|
||||
bool target_may_be_missing = false;
|
||||
std::string_view log_message;
|
||||
};
|
||||
|
||||
struct DocumentCanvasProjectSavePostCommitInput {
|
||||
bool save_succeeded = false;
|
||||
bool timelapse_encoder_available = false;
|
||||
bool progress_ui_visible = false;
|
||||
};
|
||||
|
||||
struct DocumentCanvasProjectSavePostCommitPlan {
|
||||
bool marks_document_clean = false;
|
||||
bool marks_new_document_committed = false;
|
||||
bool saves_timelapse_sidecar = false;
|
||||
bool flushes_platform_storage = false;
|
||||
bool dismisses_progress_ui = false;
|
||||
bool updates_title = true;
|
||||
};
|
||||
|
||||
class DocumentCanvasClearServices {
|
||||
public:
|
||||
virtual ~DocumentCanvasClearServices() = default;
|
||||
|
||||
virtual void clear_current_canvas(float r, float g, float b, float a) = 0;
|
||||
};
|
||||
|
||||
[[nodiscard]] inline pp::foundation::Status validate_clear_color_channel(float value) noexcept
|
||||
{
|
||||
if (!std::isfinite(value)) {
|
||||
return pp::foundation::Status::invalid_argument("clear color channel must be finite");
|
||||
}
|
||||
if (value < 0.0F || value > 1.0F) {
|
||||
return pp::foundation::Status::out_of_range("clear color channel must be within 0..1");
|
||||
}
|
||||
return pp::foundation::Status::success();
|
||||
}
|
||||
|
||||
[[nodiscard]] inline pp::foundation::Result<DocumentCanvasSnapshotResult> plan_document_canvas_snapshot(
|
||||
DocumentCanvasSnapshotInput input)
|
||||
{
|
||||
if (!input.has_canvas) {
|
||||
return pp::foundation::Result<DocumentCanvasSnapshotResult>::failure(
|
||||
pp::foundation::Status::invalid_argument("document canvas snapshot requires a canvas"));
|
||||
}
|
||||
|
||||
if (input.layers.empty()) {
|
||||
return pp::foundation::Result<DocumentCanvasSnapshotResult>::failure(
|
||||
pp::foundation::Status::invalid_argument("document canvas snapshot requires at least one layer"));
|
||||
}
|
||||
|
||||
std::size_t frame_count = 1U;
|
||||
std::size_t pending_face_payloads = 0U;
|
||||
std::size_t captured_face_payloads = 0U;
|
||||
for (const auto& layer : input.layers) {
|
||||
frame_count = std::max(frame_count, layer.frame_durations_ms.size());
|
||||
pending_face_payloads += layer.pending_face_payloads;
|
||||
captured_face_payloads += layer.captured_face_payloads.size();
|
||||
}
|
||||
|
||||
if (input.active_layer_index >= input.layers.size()) {
|
||||
return pp::foundation::Result<DocumentCanvasSnapshotResult>::failure(
|
||||
pp::foundation::Status::out_of_range("active canvas layer is outside the document snapshot"));
|
||||
}
|
||||
|
||||
if (input.active_frame_index >= frame_count) {
|
||||
return pp::foundation::Result<DocumentCanvasSnapshotResult>::failure(
|
||||
pp::foundation::Status::out_of_range("active canvas frame is outside the document snapshot"));
|
||||
}
|
||||
|
||||
std::vector<pp::document::AnimationFrame> root_frames;
|
||||
root_frames.reserve(frame_count);
|
||||
for (std::size_t frame_index = 0; frame_index < frame_count; ++frame_index) {
|
||||
std::uint32_t duration_ms = 100U;
|
||||
for (const auto& layer : input.layers) {
|
||||
if (frame_index < layer.frame_durations_ms.size()) {
|
||||
duration_ms = layer.frame_durations_ms[frame_index];
|
||||
break;
|
||||
}
|
||||
}
|
||||
root_frames.push_back(pp::document::AnimationFrame { .duration_ms = duration_ms });
|
||||
}
|
||||
|
||||
std::vector<std::string> layer_names;
|
||||
std::vector<std::vector<pp::document::AnimationFrame>> layer_frames;
|
||||
std::vector<pp::document::DocumentLayerConfig> layer_configs;
|
||||
layer_names.reserve(input.layers.size());
|
||||
layer_frames.reserve(input.layers.size());
|
||||
layer_configs.reserve(input.layers.size());
|
||||
|
||||
for (std::size_t layer_index = 0; layer_index < input.layers.size(); ++layer_index) {
|
||||
const auto& layer = input.layers[layer_index];
|
||||
if (layer.name.empty()) {
|
||||
layer_names.push_back("Layer " + std::to_string(layer_index + 1U));
|
||||
} else {
|
||||
layer_names.push_back(std::string(layer.name));
|
||||
}
|
||||
|
||||
layer_frames.push_back({});
|
||||
auto& frames = layer_frames.back();
|
||||
frames.reserve(layer.frame_durations_ms.empty() ? root_frames.size() : layer.frame_durations_ms.size());
|
||||
if (layer.frame_durations_ms.empty()) {
|
||||
frames = root_frames;
|
||||
} else {
|
||||
for (const auto duration_ms : layer.frame_durations_ms) {
|
||||
frames.push_back(pp::document::AnimationFrame { .duration_ms = duration_ms });
|
||||
}
|
||||
}
|
||||
|
||||
layer_configs.push_back(pp::document::DocumentLayerConfig {
|
||||
.name = layer_names.back(),
|
||||
.visible = layer.visible,
|
||||
.alpha_locked = layer.alpha_locked,
|
||||
.opacity = layer.opacity,
|
||||
.blend_mode = static_cast<pp::paint::BlendMode>(layer.blend_mode),
|
||||
.frames = std::span<const pp::document::AnimationFrame>(frames),
|
||||
});
|
||||
}
|
||||
|
||||
auto document = pp::document::CanvasDocument::create_from_snapshot(pp::document::DocumentSnapshotConfig {
|
||||
.width = input.width,
|
||||
.height = input.height,
|
||||
.layers = std::span<const pp::document::DocumentLayerConfig>(layer_configs),
|
||||
.frames = std::span<const pp::document::AnimationFrame>(root_frames),
|
||||
});
|
||||
if (!document) {
|
||||
return pp::foundation::Result<DocumentCanvasSnapshotResult>::failure(document.status());
|
||||
}
|
||||
|
||||
for (std::size_t layer_index = 0; layer_index < input.layers.size(); ++layer_index) {
|
||||
for (const auto& payload : input.layers[layer_index].captured_face_payloads) {
|
||||
pp::document::LayerFacePixels pixels {
|
||||
.face_index = payload.face_index,
|
||||
.x = payload.x,
|
||||
.y = payload.y,
|
||||
.width = payload.width,
|
||||
.height = payload.height,
|
||||
.rgba8 = std::vector<std::uint8_t>(payload.rgba8.begin(), payload.rgba8.end()),
|
||||
};
|
||||
const auto payload_status = document.value().set_layer_frame_face_pixels(
|
||||
layer_index,
|
||||
payload.frame_index,
|
||||
std::move(pixels));
|
||||
if (!payload_status.ok()) {
|
||||
return pp::foundation::Result<DocumentCanvasSnapshotResult>::failure(payload_status);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
auto active_status = document.value().set_active_layer(input.active_layer_index);
|
||||
if (!active_status.ok()) {
|
||||
return pp::foundation::Result<DocumentCanvasSnapshotResult>::failure(active_status);
|
||||
}
|
||||
|
||||
active_status = document.value().set_active_frame(input.active_frame_index);
|
||||
if (!active_status.ok()) {
|
||||
return pp::foundation::Result<DocumentCanvasSnapshotResult>::failure(active_status);
|
||||
}
|
||||
|
||||
return pp::foundation::Result<DocumentCanvasSnapshotResult>::success(DocumentCanvasSnapshotResult {
|
||||
.document = std::move(document.value()),
|
||||
.layer_count = input.layers.size(),
|
||||
.frame_count = frame_count,
|
||||
.pending_face_payloads = pending_face_payloads,
|
||||
.captured_face_payloads = captured_face_payloads,
|
||||
.metadata_only = captured_face_payloads == 0U,
|
||||
.requires_renderer_payload_readback = pending_face_payloads > captured_face_payloads,
|
||||
});
|
||||
}
|
||||
|
||||
[[nodiscard]] inline DocumentCanvasSaveSnapshotReport make_document_canvas_save_snapshot_report(
|
||||
const DocumentCanvasSnapshotResult& snapshot) noexcept
|
||||
{
|
||||
return DocumentCanvasSaveSnapshotReport {
|
||||
.width = snapshot.document.width(),
|
||||
.height = snapshot.document.height(),
|
||||
.layer_count = snapshot.layer_count,
|
||||
.frame_count = snapshot.frame_count,
|
||||
.captured_face_payloads = snapshot.captured_face_payloads,
|
||||
.pending_face_payloads = snapshot.pending_face_payloads,
|
||||
.payload_complete = !snapshot.requires_renderer_payload_readback,
|
||||
.can_export_ppi = !snapshot.requires_renderer_payload_readback,
|
||||
};
|
||||
}
|
||||
|
||||
[[nodiscard]] constexpr DocumentCanvasSaveWriterRoutePlan plan_document_canvas_save_writer_route(
|
||||
DocumentCanvasSaveSnapshotReport report) noexcept
|
||||
{
|
||||
DocumentCanvasSaveWriterRoutePlan plan;
|
||||
plan.payload_complete = report.payload_complete;
|
||||
plan.can_export_ppi = report.can_export_ppi;
|
||||
|
||||
if (!report.payload_complete || !report.can_export_ppi) {
|
||||
plan.fallback_reason = "canvas document snapshot still requires renderer payload readback";
|
||||
return plan;
|
||||
}
|
||||
|
||||
plan.action = DocumentCanvasSaveWriterAction::use_document_ppi_writer;
|
||||
plan.uses_document_ppi_writer = true;
|
||||
return plan;
|
||||
}
|
||||
|
||||
[[nodiscard]] inline pp::foundation::Result<DocumentCanvasPpiExportResult>
|
||||
export_document_canvas_save_snapshot_to_ppi(const DocumentCanvasSnapshotResult& snapshot)
|
||||
{
|
||||
const auto report = make_document_canvas_save_snapshot_report(snapshot);
|
||||
const auto route = plan_document_canvas_save_writer_route(report);
|
||||
if (!route.uses_document_ppi_writer) {
|
||||
return pp::foundation::Result<DocumentCanvasPpiExportResult>::failure(
|
||||
pp::foundation::Status::invalid_argument(route.fallback_reason.data()));
|
||||
}
|
||||
|
||||
auto bytes = pp::document::export_ppi_project_document(snapshot.document);
|
||||
if (!bytes) {
|
||||
return pp::foundation::Result<DocumentCanvasPpiExportResult>::failure(bytes.status());
|
||||
}
|
||||
|
||||
return pp::foundation::Result<DocumentCanvasPpiExportResult>::success(DocumentCanvasPpiExportResult {
|
||||
.report = report,
|
||||
.bytes = std::move(bytes.value()),
|
||||
});
|
||||
}
|
||||
|
||||
[[nodiscard]] inline pp::foundation::Result<DocumentCanvasProjectSaveTargetPlan>
|
||||
plan_document_canvas_project_save_target(
|
||||
std::string_view data_directory,
|
||||
std::string_view target_path)
|
||||
{
|
||||
if (data_directory.empty()) {
|
||||
return pp::foundation::Result<DocumentCanvasProjectSaveTargetPlan>::failure(
|
||||
pp::foundation::Status::invalid_argument("project save data directory must not be empty"));
|
||||
}
|
||||
if (target_path.empty()) {
|
||||
return pp::foundation::Result<DocumentCanvasProjectSaveTargetPlan>::failure(
|
||||
pp::foundation::Status::invalid_argument("project save target path must not be empty"));
|
||||
}
|
||||
|
||||
const auto basename_start = target_path.find_last_of("/\\");
|
||||
const auto file_name_start = basename_start == std::string_view::npos ? 0U : basename_start + 1U;
|
||||
auto file_name = target_path.substr(file_name_start);
|
||||
if (file_name.empty()) {
|
||||
return pp::foundation::Result<DocumentCanvasProjectSaveTargetPlan>::failure(
|
||||
pp::foundation::Status::invalid_argument("project save target file name must not be empty"));
|
||||
}
|
||||
|
||||
constexpr std::string_view ppi_extension = ".ppi";
|
||||
if (file_name.size() > ppi_extension.size()
|
||||
&& file_name.substr(file_name.size() - ppi_extension.size()) == ppi_extension) {
|
||||
file_name.remove_suffix(ppi_extension.size());
|
||||
}
|
||||
|
||||
DocumentCanvasProjectSaveTargetPlan plan;
|
||||
plan.target_path = std::string(target_path);
|
||||
plan.file_name = std::string(file_name);
|
||||
plan.temporary_path.reserve(data_directory.size() + plan.file_name.size() + 10U);
|
||||
plan.temporary_path += data_directory;
|
||||
plan.temporary_path += "/";
|
||||
plan.temporary_path += plan.file_name;
|
||||
plan.temporary_path += ".tmp.ppi";
|
||||
plan.timelapse_path.reserve(data_directory.size() + plan.file_name.size() + 6U);
|
||||
plan.timelapse_path += data_directory;
|
||||
plan.timelapse_path += "/";
|
||||
plan.timelapse_path += plan.file_name;
|
||||
plan.timelapse_path += ".pptl";
|
||||
return pp::foundation::Result<DocumentCanvasProjectSaveTargetPlan>::success(std::move(plan));
|
||||
}
|
||||
|
||||
[[nodiscard]] inline pp::foundation::Result<DocumentCanvasProjectSaveWritePlan>
|
||||
plan_document_canvas_project_save_write(
|
||||
const DocumentCanvasProjectSaveTargetPlan& target,
|
||||
bool target_exists)
|
||||
{
|
||||
if (target.target_path.empty()) {
|
||||
return pp::foundation::Result<DocumentCanvasProjectSaveWritePlan>::failure(
|
||||
pp::foundation::Status::invalid_argument("project save write target path must not be empty"));
|
||||
}
|
||||
|
||||
DocumentCanvasProjectSaveWritePlan plan;
|
||||
plan.target_exists = target_exists;
|
||||
plan.target_path = target.target_path;
|
||||
plan.temporary_path = target.temporary_path;
|
||||
|
||||
if (!target_exists) {
|
||||
plan.write_path = target.target_path;
|
||||
return pp::foundation::Result<DocumentCanvasProjectSaveWritePlan>::success(std::move(plan));
|
||||
}
|
||||
|
||||
if (target.temporary_path.empty()) {
|
||||
return pp::foundation::Result<DocumentCanvasProjectSaveWritePlan>::failure(
|
||||
pp::foundation::Status::invalid_argument("project save temporary path must not be empty"));
|
||||
}
|
||||
|
||||
plan.action = DocumentCanvasProjectSaveWriteAction::write_temporary_then_swap;
|
||||
plan.write_path = target.temporary_path;
|
||||
plan.uses_temporary = true;
|
||||
plan.falls_back_to_direct_on_temporary_open_failure = true;
|
||||
return pp::foundation::Result<DocumentCanvasProjectSaveWritePlan>::success(std::move(plan));
|
||||
}
|
||||
|
||||
[[nodiscard]] constexpr DocumentCanvasProjectSaveCommitPlan plan_document_canvas_project_save_commit(
|
||||
DocumentCanvasProjectSaveCommitInput input) noexcept
|
||||
{
|
||||
DocumentCanvasProjectSaveCommitPlan plan;
|
||||
plan.used_temporary = input.used_temporary;
|
||||
|
||||
if (!input.used_temporary) {
|
||||
plan.saved = true;
|
||||
plan.log_message = "project saved to target";
|
||||
return plan;
|
||||
}
|
||||
|
||||
if (!input.target_remove_attempted || !input.target_remove_succeeded) {
|
||||
plan.log_message = "could not remove target project before temporary swap";
|
||||
return plan;
|
||||
}
|
||||
|
||||
plan.target_removed = true;
|
||||
if (!input.temporary_rename_attempted || !input.temporary_rename_succeeded) {
|
||||
plan.target_may_be_missing = true;
|
||||
plan.log_message = "temporary project not swapped after original removal";
|
||||
return plan;
|
||||
}
|
||||
|
||||
plan.saved = true;
|
||||
plan.temporary_renamed = true;
|
||||
plan.log_message = "temporary project swapped successfully";
|
||||
return plan;
|
||||
}
|
||||
|
||||
[[nodiscard]] constexpr DocumentCanvasProjectSavePostCommitPlan plan_document_canvas_project_save_post_commit(
|
||||
DocumentCanvasProjectSavePostCommitInput input) noexcept
|
||||
{
|
||||
DocumentCanvasProjectSavePostCommitPlan plan;
|
||||
plan.dismisses_progress_ui = input.progress_ui_visible;
|
||||
|
||||
if (!input.save_succeeded) {
|
||||
return plan;
|
||||
}
|
||||
|
||||
plan.marks_document_clean = true;
|
||||
plan.marks_new_document_committed = true;
|
||||
plan.saves_timelapse_sidecar = input.timelapse_encoder_available;
|
||||
plan.flushes_platform_storage = true;
|
||||
return plan;
|
||||
}
|
||||
|
||||
[[nodiscard]] inline pp::foundation::Result<DocumentCanvasClearPlan> plan_document_canvas_clear(
|
||||
bool has_canvas,
|
||||
float r = 0.0F,
|
||||
float g = 0.0F,
|
||||
float b = 0.0F,
|
||||
float a = 0.0F) noexcept
|
||||
{
|
||||
const float channels[] { r, g, b, a };
|
||||
for (const float channel : channels) {
|
||||
const auto status = validate_clear_color_channel(channel);
|
||||
if (!status.ok()) {
|
||||
return pp::foundation::Result<DocumentCanvasClearPlan>::failure(status);
|
||||
}
|
||||
}
|
||||
|
||||
DocumentCanvasClearPlan plan;
|
||||
plan.r = r;
|
||||
plan.g = g;
|
||||
plan.b = b;
|
||||
plan.a = a;
|
||||
plan.clears_canvas = has_canvas;
|
||||
plan.records_undo = has_canvas;
|
||||
plan.marks_unsaved = has_canvas;
|
||||
plan.no_op = !has_canvas;
|
||||
return pp::foundation::Result<DocumentCanvasClearPlan>::success(plan);
|
||||
}
|
||||
|
||||
[[nodiscard]] inline pp::foundation::Status execute_document_canvas_clear_plan(
|
||||
const DocumentCanvasClearPlan& plan,
|
||||
DocumentCanvasClearServices& services)
|
||||
{
|
||||
const float channels[] { plan.r, plan.g, plan.b, plan.a };
|
||||
for (const float channel : channels) {
|
||||
const auto status = validate_clear_color_channel(channel);
|
||||
if (!status.ok()) {
|
||||
return status;
|
||||
}
|
||||
}
|
||||
|
||||
if (plan.no_op || !plan.clears_canvas) {
|
||||
return pp::foundation::Status::success();
|
||||
}
|
||||
|
||||
services.clear_current_canvas(plan.r, plan.g, plan.b, plan.a);
|
||||
return pp::foundation::Status::success();
|
||||
}
|
||||
|
||||
} // namespace pp::app
|
||||
315
src/app_core/document_cloud.h
Normal file
315
src/app_core/document_cloud.h
Normal file
@@ -0,0 +1,315 @@
|
||||
#pragma once
|
||||
|
||||
#include "app_core/app_dialog.h"
|
||||
#include "foundation/result.h"
|
||||
|
||||
#include <cstdio>
|
||||
#include <cstddef>
|
||||
#include <cstdint>
|
||||
#include <limits>
|
||||
#include <string>
|
||||
#include <string_view>
|
||||
|
||||
namespace pp::app {
|
||||
|
||||
enum class CloudUploadAction {
|
||||
unavailable_no_canvas,
|
||||
show_save_required_warning,
|
||||
prompt_publish,
|
||||
};
|
||||
|
||||
enum class CloudBrowseAction {
|
||||
unavailable_no_canvas,
|
||||
show_browser,
|
||||
};
|
||||
|
||||
enum class CloudDownloadSelectionAction {
|
||||
wait_for_selection,
|
||||
start_download,
|
||||
};
|
||||
|
||||
enum class CloudTransferDirection {
|
||||
download,
|
||||
upload,
|
||||
};
|
||||
|
||||
enum class CloudTransferAction {
|
||||
reject_missing_source,
|
||||
reject_missing_destination,
|
||||
start_transfer,
|
||||
};
|
||||
|
||||
struct CloudUploadPlan {
|
||||
CloudUploadAction action = CloudUploadAction::unavailable_no_canvas;
|
||||
bool save_before_upload = false;
|
||||
};
|
||||
|
||||
struct CloudBulkUploadPlan {
|
||||
std::size_t file_count = 0;
|
||||
int progress_total = 0;
|
||||
bool show_progress = false;
|
||||
};
|
||||
|
||||
struct CloudDownloadRequest {
|
||||
std::string selected_file;
|
||||
std::string selected_path;
|
||||
std::string selected_name;
|
||||
};
|
||||
|
||||
struct CloudTransferPlan {
|
||||
CloudTransferDirection direction = CloudTransferDirection::download;
|
||||
CloudTransferAction action = CloudTransferAction::reject_missing_source;
|
||||
bool enable_progress = false;
|
||||
bool disable_tls_verification = false;
|
||||
};
|
||||
|
||||
struct CloudTransferProgressPlan {
|
||||
bool notify = false;
|
||||
float fraction = 0.0F;
|
||||
};
|
||||
|
||||
[[nodiscard]] inline AppMessageDialogPlan plan_cloud_save_required_prompt()
|
||||
{
|
||||
return plan_app_message_dialog(
|
||||
"Warning",
|
||||
"This document needs to be saved before upload.",
|
||||
false);
|
||||
}
|
||||
|
||||
[[nodiscard]] inline AppMessageDialogPlan plan_cloud_publish_prompt()
|
||||
{
|
||||
return plan_app_message_dialog(
|
||||
"Publish document",
|
||||
"Would you like to upload to the public domain?",
|
||||
true,
|
||||
"Yes",
|
||||
"No");
|
||||
}
|
||||
|
||||
[[nodiscard]] inline AppMessageDialogPlan plan_cloud_upload_success_prompt()
|
||||
{
|
||||
return plan_app_message_dialog(
|
||||
"Success",
|
||||
"This document has been succesfully uploaded.",
|
||||
false);
|
||||
}
|
||||
|
||||
[[nodiscard]] inline AppProgressDialogPlan plan_cloud_upload_progress_dialog()
|
||||
{
|
||||
return plan_app_progress_dialog("Uploading", 0);
|
||||
}
|
||||
|
||||
[[nodiscard]] inline AppProgressDialogPlan plan_cloud_bulk_upload_progress_dialog(int progress_total)
|
||||
{
|
||||
return plan_app_progress_dialog("Export Pano Image", progress_total);
|
||||
}
|
||||
|
||||
[[nodiscard]] inline AppMessageDialogPlan plan_cloud_download_progress_prompt()
|
||||
{
|
||||
return plan_app_message_dialog(
|
||||
"Downloading",
|
||||
"Download in progress",
|
||||
true);
|
||||
}
|
||||
|
||||
[[nodiscard]] inline std::string format_cloud_download_progress_message(float progress_fraction)
|
||||
{
|
||||
char buffer[64] {};
|
||||
std::snprintf(
|
||||
buffer,
|
||||
sizeof(buffer),
|
||||
"Download in progress %.2f%%",
|
||||
progress_fraction * 100.0F);
|
||||
return buffer;
|
||||
}
|
||||
|
||||
class CloudServices {
|
||||
public:
|
||||
virtual ~CloudServices() = default;
|
||||
|
||||
virtual void show_save_required_warning() = 0;
|
||||
virtual void prompt_publish(bool save_before_upload) = 0;
|
||||
virtual void begin_bulk_upload(int progress_total, bool show_progress) = 0;
|
||||
virtual void upload_all_bulk_files() = 0;
|
||||
virtual void end_bulk_upload() = 0;
|
||||
virtual void show_browser() = 0;
|
||||
virtual void start_download(const CloudDownloadRequest& request) = 0;
|
||||
};
|
||||
|
||||
[[nodiscard]] constexpr CloudUploadPlan plan_cloud_upload(
|
||||
bool has_canvas,
|
||||
bool is_new_document,
|
||||
bool has_unsaved_changes) noexcept
|
||||
{
|
||||
if (!has_canvas) {
|
||||
return { CloudUploadAction::unavailable_no_canvas, false };
|
||||
}
|
||||
|
||||
if (is_new_document) {
|
||||
return { CloudUploadAction::show_save_required_warning, false };
|
||||
}
|
||||
|
||||
return { CloudUploadAction::prompt_publish, has_unsaved_changes };
|
||||
}
|
||||
|
||||
[[nodiscard]] constexpr CloudBrowseAction plan_cloud_browse(bool has_canvas) noexcept
|
||||
{
|
||||
return has_canvas
|
||||
? CloudBrowseAction::show_browser
|
||||
: CloudBrowseAction::unavailable_no_canvas;
|
||||
}
|
||||
|
||||
[[nodiscard]] constexpr CloudDownloadSelectionAction plan_cloud_download_selection(
|
||||
std::string_view selected_file) noexcept
|
||||
{
|
||||
return selected_file.empty()
|
||||
? CloudDownloadSelectionAction::wait_for_selection
|
||||
: CloudDownloadSelectionAction::start_download;
|
||||
}
|
||||
|
||||
[[nodiscard]] constexpr CloudBulkUploadPlan plan_cloud_bulk_upload(
|
||||
std::size_t file_count,
|
||||
bool progress_ui_available) noexcept
|
||||
{
|
||||
const auto max_progress_total = static_cast<std::size_t>(std::numeric_limits<int>::max());
|
||||
return {
|
||||
file_count,
|
||||
file_count > max_progress_total ? std::numeric_limits<int>::max() : static_cast<int>(file_count),
|
||||
progress_ui_available,
|
||||
};
|
||||
}
|
||||
|
||||
[[nodiscard]] constexpr CloudTransferPlan plan_cloud_download_transfer(
|
||||
std::string_view url,
|
||||
std::string_view destination_path,
|
||||
bool has_progress_callback,
|
||||
bool disables_tls_verification) noexcept
|
||||
{
|
||||
if (url.empty()) {
|
||||
return {
|
||||
CloudTransferDirection::download,
|
||||
CloudTransferAction::reject_missing_source,
|
||||
false,
|
||||
false,
|
||||
};
|
||||
}
|
||||
|
||||
if (destination_path.empty()) {
|
||||
return {
|
||||
CloudTransferDirection::download,
|
||||
CloudTransferAction::reject_missing_destination,
|
||||
false,
|
||||
false,
|
||||
};
|
||||
}
|
||||
|
||||
return {
|
||||
CloudTransferDirection::download,
|
||||
CloudTransferAction::start_transfer,
|
||||
has_progress_callback,
|
||||
disables_tls_verification,
|
||||
};
|
||||
}
|
||||
|
||||
[[nodiscard]] constexpr CloudTransferPlan plan_cloud_upload_transfer(
|
||||
std::string_view filename,
|
||||
bool has_progress_callback,
|
||||
bool disables_tls_verification) noexcept
|
||||
{
|
||||
if (filename.empty()) {
|
||||
return {
|
||||
CloudTransferDirection::upload,
|
||||
CloudTransferAction::reject_missing_source,
|
||||
false,
|
||||
false,
|
||||
};
|
||||
}
|
||||
|
||||
return {
|
||||
CloudTransferDirection::upload,
|
||||
CloudTransferAction::start_transfer,
|
||||
has_progress_callback,
|
||||
disables_tls_verification,
|
||||
};
|
||||
}
|
||||
|
||||
[[nodiscard]] constexpr CloudTransferProgressPlan plan_cloud_transfer_progress(
|
||||
std::int64_t total,
|
||||
std::int64_t current) noexcept
|
||||
{
|
||||
if (total <= 0) {
|
||||
return {};
|
||||
}
|
||||
|
||||
const auto clamped_current = current < 0
|
||||
? std::int64_t { 0 }
|
||||
: (current > total ? total : current);
|
||||
return {
|
||||
true,
|
||||
static_cast<float>(static_cast<double>(clamped_current) / static_cast<double>(total)),
|
||||
};
|
||||
}
|
||||
|
||||
[[nodiscard]] inline pp::foundation::Status execute_cloud_upload_plan(
|
||||
const CloudUploadPlan& plan,
|
||||
CloudServices& services)
|
||||
{
|
||||
switch (plan.action) {
|
||||
case CloudUploadAction::unavailable_no_canvas:
|
||||
return pp::foundation::Status::success();
|
||||
case CloudUploadAction::show_save_required_warning:
|
||||
services.show_save_required_warning();
|
||||
return pp::foundation::Status::success();
|
||||
case CloudUploadAction::prompt_publish:
|
||||
services.prompt_publish(plan.save_before_upload);
|
||||
return pp::foundation::Status::success();
|
||||
}
|
||||
|
||||
return pp::foundation::Status::invalid_argument("unknown cloud upload action");
|
||||
}
|
||||
|
||||
[[nodiscard]] inline pp::foundation::Status execute_cloud_bulk_upload_plan(
|
||||
const CloudBulkUploadPlan& plan,
|
||||
CloudServices& services)
|
||||
{
|
||||
services.begin_bulk_upload(plan.progress_total, plan.show_progress);
|
||||
services.upload_all_bulk_files();
|
||||
services.end_bulk_upload();
|
||||
return pp::foundation::Status::success();
|
||||
}
|
||||
|
||||
[[nodiscard]] inline pp::foundation::Status execute_cloud_browse_action(
|
||||
CloudBrowseAction action,
|
||||
CloudServices& services)
|
||||
{
|
||||
switch (action) {
|
||||
case CloudBrowseAction::unavailable_no_canvas:
|
||||
return pp::foundation::Status::success();
|
||||
case CloudBrowseAction::show_browser:
|
||||
services.show_browser();
|
||||
return pp::foundation::Status::success();
|
||||
}
|
||||
|
||||
return pp::foundation::Status::invalid_argument("unknown cloud browse action");
|
||||
}
|
||||
|
||||
[[nodiscard]] inline pp::foundation::Status execute_cloud_download_selection_action(
|
||||
CloudDownloadSelectionAction action,
|
||||
CloudServices& services,
|
||||
const CloudDownloadRequest& request)
|
||||
{
|
||||
switch (action) {
|
||||
case CloudDownloadSelectionAction::wait_for_selection:
|
||||
return pp::foundation::Status::success();
|
||||
case CloudDownloadSelectionAction::start_download:
|
||||
if (request.selected_file.empty()) {
|
||||
return pp::foundation::Status::invalid_argument("cloud download requires a selected file");
|
||||
}
|
||||
services.start_download(request);
|
||||
return pp::foundation::Status::success();
|
||||
}
|
||||
|
||||
return pp::foundation::Status::invalid_argument("unknown cloud download selection action");
|
||||
}
|
||||
|
||||
}
|
||||
1
src/app_core/document_export.cpp
Normal file
1
src/app_core/document_export.cpp
Normal file
@@ -0,0 +1 @@
|
||||
#include "app_core/document_export.h"
|
||||
1145
src/app_core/document_export.h
Normal file
1145
src/app_core/document_export.h
Normal file
File diff suppressed because it is too large
Load Diff
88
src/app_core/document_import.h
Normal file
88
src/app_core/document_import.h
Normal file
@@ -0,0 +1,88 @@
|
||||
#pragma once
|
||||
|
||||
#include "foundation/result.h"
|
||||
|
||||
#include <string_view>
|
||||
|
||||
namespace pp::app {
|
||||
|
||||
enum class DocumentImageImportAction {
|
||||
import_equirectangular,
|
||||
place_transform,
|
||||
};
|
||||
|
||||
struct DocumentImageImportPlan {
|
||||
int width = 0;
|
||||
int height = 0;
|
||||
DocumentImageImportAction action = DocumentImageImportAction::place_transform;
|
||||
bool imports_equirectangular = false;
|
||||
bool enters_transform_mode = false;
|
||||
};
|
||||
|
||||
class DocumentImageImportServices {
|
||||
public:
|
||||
virtual ~DocumentImageImportServices() = default;
|
||||
|
||||
virtual void import_equirectangular(std::string_view path) = 0;
|
||||
virtual void enter_transform_import(std::string_view path) = 0;
|
||||
};
|
||||
|
||||
[[nodiscard]] inline pp::foundation::Status validate_document_image_import_dimensions(
|
||||
int width,
|
||||
int height) noexcept
|
||||
{
|
||||
if (width <= 0 || height <= 0) {
|
||||
return pp::foundation::Status::invalid_argument("image dimensions must be positive");
|
||||
}
|
||||
return pp::foundation::Status::success();
|
||||
}
|
||||
|
||||
[[nodiscard]] inline pp::foundation::Result<DocumentImageImportPlan> plan_document_image_import(
|
||||
int width,
|
||||
int height) noexcept
|
||||
{
|
||||
const auto dimensions = validate_document_image_import_dimensions(width, height);
|
||||
if (!dimensions.ok()) {
|
||||
return pp::foundation::Result<DocumentImageImportPlan>::failure(dimensions);
|
||||
}
|
||||
|
||||
const auto wide_equirect = static_cast<long long>(width) == static_cast<long long>(height) * 2LL;
|
||||
const auto vertical_cube_strip = width == height / 6;
|
||||
|
||||
DocumentImageImportPlan plan;
|
||||
plan.width = width;
|
||||
plan.height = height;
|
||||
plan.imports_equirectangular = wide_equirect || vertical_cube_strip;
|
||||
plan.enters_transform_mode = !plan.imports_equirectangular;
|
||||
plan.action = plan.imports_equirectangular
|
||||
? DocumentImageImportAction::import_equirectangular
|
||||
: DocumentImageImportAction::place_transform;
|
||||
return pp::foundation::Result<DocumentImageImportPlan>::success(plan);
|
||||
}
|
||||
|
||||
[[nodiscard]] inline pp::foundation::Status execute_document_image_import_plan(
|
||||
const DocumentImageImportPlan& plan,
|
||||
std::string_view path,
|
||||
DocumentImageImportServices& services)
|
||||
{
|
||||
const auto dimensions = validate_document_image_import_dimensions(plan.width, plan.height);
|
||||
if (!dimensions.ok()) {
|
||||
return dimensions;
|
||||
}
|
||||
if (path.empty()) {
|
||||
return pp::foundation::Status::invalid_argument("image import path must not be empty");
|
||||
}
|
||||
|
||||
switch (plan.action) {
|
||||
case DocumentImageImportAction::import_equirectangular:
|
||||
services.import_equirectangular(path);
|
||||
return pp::foundation::Status::success();
|
||||
case DocumentImageImportAction::place_transform:
|
||||
services.enter_transform_import(path);
|
||||
return pp::foundation::Status::success();
|
||||
}
|
||||
|
||||
return pp::foundation::Status::invalid_argument("unknown image import action");
|
||||
}
|
||||
|
||||
} // namespace pp::app
|
||||
720
src/app_core/document_layer.h
Normal file
720
src/app_core/document_layer.h
Normal file
@@ -0,0 +1,720 @@
|
||||
#pragma once
|
||||
|
||||
#include "foundation/result.h"
|
||||
|
||||
#include <cmath>
|
||||
#include <cstddef>
|
||||
#include <cstdint>
|
||||
#include <string>
|
||||
#include <string_view>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
namespace pp::app {
|
||||
|
||||
inline constexpr std::size_t document_layer_name_max_length = 128;
|
||||
inline constexpr int document_layer_legacy_blend_mode_count = 5;
|
||||
|
||||
enum class DocumentLayerRenameAction {
|
||||
no_op_same_name,
|
||||
rename_and_record_undo,
|
||||
};
|
||||
|
||||
enum class DocumentLayerOperation {
|
||||
add,
|
||||
duplicate,
|
||||
select,
|
||||
reorder,
|
||||
remove,
|
||||
set_opacity,
|
||||
set_visibility,
|
||||
set_alpha_lock,
|
||||
set_blend_mode,
|
||||
set_highlight,
|
||||
};
|
||||
|
||||
enum class DocumentLayerMenuCommand {
|
||||
clear,
|
||||
rename,
|
||||
merge_down,
|
||||
};
|
||||
|
||||
enum class DocumentLayerMenuAction {
|
||||
clear_current_layer,
|
||||
show_rename_dialog,
|
||||
merge_with_lower_layer,
|
||||
show_merge_animated_not_supported,
|
||||
no_op_select_layer,
|
||||
no_op_select_upper_layer,
|
||||
};
|
||||
|
||||
struct DocumentLayerRenamePlan {
|
||||
std::string old_name;
|
||||
std::string new_name;
|
||||
DocumentLayerRenameAction action = DocumentLayerRenameAction::no_op_same_name;
|
||||
};
|
||||
|
||||
struct DocumentLayerOperationPlan {
|
||||
DocumentLayerOperation operation = DocumentLayerOperation::select;
|
||||
int index = 0;
|
||||
int from_index = 0;
|
||||
int to_index = 0;
|
||||
int insert_index = 0;
|
||||
int source_index = 0;
|
||||
std::string name;
|
||||
float opacity = 1.0F;
|
||||
bool flag = false;
|
||||
int blend_mode = 0;
|
||||
bool mutates_document = false;
|
||||
bool marks_unsaved = false;
|
||||
bool reloads_animation_layers = false;
|
||||
bool updates_title = false;
|
||||
};
|
||||
|
||||
struct DocumentLayerMenuPlan {
|
||||
DocumentLayerMenuCommand command = DocumentLayerMenuCommand::clear;
|
||||
DocumentLayerMenuAction action = DocumentLayerMenuAction::clear_current_layer;
|
||||
std::string label;
|
||||
int from_index = 0;
|
||||
int to_index = 0;
|
||||
};
|
||||
|
||||
struct DocumentLayerMergePlan {
|
||||
int from_index = 0;
|
||||
int to_index = 0;
|
||||
bool create_history = true;
|
||||
};
|
||||
|
||||
struct DocumentLayerPanelInput {
|
||||
int layer_index = 0;
|
||||
std::string name;
|
||||
float opacity = 1.0F;
|
||||
bool visible = true;
|
||||
bool alpha_locked = false;
|
||||
int blend_mode = 0;
|
||||
};
|
||||
|
||||
struct DocumentLayerPanelLayerView {
|
||||
int layer_index = 0;
|
||||
std::string name;
|
||||
float opacity = 1.0F;
|
||||
bool visible = true;
|
||||
bool alpha_locked = false;
|
||||
int blend_mode = 0;
|
||||
bool current = false;
|
||||
};
|
||||
|
||||
struct DocumentLayerPanelView {
|
||||
int current_index = 0;
|
||||
float current_opacity = 1.0F;
|
||||
bool current_alpha_locked = false;
|
||||
int current_blend_mode = 0;
|
||||
std::vector<DocumentLayerPanelLayerView> layers;
|
||||
};
|
||||
|
||||
class DocumentLayerMenuServices {
|
||||
public:
|
||||
virtual ~DocumentLayerMenuServices() = default;
|
||||
|
||||
virtual void clear_current_layer() = 0;
|
||||
virtual void show_rename_dialog() = 0;
|
||||
virtual void merge_with_lower_layer(int from_index, int to_index) = 0;
|
||||
virtual void show_merge_animated_not_supported() = 0;
|
||||
};
|
||||
|
||||
class DocumentLayerRenameServices {
|
||||
public:
|
||||
virtual ~DocumentLayerRenameServices() = default;
|
||||
|
||||
virtual void rename_layer(std::string_view old_name, std::string_view new_name) = 0;
|
||||
virtual void finish_layer_rename() = 0;
|
||||
};
|
||||
|
||||
class DocumentLayerOperationServices {
|
||||
public:
|
||||
virtual ~DocumentLayerOperationServices() = default;
|
||||
|
||||
virtual void add_layer(std::string_view name, int insert_index) = 0;
|
||||
virtual void duplicate_layer(int source_index, int insert_index) = 0;
|
||||
virtual void select_layer(int index) = 0;
|
||||
virtual void reorder_layer(int from_index, int to_index) = 0;
|
||||
virtual void remove_layer(int index) = 0;
|
||||
virtual void set_layer_opacity(int index, float opacity) = 0;
|
||||
virtual void set_layer_visibility(int index, bool visible) = 0;
|
||||
virtual void set_layer_alpha_lock(int index, bool locked) = 0;
|
||||
virtual void set_layer_blend_mode(int index, int blend_mode) = 0;
|
||||
virtual void set_layer_highlight(int index, bool highlighted) = 0;
|
||||
virtual void mark_unsaved() = 0;
|
||||
virtual void reload_animation_layers() = 0;
|
||||
virtual void update_title() = 0;
|
||||
};
|
||||
|
||||
class DocumentLayerMergeServices {
|
||||
public:
|
||||
virtual ~DocumentLayerMergeServices() = default;
|
||||
|
||||
virtual void merge_layers(int from_index, int to_index, bool create_history) = 0;
|
||||
};
|
||||
|
||||
[[nodiscard]] inline pp::foundation::Status validate_layer_index(
|
||||
int layer_count,
|
||||
int index) noexcept
|
||||
{
|
||||
if (layer_count <= 0) {
|
||||
return pp::foundation::Status::invalid_argument("document must contain at least one layer");
|
||||
}
|
||||
|
||||
if (index < 0 || index >= layer_count) {
|
||||
return pp::foundation::Status::out_of_range("layer index is outside the document");
|
||||
}
|
||||
|
||||
return pp::foundation::Status::success();
|
||||
}
|
||||
|
||||
[[nodiscard]] inline pp::foundation::Status validate_layer_insert_index(
|
||||
int layer_count,
|
||||
int index) noexcept
|
||||
{
|
||||
if (layer_count < 0) {
|
||||
return pp::foundation::Status::invalid_argument("layer count must not be negative");
|
||||
}
|
||||
|
||||
if (index < 0 || index > layer_count) {
|
||||
return pp::foundation::Status::out_of_range("layer insert index is outside the document");
|
||||
}
|
||||
|
||||
return pp::foundation::Status::success();
|
||||
}
|
||||
|
||||
[[nodiscard]] inline pp::foundation::Result<DocumentLayerPanelView> plan_document_layer_panel_view(
|
||||
const std::vector<DocumentLayerPanelInput>& layers,
|
||||
int current_index)
|
||||
{
|
||||
if (layers.empty()) {
|
||||
return pp::foundation::Result<DocumentLayerPanelView>::failure(
|
||||
pp::foundation::Status::invalid_argument("layer panel requires at least one layer"));
|
||||
}
|
||||
|
||||
const auto current_status = validate_layer_index(static_cast<int>(layers.size()), current_index);
|
||||
if (!current_status.ok()) {
|
||||
return pp::foundation::Result<DocumentLayerPanelView>::failure(current_status);
|
||||
}
|
||||
|
||||
DocumentLayerPanelView view;
|
||||
view.current_index = current_index;
|
||||
view.layers.reserve(layers.size());
|
||||
|
||||
for (const auto& input : layers) {
|
||||
const auto index_status = validate_layer_index(static_cast<int>(layers.size()), input.layer_index);
|
||||
if (!index_status.ok()) {
|
||||
return pp::foundation::Result<DocumentLayerPanelView>::failure(index_status);
|
||||
}
|
||||
|
||||
if (!std::isfinite(input.opacity) || input.opacity < 0.0F || input.opacity > 1.0F) {
|
||||
return pp::foundation::Result<DocumentLayerPanelView>::failure(
|
||||
pp::foundation::Status::out_of_range("layer opacity must be finite and within 0..1"));
|
||||
}
|
||||
|
||||
if (input.blend_mode < 0 || input.blend_mode >= document_layer_legacy_blend_mode_count) {
|
||||
return pp::foundation::Result<DocumentLayerPanelView>::failure(
|
||||
pp::foundation::Status::out_of_range("layer blend mode is outside the supported range"));
|
||||
}
|
||||
|
||||
const bool current = input.layer_index == current_index;
|
||||
DocumentLayerPanelLayerView layer;
|
||||
layer.layer_index = input.layer_index;
|
||||
layer.name = input.name;
|
||||
layer.opacity = input.opacity;
|
||||
layer.visible = input.visible;
|
||||
layer.alpha_locked = input.alpha_locked;
|
||||
layer.blend_mode = input.blend_mode;
|
||||
layer.current = current;
|
||||
if (current) {
|
||||
view.current_opacity = input.opacity;
|
||||
view.current_alpha_locked = input.alpha_locked;
|
||||
view.current_blend_mode = input.blend_mode;
|
||||
}
|
||||
view.layers.push_back(std::move(layer));
|
||||
}
|
||||
|
||||
return pp::foundation::Result<DocumentLayerPanelView>::success(std::move(view));
|
||||
}
|
||||
|
||||
[[nodiscard]] inline pp::foundation::Result<DocumentLayerRenamePlan> plan_document_layer_rename(
|
||||
std::string_view old_name,
|
||||
std::string_view requested_name)
|
||||
{
|
||||
if (requested_name.empty()) {
|
||||
return pp::foundation::Result<DocumentLayerRenamePlan>::failure(
|
||||
pp::foundation::Status::invalid_argument("layer name must not be empty"));
|
||||
}
|
||||
|
||||
if (requested_name.size() > document_layer_name_max_length) {
|
||||
return pp::foundation::Result<DocumentLayerRenamePlan>::failure(
|
||||
pp::foundation::Status::out_of_range("layer name length exceeds the configured limit"));
|
||||
}
|
||||
|
||||
DocumentLayerRenamePlan plan;
|
||||
plan.old_name = std::string(old_name);
|
||||
plan.new_name = std::string(requested_name);
|
||||
plan.action = old_name == requested_name
|
||||
? DocumentLayerRenameAction::no_op_same_name
|
||||
: DocumentLayerRenameAction::rename_and_record_undo;
|
||||
return pp::foundation::Result<DocumentLayerRenamePlan>::success(std::move(plan));
|
||||
}
|
||||
|
||||
[[nodiscard]] inline pp::foundation::Result<DocumentLayerOperationPlan> plan_document_layer_add(
|
||||
int layer_count,
|
||||
int insert_index,
|
||||
std::string_view name)
|
||||
{
|
||||
const auto index_status = validate_layer_insert_index(layer_count, insert_index);
|
||||
if (!index_status.ok()) {
|
||||
return pp::foundation::Result<DocumentLayerOperationPlan>::failure(index_status);
|
||||
}
|
||||
|
||||
const auto rename = plan_document_layer_rename({}, name);
|
||||
if (!rename) {
|
||||
return pp::foundation::Result<DocumentLayerOperationPlan>::failure(rename.status());
|
||||
}
|
||||
|
||||
DocumentLayerOperationPlan plan;
|
||||
plan.operation = DocumentLayerOperation::add;
|
||||
plan.insert_index = insert_index;
|
||||
plan.name = std::string(name);
|
||||
plan.mutates_document = true;
|
||||
plan.marks_unsaved = true;
|
||||
plan.reloads_animation_layers = true;
|
||||
plan.updates_title = true;
|
||||
return pp::foundation::Result<DocumentLayerOperationPlan>::success(std::move(plan));
|
||||
}
|
||||
|
||||
[[nodiscard]] inline pp::foundation::Result<DocumentLayerOperationPlan> plan_document_layer_duplicate(
|
||||
int layer_count,
|
||||
int source_index)
|
||||
{
|
||||
const auto index_status = validate_layer_index(layer_count, source_index);
|
||||
if (!index_status.ok()) {
|
||||
return pp::foundation::Result<DocumentLayerOperationPlan>::failure(index_status);
|
||||
}
|
||||
|
||||
DocumentLayerOperationPlan plan;
|
||||
plan.operation = DocumentLayerOperation::duplicate;
|
||||
plan.source_index = source_index;
|
||||
plan.insert_index = source_index + 1;
|
||||
plan.mutates_document = true;
|
||||
plan.marks_unsaved = true;
|
||||
plan.reloads_animation_layers = true;
|
||||
plan.updates_title = true;
|
||||
return pp::foundation::Result<DocumentLayerOperationPlan>::success(plan);
|
||||
}
|
||||
|
||||
[[nodiscard]] inline pp::foundation::Result<DocumentLayerOperationPlan> plan_document_layer_select(
|
||||
int layer_count,
|
||||
int index)
|
||||
{
|
||||
const auto index_status = validate_layer_index(layer_count, index);
|
||||
if (!index_status.ok()) {
|
||||
return pp::foundation::Result<DocumentLayerOperationPlan>::failure(index_status);
|
||||
}
|
||||
|
||||
DocumentLayerOperationPlan plan;
|
||||
plan.operation = DocumentLayerOperation::select;
|
||||
plan.index = index;
|
||||
plan.reloads_animation_layers = true;
|
||||
return pp::foundation::Result<DocumentLayerOperationPlan>::success(plan);
|
||||
}
|
||||
|
||||
[[nodiscard]] inline pp::foundation::Result<DocumentLayerOperationPlan> plan_document_layer_reorder(
|
||||
int layer_count,
|
||||
int from_index,
|
||||
int to_index)
|
||||
{
|
||||
auto index_status = validate_layer_index(layer_count, from_index);
|
||||
if (!index_status.ok()) {
|
||||
return pp::foundation::Result<DocumentLayerOperationPlan>::failure(index_status);
|
||||
}
|
||||
|
||||
index_status = validate_layer_index(layer_count, to_index);
|
||||
if (!index_status.ok()) {
|
||||
return pp::foundation::Result<DocumentLayerOperationPlan>::failure(index_status);
|
||||
}
|
||||
|
||||
DocumentLayerOperationPlan plan;
|
||||
plan.operation = DocumentLayerOperation::reorder;
|
||||
plan.from_index = from_index;
|
||||
plan.to_index = to_index;
|
||||
plan.mutates_document = from_index != to_index;
|
||||
plan.marks_unsaved = plan.mutates_document;
|
||||
plan.reloads_animation_layers = plan.mutates_document;
|
||||
plan.updates_title = plan.mutates_document;
|
||||
return pp::foundation::Result<DocumentLayerOperationPlan>::success(plan);
|
||||
}
|
||||
|
||||
[[nodiscard]] inline pp::foundation::Result<DocumentLayerOperationPlan> plan_document_layer_remove(
|
||||
int layer_count,
|
||||
int index)
|
||||
{
|
||||
const auto index_status = validate_layer_index(layer_count, index);
|
||||
if (!index_status.ok()) {
|
||||
return pp::foundation::Result<DocumentLayerOperationPlan>::failure(index_status);
|
||||
}
|
||||
|
||||
if (layer_count <= 1) {
|
||||
return pp::foundation::Result<DocumentLayerOperationPlan>::failure(
|
||||
pp::foundation::Status::invalid_argument("document must keep at least one layer"));
|
||||
}
|
||||
|
||||
DocumentLayerOperationPlan plan;
|
||||
plan.operation = DocumentLayerOperation::remove;
|
||||
plan.index = index;
|
||||
plan.mutates_document = true;
|
||||
plan.marks_unsaved = true;
|
||||
plan.reloads_animation_layers = true;
|
||||
plan.updates_title = true;
|
||||
return pp::foundation::Result<DocumentLayerOperationPlan>::success(plan);
|
||||
}
|
||||
|
||||
[[nodiscard]] inline pp::foundation::Result<DocumentLayerOperationPlan> plan_document_layer_opacity(
|
||||
int layer_count,
|
||||
int index,
|
||||
float opacity)
|
||||
{
|
||||
const auto index_status = validate_layer_index(layer_count, index);
|
||||
if (!index_status.ok()) {
|
||||
return pp::foundation::Result<DocumentLayerOperationPlan>::failure(index_status);
|
||||
}
|
||||
|
||||
if (!std::isfinite(opacity) || opacity < 0.0F || opacity > 1.0F) {
|
||||
return pp::foundation::Result<DocumentLayerOperationPlan>::failure(
|
||||
pp::foundation::Status::out_of_range("layer opacity must be finite and within 0..1"));
|
||||
}
|
||||
|
||||
DocumentLayerOperationPlan plan;
|
||||
plan.operation = DocumentLayerOperation::set_opacity;
|
||||
plan.index = index;
|
||||
plan.opacity = opacity;
|
||||
plan.mutates_document = true;
|
||||
plan.marks_unsaved = true;
|
||||
plan.updates_title = true;
|
||||
return pp::foundation::Result<DocumentLayerOperationPlan>::success(plan);
|
||||
}
|
||||
|
||||
[[nodiscard]] inline pp::foundation::Result<DocumentLayerOperationPlan> plan_document_layer_visibility(
|
||||
int layer_count,
|
||||
int index,
|
||||
bool visible)
|
||||
{
|
||||
const auto index_status = validate_layer_index(layer_count, index);
|
||||
if (!index_status.ok()) {
|
||||
return pp::foundation::Result<DocumentLayerOperationPlan>::failure(index_status);
|
||||
}
|
||||
|
||||
DocumentLayerOperationPlan plan;
|
||||
plan.operation = DocumentLayerOperation::set_visibility;
|
||||
plan.index = index;
|
||||
plan.flag = visible;
|
||||
plan.mutates_document = true;
|
||||
plan.marks_unsaved = true;
|
||||
plan.reloads_animation_layers = true;
|
||||
plan.updates_title = true;
|
||||
return pp::foundation::Result<DocumentLayerOperationPlan>::success(plan);
|
||||
}
|
||||
|
||||
[[nodiscard]] inline pp::foundation::Result<DocumentLayerOperationPlan> plan_document_layer_alpha_lock(
|
||||
int layer_count,
|
||||
int index,
|
||||
bool locked)
|
||||
{
|
||||
const auto index_status = validate_layer_index(layer_count, index);
|
||||
if (!index_status.ok()) {
|
||||
return pp::foundation::Result<DocumentLayerOperationPlan>::failure(index_status);
|
||||
}
|
||||
|
||||
DocumentLayerOperationPlan plan;
|
||||
plan.operation = DocumentLayerOperation::set_alpha_lock;
|
||||
plan.index = index;
|
||||
plan.flag = locked;
|
||||
plan.mutates_document = true;
|
||||
plan.marks_unsaved = true;
|
||||
plan.updates_title = true;
|
||||
return pp::foundation::Result<DocumentLayerOperationPlan>::success(plan);
|
||||
}
|
||||
|
||||
[[nodiscard]] inline pp::foundation::Result<DocumentLayerOperationPlan> plan_document_layer_blend_mode(
|
||||
int layer_count,
|
||||
int index,
|
||||
int blend_mode)
|
||||
{
|
||||
const auto index_status = validate_layer_index(layer_count, index);
|
||||
if (!index_status.ok()) {
|
||||
return pp::foundation::Result<DocumentLayerOperationPlan>::failure(index_status);
|
||||
}
|
||||
|
||||
if (blend_mode < 0 || blend_mode >= document_layer_legacy_blend_mode_count) {
|
||||
return pp::foundation::Result<DocumentLayerOperationPlan>::failure(
|
||||
pp::foundation::Status::out_of_range("layer blend mode is outside the supported range"));
|
||||
}
|
||||
|
||||
DocumentLayerOperationPlan plan;
|
||||
plan.operation = DocumentLayerOperation::set_blend_mode;
|
||||
plan.index = index;
|
||||
plan.blend_mode = blend_mode;
|
||||
plan.mutates_document = true;
|
||||
plan.marks_unsaved = true;
|
||||
plan.updates_title = true;
|
||||
return pp::foundation::Result<DocumentLayerOperationPlan>::success(plan);
|
||||
}
|
||||
|
||||
[[nodiscard]] inline pp::foundation::Result<DocumentLayerOperationPlan> plan_document_layer_highlight(
|
||||
int layer_count,
|
||||
int index,
|
||||
bool highlight)
|
||||
{
|
||||
const auto index_status = validate_layer_index(layer_count, index);
|
||||
if (!index_status.ok()) {
|
||||
return pp::foundation::Result<DocumentLayerOperationPlan>::failure(index_status);
|
||||
}
|
||||
|
||||
DocumentLayerOperationPlan plan;
|
||||
plan.operation = DocumentLayerOperation::set_highlight;
|
||||
plan.index = index;
|
||||
plan.flag = highlight;
|
||||
return pp::foundation::Result<DocumentLayerOperationPlan>::success(plan);
|
||||
}
|
||||
|
||||
[[nodiscard]] inline pp::foundation::Result<DocumentLayerMenuPlan> plan_document_layer_menu(
|
||||
DocumentLayerMenuCommand command,
|
||||
bool has_current_layer,
|
||||
int current_index,
|
||||
int animation_duration,
|
||||
std::string_view current_layer_name,
|
||||
std::string_view lower_layer_name)
|
||||
{
|
||||
if (current_index < 0) {
|
||||
return pp::foundation::Result<DocumentLayerMenuPlan>::failure(
|
||||
pp::foundation::Status::out_of_range("current layer index must not be negative"));
|
||||
}
|
||||
if (animation_duration < 0) {
|
||||
return pp::foundation::Result<DocumentLayerMenuPlan>::failure(
|
||||
pp::foundation::Status::out_of_range("animation duration must not be negative"));
|
||||
}
|
||||
|
||||
DocumentLayerMenuPlan plan;
|
||||
plan.command = command;
|
||||
plan.from_index = current_index;
|
||||
plan.to_index = current_index > 0 ? current_index - 1 : 0;
|
||||
|
||||
switch (command) {
|
||||
case DocumentLayerMenuCommand::clear:
|
||||
plan.action = has_current_layer
|
||||
? DocumentLayerMenuAction::clear_current_layer
|
||||
: DocumentLayerMenuAction::no_op_select_layer;
|
||||
plan.label = has_current_layer
|
||||
? "Clear Layer " + std::string(current_layer_name)
|
||||
: "Clear Layer (Select a layer)";
|
||||
break;
|
||||
case DocumentLayerMenuCommand::rename:
|
||||
plan.action = has_current_layer
|
||||
? DocumentLayerMenuAction::show_rename_dialog
|
||||
: DocumentLayerMenuAction::no_op_select_layer;
|
||||
plan.label = has_current_layer
|
||||
? "Rename Layer " + std::string(current_layer_name)
|
||||
: "Rename Layer (Select a layer)";
|
||||
break;
|
||||
case DocumentLayerMenuCommand::merge_down:
|
||||
if (!has_current_layer) {
|
||||
plan.action = DocumentLayerMenuAction::no_op_select_layer;
|
||||
plan.label = "Merge Layer (Select a layer)";
|
||||
} else if (animation_duration > 1) {
|
||||
plan.action = DocumentLayerMenuAction::show_merge_animated_not_supported;
|
||||
plan.label = "Merge Layer (Animation not supported)";
|
||||
} else if (current_index <= 0) {
|
||||
plan.action = DocumentLayerMenuAction::no_op_select_upper_layer;
|
||||
plan.label = "Merge Layer (Select upper layers)";
|
||||
} else {
|
||||
plan.action = DocumentLayerMenuAction::merge_with_lower_layer;
|
||||
plan.label = "Merge with " + std::string(lower_layer_name);
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
return pp::foundation::Result<DocumentLayerMenuPlan>::success(std::move(plan));
|
||||
}
|
||||
|
||||
[[nodiscard]] inline pp::foundation::Result<DocumentLayerMergePlan> plan_document_layer_merge(
|
||||
int layer_count,
|
||||
int from_index,
|
||||
int to_index,
|
||||
int animation_duration,
|
||||
bool create_history = true)
|
||||
{
|
||||
auto index_status = validate_layer_index(layer_count, from_index);
|
||||
if (!index_status.ok()) {
|
||||
return pp::foundation::Result<DocumentLayerMergePlan>::failure(index_status);
|
||||
}
|
||||
|
||||
index_status = validate_layer_index(layer_count, to_index);
|
||||
if (!index_status.ok()) {
|
||||
return pp::foundation::Result<DocumentLayerMergePlan>::failure(index_status);
|
||||
}
|
||||
|
||||
if (animation_duration < 0) {
|
||||
return pp::foundation::Result<DocumentLayerMergePlan>::failure(
|
||||
pp::foundation::Status::out_of_range("animation duration must not be negative"));
|
||||
}
|
||||
|
||||
if (animation_duration > 1) {
|
||||
return pp::foundation::Result<DocumentLayerMergePlan>::failure(
|
||||
pp::foundation::Status::invalid_argument("animated layer merge is not supported"));
|
||||
}
|
||||
|
||||
if (from_index <= to_index) {
|
||||
return pp::foundation::Result<DocumentLayerMergePlan>::failure(
|
||||
pp::foundation::Status::invalid_argument("layer merge source must be above the destination"));
|
||||
}
|
||||
|
||||
DocumentLayerMergePlan plan;
|
||||
plan.from_index = from_index;
|
||||
plan.to_index = to_index;
|
||||
plan.create_history = create_history;
|
||||
return pp::foundation::Result<DocumentLayerMergePlan>::success(plan);
|
||||
}
|
||||
|
||||
[[nodiscard]] inline pp::foundation::Status execute_document_layer_rename_plan(
|
||||
const DocumentLayerRenamePlan& plan,
|
||||
DocumentLayerRenameServices& services)
|
||||
{
|
||||
switch (plan.action) {
|
||||
case DocumentLayerRenameAction::no_op_same_name:
|
||||
services.finish_layer_rename();
|
||||
return pp::foundation::Status::success();
|
||||
case DocumentLayerRenameAction::rename_and_record_undo:
|
||||
if (plan.new_name.empty()) {
|
||||
return pp::foundation::Status::invalid_argument("layer rename plan must include a new name");
|
||||
}
|
||||
if (plan.old_name == plan.new_name) {
|
||||
return pp::foundation::Status::invalid_argument("layer rename plan must change the name");
|
||||
}
|
||||
services.rename_layer(plan.old_name, plan.new_name);
|
||||
services.finish_layer_rename();
|
||||
return pp::foundation::Status::success();
|
||||
}
|
||||
|
||||
return pp::foundation::Status::invalid_argument("unknown document layer rename action");
|
||||
}
|
||||
|
||||
inline void execute_document_layer_operation_side_effects(
|
||||
const DocumentLayerOperationPlan& plan,
|
||||
DocumentLayerOperationServices& services)
|
||||
{
|
||||
if (plan.marks_unsaved)
|
||||
services.mark_unsaved();
|
||||
if (plan.reloads_animation_layers)
|
||||
services.reload_animation_layers();
|
||||
if (plan.updates_title)
|
||||
services.update_title();
|
||||
}
|
||||
|
||||
[[nodiscard]] inline pp::foundation::Status execute_document_layer_operation_plan(
|
||||
const DocumentLayerOperationPlan& plan,
|
||||
DocumentLayerOperationServices& services)
|
||||
{
|
||||
switch (plan.operation) {
|
||||
case DocumentLayerOperation::add:
|
||||
if (!plan.mutates_document || plan.name.empty()) {
|
||||
return pp::foundation::Status::invalid_argument("layer add plan must mutate with a name");
|
||||
}
|
||||
services.add_layer(plan.name, plan.insert_index);
|
||||
break;
|
||||
case DocumentLayerOperation::duplicate:
|
||||
if (!plan.mutates_document) {
|
||||
return pp::foundation::Status::invalid_argument("layer duplicate plan must mutate the document");
|
||||
}
|
||||
services.duplicate_layer(plan.source_index, plan.insert_index);
|
||||
break;
|
||||
case DocumentLayerOperation::select:
|
||||
services.select_layer(plan.index);
|
||||
break;
|
||||
case DocumentLayerOperation::reorder:
|
||||
if (plan.mutates_document)
|
||||
services.reorder_layer(plan.from_index, plan.to_index);
|
||||
break;
|
||||
case DocumentLayerOperation::remove:
|
||||
if (!plan.mutates_document) {
|
||||
return pp::foundation::Status::invalid_argument("layer remove plan must mutate the document");
|
||||
}
|
||||
services.remove_layer(plan.index);
|
||||
break;
|
||||
case DocumentLayerOperation::set_opacity:
|
||||
if (!plan.mutates_document) {
|
||||
return pp::foundation::Status::invalid_argument("layer opacity plan must mutate the document");
|
||||
}
|
||||
services.set_layer_opacity(plan.index, plan.opacity);
|
||||
break;
|
||||
case DocumentLayerOperation::set_visibility:
|
||||
if (!plan.mutates_document) {
|
||||
return pp::foundation::Status::invalid_argument("layer visibility plan must mutate the document");
|
||||
}
|
||||
services.set_layer_visibility(plan.index, plan.flag);
|
||||
break;
|
||||
case DocumentLayerOperation::set_alpha_lock:
|
||||
if (!plan.mutates_document) {
|
||||
return pp::foundation::Status::invalid_argument("layer alpha-lock plan must mutate the document");
|
||||
}
|
||||
services.set_layer_alpha_lock(plan.index, plan.flag);
|
||||
break;
|
||||
case DocumentLayerOperation::set_blend_mode:
|
||||
if (!plan.mutates_document) {
|
||||
return pp::foundation::Status::invalid_argument("layer blend-mode plan must mutate the document");
|
||||
}
|
||||
services.set_layer_blend_mode(plan.index, plan.blend_mode);
|
||||
break;
|
||||
case DocumentLayerOperation::set_highlight:
|
||||
services.set_layer_highlight(plan.index, plan.flag);
|
||||
break;
|
||||
}
|
||||
|
||||
execute_document_layer_operation_side_effects(plan, services);
|
||||
return pp::foundation::Status::success();
|
||||
}
|
||||
|
||||
[[nodiscard]] inline pp::foundation::Status execute_document_layer_merge_plan(
|
||||
const DocumentLayerMergePlan& plan,
|
||||
DocumentLayerMergeServices& services)
|
||||
{
|
||||
if (plan.from_index <= plan.to_index) {
|
||||
return pp::foundation::Status::invalid_argument(
|
||||
"layer merge source must be above the destination");
|
||||
}
|
||||
|
||||
services.merge_layers(plan.from_index, plan.to_index, plan.create_history);
|
||||
return pp::foundation::Status::success();
|
||||
}
|
||||
|
||||
[[nodiscard]] inline pp::foundation::Status execute_document_layer_menu_plan(
|
||||
const DocumentLayerMenuPlan& plan,
|
||||
DocumentLayerMenuServices& services)
|
||||
{
|
||||
switch (plan.action) {
|
||||
case DocumentLayerMenuAction::clear_current_layer:
|
||||
services.clear_current_layer();
|
||||
return pp::foundation::Status::success();
|
||||
case DocumentLayerMenuAction::show_rename_dialog:
|
||||
services.show_rename_dialog();
|
||||
return pp::foundation::Status::success();
|
||||
case DocumentLayerMenuAction::merge_with_lower_layer:
|
||||
services.merge_with_lower_layer(plan.from_index, plan.to_index);
|
||||
return pp::foundation::Status::success();
|
||||
case DocumentLayerMenuAction::show_merge_animated_not_supported:
|
||||
services.show_merge_animated_not_supported();
|
||||
return pp::foundation::Status::success();
|
||||
case DocumentLayerMenuAction::no_op_select_layer:
|
||||
case DocumentLayerMenuAction::no_op_select_upper_layer:
|
||||
return pp::foundation::Status::success();
|
||||
}
|
||||
|
||||
return pp::foundation::Status::invalid_argument("unknown document layer menu action");
|
||||
}
|
||||
|
||||
}
|
||||
73
src/app_core/document_platform_io.h
Normal file
73
src/app_core/document_platform_io.h
Normal file
@@ -0,0 +1,73 @@
|
||||
#pragma once
|
||||
|
||||
#include <string_view>
|
||||
|
||||
namespace pp::app {
|
||||
|
||||
enum class PickedPathAction {
|
||||
ignore_empty_path,
|
||||
invoke_callback,
|
||||
};
|
||||
|
||||
enum class DisplayFileAction {
|
||||
ignore_empty_path,
|
||||
open_external_file,
|
||||
};
|
||||
|
||||
enum class VirtualKeyboardAction {
|
||||
show_keyboard,
|
||||
hide_keyboard,
|
||||
};
|
||||
|
||||
enum class CursorVisibilityAction {
|
||||
show_cursor,
|
||||
hide_cursor,
|
||||
};
|
||||
|
||||
enum class ClipboardReadAction {
|
||||
read_text,
|
||||
};
|
||||
|
||||
enum class ClipboardWriteAction {
|
||||
write_text,
|
||||
};
|
||||
|
||||
[[nodiscard]] constexpr PickedPathAction plan_picked_path(std::string_view path) noexcept
|
||||
{
|
||||
return path.empty()
|
||||
? PickedPathAction::ignore_empty_path
|
||||
: PickedPathAction::invoke_callback;
|
||||
}
|
||||
|
||||
[[nodiscard]] constexpr DisplayFileAction plan_display_file(std::string_view path) noexcept
|
||||
{
|
||||
return path.empty()
|
||||
? DisplayFileAction::ignore_empty_path
|
||||
: DisplayFileAction::open_external_file;
|
||||
}
|
||||
|
||||
[[nodiscard]] constexpr VirtualKeyboardAction plan_virtual_keyboard(bool visible) noexcept
|
||||
{
|
||||
return visible
|
||||
? VirtualKeyboardAction::show_keyboard
|
||||
: VirtualKeyboardAction::hide_keyboard;
|
||||
}
|
||||
|
||||
[[nodiscard]] constexpr CursorVisibilityAction plan_cursor_visibility(bool visible) noexcept
|
||||
{
|
||||
return visible
|
||||
? CursorVisibilityAction::show_cursor
|
||||
: CursorVisibilityAction::hide_cursor;
|
||||
}
|
||||
|
||||
[[nodiscard]] constexpr ClipboardReadAction plan_clipboard_read() noexcept
|
||||
{
|
||||
return ClipboardReadAction::read_text;
|
||||
}
|
||||
|
||||
[[nodiscard]] constexpr ClipboardWriteAction plan_clipboard_write(std::string_view) noexcept
|
||||
{
|
||||
return ClipboardWriteAction::write_text;
|
||||
}
|
||||
|
||||
}
|
||||
164
src/app_core/document_recording.h
Normal file
164
src/app_core/document_recording.h
Normal file
@@ -0,0 +1,164 @@
|
||||
#pragma once
|
||||
|
||||
#include "app_core/app_dialog.h"
|
||||
#include "foundation/result.h"
|
||||
|
||||
#include <cstddef>
|
||||
#include <limits>
|
||||
#include <string_view>
|
||||
|
||||
namespace pp::app {
|
||||
|
||||
enum class RecordingStartAction {
|
||||
start_thread,
|
||||
no_op_already_running,
|
||||
};
|
||||
|
||||
enum class RecordingStopAction {
|
||||
stop_thread,
|
||||
no_op_not_running,
|
||||
};
|
||||
|
||||
struct RecordingClearPlan {
|
||||
bool stop_running_recording = false;
|
||||
bool delete_recorded_files = false;
|
||||
int frame_count_after_clear = 0;
|
||||
};
|
||||
|
||||
struct RecordingExportPlan {
|
||||
std::size_t frame_count = 0;
|
||||
int progress_total = 0;
|
||||
};
|
||||
|
||||
struct RecordingWorkerIterationPlan {
|
||||
bool continue_running = true;
|
||||
bool encode_frame = false;
|
||||
bool clear_dirty_stroke = false;
|
||||
bool update_frame_label = false;
|
||||
};
|
||||
|
||||
class RecordingServices {
|
||||
public:
|
||||
virtual ~RecordingServices() = default;
|
||||
|
||||
virtual void start_thread() = 0;
|
||||
virtual void stop_thread() = 0;
|
||||
virtual void delete_recorded_files() = 0;
|
||||
virtual void set_frame_count(int frame_count) = 0;
|
||||
virtual void update_frame_label() = 0;
|
||||
virtual void begin_export(int progress_total) = 0;
|
||||
virtual void write_mp4(std::string_view path) = 0;
|
||||
virtual void end_export() = 0;
|
||||
};
|
||||
|
||||
[[nodiscard]] constexpr RecordingStartAction plan_recording_start(bool is_running) noexcept
|
||||
{
|
||||
return is_running
|
||||
? RecordingStartAction::no_op_already_running
|
||||
: RecordingStartAction::start_thread;
|
||||
}
|
||||
|
||||
[[nodiscard]] constexpr RecordingStopAction plan_recording_stop(bool is_running) noexcept
|
||||
{
|
||||
return is_running
|
||||
? RecordingStopAction::stop_thread
|
||||
: RecordingStopAction::no_op_not_running;
|
||||
}
|
||||
|
||||
[[nodiscard]] constexpr RecordingClearPlan plan_recording_clear(
|
||||
bool is_running,
|
||||
bool platform_deletes_recorded_files) noexcept
|
||||
{
|
||||
return {
|
||||
is_running,
|
||||
platform_deletes_recorded_files,
|
||||
0,
|
||||
};
|
||||
}
|
||||
|
||||
[[nodiscard]] constexpr RecordingExportPlan plan_recording_export(std::size_t frame_count) noexcept
|
||||
{
|
||||
const auto max_progress_total = static_cast<std::size_t>(std::numeric_limits<int>::max());
|
||||
return {
|
||||
frame_count,
|
||||
frame_count > max_progress_total ? std::numeric_limits<int>::max() : static_cast<int>(frame_count),
|
||||
};
|
||||
}
|
||||
|
||||
[[nodiscard]] inline AppProgressDialogPlan plan_recording_export_progress_dialog(
|
||||
const RecordingExportPlan& plan)
|
||||
{
|
||||
return plan_app_progress_dialog("Exporting MP4 movie", plan.progress_total);
|
||||
}
|
||||
|
||||
[[nodiscard]] constexpr RecordingWorkerIterationPlan plan_recording_worker_iteration(
|
||||
bool is_running_after_wake,
|
||||
bool has_encoder,
|
||||
bool has_canvas_document) noexcept
|
||||
{
|
||||
const bool encode = is_running_after_wake && has_encoder && has_canvas_document;
|
||||
return {
|
||||
.continue_running = is_running_after_wake,
|
||||
.encode_frame = encode,
|
||||
.clear_dirty_stroke = encode,
|
||||
.update_frame_label = encode,
|
||||
};
|
||||
}
|
||||
|
||||
[[nodiscard]] inline pp::foundation::Status execute_recording_start_action(
|
||||
RecordingStartAction action,
|
||||
RecordingServices& services)
|
||||
{
|
||||
switch (action) {
|
||||
case RecordingStartAction::start_thread:
|
||||
services.start_thread();
|
||||
return pp::foundation::Status::success();
|
||||
case RecordingStartAction::no_op_already_running:
|
||||
return pp::foundation::Status::success();
|
||||
}
|
||||
|
||||
return pp::foundation::Status::invalid_argument("unknown recording start action");
|
||||
}
|
||||
|
||||
[[nodiscard]] inline pp::foundation::Status execute_recording_stop_action(
|
||||
RecordingStopAction action,
|
||||
RecordingServices& services)
|
||||
{
|
||||
switch (action) {
|
||||
case RecordingStopAction::stop_thread:
|
||||
services.stop_thread();
|
||||
return pp::foundation::Status::success();
|
||||
case RecordingStopAction::no_op_not_running:
|
||||
return pp::foundation::Status::success();
|
||||
}
|
||||
|
||||
return pp::foundation::Status::invalid_argument("unknown recording stop action");
|
||||
}
|
||||
|
||||
[[nodiscard]] inline pp::foundation::Status execute_recording_clear_plan(
|
||||
const RecordingClearPlan& plan,
|
||||
RecordingServices& services)
|
||||
{
|
||||
if (plan.stop_running_recording) {
|
||||
services.stop_thread();
|
||||
}
|
||||
if (plan.delete_recorded_files) {
|
||||
services.delete_recorded_files();
|
||||
}
|
||||
services.set_frame_count(plan.frame_count_after_clear);
|
||||
services.update_frame_label();
|
||||
return pp::foundation::Status::success();
|
||||
}
|
||||
|
||||
[[nodiscard]] inline pp::foundation::Status execute_recording_export_plan(
|
||||
const RecordingExportPlan& plan,
|
||||
RecordingServices& services,
|
||||
std::string_view path)
|
||||
{
|
||||
services.begin_export(plan.progress_total);
|
||||
services.write_mp4(path);
|
||||
services.end_export();
|
||||
return pp::foundation::Status::success();
|
||||
}
|
||||
|
||||
}
|
||||
82
src/app_core/document_resize.h
Normal file
82
src/app_core/document_resize.h
Normal file
@@ -0,0 +1,82 @@
|
||||
#pragma once
|
||||
|
||||
#include "app_core/app_status.h"
|
||||
#include "foundation/result.h"
|
||||
|
||||
#include <string>
|
||||
#include <string_view>
|
||||
|
||||
namespace pp::app {
|
||||
|
||||
struct DocumentResizeDialogState {
|
||||
int current_resolution = 0;
|
||||
std::string current_resolution_text;
|
||||
int current_resolution_index = 0;
|
||||
};
|
||||
|
||||
struct DocumentResizePlan {
|
||||
int resolution = 0;
|
||||
int width = 0;
|
||||
int height = 0;
|
||||
bool clears_history = false;
|
||||
};
|
||||
|
||||
class DocumentResizeServices {
|
||||
public:
|
||||
virtual ~DocumentResizeServices() = default;
|
||||
|
||||
virtual void resize_document(int width, int height) = 0;
|
||||
virtual void update_title() = 0;
|
||||
virtual void clear_history() = 0;
|
||||
};
|
||||
|
||||
[[nodiscard]] inline DocumentResizeDialogState make_document_resize_dialog_state(
|
||||
int current_resolution)
|
||||
{
|
||||
const auto label = document_resolution_label(current_resolution);
|
||||
const auto index = document_resolution_to_index(current_resolution);
|
||||
std::string text = "Current: ";
|
||||
text.append(label ? std::string_view(label.value()) : std::string_view("unknown"));
|
||||
|
||||
return {
|
||||
current_resolution,
|
||||
text,
|
||||
index ? static_cast<int>(index.value()) : static_cast<int>(document_resolution_values.size()),
|
||||
};
|
||||
}
|
||||
|
||||
[[nodiscard]] inline pp::foundation::Result<DocumentResizePlan> plan_document_resize(
|
||||
int selected_resolution_index)
|
||||
{
|
||||
const auto resolution = display_resolution_from_index(selected_resolution_index);
|
||||
if (!resolution) {
|
||||
return pp::foundation::Result<DocumentResizePlan>::failure(resolution.status());
|
||||
}
|
||||
|
||||
const auto value = resolution.value();
|
||||
return pp::foundation::Result<DocumentResizePlan>::success(
|
||||
DocumentResizePlan {
|
||||
value,
|
||||
value,
|
||||
value,
|
||||
true,
|
||||
});
|
||||
}
|
||||
|
||||
[[nodiscard]] inline pp::foundation::Status execute_document_resize_plan(
|
||||
const DocumentResizePlan& plan,
|
||||
DocumentResizeServices& services)
|
||||
{
|
||||
if (plan.width <= 0 || plan.height <= 0) {
|
||||
return pp::foundation::Status::out_of_range("resize dimensions must be positive");
|
||||
}
|
||||
|
||||
services.resize_document(plan.width, plan.height);
|
||||
services.update_title();
|
||||
if (plan.clears_history) {
|
||||
services.clear_history();
|
||||
}
|
||||
return pp::foundation::Status::success();
|
||||
}
|
||||
|
||||
}
|
||||
67
src/app_core/document_route.cpp
Normal file
67
src/app_core/document_route.cpp
Normal file
@@ -0,0 +1,67 @@
|
||||
#include "app_core/document_route.h"
|
||||
|
||||
#include <cctype>
|
||||
#include <utility>
|
||||
|
||||
namespace pp::app {
|
||||
namespace {
|
||||
|
||||
[[nodiscard]] bool is_extension_char(char value) noexcept
|
||||
{
|
||||
const auto ch = static_cast<unsigned char>(value);
|
||||
return std::isalnum(ch) != 0 || value == '_';
|
||||
}
|
||||
|
||||
[[nodiscard]] std::string lowercase_ascii(std::string_view value)
|
||||
{
|
||||
std::string lowered;
|
||||
lowered.reserve(value.size());
|
||||
for (const char ch : value) {
|
||||
lowered.push_back(static_cast<char>(std::tolower(static_cast<unsigned char>(ch))));
|
||||
}
|
||||
return lowered;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
pp::foundation::Result<DocumentOpenRoute> classify_document_open_path(std::string_view path)
|
||||
{
|
||||
const auto separator = path.find_last_of("/\\");
|
||||
if (separator == std::string_view::npos || separator + 1U >= path.size()) {
|
||||
return pp::foundation::Result<DocumentOpenRoute>::failure(
|
||||
pp::foundation::Status::invalid_argument("document path must include a directory and file name"));
|
||||
}
|
||||
|
||||
const auto dot = path.find_last_of('.');
|
||||
if (dot == std::string_view::npos || dot <= separator + 1U || dot + 1U >= path.size()) {
|
||||
return pp::foundation::Result<DocumentOpenRoute>::failure(
|
||||
pp::foundation::Status::invalid_argument("document path must include a file extension"));
|
||||
}
|
||||
|
||||
const std::string_view extension = path.substr(dot + 1U);
|
||||
for (const char ch : extension) {
|
||||
if (!is_extension_char(ch)) {
|
||||
return pp::foundation::Result<DocumentOpenRoute>::failure(
|
||||
pp::foundation::Status::invalid_argument("document extension contains unsupported characters"));
|
||||
}
|
||||
}
|
||||
|
||||
auto lowered_extension = lowercase_ascii(extension);
|
||||
auto kind = DocumentOpenKind::open_project;
|
||||
if (lowered_extension == "abr") {
|
||||
kind = DocumentOpenKind::import_abr;
|
||||
} else if (lowered_extension == "ppbr") {
|
||||
kind = DocumentOpenKind::import_ppbr;
|
||||
}
|
||||
|
||||
return pp::foundation::Result<DocumentOpenRoute>::success(
|
||||
DocumentOpenRoute {
|
||||
.kind = kind,
|
||||
.path = std::string(path),
|
||||
.directory = std::string(path.substr(0U, separator)),
|
||||
.name = std::string(path.substr(separator + 1U, dot - separator - 1U)),
|
||||
.extension = std::move(lowered_extension),
|
||||
});
|
||||
}
|
||||
|
||||
}
|
||||
27
src/app_core/document_route.h
Normal file
27
src/app_core/document_route.h
Normal file
@@ -0,0 +1,27 @@
|
||||
#pragma once
|
||||
|
||||
#include "foundation/result.h"
|
||||
|
||||
#include <string>
|
||||
#include <string_view>
|
||||
|
||||
namespace pp::app {
|
||||
|
||||
enum class DocumentOpenKind {
|
||||
import_abr,
|
||||
import_ppbr,
|
||||
open_project,
|
||||
};
|
||||
|
||||
struct DocumentOpenRoute {
|
||||
DocumentOpenKind kind = DocumentOpenKind::open_project;
|
||||
std::string path;
|
||||
std::string directory;
|
||||
std::string name;
|
||||
std::string extension;
|
||||
};
|
||||
|
||||
[[nodiscard]] pp::foundation::Result<DocumentOpenRoute> classify_document_open_path(
|
||||
std::string_view path);
|
||||
|
||||
}
|
||||
1
src/app_core/document_session.cpp
Normal file
1
src/app_core/document_session.cpp
Normal file
@@ -0,0 +1 @@
|
||||
#include "app_core/document_session.h"
|
||||
567
src/app_core/document_session.h
Normal file
567
src/app_core/document_session.h
Normal file
@@ -0,0 +1,567 @@
|
||||
#pragma once
|
||||
|
||||
#include "app_core/app_dialog.h"
|
||||
#include "app_core/document_route.h"
|
||||
#include "foundation/result.h"
|
||||
|
||||
#include <array>
|
||||
#include <cctype>
|
||||
#include <cstdio>
|
||||
#include <string>
|
||||
#include <string_view>
|
||||
#include <utility>
|
||||
|
||||
namespace pp::app {
|
||||
|
||||
enum class ProjectOpenDecision {
|
||||
open_now,
|
||||
prompt_discard_unsaved,
|
||||
};
|
||||
|
||||
enum class CloseRequestDecision {
|
||||
close_now,
|
||||
show_unsaved_prompt,
|
||||
wait_for_existing_prompt,
|
||||
};
|
||||
|
||||
enum class DocumentSaveIntent {
|
||||
save,
|
||||
save_as,
|
||||
save_version,
|
||||
save_dirty_version,
|
||||
};
|
||||
|
||||
enum class DocumentSaveDecision {
|
||||
no_op,
|
||||
show_save_dialog,
|
||||
save_existing,
|
||||
save_version,
|
||||
};
|
||||
|
||||
enum class DocumentWorkflowDecision {
|
||||
unavailable,
|
||||
continue_now,
|
||||
prompt_save_before_continue,
|
||||
};
|
||||
|
||||
enum class DocumentFileWriteDecision {
|
||||
save_now,
|
||||
prompt_overwrite,
|
||||
};
|
||||
|
||||
enum class DocumentOpenPlanAction {
|
||||
open_project_now,
|
||||
prompt_discard_unsaved_project,
|
||||
prompt_import_abr,
|
||||
prompt_import_ppbr,
|
||||
};
|
||||
|
||||
enum class DocumentSessionPromptKind {
|
||||
close_unsaved_document,
|
||||
save_before_workflow_continue,
|
||||
new_document_overwrite,
|
||||
document_file_overwrite,
|
||||
document_save_error,
|
||||
};
|
||||
|
||||
class DocumentOpenServices {
|
||||
public:
|
||||
virtual ~DocumentOpenServices() = default;
|
||||
|
||||
virtual void prompt_import_abr(const DocumentOpenRoute& route) = 0;
|
||||
virtual void prompt_import_ppbr(const DocumentOpenRoute& route) = 0;
|
||||
virtual void open_project_now(const DocumentOpenRoute& route) = 0;
|
||||
virtual void prompt_discard_unsaved_project(const DocumentOpenRoute& route) = 0;
|
||||
};
|
||||
|
||||
class CloseRequestServices {
|
||||
public:
|
||||
virtual ~CloseRequestServices() = default;
|
||||
|
||||
virtual void request_close_now() = 0;
|
||||
virtual void show_unsaved_close_prompt() = 0;
|
||||
};
|
||||
|
||||
class DocumentSaveServices {
|
||||
public:
|
||||
virtual ~DocumentSaveServices() = default;
|
||||
|
||||
virtual void show_save_dialog() = 0;
|
||||
virtual void save_existing_document() = 0;
|
||||
virtual void save_document_version() = 0;
|
||||
};
|
||||
|
||||
class DocumentWorkflowServices {
|
||||
public:
|
||||
virtual ~DocumentWorkflowServices() = default;
|
||||
|
||||
virtual void continue_workflow_now() = 0;
|
||||
virtual void prompt_save_before_continue() = 0;
|
||||
};
|
||||
|
||||
struct DocumentFileTarget {
|
||||
std::string name;
|
||||
std::string directory;
|
||||
std::string path;
|
||||
};
|
||||
|
||||
struct DocumentVersionTarget {
|
||||
std::string name;
|
||||
std::string path;
|
||||
};
|
||||
|
||||
struct DocumentFileSavePlan {
|
||||
DocumentFileTarget target;
|
||||
DocumentFileWriteDecision write_decision = DocumentFileWriteDecision::save_now;
|
||||
};
|
||||
|
||||
class DocumentFileSaveServices {
|
||||
public:
|
||||
virtual ~DocumentFileSaveServices() = default;
|
||||
|
||||
virtual void save_document_file(const DocumentFileSavePlan& plan) = 0;
|
||||
virtual void prompt_overwrite_document_file(const DocumentFileSavePlan& plan) = 0;
|
||||
};
|
||||
|
||||
class DocumentVersionSaveServices {
|
||||
public:
|
||||
virtual ~DocumentVersionSaveServices() = default;
|
||||
|
||||
virtual void save_document_version(const DocumentVersionTarget& target) = 0;
|
||||
};
|
||||
|
||||
struct NewDocumentPlan {
|
||||
DocumentFileTarget target;
|
||||
int resolution = 0;
|
||||
DocumentFileWriteDecision write_decision = DocumentFileWriteDecision::save_now;
|
||||
};
|
||||
|
||||
class NewDocumentServices {
|
||||
public:
|
||||
virtual ~NewDocumentServices() = default;
|
||||
|
||||
virtual void create_new_document(const NewDocumentPlan& plan) = 0;
|
||||
virtual void prompt_overwrite_new_document(const NewDocumentPlan& plan) = 0;
|
||||
};
|
||||
|
||||
[[nodiscard]] inline AppMessageDialogPlan plan_document_session_prompt(
|
||||
DocumentSessionPromptKind kind,
|
||||
std::string_view document_name = {})
|
||||
{
|
||||
switch (kind) {
|
||||
case DocumentSessionPromptKind::close_unsaved_document:
|
||||
return plan_app_message_dialog(
|
||||
"Unsaved document",
|
||||
"Do you want to close without saving?",
|
||||
true,
|
||||
"Yes",
|
||||
"No");
|
||||
case DocumentSessionPromptKind::save_before_workflow_continue:
|
||||
return plan_app_message_dialog(
|
||||
"Unsaved document",
|
||||
"Would you like to save this document before closing?",
|
||||
true,
|
||||
"Yes",
|
||||
"No");
|
||||
case DocumentSessionPromptKind::new_document_overwrite:
|
||||
return plan_app_message_dialog(
|
||||
"Warning",
|
||||
"A document with this name already exists, continue?",
|
||||
true);
|
||||
case DocumentSessionPromptKind::document_file_overwrite:
|
||||
{
|
||||
std::string message = "Are you sure you want to overwrite ";
|
||||
message += document_name;
|
||||
message += "?";
|
||||
return plan_app_message_dialog("Warning", message, true);
|
||||
}
|
||||
case DocumentSessionPromptKind::document_save_error:
|
||||
return plan_app_message_dialog(
|
||||
"Saving Error",
|
||||
"There was a problem saving the document",
|
||||
false);
|
||||
}
|
||||
|
||||
return plan_app_message_dialog("Warning", "", false);
|
||||
}
|
||||
|
||||
[[nodiscard]] constexpr ProjectOpenDecision plan_project_open(bool has_unsaved_changes) noexcept
|
||||
{
|
||||
return has_unsaved_changes
|
||||
? ProjectOpenDecision::prompt_discard_unsaved
|
||||
: ProjectOpenDecision::open_now;
|
||||
}
|
||||
|
||||
[[nodiscard]] constexpr DocumentOpenPlanAction plan_document_open(
|
||||
DocumentOpenKind kind,
|
||||
bool has_unsaved_changes) noexcept
|
||||
{
|
||||
switch (kind) {
|
||||
case DocumentOpenKind::import_abr:
|
||||
return DocumentOpenPlanAction::prompt_import_abr;
|
||||
case DocumentOpenKind::import_ppbr:
|
||||
return DocumentOpenPlanAction::prompt_import_ppbr;
|
||||
case DocumentOpenKind::open_project:
|
||||
return has_unsaved_changes
|
||||
? DocumentOpenPlanAction::prompt_discard_unsaved_project
|
||||
: DocumentOpenPlanAction::open_project_now;
|
||||
}
|
||||
|
||||
return DocumentOpenPlanAction::open_project_now;
|
||||
}
|
||||
|
||||
[[nodiscard]] inline pp::foundation::Status execute_document_open_plan(
|
||||
DocumentOpenPlanAction action,
|
||||
const DocumentOpenRoute& route,
|
||||
DocumentOpenServices& services)
|
||||
{
|
||||
switch (action) {
|
||||
case DocumentOpenPlanAction::open_project_now:
|
||||
if (route.kind != DocumentOpenKind::open_project) {
|
||||
return pp::foundation::Status::invalid_argument("open-project action requires a project route");
|
||||
}
|
||||
services.open_project_now(route);
|
||||
return pp::foundation::Status::success();
|
||||
case DocumentOpenPlanAction::prompt_discard_unsaved_project:
|
||||
if (route.kind != DocumentOpenKind::open_project) {
|
||||
return pp::foundation::Status::invalid_argument("discard prompt requires a project route");
|
||||
}
|
||||
services.prompt_discard_unsaved_project(route);
|
||||
return pp::foundation::Status::success();
|
||||
case DocumentOpenPlanAction::prompt_import_abr:
|
||||
if (route.kind != DocumentOpenKind::import_abr) {
|
||||
return pp::foundation::Status::invalid_argument("ABR import prompt requires an ABR route");
|
||||
}
|
||||
services.prompt_import_abr(route);
|
||||
return pp::foundation::Status::success();
|
||||
case DocumentOpenPlanAction::prompt_import_ppbr:
|
||||
if (route.kind != DocumentOpenKind::import_ppbr) {
|
||||
return pp::foundation::Status::invalid_argument("PPBR import prompt requires a PPBR route");
|
||||
}
|
||||
services.prompt_import_ppbr(route);
|
||||
return pp::foundation::Status::success();
|
||||
}
|
||||
|
||||
return pp::foundation::Status::invalid_argument("unknown document open action");
|
||||
}
|
||||
|
||||
[[nodiscard]] constexpr CloseRequestDecision plan_close_request(
|
||||
bool has_unsaved_changes,
|
||||
bool close_prompt_already_open) noexcept
|
||||
{
|
||||
if (!has_unsaved_changes) {
|
||||
return CloseRequestDecision::close_now;
|
||||
}
|
||||
|
||||
return close_prompt_already_open
|
||||
? CloseRequestDecision::wait_for_existing_prompt
|
||||
: CloseRequestDecision::show_unsaved_prompt;
|
||||
}
|
||||
|
||||
[[nodiscard]] inline pp::foundation::Status execute_close_request_decision(
|
||||
CloseRequestDecision decision,
|
||||
CloseRequestServices& services)
|
||||
{
|
||||
switch (decision) {
|
||||
case CloseRequestDecision::close_now:
|
||||
services.request_close_now();
|
||||
return pp::foundation::Status::success();
|
||||
case CloseRequestDecision::show_unsaved_prompt:
|
||||
services.show_unsaved_close_prompt();
|
||||
return pp::foundation::Status::success();
|
||||
case CloseRequestDecision::wait_for_existing_prompt:
|
||||
return pp::foundation::Status::success();
|
||||
}
|
||||
|
||||
return pp::foundation::Status::invalid_argument("unknown close request decision");
|
||||
}
|
||||
|
||||
[[nodiscard]] constexpr DocumentSaveDecision plan_document_save(
|
||||
bool is_new_document,
|
||||
bool has_unsaved_changes,
|
||||
DocumentSaveIntent intent) noexcept
|
||||
{
|
||||
switch (intent) {
|
||||
case DocumentSaveIntent::save:
|
||||
if (is_new_document) {
|
||||
return DocumentSaveDecision::show_save_dialog;
|
||||
}
|
||||
return has_unsaved_changes
|
||||
? DocumentSaveDecision::save_existing
|
||||
: DocumentSaveDecision::no_op;
|
||||
case DocumentSaveIntent::save_as:
|
||||
return DocumentSaveDecision::show_save_dialog;
|
||||
case DocumentSaveIntent::save_version:
|
||||
return is_new_document
|
||||
? DocumentSaveDecision::show_save_dialog
|
||||
: DocumentSaveDecision::save_version;
|
||||
case DocumentSaveIntent::save_dirty_version:
|
||||
if (is_new_document) {
|
||||
return DocumentSaveDecision::show_save_dialog;
|
||||
}
|
||||
return has_unsaved_changes
|
||||
? DocumentSaveDecision::save_version
|
||||
: DocumentSaveDecision::no_op;
|
||||
}
|
||||
|
||||
return DocumentSaveDecision::no_op;
|
||||
}
|
||||
|
||||
[[nodiscard]] inline pp::foundation::Status execute_document_save_decision(
|
||||
DocumentSaveDecision decision,
|
||||
DocumentSaveServices& services)
|
||||
{
|
||||
switch (decision) {
|
||||
case DocumentSaveDecision::no_op:
|
||||
return pp::foundation::Status::success();
|
||||
case DocumentSaveDecision::show_save_dialog:
|
||||
services.show_save_dialog();
|
||||
return pp::foundation::Status::success();
|
||||
case DocumentSaveDecision::save_existing:
|
||||
services.save_existing_document();
|
||||
return pp::foundation::Status::success();
|
||||
case DocumentSaveDecision::save_version:
|
||||
services.save_document_version();
|
||||
return pp::foundation::Status::success();
|
||||
}
|
||||
|
||||
return pp::foundation::Status::invalid_argument("unknown document save decision");
|
||||
}
|
||||
|
||||
[[nodiscard]] constexpr DocumentWorkflowDecision plan_document_workflow(
|
||||
bool has_canvas,
|
||||
bool has_unsaved_changes) noexcept
|
||||
{
|
||||
if (!has_canvas) {
|
||||
return DocumentWorkflowDecision::unavailable;
|
||||
}
|
||||
|
||||
return has_unsaved_changes
|
||||
? DocumentWorkflowDecision::prompt_save_before_continue
|
||||
: DocumentWorkflowDecision::continue_now;
|
||||
}
|
||||
|
||||
[[nodiscard]] inline pp::foundation::Status execute_document_workflow_decision(
|
||||
DocumentWorkflowDecision decision,
|
||||
DocumentWorkflowServices& services)
|
||||
{
|
||||
switch (decision) {
|
||||
case DocumentWorkflowDecision::unavailable:
|
||||
return pp::foundation::Status::success();
|
||||
case DocumentWorkflowDecision::continue_now:
|
||||
services.continue_workflow_now();
|
||||
return pp::foundation::Status::success();
|
||||
case DocumentWorkflowDecision::prompt_save_before_continue:
|
||||
services.prompt_save_before_continue();
|
||||
return pp::foundation::Status::success();
|
||||
}
|
||||
|
||||
return pp::foundation::Status::invalid_argument("unknown document workflow decision");
|
||||
}
|
||||
|
||||
[[nodiscard]] inline pp::foundation::Result<DocumentFileTarget> make_document_file_target(
|
||||
std::string_view work_directory,
|
||||
std::string_view document_name)
|
||||
{
|
||||
if (document_name.empty()) {
|
||||
return pp::foundation::Result<DocumentFileTarget>::failure(
|
||||
pp::foundation::Status::invalid_argument("document name must not be empty"));
|
||||
}
|
||||
|
||||
DocumentFileTarget target;
|
||||
target.name = std::string(document_name);
|
||||
target.directory = std::string(work_directory);
|
||||
target.path.reserve(target.directory.size() + target.name.size() + 5);
|
||||
target.path += target.directory;
|
||||
target.path += "/";
|
||||
target.path += target.name;
|
||||
target.path += ".ppi";
|
||||
return pp::foundation::Result<DocumentFileTarget>::success(std::move(target));
|
||||
}
|
||||
|
||||
[[nodiscard]] constexpr DocumentFileWriteDecision plan_document_file_write(
|
||||
bool target_exists) noexcept
|
||||
{
|
||||
return target_exists
|
||||
? DocumentFileWriteDecision::prompt_overwrite
|
||||
: DocumentFileWriteDecision::save_now;
|
||||
}
|
||||
|
||||
template <typename ExistsPredicate>
|
||||
[[nodiscard]] pp::foundation::Result<DocumentFileSavePlan> plan_document_file_save(
|
||||
std::string_view work_directory,
|
||||
std::string_view document_name,
|
||||
ExistsPredicate&& exists)
|
||||
{
|
||||
auto target = make_document_file_target(work_directory, document_name);
|
||||
if (!target) {
|
||||
return pp::foundation::Result<DocumentFileSavePlan>::failure(target.status());
|
||||
}
|
||||
|
||||
DocumentFileSavePlan plan;
|
||||
plan.target = std::move(target.value());
|
||||
plan.write_decision = plan_document_file_write(exists(plan.target.path));
|
||||
return pp::foundation::Result<DocumentFileSavePlan>::success(std::move(plan));
|
||||
}
|
||||
|
||||
[[nodiscard]] inline pp::foundation::Status execute_document_file_save_plan(
|
||||
const DocumentFileSavePlan& plan,
|
||||
DocumentFileSaveServices& services)
|
||||
{
|
||||
switch (plan.write_decision) {
|
||||
case DocumentFileWriteDecision::save_now:
|
||||
services.save_document_file(plan);
|
||||
return pp::foundation::Status::success();
|
||||
case DocumentFileWriteDecision::prompt_overwrite:
|
||||
services.prompt_overwrite_document_file(plan);
|
||||
return pp::foundation::Status::success();
|
||||
}
|
||||
|
||||
return pp::foundation::Status::invalid_argument("unknown document file save write decision");
|
||||
}
|
||||
|
||||
[[nodiscard]] constexpr pp::foundation::Result<int> document_resolution_from_index(int index) noexcept
|
||||
{
|
||||
constexpr std::array<int, 6> resolutions{ 512, 1024, 1536, 2048, 4096, 8192 };
|
||||
if (index < 0 || static_cast<std::size_t>(index) >= resolutions.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(resolutions[static_cast<std::size_t>(index)]);
|
||||
}
|
||||
|
||||
template <typename ExistsPredicate>
|
||||
[[nodiscard]] pp::foundation::Result<NewDocumentPlan> plan_new_document(
|
||||
std::string_view work_directory,
|
||||
std::string_view document_name,
|
||||
int resolution_index,
|
||||
ExistsPredicate&& exists)
|
||||
{
|
||||
const auto resolution = document_resolution_from_index(resolution_index);
|
||||
if (!resolution) {
|
||||
return pp::foundation::Result<NewDocumentPlan>::failure(resolution.status());
|
||||
}
|
||||
|
||||
auto save_plan = plan_document_file_save(
|
||||
work_directory,
|
||||
document_name,
|
||||
std::forward<ExistsPredicate>(exists));
|
||||
if (!save_plan) {
|
||||
return pp::foundation::Result<NewDocumentPlan>::failure(save_plan.status());
|
||||
}
|
||||
|
||||
NewDocumentPlan plan;
|
||||
plan.target = std::move(save_plan.value().target);
|
||||
plan.resolution = resolution.value();
|
||||
plan.write_decision = save_plan.value().write_decision;
|
||||
return pp::foundation::Result<NewDocumentPlan>::success(std::move(plan));
|
||||
}
|
||||
|
||||
[[nodiscard]] inline pp::foundation::Status execute_new_document_plan(
|
||||
const NewDocumentPlan& plan,
|
||||
NewDocumentServices& services)
|
||||
{
|
||||
switch (plan.write_decision) {
|
||||
case DocumentFileWriteDecision::save_now:
|
||||
services.create_new_document(plan);
|
||||
return pp::foundation::Status::success();
|
||||
case DocumentFileWriteDecision::prompt_overwrite:
|
||||
services.prompt_overwrite_new_document(plan);
|
||||
return pp::foundation::Status::success();
|
||||
}
|
||||
|
||||
return pp::foundation::Status::invalid_argument("unknown new document write decision");
|
||||
}
|
||||
|
||||
[[nodiscard]] inline bool has_legacy_two_character_version_suffix(std::string_view document_name) noexcept
|
||||
{
|
||||
const auto dot = document_name.rfind('.');
|
||||
if (dot == std::string_view::npos || dot + 3 != document_name.size()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const auto is_word = [](char ch) noexcept {
|
||||
return std::isalnum(static_cast<unsigned char>(ch)) != 0 || ch == '_';
|
||||
};
|
||||
return is_word(document_name[dot + 1]) && is_word(document_name[dot + 2]);
|
||||
}
|
||||
|
||||
[[nodiscard]] inline int legacy_version_number(std::string_view suffix) noexcept
|
||||
{
|
||||
int value = 0;
|
||||
for (const char ch : suffix) {
|
||||
if (ch < '0' || ch > '9') {
|
||||
break;
|
||||
}
|
||||
|
||||
value = value * 10 + (ch - '0');
|
||||
}
|
||||
return value;
|
||||
}
|
||||
|
||||
[[nodiscard]] inline std::string make_legacy_version_name(std::string_view base_name, int version)
|
||||
{
|
||||
char suffix[4] {};
|
||||
std::snprintf(suffix, sizeof(suffix), ".%02d", version);
|
||||
std::string name;
|
||||
name.reserve(base_name.size() + 3);
|
||||
name += base_name;
|
||||
name += suffix;
|
||||
return name;
|
||||
}
|
||||
|
||||
template <typename ExistsPredicate>
|
||||
[[nodiscard]] pp::foundation::Result<DocumentVersionTarget> find_next_document_version_target(
|
||||
std::string_view directory,
|
||||
std::string_view document_name,
|
||||
ExistsPredicate&& exists)
|
||||
{
|
||||
if (directory.empty()) {
|
||||
return pp::foundation::Result<DocumentVersionTarget>::failure(
|
||||
pp::foundation::Status::invalid_argument("directory must not be empty"));
|
||||
}
|
||||
|
||||
if (document_name.empty()) {
|
||||
return pp::foundation::Result<DocumentVersionTarget>::failure(
|
||||
pp::foundation::Status::invalid_argument("document name must not be empty"));
|
||||
}
|
||||
|
||||
int current = 0;
|
||||
std::string_view base = document_name;
|
||||
if (has_legacy_two_character_version_suffix(document_name)) {
|
||||
const auto dot = document_name.rfind('.');
|
||||
base = document_name.substr(0, dot);
|
||||
current = legacy_version_number(document_name.substr(dot + 1));
|
||||
}
|
||||
|
||||
for (int version = current + 1; version < 99; ++version) {
|
||||
DocumentVersionTarget target;
|
||||
target.name = make_legacy_version_name(base, version);
|
||||
target.path.reserve(directory.size() + target.name.size() + 5);
|
||||
target.path += directory;
|
||||
target.path += "/";
|
||||
target.path += target.name;
|
||||
target.path += ".ppi";
|
||||
if (!exists(target.path)) {
|
||||
return pp::foundation::Result<DocumentVersionTarget>::success(std::move(target));
|
||||
}
|
||||
}
|
||||
|
||||
return pp::foundation::Result<DocumentVersionTarget>::failure(
|
||||
pp::foundation::Status::out_of_range("no available document version target"));
|
||||
}
|
||||
|
||||
[[nodiscard]] inline pp::foundation::Status execute_document_version_save(
|
||||
const DocumentVersionTarget& target,
|
||||
DocumentVersionSaveServices& services)
|
||||
{
|
||||
if (target.name.empty() || target.path.empty()) {
|
||||
return pp::foundation::Status::invalid_argument("document version target requires a name and path");
|
||||
}
|
||||
|
||||
services.save_document_version(target);
|
||||
return pp::foundation::Status::success();
|
||||
}
|
||||
|
||||
}
|
||||
19
src/app_core/document_sharing.h
Normal file
19
src/app_core/document_sharing.h
Normal file
@@ -0,0 +1,19 @@
|
||||
#pragma once
|
||||
|
||||
#include <string_view>
|
||||
|
||||
namespace pp::app {
|
||||
|
||||
enum class DocumentShareAction {
|
||||
show_save_required_warning,
|
||||
share_now,
|
||||
};
|
||||
|
||||
[[nodiscard]] constexpr DocumentShareAction plan_document_share(std::string_view path) noexcept
|
||||
{
|
||||
return path.empty()
|
||||
? DocumentShareAction::show_save_required_warning
|
||||
: DocumentShareAction::share_now;
|
||||
}
|
||||
|
||||
}
|
||||
209
src/app_core/file_menu.h
Normal file
209
src/app_core/file_menu.h
Normal file
@@ -0,0 +1,209 @@
|
||||
#pragma once
|
||||
|
||||
#include "app_core/document_export.h"
|
||||
#include "app_core/document_session.h"
|
||||
#include "foundation/result.h"
|
||||
|
||||
#include <string_view>
|
||||
|
||||
namespace pp::app {
|
||||
|
||||
enum class FileMenuCommand {
|
||||
new_document,
|
||||
import_image,
|
||||
open_project,
|
||||
browse_cloud,
|
||||
save,
|
||||
save_as,
|
||||
save_version,
|
||||
export_jpeg,
|
||||
export_submenu,
|
||||
share,
|
||||
resize,
|
||||
cloud_upload,
|
||||
cloud_browse,
|
||||
};
|
||||
|
||||
enum class FileMenuAction {
|
||||
show_new_document_dialog,
|
||||
pick_image_for_import,
|
||||
pick_project_file,
|
||||
show_cloud_browser_dialog,
|
||||
save_document,
|
||||
show_export_jpeg_dialog,
|
||||
show_export_submenu,
|
||||
share_document,
|
||||
show_resize_dialog,
|
||||
upload_to_cloud,
|
||||
browse_cloud_documents,
|
||||
};
|
||||
|
||||
struct FileMenuPlan {
|
||||
FileMenuCommand command = FileMenuCommand::new_document;
|
||||
FileMenuAction action = FileMenuAction::show_new_document_dialog;
|
||||
DocumentSaveIntent save_intent = DocumentSaveIntent::save;
|
||||
DocumentExportMenuKind export_kind = DocumentExportMenuKind::jpeg;
|
||||
};
|
||||
|
||||
class FileMenuServices {
|
||||
public:
|
||||
virtual ~FileMenuServices() = default;
|
||||
|
||||
virtual void show_new_document_dialog() = 0;
|
||||
virtual void pick_image_for_import() = 0;
|
||||
virtual void pick_project_file() = 0;
|
||||
virtual void show_cloud_browser_dialog() = 0;
|
||||
virtual void save_document(DocumentSaveIntent intent) = 0;
|
||||
virtual void show_export_jpeg_dialog(DocumentExportMenuKind kind) = 0;
|
||||
virtual void show_export_submenu() = 0;
|
||||
virtual void share_document() = 0;
|
||||
virtual void show_resize_dialog() = 0;
|
||||
virtual void upload_to_cloud() = 0;
|
||||
virtual void browse_cloud_documents() = 0;
|
||||
};
|
||||
|
||||
[[nodiscard]] constexpr FileMenuPlan plan_file_menu_command(FileMenuCommand command) noexcept
|
||||
{
|
||||
FileMenuPlan plan;
|
||||
plan.command = command;
|
||||
|
||||
switch (command) {
|
||||
case FileMenuCommand::new_document:
|
||||
plan.action = FileMenuAction::show_new_document_dialog;
|
||||
break;
|
||||
case FileMenuCommand::import_image:
|
||||
plan.action = FileMenuAction::pick_image_for_import;
|
||||
break;
|
||||
case FileMenuCommand::open_project:
|
||||
plan.action = FileMenuAction::pick_project_file;
|
||||
break;
|
||||
case FileMenuCommand::browse_cloud:
|
||||
plan.action = FileMenuAction::show_cloud_browser_dialog;
|
||||
break;
|
||||
case FileMenuCommand::save:
|
||||
plan.action = FileMenuAction::save_document;
|
||||
plan.save_intent = DocumentSaveIntent::save;
|
||||
break;
|
||||
case FileMenuCommand::save_as:
|
||||
plan.action = FileMenuAction::save_document;
|
||||
plan.save_intent = DocumentSaveIntent::save_as;
|
||||
break;
|
||||
case FileMenuCommand::save_version:
|
||||
plan.action = FileMenuAction::save_document;
|
||||
plan.save_intent = DocumentSaveIntent::save_version;
|
||||
break;
|
||||
case FileMenuCommand::export_jpeg:
|
||||
plan.action = FileMenuAction::show_export_jpeg_dialog;
|
||||
plan.export_kind = DocumentExportMenuKind::jpeg;
|
||||
break;
|
||||
case FileMenuCommand::export_submenu:
|
||||
plan.action = FileMenuAction::show_export_submenu;
|
||||
break;
|
||||
case FileMenuCommand::share:
|
||||
plan.action = FileMenuAction::share_document;
|
||||
break;
|
||||
case FileMenuCommand::resize:
|
||||
plan.action = FileMenuAction::show_resize_dialog;
|
||||
break;
|
||||
case FileMenuCommand::cloud_upload:
|
||||
plan.action = FileMenuAction::upload_to_cloud;
|
||||
break;
|
||||
case FileMenuCommand::cloud_browse:
|
||||
plan.action = FileMenuAction::browse_cloud_documents;
|
||||
break;
|
||||
}
|
||||
|
||||
return plan;
|
||||
}
|
||||
|
||||
[[nodiscard]] inline pp::foundation::Result<FileMenuCommand> parse_file_menu_command(
|
||||
std::string_view command) noexcept
|
||||
{
|
||||
if (command == "new" || command == "new-document") {
|
||||
return pp::foundation::Result<FileMenuCommand>::success(FileMenuCommand::new_document);
|
||||
}
|
||||
if (command == "import" || command == "import-image") {
|
||||
return pp::foundation::Result<FileMenuCommand>::success(FileMenuCommand::import_image);
|
||||
}
|
||||
if (command == "open" || command == "open-project") {
|
||||
return pp::foundation::Result<FileMenuCommand>::success(FileMenuCommand::open_project);
|
||||
}
|
||||
if (command == "browse") {
|
||||
return pp::foundation::Result<FileMenuCommand>::success(FileMenuCommand::browse_cloud);
|
||||
}
|
||||
if (command == "save") {
|
||||
return pp::foundation::Result<FileMenuCommand>::success(FileMenuCommand::save);
|
||||
}
|
||||
if (command == "save-as") {
|
||||
return pp::foundation::Result<FileMenuCommand>::success(FileMenuCommand::save_as);
|
||||
}
|
||||
if (command == "save-version") {
|
||||
return pp::foundation::Result<FileMenuCommand>::success(FileMenuCommand::save_version);
|
||||
}
|
||||
if (command == "export" || command == "export-jpeg") {
|
||||
return pp::foundation::Result<FileMenuCommand>::success(FileMenuCommand::export_jpeg);
|
||||
}
|
||||
if (command == "export-submenu") {
|
||||
return pp::foundation::Result<FileMenuCommand>::success(FileMenuCommand::export_submenu);
|
||||
}
|
||||
if (command == "share") {
|
||||
return pp::foundation::Result<FileMenuCommand>::success(FileMenuCommand::share);
|
||||
}
|
||||
if (command == "resize") {
|
||||
return pp::foundation::Result<FileMenuCommand>::success(FileMenuCommand::resize);
|
||||
}
|
||||
if (command == "cloud-upload") {
|
||||
return pp::foundation::Result<FileMenuCommand>::success(FileMenuCommand::cloud_upload);
|
||||
}
|
||||
if (command == "cloud-browse") {
|
||||
return pp::foundation::Result<FileMenuCommand>::success(FileMenuCommand::cloud_browse);
|
||||
}
|
||||
|
||||
return pp::foundation::Result<FileMenuCommand>::failure(
|
||||
pp::foundation::Status::invalid_argument("unknown file menu command"));
|
||||
}
|
||||
|
||||
[[nodiscard]] inline pp::foundation::Status execute_file_menu_plan(
|
||||
const FileMenuPlan& plan,
|
||||
FileMenuServices& services)
|
||||
{
|
||||
switch (plan.action) {
|
||||
case FileMenuAction::show_new_document_dialog:
|
||||
services.show_new_document_dialog();
|
||||
return pp::foundation::Status::success();
|
||||
case FileMenuAction::pick_image_for_import:
|
||||
services.pick_image_for_import();
|
||||
return pp::foundation::Status::success();
|
||||
case FileMenuAction::pick_project_file:
|
||||
services.pick_project_file();
|
||||
return pp::foundation::Status::success();
|
||||
case FileMenuAction::show_cloud_browser_dialog:
|
||||
services.show_cloud_browser_dialog();
|
||||
return pp::foundation::Status::success();
|
||||
case FileMenuAction::save_document:
|
||||
services.save_document(plan.save_intent);
|
||||
return pp::foundation::Status::success();
|
||||
case FileMenuAction::show_export_jpeg_dialog:
|
||||
services.show_export_jpeg_dialog(plan.export_kind);
|
||||
return pp::foundation::Status::success();
|
||||
case FileMenuAction::show_export_submenu:
|
||||
services.show_export_submenu();
|
||||
return pp::foundation::Status::success();
|
||||
case FileMenuAction::share_document:
|
||||
services.share_document();
|
||||
return pp::foundation::Status::success();
|
||||
case FileMenuAction::show_resize_dialog:
|
||||
services.show_resize_dialog();
|
||||
return pp::foundation::Status::success();
|
||||
case FileMenuAction::upload_to_cloud:
|
||||
services.upload_to_cloud();
|
||||
return pp::foundation::Status::success();
|
||||
case FileMenuAction::browse_cloud_documents:
|
||||
services.browse_cloud_documents();
|
||||
return pp::foundation::Status::success();
|
||||
}
|
||||
|
||||
return pp::foundation::Status::invalid_argument("unknown file menu action");
|
||||
}
|
||||
|
||||
} // namespace pp::app
|
||||
209
src/app_core/grid_ui.h
Normal file
209
src/app_core/grid_ui.h
Normal file
@@ -0,0 +1,209 @@
|
||||
#pragma once
|
||||
|
||||
#include "foundation/result.h"
|
||||
|
||||
#include <string>
|
||||
#include <string_view>
|
||||
#include <utility>
|
||||
|
||||
namespace pp::app {
|
||||
|
||||
enum class GridUiOperation {
|
||||
request_heightmap_pick,
|
||||
load_heightmap,
|
||||
clear_heightmap,
|
||||
reload_heightmap,
|
||||
render_lightmap,
|
||||
commit_heightmap,
|
||||
};
|
||||
|
||||
struct GridUiPlan {
|
||||
GridUiOperation operation = GridUiOperation::request_heightmap_pick;
|
||||
std::string path;
|
||||
int texture_resolution = 0;
|
||||
int sample_count = 0;
|
||||
bool opens_picker = false;
|
||||
bool loads_heightmap = false;
|
||||
bool clears_heightmap = false;
|
||||
bool renders_lightmap = false;
|
||||
bool commits_heightmap = false;
|
||||
bool updates_preview = false;
|
||||
bool updates_ground_opacity = false;
|
||||
bool updates_shading_mode = false;
|
||||
bool shows_unsupported_message = false;
|
||||
bool shows_progress = false;
|
||||
bool mutates_grid_state = false;
|
||||
};
|
||||
|
||||
class GridUiServices {
|
||||
public:
|
||||
virtual ~GridUiServices() = default;
|
||||
|
||||
virtual void request_heightmap_pick() = 0;
|
||||
virtual pp::foundation::Status load_heightmap(std::string_view path, bool raise_ground_opacity) = 0;
|
||||
virtual void clear_heightmap(bool updates_preview) = 0;
|
||||
virtual void render_lightmap(bool shows_unsupported_message, bool renders_lightmap) = 0;
|
||||
virtual void commit_heightmap(bool updates_ground_opacity) = 0;
|
||||
};
|
||||
|
||||
[[nodiscard]] inline pp::foundation::Status validate_grid_texture_resolution(int texture_resolution) noexcept
|
||||
{
|
||||
if (texture_resolution <= 0 || texture_resolution > 16384) {
|
||||
return pp::foundation::Status::out_of_range("grid texture resolution must be within 1..16384");
|
||||
}
|
||||
|
||||
return pp::foundation::Status::success();
|
||||
}
|
||||
|
||||
[[nodiscard]] inline pp::foundation::Status validate_grid_lightmap_samples(int sample_count) noexcept
|
||||
{
|
||||
if (sample_count <= 0 || sample_count > 4096) {
|
||||
return pp::foundation::Status::out_of_range("grid lightmap samples must be within 1..4096");
|
||||
}
|
||||
|
||||
return pp::foundation::Status::success();
|
||||
}
|
||||
|
||||
[[nodiscard]] inline constexpr GridUiPlan plan_grid_heightmap_pick() noexcept
|
||||
{
|
||||
GridUiPlan plan;
|
||||
plan.operation = GridUiOperation::request_heightmap_pick;
|
||||
plan.opens_picker = true;
|
||||
return plan;
|
||||
}
|
||||
|
||||
[[nodiscard]] inline pp::foundation::Result<GridUiPlan> plan_grid_heightmap_load(std::string_view path)
|
||||
{
|
||||
if (path.empty()) {
|
||||
return pp::foundation::Result<GridUiPlan>::failure(
|
||||
pp::foundation::Status::invalid_argument("heightmap path must not be empty"));
|
||||
}
|
||||
|
||||
GridUiPlan plan;
|
||||
plan.operation = GridUiOperation::load_heightmap;
|
||||
plan.path = std::string(path);
|
||||
plan.loads_heightmap = true;
|
||||
plan.updates_preview = true;
|
||||
plan.updates_ground_opacity = true;
|
||||
plan.mutates_grid_state = true;
|
||||
return pp::foundation::Result<GridUiPlan>::success(std::move(plan));
|
||||
}
|
||||
|
||||
[[nodiscard]] inline constexpr GridUiPlan plan_grid_heightmap_clear(bool has_heightmap) noexcept
|
||||
{
|
||||
GridUiPlan plan;
|
||||
plan.operation = GridUiOperation::clear_heightmap;
|
||||
plan.clears_heightmap = true;
|
||||
plan.updates_preview = has_heightmap;
|
||||
plan.mutates_grid_state = has_heightmap;
|
||||
return plan;
|
||||
}
|
||||
|
||||
[[nodiscard]] inline pp::foundation::Result<GridUiPlan> plan_grid_heightmap_reload(std::string_view path)
|
||||
{
|
||||
auto plan = plan_grid_heightmap_load(path);
|
||||
if (!plan) {
|
||||
return pp::foundation::Result<GridUiPlan>::failure(plan.status());
|
||||
}
|
||||
plan.value().operation = GridUiOperation::reload_heightmap;
|
||||
plan.value().updates_ground_opacity = false;
|
||||
return plan;
|
||||
}
|
||||
|
||||
[[nodiscard]] inline pp::foundation::Result<GridUiPlan> plan_grid_lightmap_render(
|
||||
bool has_heightmap,
|
||||
bool supports_float32,
|
||||
bool supports_float16,
|
||||
int texture_resolution,
|
||||
int sample_count)
|
||||
{
|
||||
const auto texture_status = validate_grid_texture_resolution(texture_resolution);
|
||||
if (!texture_status.ok()) {
|
||||
return pp::foundation::Result<GridUiPlan>::failure(texture_status);
|
||||
}
|
||||
|
||||
const auto sample_status = validate_grid_lightmap_samples(sample_count);
|
||||
if (!sample_status.ok()) {
|
||||
return pp::foundation::Result<GridUiPlan>::failure(sample_status);
|
||||
}
|
||||
|
||||
GridUiPlan plan;
|
||||
plan.operation = GridUiOperation::render_lightmap;
|
||||
plan.texture_resolution = texture_resolution;
|
||||
plan.sample_count = sample_count;
|
||||
if (!supports_float32 && !supports_float16) {
|
||||
plan.shows_unsupported_message = true;
|
||||
return pp::foundation::Result<GridUiPlan>::success(plan);
|
||||
}
|
||||
|
||||
plan.renders_lightmap = has_heightmap;
|
||||
plan.shows_progress = has_heightmap;
|
||||
plan.updates_shading_mode = has_heightmap;
|
||||
plan.mutates_grid_state = has_heightmap;
|
||||
return pp::foundation::Result<GridUiPlan>::success(plan);
|
||||
}
|
||||
|
||||
[[nodiscard]] inline constexpr GridUiPlan plan_grid_heightmap_commit(bool has_canvas) noexcept
|
||||
{
|
||||
GridUiPlan plan;
|
||||
plan.operation = GridUiOperation::commit_heightmap;
|
||||
plan.commits_heightmap = has_canvas;
|
||||
plan.updates_ground_opacity = has_canvas;
|
||||
plan.mutates_grid_state = has_canvas;
|
||||
return plan;
|
||||
}
|
||||
|
||||
[[nodiscard]] inline pp::foundation::Status execute_grid_ui_plan(
|
||||
const GridUiPlan& plan,
|
||||
GridUiServices& services)
|
||||
{
|
||||
switch (plan.operation) {
|
||||
case GridUiOperation::request_heightmap_pick:
|
||||
if (!plan.opens_picker) {
|
||||
return pp::foundation::Status::invalid_argument("grid heightmap pick plan must open a picker");
|
||||
}
|
||||
services.request_heightmap_pick();
|
||||
return pp::foundation::Status::success();
|
||||
|
||||
case GridUiOperation::load_heightmap:
|
||||
case GridUiOperation::reload_heightmap:
|
||||
if (!plan.loads_heightmap || plan.path.empty()) {
|
||||
return pp::foundation::Status::invalid_argument("grid heightmap load plan must provide a path");
|
||||
}
|
||||
return services.load_heightmap(plan.path, plan.updates_ground_opacity);
|
||||
|
||||
case GridUiOperation::clear_heightmap:
|
||||
if (!plan.clears_heightmap) {
|
||||
return pp::foundation::Status::invalid_argument("grid heightmap clear plan must clear heightmap state");
|
||||
}
|
||||
services.clear_heightmap(plan.updates_preview);
|
||||
return pp::foundation::Status::success();
|
||||
|
||||
case GridUiOperation::render_lightmap:
|
||||
{
|
||||
const auto texture_status = validate_grid_texture_resolution(plan.texture_resolution);
|
||||
if (!texture_status.ok()) {
|
||||
return texture_status;
|
||||
}
|
||||
const auto sample_status = validate_grid_lightmap_samples(plan.sample_count);
|
||||
if (!sample_status.ok()) {
|
||||
return sample_status;
|
||||
}
|
||||
if (!plan.shows_unsupported_message && !plan.renders_lightmap) {
|
||||
return pp::foundation::Status::success();
|
||||
}
|
||||
services.render_lightmap(plan.shows_unsupported_message, plan.renders_lightmap);
|
||||
return pp::foundation::Status::success();
|
||||
}
|
||||
|
||||
case GridUiOperation::commit_heightmap:
|
||||
if (plan.commits_heightmap) {
|
||||
services.commit_heightmap(plan.updates_ground_opacity);
|
||||
}
|
||||
return pp::foundation::Status::success();
|
||||
}
|
||||
|
||||
return pp::foundation::Status::invalid_argument("unknown grid UI operation");
|
||||
}
|
||||
|
||||
} // namespace pp::app
|
||||
161
src/app_core/history_ui.h
Normal file
161
src/app_core/history_ui.h
Normal file
@@ -0,0 +1,161 @@
|
||||
#pragma once
|
||||
|
||||
#include "foundation/result.h"
|
||||
|
||||
namespace pp::app {
|
||||
|
||||
enum class HistoryUiOperation {
|
||||
undo,
|
||||
redo,
|
||||
clear,
|
||||
};
|
||||
|
||||
struct HistoryUiPlan {
|
||||
HistoryUiOperation operation = HistoryUiOperation::undo;
|
||||
int undo_count = 0;
|
||||
int redo_count = 0;
|
||||
int memory_bytes = 0;
|
||||
bool invokes_undo = false;
|
||||
bool invokes_redo = false;
|
||||
bool clears_history = false;
|
||||
bool updates_memory_label = false;
|
||||
bool updates_title = false;
|
||||
bool no_op = false;
|
||||
};
|
||||
|
||||
class HistoryUiServices {
|
||||
public:
|
||||
virtual ~HistoryUiServices() = default;
|
||||
|
||||
virtual void invoke_undo() = 0;
|
||||
virtual void invoke_redo() = 0;
|
||||
virtual void clear_history() = 0;
|
||||
};
|
||||
|
||||
[[nodiscard]] inline pp::foundation::Status validate_history_metric(int value, const char* message) noexcept
|
||||
{
|
||||
if (value < 0) {
|
||||
return pp::foundation::Status::out_of_range(message);
|
||||
}
|
||||
|
||||
return pp::foundation::Status::success();
|
||||
}
|
||||
|
||||
[[nodiscard]] inline pp::foundation::Result<HistoryUiPlan> plan_history_undo(int undo_count)
|
||||
{
|
||||
const auto count_status = validate_history_metric(undo_count, "undo action count must not be negative");
|
||||
if (!count_status.ok()) {
|
||||
return pp::foundation::Result<HistoryUiPlan>::failure(count_status);
|
||||
}
|
||||
|
||||
HistoryUiPlan plan;
|
||||
plan.operation = HistoryUiOperation::undo;
|
||||
plan.undo_count = undo_count;
|
||||
if (undo_count == 0) {
|
||||
plan.no_op = true;
|
||||
return pp::foundation::Result<HistoryUiPlan>::success(plan);
|
||||
}
|
||||
|
||||
plan.invokes_undo = true;
|
||||
plan.updates_memory_label = true;
|
||||
plan.updates_title = true;
|
||||
return pp::foundation::Result<HistoryUiPlan>::success(plan);
|
||||
}
|
||||
|
||||
[[nodiscard]] inline pp::foundation::Result<HistoryUiPlan> plan_history_redo(int redo_count)
|
||||
{
|
||||
const auto count_status = validate_history_metric(redo_count, "redo action count must not be negative");
|
||||
if (!count_status.ok()) {
|
||||
return pp::foundation::Result<HistoryUiPlan>::failure(count_status);
|
||||
}
|
||||
|
||||
HistoryUiPlan plan;
|
||||
plan.operation = HistoryUiOperation::redo;
|
||||
plan.redo_count = redo_count;
|
||||
if (redo_count == 0) {
|
||||
plan.no_op = true;
|
||||
return pp::foundation::Result<HistoryUiPlan>::success(plan);
|
||||
}
|
||||
|
||||
plan.invokes_redo = true;
|
||||
plan.updates_memory_label = true;
|
||||
plan.updates_title = true;
|
||||
return pp::foundation::Result<HistoryUiPlan>::success(plan);
|
||||
}
|
||||
|
||||
[[nodiscard]] inline pp::foundation::Result<HistoryUiPlan> plan_history_clear(
|
||||
int undo_count,
|
||||
int redo_count,
|
||||
int memory_bytes)
|
||||
{
|
||||
const auto undo_status = validate_history_metric(undo_count, "undo action count must not be negative");
|
||||
if (!undo_status.ok()) {
|
||||
return pp::foundation::Result<HistoryUiPlan>::failure(undo_status);
|
||||
}
|
||||
const auto redo_status = validate_history_metric(redo_count, "redo action count must not be negative");
|
||||
if (!redo_status.ok()) {
|
||||
return pp::foundation::Result<HistoryUiPlan>::failure(redo_status);
|
||||
}
|
||||
const auto memory_status = validate_history_metric(memory_bytes, "history memory bytes must not be negative");
|
||||
if (!memory_status.ok()) {
|
||||
return pp::foundation::Result<HistoryUiPlan>::failure(memory_status);
|
||||
}
|
||||
|
||||
HistoryUiPlan plan;
|
||||
plan.operation = HistoryUiOperation::clear;
|
||||
plan.undo_count = undo_count;
|
||||
plan.redo_count = redo_count;
|
||||
plan.memory_bytes = memory_bytes;
|
||||
if (undo_count == 0 && redo_count == 0 && memory_bytes == 0) {
|
||||
plan.no_op = true;
|
||||
return pp::foundation::Result<HistoryUiPlan>::success(plan);
|
||||
}
|
||||
|
||||
plan.clears_history = true;
|
||||
plan.updates_memory_label = true;
|
||||
return pp::foundation::Result<HistoryUiPlan>::success(plan);
|
||||
}
|
||||
|
||||
[[nodiscard]] inline pp::foundation::Status execute_history_ui_plan(
|
||||
const HistoryUiPlan& plan,
|
||||
HistoryUiServices& services)
|
||||
{
|
||||
const auto undo_status = validate_history_metric(plan.undo_count, "undo action count must not be negative");
|
||||
if (!undo_status.ok()) {
|
||||
return undo_status;
|
||||
}
|
||||
const auto redo_status = validate_history_metric(plan.redo_count, "redo action count must not be negative");
|
||||
if (!redo_status.ok()) {
|
||||
return redo_status;
|
||||
}
|
||||
const auto memory_status = validate_history_metric(plan.memory_bytes, "history memory bytes must not be negative");
|
||||
if (!memory_status.ok()) {
|
||||
return memory_status;
|
||||
}
|
||||
|
||||
if (plan.no_op) {
|
||||
return pp::foundation::Status::success();
|
||||
}
|
||||
|
||||
switch (plan.operation) {
|
||||
case HistoryUiOperation::undo:
|
||||
if (plan.invokes_undo) {
|
||||
services.invoke_undo();
|
||||
}
|
||||
return pp::foundation::Status::success();
|
||||
case HistoryUiOperation::redo:
|
||||
if (plan.invokes_redo) {
|
||||
services.invoke_redo();
|
||||
}
|
||||
return pp::foundation::Status::success();
|
||||
case HistoryUiOperation::clear:
|
||||
if (plan.clears_history) {
|
||||
services.clear_history();
|
||||
}
|
||||
return pp::foundation::Status::success();
|
||||
}
|
||||
|
||||
return pp::foundation::Status::invalid_argument("unknown history operation");
|
||||
}
|
||||
|
||||
} // namespace pp::app
|
||||
211
src/app_core/main_toolbar.h
Normal file
211
src/app_core/main_toolbar.h
Normal file
@@ -0,0 +1,211 @@
|
||||
#pragma once
|
||||
|
||||
#include "app_core/app_dialog.h"
|
||||
#include "app_core/document_canvas.h"
|
||||
#include "app_core/history_ui.h"
|
||||
#include "foundation/result.h"
|
||||
|
||||
#include <string>
|
||||
|
||||
namespace pp::app {
|
||||
|
||||
enum class MainToolbarCommand {
|
||||
open_document,
|
||||
save_document,
|
||||
undo,
|
||||
redo,
|
||||
clear_history,
|
||||
clear_canvas,
|
||||
show_message_box,
|
||||
show_settings,
|
||||
};
|
||||
|
||||
enum class MainToolbarAction {
|
||||
show_open_dialog,
|
||||
show_save_dialog,
|
||||
invoke_undo,
|
||||
invoke_redo,
|
||||
clear_history,
|
||||
clear_canvas,
|
||||
show_message_box,
|
||||
show_settings_dialog,
|
||||
no_op_unavailable,
|
||||
};
|
||||
|
||||
struct MainToolbarPlan {
|
||||
MainToolbarCommand command = MainToolbarCommand::open_document;
|
||||
MainToolbarAction action = MainToolbarAction::show_open_dialog;
|
||||
std::string label;
|
||||
bool requires_canvas = false;
|
||||
bool updates_memory_label = false;
|
||||
bool updates_title = false;
|
||||
bool records_undo = false;
|
||||
bool marks_unsaved = false;
|
||||
bool no_op = false;
|
||||
HistoryUiPlan history;
|
||||
DocumentCanvasClearPlan canvas_clear;
|
||||
};
|
||||
|
||||
class MainToolbarServices {
|
||||
public:
|
||||
virtual ~MainToolbarServices() = default;
|
||||
|
||||
virtual void show_open_dialog() = 0;
|
||||
virtual void show_save_dialog() = 0;
|
||||
virtual void invoke_undo(const HistoryUiPlan& plan) = 0;
|
||||
virtual void invoke_redo(const HistoryUiPlan& plan) = 0;
|
||||
virtual void clear_history(const HistoryUiPlan& plan) = 0;
|
||||
virtual void clear_canvas(const DocumentCanvasClearPlan& plan) = 0;
|
||||
virtual void show_message_box() = 0;
|
||||
virtual void show_settings_dialog() = 0;
|
||||
};
|
||||
|
||||
[[nodiscard]] inline AppMessageDialogPlan plan_main_toolbar_message_dialog()
|
||||
{
|
||||
return plan_app_message_dialog(
|
||||
"Just a test message",
|
||||
"Longer description for the error or the message.",
|
||||
true);
|
||||
}
|
||||
|
||||
[[nodiscard]] inline pp::foundation::Result<MainToolbarPlan> plan_main_toolbar_command(
|
||||
MainToolbarCommand command,
|
||||
int undo_count = 0,
|
||||
int redo_count = 0,
|
||||
int memory_bytes = 0,
|
||||
bool has_canvas = true)
|
||||
{
|
||||
MainToolbarPlan plan;
|
||||
plan.command = command;
|
||||
|
||||
switch (command) {
|
||||
case MainToolbarCommand::open_document:
|
||||
plan.action = MainToolbarAction::show_open_dialog;
|
||||
plan.label = "Open";
|
||||
return pp::foundation::Result<MainToolbarPlan>::success(plan);
|
||||
|
||||
case MainToolbarCommand::save_document:
|
||||
plan.action = MainToolbarAction::show_save_dialog;
|
||||
plan.label = "Save";
|
||||
return pp::foundation::Result<MainToolbarPlan>::success(plan);
|
||||
|
||||
case MainToolbarCommand::undo:
|
||||
{
|
||||
const auto history = plan_history_undo(undo_count);
|
||||
if (!history) {
|
||||
return pp::foundation::Result<MainToolbarPlan>::failure(history.status());
|
||||
}
|
||||
plan.action = history.value().invokes_undo
|
||||
? MainToolbarAction::invoke_undo
|
||||
: MainToolbarAction::no_op_unavailable;
|
||||
plan.label = history.value().invokes_undo ? "Undo" : "Undo (No history)";
|
||||
plan.updates_memory_label = history.value().updates_memory_label;
|
||||
plan.updates_title = history.value().updates_title;
|
||||
plan.no_op = history.value().no_op;
|
||||
plan.history = history.value();
|
||||
return pp::foundation::Result<MainToolbarPlan>::success(plan);
|
||||
}
|
||||
|
||||
case MainToolbarCommand::redo:
|
||||
{
|
||||
const auto history = plan_history_redo(redo_count);
|
||||
if (!history) {
|
||||
return pp::foundation::Result<MainToolbarPlan>::failure(history.status());
|
||||
}
|
||||
plan.action = history.value().invokes_redo
|
||||
? MainToolbarAction::invoke_redo
|
||||
: MainToolbarAction::no_op_unavailable;
|
||||
plan.label = history.value().invokes_redo ? "Redo" : "Redo (No history)";
|
||||
plan.updates_memory_label = history.value().updates_memory_label;
|
||||
plan.updates_title = history.value().updates_title;
|
||||
plan.no_op = history.value().no_op;
|
||||
plan.history = history.value();
|
||||
return pp::foundation::Result<MainToolbarPlan>::success(plan);
|
||||
}
|
||||
|
||||
case MainToolbarCommand::clear_history:
|
||||
{
|
||||
const auto history = plan_history_clear(undo_count, redo_count, memory_bytes);
|
||||
if (!history) {
|
||||
return pp::foundation::Result<MainToolbarPlan>::failure(history.status());
|
||||
}
|
||||
plan.action = history.value().clears_history
|
||||
? MainToolbarAction::clear_history
|
||||
: MainToolbarAction::no_op_unavailable;
|
||||
plan.label = history.value().clears_history ? "Clear History" : "Clear History (Empty)";
|
||||
plan.updates_memory_label = history.value().updates_memory_label;
|
||||
plan.no_op = history.value().no_op;
|
||||
plan.history = history.value();
|
||||
return pp::foundation::Result<MainToolbarPlan>::success(plan);
|
||||
}
|
||||
|
||||
case MainToolbarCommand::clear_canvas:
|
||||
{
|
||||
const auto clear = plan_document_canvas_clear(has_canvas);
|
||||
if (!clear) {
|
||||
return pp::foundation::Result<MainToolbarPlan>::failure(clear.status());
|
||||
}
|
||||
plan.action = clear.value().clears_canvas
|
||||
? MainToolbarAction::clear_canvas
|
||||
: MainToolbarAction::no_op_unavailable;
|
||||
plan.label = clear.value().clears_canvas ? "Clear Canvas" : "Clear Canvas (No canvas)";
|
||||
plan.requires_canvas = true;
|
||||
plan.records_undo = clear.value().records_undo;
|
||||
plan.marks_unsaved = clear.value().marks_unsaved;
|
||||
plan.no_op = clear.value().no_op;
|
||||
plan.canvas_clear = clear.value();
|
||||
return pp::foundation::Result<MainToolbarPlan>::success(plan);
|
||||
}
|
||||
|
||||
case MainToolbarCommand::show_message_box:
|
||||
plan.action = MainToolbarAction::show_message_box;
|
||||
plan.label = "Show Message Box";
|
||||
return pp::foundation::Result<MainToolbarPlan>::success(plan);
|
||||
|
||||
case MainToolbarCommand::show_settings:
|
||||
plan.action = MainToolbarAction::show_settings_dialog;
|
||||
plan.label = "Settings";
|
||||
return pp::foundation::Result<MainToolbarPlan>::success(plan);
|
||||
}
|
||||
|
||||
return pp::foundation::Result<MainToolbarPlan>::failure(
|
||||
pp::foundation::Status::invalid_argument("unknown main toolbar command"));
|
||||
}
|
||||
|
||||
[[nodiscard]] inline pp::foundation::Status execute_main_toolbar_plan(
|
||||
const MainToolbarPlan& plan,
|
||||
MainToolbarServices& services)
|
||||
{
|
||||
switch (plan.action) {
|
||||
case MainToolbarAction::show_open_dialog:
|
||||
services.show_open_dialog();
|
||||
return pp::foundation::Status::success();
|
||||
case MainToolbarAction::show_save_dialog:
|
||||
services.show_save_dialog();
|
||||
return pp::foundation::Status::success();
|
||||
case MainToolbarAction::invoke_undo:
|
||||
services.invoke_undo(plan.history);
|
||||
return pp::foundation::Status::success();
|
||||
case MainToolbarAction::invoke_redo:
|
||||
services.invoke_redo(plan.history);
|
||||
return pp::foundation::Status::success();
|
||||
case MainToolbarAction::clear_history:
|
||||
services.clear_history(plan.history);
|
||||
return pp::foundation::Status::success();
|
||||
case MainToolbarAction::clear_canvas:
|
||||
services.clear_canvas(plan.canvas_clear);
|
||||
return pp::foundation::Status::success();
|
||||
case MainToolbarAction::show_message_box:
|
||||
services.show_message_box();
|
||||
return pp::foundation::Status::success();
|
||||
case MainToolbarAction::show_settings_dialog:
|
||||
services.show_settings_dialog();
|
||||
return pp::foundation::Status::success();
|
||||
case MainToolbarAction::no_op_unavailable:
|
||||
return pp::foundation::Status::success();
|
||||
}
|
||||
|
||||
return pp::foundation::Status::invalid_argument("unknown main toolbar action");
|
||||
}
|
||||
|
||||
} // namespace pp::app
|
||||
302
src/app_core/quick_ui.h
Normal file
302
src/app_core/quick_ui.h
Normal file
@@ -0,0 +1,302 @@
|
||||
#pragma once
|
||||
|
||||
#include "foundation/result.h"
|
||||
|
||||
#include <cmath>
|
||||
|
||||
namespace pp::app {
|
||||
|
||||
enum class QuickUiSlotKind {
|
||||
brush,
|
||||
color,
|
||||
};
|
||||
|
||||
enum class QuickUiOperation {
|
||||
select_slot,
|
||||
open_slot_popup,
|
||||
restore_state,
|
||||
reset_state,
|
||||
};
|
||||
|
||||
struct QuickUiPlan {
|
||||
QuickUiOperation operation = QuickUiOperation::select_slot;
|
||||
QuickUiSlotKind slot_kind = QuickUiSlotKind::brush;
|
||||
int slot_index = 0;
|
||||
int previous_index = 0;
|
||||
int brush_index = 0;
|
||||
int color_index = 0;
|
||||
int slot_count = 0;
|
||||
bool fire_event = false;
|
||||
bool updates_selection = false;
|
||||
bool opens_brush_popup = false;
|
||||
bool opens_color_picker = false;
|
||||
bool invokes_change_callback = false;
|
||||
bool restores_slots = false;
|
||||
bool resets_slots = false;
|
||||
bool redraws_brush_previews = false;
|
||||
bool mutates_quick_state = false;
|
||||
};
|
||||
|
||||
struct QuickSliderPreviewInput {
|
||||
bool ui_rtl = false;
|
||||
float slider_x = 0.0F;
|
||||
float slider_y = 0.0F;
|
||||
float slider_height = 0.0F;
|
||||
float zoom = 1.0F;
|
||||
bool has_pen_mode = false;
|
||||
bool has_line_mode = false;
|
||||
};
|
||||
|
||||
struct QuickSliderPreviewPlan {
|
||||
float cursor_x = 0.0F;
|
||||
float cursor_y = 0.0F;
|
||||
bool updates_pen_mode = false;
|
||||
bool updates_line_mode = false;
|
||||
bool draws_tip = false;
|
||||
bool disables_pen_outline = false;
|
||||
bool redraws_brush_preview = false;
|
||||
bool invokes_change_callback = false;
|
||||
};
|
||||
|
||||
class QuickUiServices {
|
||||
public:
|
||||
virtual ~QuickUiServices() = default;
|
||||
|
||||
virtual void select_slot(QuickUiSlotKind slot_kind, int slot_index, bool fire_event) = 0;
|
||||
virtual void open_slot_popup(QuickUiSlotKind slot_kind, int slot_index) = 0;
|
||||
virtual void restore_state(int brush_index, int color_index, bool fire_event) = 0;
|
||||
virtual void reset_state(bool fire_event) = 0;
|
||||
};
|
||||
|
||||
[[nodiscard]] inline pp::foundation::Status validate_quick_slider_float(float value) noexcept
|
||||
{
|
||||
if (!std::isfinite(value)) {
|
||||
return pp::foundation::Status::invalid_argument("quick slider preview value must be finite");
|
||||
}
|
||||
|
||||
return pp::foundation::Status::success();
|
||||
}
|
||||
|
||||
[[nodiscard]] inline pp::foundation::Status validate_quick_slot_count(int slot_count) noexcept
|
||||
{
|
||||
if (slot_count <= 0) {
|
||||
return pp::foundation::Status::out_of_range("quick slot count must be greater than zero");
|
||||
}
|
||||
|
||||
return pp::foundation::Status::success();
|
||||
}
|
||||
|
||||
[[nodiscard]] inline pp::foundation::Status validate_quick_slot_index(int slot_index, int slot_count) noexcept
|
||||
{
|
||||
const auto count_status = validate_quick_slot_count(slot_count);
|
||||
if (!count_status.ok()) {
|
||||
return count_status;
|
||||
}
|
||||
|
||||
if (slot_index < 0 || slot_index >= slot_count) {
|
||||
return pp::foundation::Status::out_of_range("quick slot index is out of range");
|
||||
}
|
||||
|
||||
return pp::foundation::Status::success();
|
||||
}
|
||||
|
||||
[[nodiscard]] inline pp::foundation::Result<QuickSliderPreviewPlan> plan_quick_slider_preview(
|
||||
const QuickSliderPreviewInput& input)
|
||||
{
|
||||
const auto x_status = validate_quick_slider_float(input.slider_x);
|
||||
if (!x_status.ok()) {
|
||||
return pp::foundation::Result<QuickSliderPreviewPlan>::failure(x_status);
|
||||
}
|
||||
const auto y_status = validate_quick_slider_float(input.slider_y);
|
||||
if (!y_status.ok()) {
|
||||
return pp::foundation::Result<QuickSliderPreviewPlan>::failure(y_status);
|
||||
}
|
||||
const auto height_status = validate_quick_slider_float(input.slider_height);
|
||||
if (!height_status.ok()) {
|
||||
return pp::foundation::Result<QuickSliderPreviewPlan>::failure(height_status);
|
||||
}
|
||||
if (input.slider_height < 0.0F) {
|
||||
return pp::foundation::Result<QuickSliderPreviewPlan>::failure(
|
||||
pp::foundation::Status::invalid_argument("quick slider preview height must not be negative"));
|
||||
}
|
||||
const auto zoom_status = validate_quick_slider_float(input.zoom);
|
||||
if (!zoom_status.ok()) {
|
||||
return pp::foundation::Result<QuickSliderPreviewPlan>::failure(zoom_status);
|
||||
}
|
||||
if (input.zoom <= 0.0F) {
|
||||
return pp::foundation::Result<QuickSliderPreviewPlan>::failure(
|
||||
pp::foundation::Status::invalid_argument("quick slider preview zoom must be positive"));
|
||||
}
|
||||
|
||||
const float offset = input.ui_rtl ? -100.0F : 100.0F;
|
||||
QuickSliderPreviewPlan plan;
|
||||
plan.cursor_x = (input.slider_x + offset) * input.zoom;
|
||||
plan.cursor_y = (input.slider_y + input.slider_height * 0.5F) * input.zoom;
|
||||
plan.updates_pen_mode = input.has_pen_mode;
|
||||
plan.updates_line_mode = input.has_line_mode;
|
||||
plan.draws_tip = input.has_pen_mode || input.has_line_mode;
|
||||
plan.disables_pen_outline = input.has_pen_mode;
|
||||
plan.redraws_brush_preview = true;
|
||||
plan.invokes_change_callback = true;
|
||||
return pp::foundation::Result<QuickSliderPreviewPlan>::success(plan);
|
||||
}
|
||||
|
||||
[[nodiscard]] inline pp::foundation::Result<QuickUiPlan> plan_quick_slot_click(
|
||||
QuickUiSlotKind slot_kind,
|
||||
int current_index,
|
||||
int clicked_index,
|
||||
int slot_count)
|
||||
{
|
||||
const auto current_status = validate_quick_slot_index(current_index, slot_count);
|
||||
if (!current_status.ok()) {
|
||||
return pp::foundation::Result<QuickUiPlan>::failure(current_status);
|
||||
}
|
||||
|
||||
const auto clicked_status = validate_quick_slot_index(clicked_index, slot_count);
|
||||
if (!clicked_status.ok()) {
|
||||
return pp::foundation::Result<QuickUiPlan>::failure(clicked_status);
|
||||
}
|
||||
|
||||
QuickUiPlan plan;
|
||||
plan.slot_kind = slot_kind;
|
||||
plan.slot_index = clicked_index;
|
||||
plan.previous_index = current_index;
|
||||
plan.brush_index = slot_kind == QuickUiSlotKind::brush ? clicked_index : 0;
|
||||
plan.color_index = slot_kind == QuickUiSlotKind::color ? clicked_index : 0;
|
||||
plan.slot_count = slot_count;
|
||||
if (clicked_index != current_index) {
|
||||
plan.operation = QuickUiOperation::select_slot;
|
||||
plan.updates_selection = true;
|
||||
plan.invokes_change_callback = true;
|
||||
plan.mutates_quick_state = true;
|
||||
return pp::foundation::Result<QuickUiPlan>::success(plan);
|
||||
}
|
||||
|
||||
plan.operation = QuickUiOperation::open_slot_popup;
|
||||
plan.opens_brush_popup = slot_kind == QuickUiSlotKind::brush;
|
||||
plan.opens_color_picker = slot_kind == QuickUiSlotKind::color;
|
||||
return pp::foundation::Result<QuickUiPlan>::success(plan);
|
||||
}
|
||||
|
||||
[[nodiscard]] inline pp::foundation::Result<QuickUiPlan> plan_quick_state_restore(
|
||||
int brush_index,
|
||||
int color_index,
|
||||
int slot_count,
|
||||
bool fire_event)
|
||||
{
|
||||
const auto brush_status = validate_quick_slot_index(brush_index, slot_count);
|
||||
if (!brush_status.ok()) {
|
||||
return pp::foundation::Result<QuickUiPlan>::failure(brush_status);
|
||||
}
|
||||
|
||||
const auto color_status = validate_quick_slot_index(color_index, slot_count);
|
||||
if (!color_status.ok()) {
|
||||
return pp::foundation::Result<QuickUiPlan>::failure(color_status);
|
||||
}
|
||||
|
||||
QuickUiPlan plan;
|
||||
plan.operation = QuickUiOperation::restore_state;
|
||||
plan.brush_index = brush_index;
|
||||
plan.color_index = color_index;
|
||||
plan.slot_count = slot_count;
|
||||
plan.fire_event = fire_event;
|
||||
plan.updates_selection = true;
|
||||
plan.invokes_change_callback = fire_event;
|
||||
plan.restores_slots = true;
|
||||
plan.redraws_brush_previews = true;
|
||||
plan.mutates_quick_state = true;
|
||||
return pp::foundation::Result<QuickUiPlan>::success(plan);
|
||||
}
|
||||
|
||||
[[nodiscard]] inline pp::foundation::Result<QuickUiPlan> plan_quick_state_reset(
|
||||
int slot_count,
|
||||
bool fire_event)
|
||||
{
|
||||
const auto count_status = validate_quick_slot_count(slot_count);
|
||||
if (!count_status.ok()) {
|
||||
return pp::foundation::Result<QuickUiPlan>::failure(count_status);
|
||||
}
|
||||
|
||||
QuickUiPlan plan;
|
||||
plan.operation = QuickUiOperation::reset_state;
|
||||
plan.brush_index = 0;
|
||||
plan.color_index = 0;
|
||||
plan.slot_count = slot_count;
|
||||
plan.fire_event = fire_event;
|
||||
plan.updates_selection = true;
|
||||
plan.invokes_change_callback = fire_event;
|
||||
plan.resets_slots = true;
|
||||
plan.redraws_brush_previews = true;
|
||||
plan.mutates_quick_state = true;
|
||||
return pp::foundation::Result<QuickUiPlan>::success(plan);
|
||||
}
|
||||
|
||||
[[nodiscard]] inline pp::foundation::Status execute_quick_ui_plan(
|
||||
const QuickUiPlan& plan,
|
||||
QuickUiServices& services)
|
||||
{
|
||||
switch (plan.operation) {
|
||||
case QuickUiOperation::select_slot:
|
||||
if (!plan.updates_selection) {
|
||||
return pp::foundation::Status::invalid_argument("quick select plan must update selection");
|
||||
}
|
||||
{
|
||||
const auto status = validate_quick_slot_index(plan.slot_index, plan.slot_count);
|
||||
if (!status.ok()) {
|
||||
return status;
|
||||
}
|
||||
}
|
||||
services.select_slot(plan.slot_kind, plan.slot_index, plan.invokes_change_callback);
|
||||
return pp::foundation::Status::success();
|
||||
|
||||
case QuickUiOperation::open_slot_popup:
|
||||
if (plan.slot_kind == QuickUiSlotKind::brush && !plan.opens_brush_popup) {
|
||||
return pp::foundation::Status::invalid_argument("quick brush popup plan must open brush popup");
|
||||
}
|
||||
if (plan.slot_kind == QuickUiSlotKind::color && !plan.opens_color_picker) {
|
||||
return pp::foundation::Status::invalid_argument("quick color popup plan must open color picker");
|
||||
}
|
||||
{
|
||||
const auto status = validate_quick_slot_index(plan.slot_index, plan.slot_count);
|
||||
if (!status.ok()) {
|
||||
return status;
|
||||
}
|
||||
}
|
||||
services.open_slot_popup(plan.slot_kind, plan.slot_index);
|
||||
return pp::foundation::Status::success();
|
||||
|
||||
case QuickUiOperation::restore_state:
|
||||
if (!plan.restores_slots) {
|
||||
return pp::foundation::Status::invalid_argument("quick restore plan must restore slots");
|
||||
}
|
||||
{
|
||||
const auto brush_status = validate_quick_slot_index(plan.brush_index, plan.slot_count);
|
||||
if (!brush_status.ok()) {
|
||||
return brush_status;
|
||||
}
|
||||
const auto color_status = validate_quick_slot_index(plan.color_index, plan.slot_count);
|
||||
if (!color_status.ok()) {
|
||||
return color_status;
|
||||
}
|
||||
}
|
||||
services.restore_state(plan.brush_index, plan.color_index, plan.fire_event);
|
||||
return pp::foundation::Status::success();
|
||||
|
||||
case QuickUiOperation::reset_state:
|
||||
if (!plan.resets_slots) {
|
||||
return pp::foundation::Status::invalid_argument("quick reset plan must reset slots");
|
||||
}
|
||||
{
|
||||
const auto status = validate_quick_slot_count(plan.slot_count);
|
||||
if (!status.ok()) {
|
||||
return status;
|
||||
}
|
||||
}
|
||||
services.reset_state(plan.fire_event);
|
||||
return pp::foundation::Status::success();
|
||||
}
|
||||
|
||||
return pp::foundation::Status::invalid_argument("unknown quick UI operation");
|
||||
}
|
||||
|
||||
} // namespace pp::app
|
||||
185
src/app_core/tools_menu.h
Normal file
185
src/app_core/tools_menu.h
Normal file
@@ -0,0 +1,185 @@
|
||||
#pragma once
|
||||
|
||||
#include "foundation/result.h"
|
||||
|
||||
#include <string_view>
|
||||
|
||||
namespace pp::app {
|
||||
|
||||
enum class ToolsMenuCommand {
|
||||
panels,
|
||||
options,
|
||||
clear_grids,
|
||||
reset_camera,
|
||||
shortcuts,
|
||||
sonarpen,
|
||||
};
|
||||
|
||||
enum class ToolsMenuAction {
|
||||
show_panels_submenu,
|
||||
show_options_submenu,
|
||||
clear_grid_overlays,
|
||||
reset_camera,
|
||||
show_shortcuts_dialog,
|
||||
start_sonarpen,
|
||||
no_op_unavailable,
|
||||
};
|
||||
|
||||
enum class ToolsPanel {
|
||||
presets,
|
||||
color,
|
||||
color_advanced,
|
||||
layers,
|
||||
brush,
|
||||
grids,
|
||||
animation,
|
||||
};
|
||||
|
||||
enum class ToolsPanelAction {
|
||||
open_floating_panel,
|
||||
no_op_already_visible,
|
||||
};
|
||||
|
||||
struct ToolsMenuPlan {
|
||||
ToolsMenuCommand command = ToolsMenuCommand::panels;
|
||||
ToolsMenuAction action = ToolsMenuAction::show_panels_submenu;
|
||||
std::string_view label;
|
||||
bool closes_root_popup = false;
|
||||
};
|
||||
|
||||
struct ToolsPanelPlan {
|
||||
ToolsPanel panel = ToolsPanel::presets;
|
||||
ToolsPanelAction action = ToolsPanelAction::open_floating_panel;
|
||||
std::string_view title;
|
||||
int width = 0;
|
||||
int height = 0;
|
||||
int min_width = 0;
|
||||
int min_height = 0;
|
||||
bool droppable = true;
|
||||
bool hides_embedded_title = false;
|
||||
};
|
||||
|
||||
class ToolsMenuServices {
|
||||
public:
|
||||
virtual ~ToolsMenuServices() = default;
|
||||
|
||||
virtual void show_panels_submenu() = 0;
|
||||
virtual void show_options_submenu() = 0;
|
||||
virtual void clear_grid_overlays() = 0;
|
||||
virtual void reset_camera() = 0;
|
||||
virtual void show_shortcuts_dialog() = 0;
|
||||
virtual void start_sonarpen() = 0;
|
||||
};
|
||||
|
||||
[[nodiscard]] constexpr ToolsMenuPlan plan_tools_menu_command(
|
||||
ToolsMenuCommand command,
|
||||
bool sonarpen_available = false) noexcept
|
||||
{
|
||||
switch (command) {
|
||||
case ToolsMenuCommand::panels:
|
||||
return { command, ToolsMenuAction::show_panels_submenu, "Panels", false };
|
||||
case ToolsMenuCommand::options:
|
||||
return { command, ToolsMenuAction::show_options_submenu, "Options", false };
|
||||
case ToolsMenuCommand::clear_grids:
|
||||
return { command, ToolsMenuAction::clear_grid_overlays, "Clear Grids", true };
|
||||
case ToolsMenuCommand::reset_camera:
|
||||
return { command, ToolsMenuAction::reset_camera, "Reset Camera", true };
|
||||
case ToolsMenuCommand::shortcuts:
|
||||
return { command, ToolsMenuAction::show_shortcuts_dialog, "Shortcuts", true };
|
||||
case ToolsMenuCommand::sonarpen:
|
||||
return {
|
||||
command,
|
||||
sonarpen_available ? ToolsMenuAction::start_sonarpen : ToolsMenuAction::no_op_unavailable,
|
||||
"SonarPen",
|
||||
sonarpen_available,
|
||||
};
|
||||
}
|
||||
|
||||
return { command, ToolsMenuAction::no_op_unavailable, "", false };
|
||||
}
|
||||
|
||||
[[nodiscard]] constexpr ToolsPanelPlan plan_tools_panel(
|
||||
ToolsPanel panel,
|
||||
bool already_visible) noexcept
|
||||
{
|
||||
ToolsPanelPlan plan;
|
||||
plan.panel = panel;
|
||||
plan.action = already_visible
|
||||
? ToolsPanelAction::no_op_already_visible
|
||||
: ToolsPanelAction::open_floating_panel;
|
||||
|
||||
switch (panel) {
|
||||
case ToolsPanel::presets:
|
||||
plan.title = "Brushes";
|
||||
plan.height = 300;
|
||||
plan.min_height = 300;
|
||||
plan.min_width = 100;
|
||||
break;
|
||||
case ToolsPanel::color:
|
||||
plan.title = "Color Picker";
|
||||
plan.height = 300;
|
||||
plan.hides_embedded_title = true;
|
||||
break;
|
||||
case ToolsPanel::color_advanced:
|
||||
plan.title = "Color Picker";
|
||||
plan.width = 300;
|
||||
plan.height = 300;
|
||||
break;
|
||||
case ToolsPanel::layers:
|
||||
plan.title = "Layers";
|
||||
plan.height = 300;
|
||||
plan.min_height = 100;
|
||||
plan.hides_embedded_title = true;
|
||||
break;
|
||||
case ToolsPanel::brush:
|
||||
plan.title = "Brush Settings";
|
||||
plan.height = 300;
|
||||
plan.hides_embedded_title = true;
|
||||
break;
|
||||
case ToolsPanel::grids:
|
||||
plan.title = "Grid";
|
||||
plan.height = 300;
|
||||
plan.hides_embedded_title = true;
|
||||
break;
|
||||
case ToolsPanel::animation:
|
||||
plan.title = "Animation";
|
||||
plan.width = 500;
|
||||
plan.height = 300;
|
||||
plan.droppable = false;
|
||||
break;
|
||||
}
|
||||
|
||||
return plan;
|
||||
}
|
||||
|
||||
[[nodiscard]] inline pp::foundation::Status execute_tools_menu_plan(
|
||||
const ToolsMenuPlan& plan,
|
||||
ToolsMenuServices& services)
|
||||
{
|
||||
switch (plan.action) {
|
||||
case ToolsMenuAction::show_panels_submenu:
|
||||
services.show_panels_submenu();
|
||||
return pp::foundation::Status::success();
|
||||
case ToolsMenuAction::show_options_submenu:
|
||||
services.show_options_submenu();
|
||||
return pp::foundation::Status::success();
|
||||
case ToolsMenuAction::clear_grid_overlays:
|
||||
services.clear_grid_overlays();
|
||||
return pp::foundation::Status::success();
|
||||
case ToolsMenuAction::reset_camera:
|
||||
services.reset_camera();
|
||||
return pp::foundation::Status::success();
|
||||
case ToolsMenuAction::show_shortcuts_dialog:
|
||||
services.show_shortcuts_dialog();
|
||||
return pp::foundation::Status::success();
|
||||
case ToolsMenuAction::start_sonarpen:
|
||||
services.start_sonarpen();
|
||||
return pp::foundation::Status::success();
|
||||
case ToolsMenuAction::no_op_unavailable:
|
||||
return pp::foundation::Status::success();
|
||||
}
|
||||
|
||||
return pp::foundation::Status::invalid_argument("unknown tools menu action");
|
||||
}
|
||||
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,520 +1,694 @@
|
||||
#include "pch.h"
|
||||
#include "app.h"
|
||||
#include "app_core/app_frame.h"
|
||||
#include "app_core/app_input.h"
|
||||
#include "app_core/document_platform_io.h"
|
||||
#include "app_core/document_sharing.h"
|
||||
#include "platform_api/platform_services.h"
|
||||
#include "platform_legacy/legacy_platform_services.h"
|
||||
#include "renderer_gl/opengl_capabilities.h"
|
||||
|
||||
#ifdef __ANDROID__
|
||||
void displayKeyboard(bool pShow);
|
||||
void android_pick_file(std::function<void(std::string)> callback);
|
||||
void android_pick_file_save(std::function<void(std::string)> callback);
|
||||
std::string android_get_clipboard();
|
||||
bool android_set_clipboard(const std::string& s);
|
||||
#elif _WIN32
|
||||
std::string win32_open_file(const char* filter);
|
||||
std::string win32_save_file(const char* filter);
|
||||
std::string win32_open_dir();
|
||||
void win32_show_cursor(bool visible);
|
||||
bool win32_clipboard_set_text(const std::string & s);
|
||||
std::string win32_clipboard_get_text();
|
||||
#elif __APPLE__
|
||||
#elif __LINUX__
|
||||
#include <tinyfiledialogs.h>
|
||||
#elif __WEB__
|
||||
void webgl_pick_file(std::function<void(std::string)> callback);
|
||||
void webgl_pick_file_save(const std::string& path,
|
||||
const std::string& name, std::function<void(bool)> callback);
|
||||
void webgl_sync();
|
||||
#endif
|
||||
namespace {
|
||||
|
||||
[[nodiscard]] GLint rgba8_internal_format() noexcept
|
||||
{
|
||||
return static_cast<GLint>(pp::renderer::gl::rgba8_internal_format());
|
||||
}
|
||||
|
||||
[[nodiscard]] bool should_dispatch_keyboard_visibility(bool visible) noexcept
|
||||
{
|
||||
const auto action = pp::app::plan_virtual_keyboard(visible);
|
||||
return visible
|
||||
? action == pp::app::VirtualKeyboardAction::show_keyboard
|
||||
: action == pp::app::VirtualKeyboardAction::hide_keyboard;
|
||||
}
|
||||
|
||||
[[nodiscard]] bool should_dispatch_cursor_visibility(bool visible) noexcept
|
||||
{
|
||||
const auto action = pp::app::plan_cursor_visibility(visible);
|
||||
return visible
|
||||
? action == pp::app::CursorVisibilityAction::show_cursor
|
||||
: action == pp::app::CursorVisibilityAction::hide_cursor;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
namespace {
|
||||
|
||||
[[nodiscard]] pp::platform::PlatformServices& active_platform_services()
|
||||
{
|
||||
if (App::I)
|
||||
{
|
||||
if (auto* services = App::I->platform_services())
|
||||
return *services;
|
||||
}
|
||||
return pp::platform::legacy::platform_services();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
void App::set_platform_services(pp::platform::PlatformServices* services) noexcept
|
||||
{
|
||||
platform_services_ = services;
|
||||
}
|
||||
|
||||
pp::platform::PlatformServices* App::platform_services() const noexcept
|
||||
{
|
||||
return platform_services_;
|
||||
}
|
||||
|
||||
pp::platform::PlatformStoragePaths App::prepare_storage_paths()
|
||||
{
|
||||
return active_platform_services().prepare_storage_paths();
|
||||
}
|
||||
|
||||
std::string App::clipboard_get_text()
|
||||
{
|
||||
#if _WIN32
|
||||
return win32_clipboard_get_text();
|
||||
#elif __IOS__
|
||||
return [ios_view clipboard_get_string];
|
||||
#elif __OSX__
|
||||
return [osx_view clipboard_get_string];
|
||||
#elif __ANDROID__
|
||||
return android_get_clipboard();
|
||||
#endif
|
||||
if (pp::app::plan_clipboard_read() != pp::app::ClipboardReadAction::read_text)
|
||||
return {};
|
||||
|
||||
return active_platform_services().clipboard_text();
|
||||
}
|
||||
|
||||
bool App::clipboard_set_text(const std::string& s)
|
||||
{
|
||||
#if _WIN32
|
||||
return win32_clipboard_set_text(s);
|
||||
#elif __IOS__
|
||||
return [ios_view clipboard_set_string:s];
|
||||
#elif __OSX__
|
||||
return [osx_view clipboard_set_string:s];
|
||||
#elif __ANDROID__
|
||||
return android_set_clipboard(s);
|
||||
#endif
|
||||
if (pp::app::plan_clipboard_write(s) != pp::app::ClipboardWriteAction::write_text)
|
||||
return false;
|
||||
|
||||
return active_platform_services().set_clipboard_text(s);
|
||||
}
|
||||
|
||||
void App::stacktrace()
|
||||
{
|
||||
#if __OSX__
|
||||
NSString* callstack = [[NSThread callStackSymbols] componentsJoinedByString:@"\n"];
|
||||
LOG("callstack:\n%s", [callstack cStringUsingEncoding:NSUTF8StringEncoding]);
|
||||
#endif
|
||||
active_platform_services().log_stacktrace();
|
||||
}
|
||||
|
||||
void App::crash_test()
|
||||
{
|
||||
#ifdef __IOS__
|
||||
[ios_view crash];
|
||||
#elif __OSX__
|
||||
[osx_view hockeyapp_crash];
|
||||
#elif defined(_WIN32)
|
||||
__debugbreak();
|
||||
#elif defined(__ANDROID__)
|
||||
int *x = nullptr; *x = 42;
|
||||
LOG("%d", *x);
|
||||
#endif
|
||||
active_platform_services().trigger_crash_test();
|
||||
}
|
||||
|
||||
void App::tick(float dt)
|
||||
{
|
||||
if (auto* main = layout_designer[main_id])
|
||||
const auto tick_plan = pp::app::plan_app_frame_tick(
|
||||
layout_designer.get(main_id) != nullptr,
|
||||
layout.get(main_id) != nullptr);
|
||||
if (auto* main = layout_designer[main_id]; tick_plan.tick_designer_layout && main)
|
||||
main->tick(dt);
|
||||
if (auto* main = layout[main_id])
|
||||
if (auto* main = layout[main_id]; tick_plan.tick_main_layout && main)
|
||||
main->tick(dt);
|
||||
}
|
||||
|
||||
void App::resize(float w, float h)
|
||||
{
|
||||
LOG("App::resize %d %d", (int)w, (int)h);
|
||||
uirtt.create(w, h, -1, GL_RGBA8, true);
|
||||
redraw = true;
|
||||
width = w;
|
||||
height = h;
|
||||
const auto resize_plan = pp::app::plan_app_resize(w, h);
|
||||
if (!resize_plan) {
|
||||
LOG("App resize plan failed: %s", resize_plan.status().message);
|
||||
return;
|
||||
}
|
||||
|
||||
if (resize_plan.value().recreate_ui_render_target) {
|
||||
uirtt.create(
|
||||
resize_plan.value().render_target_width,
|
||||
resize_plan.value().render_target_height,
|
||||
-1,
|
||||
rgba8_internal_format(),
|
||||
true);
|
||||
}
|
||||
redraw = resize_plan.value().request_redraw;
|
||||
width = resize_plan.value().width;
|
||||
height = resize_plan.value().height;
|
||||
}
|
||||
|
||||
void App::show_cursor()
|
||||
{
|
||||
#ifdef _WIN32
|
||||
win32_show_cursor(true);
|
||||
#elif __OSX__
|
||||
[osx_view show_cursor:true];
|
||||
#endif
|
||||
if (!should_dispatch_cursor_visibility(true))
|
||||
return;
|
||||
|
||||
active_platform_services().set_cursor_visible(true);
|
||||
}
|
||||
|
||||
void App::hide_cursor()
|
||||
{
|
||||
#ifdef _WIN32
|
||||
win32_show_cursor(false);
|
||||
#elif __OSX__
|
||||
[osx_view show_cursor:false];
|
||||
#endif
|
||||
if (!should_dispatch_cursor_visibility(false))
|
||||
return;
|
||||
|
||||
active_platform_services().set_cursor_visible(false);
|
||||
}
|
||||
|
||||
void App::showKeyboard()
|
||||
{
|
||||
LOG("show keyboard");
|
||||
redraw = true;
|
||||
#ifdef __IOS__
|
||||
dispatch_async(dispatch_get_main_queue(), ^{
|
||||
[ios_view show_keyboard];
|
||||
});
|
||||
#elif __ANDROID__
|
||||
displayKeyboard(true);
|
||||
#endif
|
||||
if (!should_dispatch_keyboard_visibility(true))
|
||||
return;
|
||||
|
||||
active_platform_services().set_virtual_keyboard_visible(true);
|
||||
}
|
||||
|
||||
void App::hideKeyboard()
|
||||
{
|
||||
LOG("hide keyboard");
|
||||
redraw = true;
|
||||
#ifdef __IOS__
|
||||
dispatch_async(dispatch_get_main_queue(), ^{
|
||||
[ios_view hide_keyboard];
|
||||
});
|
||||
#elif __ANDROID__
|
||||
displayKeyboard(false);
|
||||
#endif
|
||||
if (!should_dispatch_keyboard_visibility(false))
|
||||
return;
|
||||
|
||||
active_platform_services().set_virtual_keyboard_visible(false);
|
||||
}
|
||||
|
||||
void App::pick_image(std::function<void(std::string path)> callback)
|
||||
{
|
||||
redraw = true;
|
||||
#ifdef __IOS__
|
||||
dispatch_async(dispatch_get_main_queue(), ^{
|
||||
[ios_view pick_photo:callback];
|
||||
});
|
||||
#elif __OSX__
|
||||
dispatch_async(dispatch_get_main_queue(), ^{
|
||||
NSArray* fileTypes = [NSArray arrayWithObjects:@"png", @"PNG", @"jpg", @"JPG", @"jpeg", nil];
|
||||
std::string path = [osx_view pick_file:fileTypes];
|
||||
if (!path.empty())
|
||||
callback(path);
|
||||
});
|
||||
#elif __ANDROID__
|
||||
android_pick_file(callback);
|
||||
#elif _WIN32
|
||||
std::string path = win32_open_file("Image Files (*.jpg, *.png)\0*.jpg;*.png");
|
||||
if (!path.empty())
|
||||
callback(path);
|
||||
#elif __LINUX__
|
||||
if (auto p = tinyfd_openFileDialog("Open File", "", 0, nullptr, nullptr, false))
|
||||
callback(p);
|
||||
#elif __WEB__
|
||||
webgl_pick_file(callback);
|
||||
#endif
|
||||
active_platform_services().pick_image(std::move(callback));
|
||||
}
|
||||
|
||||
void App::pick_file(std::vector<std::string> types, std::function<void (std::string)> callback)
|
||||
{
|
||||
redraw = true;
|
||||
#ifdef __IOS__
|
||||
dispatch_async(dispatch_get_main_queue(), ^{
|
||||
NSMutableArray<NSString*>* fileTypes = [NSMutableArray arrayWithCapacity:types.size()];
|
||||
for (const auto& t : types)
|
||||
[fileTypes addObject:[NSString stringWithCString:t.c_str() encoding:NSUTF8StringEncoding]];
|
||||
[ios_view pick_file:fileTypes then:callback];
|
||||
});
|
||||
#elif __OSX__
|
||||
dispatch_async(dispatch_get_main_queue(), ^{
|
||||
NSMutableArray<NSString*>* fileTypes = [NSMutableArray arrayWithCapacity:types.size()];
|
||||
for (const auto& t : types)
|
||||
[fileTypes addObject:[NSString stringWithCString:t.c_str() encoding:NSUTF8StringEncoding]];
|
||||
std::string path = [osx_view pick_file:fileTypes];
|
||||
if (!path.empty())
|
||||
callback(path);
|
||||
});
|
||||
#elif __ANDROID__
|
||||
android_pick_file(callback);
|
||||
#elif _WIN32
|
||||
std::string filter = "Supported Files (";
|
||||
bool first_type = true;
|
||||
for (auto& t : types)
|
||||
{
|
||||
filter.append(std::string(first_type ? "" : " ,") + "*." + t);
|
||||
first_type = false;
|
||||
}
|
||||
filter.append(")");
|
||||
filter.push_back(0);
|
||||
first_type = true;
|
||||
for (auto& t : types)
|
||||
{
|
||||
filter.append(std::string(first_type ? "" : ";") + "*." + t);
|
||||
first_type = false;
|
||||
}
|
||||
filter.push_back(0);
|
||||
std::string path = win32_open_file(filter.c_str());
|
||||
if (!path.empty())
|
||||
callback(path);
|
||||
#elif __LINUX__
|
||||
if (auto p = tinyfd_openFileDialog("Open File", "", 0, nullptr, nullptr, false))
|
||||
callback(p);
|
||||
#elif __WEB__
|
||||
webgl_pick_file(callback);
|
||||
#endif
|
||||
active_platform_services().pick_file(std::move(types), std::move(callback));
|
||||
}
|
||||
|
||||
#if __IOS__
|
||||
void App::pick_file_save(const std::string& type, const std::string& default_name,
|
||||
std::function<void(std::string)> writer, std::function<void(const std::string& path, bool saved)> callback)
|
||||
{
|
||||
redraw = true;
|
||||
std::string ext = "." + type;
|
||||
std::string path = tmp_path + "/" + default_name + ext;
|
||||
std::thread([=]{
|
||||
writer(path);
|
||||
dispatch_async(dispatch_get_main_queue(), ^{
|
||||
[ios_view pick_file_save:path];
|
||||
});
|
||||
callback(path, true);
|
||||
}).detach();
|
||||
const auto target = active_platform_services().prepare_writable_file(type, default_name, data_path, tmp_path);
|
||||
if (target.path.empty())
|
||||
{
|
||||
callback({}, false);
|
||||
return;
|
||||
}
|
||||
|
||||
LOG("App::pick_file_save %s", target.path.c_str());
|
||||
auto write_and_save = [=] {
|
||||
writer(target.path);
|
||||
save_prepared_file(target.path, target.suggested_name, callback);
|
||||
};
|
||||
|
||||
if (target.write_on_background_thread)
|
||||
std::thread(write_and_save).detach();
|
||||
else
|
||||
write_and_save();
|
||||
}
|
||||
#elif __WEB__
|
||||
void App::pick_file_save(const std::string& type, const std::string& default_name,
|
||||
std::function<void(std::string)> writer, std::function<void(const std::string& path, bool saved)> callback)
|
||||
{
|
||||
redraw = true;
|
||||
auto path = data_path + "/" + default_name + "." + type;
|
||||
LOG("App::pick_file_save %s", path.c_str());
|
||||
writer(path);
|
||||
webgl_pick_file_save(path, default_name + "." + type, callback);
|
||||
}
|
||||
#else
|
||||
|
||||
void App::pick_file_save(std::vector<std::string> types, std::function<void(std::string)> callback)
|
||||
{
|
||||
redraw = true;
|
||||
#if __OSX__
|
||||
dispatch_async(dispatch_get_main_queue(), ^{
|
||||
//NSArray* fileTypes = [NSArray arrayWithObjects:@"ppi", @"PPI", nil];
|
||||
NSMutableArray<NSString*>* fileTypes = [NSMutableArray arrayWithCapacity:types.size()];
|
||||
for (const auto& t : types)
|
||||
[fileTypes addObject:[NSString stringWithCString:t.c_str() encoding:NSUTF8StringEncoding]];
|
||||
std::string path = [osx_view pick_file_save:fileTypes];
|
||||
if (!path.empty())
|
||||
callback(path);
|
||||
});
|
||||
#elif __ANDROID__
|
||||
android_pick_file_save(callback);
|
||||
#elif _WIN32
|
||||
std::string filter = "Supported Files (";
|
||||
bool first_type = true;
|
||||
for (auto& t : types)
|
||||
{
|
||||
filter.append(std::string(first_type ? "" : " ,") + "*." + t);
|
||||
first_type = false;
|
||||
}
|
||||
filter.append(")");
|
||||
filter.push_back(0);
|
||||
first_type = true;
|
||||
for (auto& t : types)
|
||||
{
|
||||
filter.append(std::string(first_type ? "" : ";") + "*." + t);
|
||||
first_type = false;
|
||||
}
|
||||
filter.push_back(0);
|
||||
std::string path = win32_save_file(filter.c_str());
|
||||
if (!path.empty())
|
||||
callback(path);
|
||||
#endif
|
||||
active_platform_services().pick_save_file(std::move(types), std::move(callback));
|
||||
}
|
||||
|
||||
bool App::uses_prepared_file_writes() const
|
||||
{
|
||||
return active_platform_services().uses_prepared_file_writes();
|
||||
}
|
||||
|
||||
bool App::uses_work_directory_document_export_collections() const
|
||||
{
|
||||
return active_platform_services().uses_work_directory_document_export_collections();
|
||||
}
|
||||
|
||||
bool App::disables_network_tls_verification() const
|
||||
{
|
||||
return active_platform_services().disables_network_tls_verification();
|
||||
}
|
||||
|
||||
bool App::uses_ppbr_export_data_directory_override() const
|
||||
{
|
||||
return active_platform_services().uses_ppbr_export_data_directory_override();
|
||||
}
|
||||
|
||||
bool App::platform_supports_sonarpen() const
|
||||
{
|
||||
return active_platform_services().supports_sonarpen();
|
||||
}
|
||||
|
||||
void App::start_platform_sonarpen()
|
||||
{
|
||||
active_platform_services().start_sonarpen();
|
||||
}
|
||||
|
||||
int App::default_canvas_resolution() const
|
||||
{
|
||||
return active_platform_services().default_canvas_resolution();
|
||||
}
|
||||
|
||||
bool App::draws_canvas_tip_for_input(kEventSource source, kEventType type) const
|
||||
{
|
||||
return active_platform_services().draws_canvas_tip_for_pointer(
|
||||
source == kEventSource::Mouse,
|
||||
source == kEventSource::Stylus,
|
||||
type == kEventType::MouseUpL);
|
||||
}
|
||||
|
||||
float App::adjust_canvas_input_pressure(float pressure) const
|
||||
{
|
||||
return active_platform_services().adjust_canvas_input_pressure(pressure);
|
||||
}
|
||||
#endif
|
||||
|
||||
void App::pick_dir(std::function<void(std::string path)> callback)
|
||||
{
|
||||
redraw = true;
|
||||
#ifdef __IOS__
|
||||
// NOT IMPLEMENTED
|
||||
#elif __OSX__
|
||||
dispatch_async(dispatch_get_main_queue(), ^{
|
||||
std::string path = [osx_view pick_dir];
|
||||
if (!path.empty())
|
||||
callback(path);
|
||||
});
|
||||
#elif __ANDROID__
|
||||
// NOT IMPLEMENTED
|
||||
#elif _WIN32
|
||||
// TODO: to be implemented
|
||||
std::string path = win32_open_dir();
|
||||
if (!path.empty())
|
||||
callback(path);
|
||||
#endif
|
||||
active_platform_services().pick_directory(std::move(callback));
|
||||
}
|
||||
|
||||
bool App::supports_working_directory_picker() const
|
||||
{
|
||||
return active_platform_services().supports_working_directory_picker();
|
||||
}
|
||||
|
||||
std::string App::format_working_directory_path(std::string_view path) const
|
||||
{
|
||||
return active_platform_services().format_working_directory_path(path);
|
||||
}
|
||||
|
||||
void App::display_file(std::string path)
|
||||
{
|
||||
#ifdef __IOS__
|
||||
dispatch_async(dispatch_get_main_queue(), ^{
|
||||
[ios_view display_file:path];
|
||||
});
|
||||
#elif __OSX__
|
||||
[[NSWorkspace sharedWorkspace] openFile:[NSString stringWithUTF8String:path.c_str()]];
|
||||
// dispatch_async(dispatch_get_main_queue(), ^{
|
||||
// std::string path = [osx_view pick_file];
|
||||
// if (!path.empty())
|
||||
// callback(path);
|
||||
// });
|
||||
#elif __ANDROID__
|
||||
//displayKeyboard(and_app, false);
|
||||
#elif _WIN32
|
||||
// std::string path = win32_open_file();
|
||||
// if (!path.empty())
|
||||
// callback(path);
|
||||
#endif
|
||||
if (pp::app::plan_display_file(path) == pp::app::DisplayFileAction::ignore_empty_path)
|
||||
return;
|
||||
|
||||
active_platform_services().display_file(path);
|
||||
}
|
||||
|
||||
void App::share_file(std::string path)
|
||||
{
|
||||
if (path.empty())
|
||||
const auto plan = pp::app::plan_document_share(path);
|
||||
if (plan == pp::app::DocumentShareAction::show_save_required_warning)
|
||||
{
|
||||
message_box("Sharing failed", "Please save the document before sharing it.");
|
||||
return;
|
||||
}
|
||||
#ifdef __IOS__
|
||||
dispatch_async(dispatch_get_main_queue(), ^{
|
||||
[ios_view share_file:[NSString stringWithUTF8String:path.c_str()]];
|
||||
});
|
||||
#elif __OSX__
|
||||
dispatch_async(dispatch_get_main_queue(), ^{
|
||||
[osx_view share_file:[NSString stringWithUTF8String:path.c_str()]];
|
||||
});
|
||||
#elif __ANDROID__
|
||||
#elif _WIN32
|
||||
// not implemented
|
||||
#endif
|
||||
active_platform_services().share_file(path);
|
||||
}
|
||||
|
||||
void App::request_app_close()
|
||||
{
|
||||
active_platform_services().request_app_close();
|
||||
}
|
||||
|
||||
bool App::start_platform_vr_mode()
|
||||
{
|
||||
return active_platform_services().start_vr_mode();
|
||||
}
|
||||
|
||||
void App::stop_platform_vr_mode()
|
||||
{
|
||||
active_platform_services().stop_vr_mode();
|
||||
}
|
||||
|
||||
void App::attach_ui_thread()
|
||||
{
|
||||
active_platform_services().attach_ui_thread();
|
||||
}
|
||||
|
||||
void App::detach_ui_thread()
|
||||
{
|
||||
active_platform_services().detach_ui_thread();
|
||||
}
|
||||
|
||||
void App::acquire_render_context()
|
||||
{
|
||||
active_platform_services().acquire_render_context();
|
||||
}
|
||||
|
||||
void App::release_render_context()
|
||||
{
|
||||
active_platform_services().release_render_context();
|
||||
}
|
||||
|
||||
void App::present_render_context()
|
||||
{
|
||||
active_platform_services().present_render_context();
|
||||
}
|
||||
|
||||
void App::bind_default_render_target()
|
||||
{
|
||||
active_platform_services().bind_default_render_target();
|
||||
}
|
||||
|
||||
void App::bind_main_render_target()
|
||||
{
|
||||
active_platform_services().bind_main_render_target();
|
||||
}
|
||||
|
||||
void App::apply_render_platform_hints()
|
||||
{
|
||||
active_platform_services().apply_render_platform_hints();
|
||||
}
|
||||
|
||||
void App::install_render_debug_callback()
|
||||
{
|
||||
active_platform_services().install_render_debug_callback();
|
||||
}
|
||||
|
||||
void App::begin_render_capture_frame()
|
||||
{
|
||||
active_platform_services().begin_render_capture_frame();
|
||||
}
|
||||
|
||||
void App::end_render_capture_frame()
|
||||
{
|
||||
active_platform_services().end_render_capture_frame();
|
||||
}
|
||||
|
||||
bool App::platform_deletes_recorded_files_on_clear()
|
||||
{
|
||||
return active_platform_services().deletes_recorded_files_on_clear();
|
||||
}
|
||||
|
||||
void App::clear_platform_recorded_files(std::string path)
|
||||
{
|
||||
active_platform_services().clear_recorded_files(path);
|
||||
}
|
||||
|
||||
void App::publish_exported_image(std::string path)
|
||||
{
|
||||
active_platform_services().publish_exported_image(path);
|
||||
}
|
||||
|
||||
void App::flush_platform_storage()
|
||||
{
|
||||
active_platform_services().flush_persistent_storage();
|
||||
}
|
||||
|
||||
std::vector<std::string> App::document_browse_roots() const
|
||||
{
|
||||
return active_platform_services().document_browse_roots(work_path, data_path);
|
||||
}
|
||||
|
||||
void App::save_platform_ui_state()
|
||||
{
|
||||
active_platform_services().save_ui_state();
|
||||
}
|
||||
|
||||
bool App::platform_enables_live_asset_reloading()
|
||||
{
|
||||
return active_platform_services().enables_live_asset_reloading();
|
||||
}
|
||||
|
||||
void App::update_platform_frame(float delta_time_seconds)
|
||||
{
|
||||
active_platform_services().update_platform_frame(delta_time_seconds);
|
||||
}
|
||||
|
||||
void App::report_rendered_frames(int frames)
|
||||
{
|
||||
active_platform_services().report_rendered_frames(frames);
|
||||
}
|
||||
|
||||
void App::save_prepared_file(
|
||||
std::string path,
|
||||
std::string suggested_name,
|
||||
std::function<void(const std::string& path, bool saved)> callback)
|
||||
{
|
||||
active_platform_services().save_prepared_file(
|
||||
path,
|
||||
suggested_name,
|
||||
[callback = std::move(callback)](std::string saved_path, bool saved) {
|
||||
callback(saved_path, saved);
|
||||
});
|
||||
}
|
||||
|
||||
bool App::mouse_down(int button, float x, float y, float pressure, kEventSource source, bool eraser)
|
||||
{
|
||||
redraw = true;
|
||||
const auto plan = pp::app::plan_app_pointer_dispatch(
|
||||
x,
|
||||
y,
|
||||
zoom,
|
||||
layout_designer.get(main_id) != nullptr,
|
||||
layout.get(main_id) != nullptr);
|
||||
if (!plan) {
|
||||
LOG("Mouse down dispatch plan failed: %s", plan.status().message);
|
||||
return false;
|
||||
}
|
||||
|
||||
redraw = plan.value().request_redraw;
|
||||
MouseEvent e;
|
||||
e.m_type = button ? kEventType::MouseDownR : kEventType::MouseDownL;
|
||||
e.m_pos = { x / zoom, y / zoom };
|
||||
e.m_pos = { plan.value().normalized_x, plan.value().normalized_y };
|
||||
e.m_pressure = pressure;
|
||||
e.m_source = source;
|
||||
e.m_eraser = eraser;
|
||||
kEventResult ret = kEventResult::Available;
|
||||
if (auto* main = layout_designer[main_id])
|
||||
if (auto* main = layout_designer[main_id]; plan.value().dispatch_designer_first && main)
|
||||
return main->on_event(&e) == kEventResult::Consumed;
|
||||
if (auto* main = layout[main_id])
|
||||
if (auto* main = layout[main_id]; plan.value().dispatch_main_if_not_consumed && main)
|
||||
ret = main->on_event(&e);
|
||||
return ret == kEventResult::Consumed;
|
||||
}
|
||||
bool App::mouse_move(float x, float y, float pressure, kEventSource source, bool eraser)
|
||||
{
|
||||
cursor = { x / zoom, y / zoom };
|
||||
redraw = true;
|
||||
const auto plan = pp::app::plan_app_pointer_dispatch(
|
||||
x,
|
||||
y,
|
||||
zoom,
|
||||
layout_designer.get(main_id) != nullptr,
|
||||
layout.get(main_id) != nullptr);
|
||||
if (!plan) {
|
||||
LOG("Mouse move dispatch plan failed: %s", plan.status().message);
|
||||
return false;
|
||||
}
|
||||
|
||||
cursor = { plan.value().normalized_x, plan.value().normalized_y };
|
||||
redraw = plan.value().request_redraw;
|
||||
MouseEvent e;
|
||||
e.m_type = kEventType::MouseMove;
|
||||
e.m_pos = { x / zoom, y / zoom };
|
||||
e.m_pos = { plan.value().normalized_x, plan.value().normalized_y };
|
||||
e.m_pressure = pressure;
|
||||
e.m_source = source;
|
||||
e.m_eraser = eraser;
|
||||
kEventResult ret = kEventResult::Available;
|
||||
if (auto* main = layout_designer[main_id])
|
||||
if (auto* main = layout_designer[main_id]; plan.value().dispatch_designer_first && main)
|
||||
return main->on_event(&e) == kEventResult::Consumed;
|
||||
if (auto* main = layout[main_id])
|
||||
if (auto* main = layout[main_id]; plan.value().dispatch_main_if_not_consumed && main)
|
||||
ret = main->on_event(&e);
|
||||
return ret == kEventResult::Consumed;
|
||||
}
|
||||
bool App::mouse_up(int button, float x, float y, kEventSource source, bool eraser)
|
||||
{
|
||||
redraw = true;
|
||||
const auto plan = pp::app::plan_app_pointer_dispatch(
|
||||
x,
|
||||
y,
|
||||
zoom,
|
||||
layout_designer.get(main_id) != nullptr,
|
||||
layout.get(main_id) != nullptr);
|
||||
if (!plan) {
|
||||
LOG("Mouse up dispatch plan failed: %s", plan.status().message);
|
||||
return false;
|
||||
}
|
||||
|
||||
redraw = plan.value().request_redraw;
|
||||
MouseEvent e;
|
||||
e.m_type = button ? kEventType::MouseUpR : kEventType::MouseUpL;
|
||||
e.m_pos = { x / zoom, y / zoom };
|
||||
e.m_pos = { plan.value().normalized_x, plan.value().normalized_y };
|
||||
e.m_source = source;
|
||||
e.m_eraser = eraser;
|
||||
kEventResult ret = kEventResult::Available;
|
||||
if (auto* main = layout_designer[main_id])
|
||||
if (auto* main = layout_designer[main_id]; plan.value().dispatch_designer_first && main)
|
||||
return main->on_event(&e) == kEventResult::Consumed;
|
||||
if (auto* main = layout[main_id])
|
||||
if (auto* main = layout[main_id]; plan.value().dispatch_main_if_not_consumed && main)
|
||||
ret = main->on_event(&e);
|
||||
return ret == kEventResult::Consumed;
|
||||
}
|
||||
bool App::mouse_scroll(float x, float y, float delta)
|
||||
{
|
||||
redraw = true;
|
||||
const auto plan = pp::app::plan_app_pointer_dispatch(
|
||||
x,
|
||||
y,
|
||||
zoom,
|
||||
layout_designer.get(main_id) != nullptr,
|
||||
layout.get(main_id) != nullptr);
|
||||
if (!plan) {
|
||||
LOG("Mouse scroll dispatch plan failed: %s", plan.status().message);
|
||||
return false;
|
||||
}
|
||||
|
||||
redraw = plan.value().request_redraw;
|
||||
MouseEvent e;
|
||||
e.m_type = kEventType::MouseScroll;
|
||||
e.m_pos = { x / zoom, y / zoom };
|
||||
e.m_pos = { plan.value().normalized_x, plan.value().normalized_y };
|
||||
e.m_scroll_delta = delta;
|
||||
kEventResult ret = kEventResult::Available;
|
||||
if (auto* main = layout_designer[main_id])
|
||||
if (auto* main = layout_designer[main_id]; plan.value().dispatch_designer_first && main)
|
||||
return main->on_event(&e) == kEventResult::Consumed;
|
||||
if (auto* main = layout[main_id])
|
||||
if (auto* main = layout[main_id]; plan.value().dispatch_main_if_not_consumed && main)
|
||||
ret = main->on_event(&e);
|
||||
return ret == kEventResult::Consumed;
|
||||
}
|
||||
bool App::mouse_cancel(int button)
|
||||
{
|
||||
redraw = true;
|
||||
const auto plan = pp::app::plan_app_mouse_cancel_dispatch(
|
||||
layout_designer.get(main_id) != nullptr,
|
||||
layout.get(main_id) != nullptr);
|
||||
redraw = plan.request_redraw;
|
||||
MouseEvent e;
|
||||
e.m_type = kEventType::MouseCancel;
|
||||
kEventResult ret = kEventResult::Available;
|
||||
if (auto* main = layout_designer[main_id])
|
||||
if (auto* main = layout_designer[main_id]; plan.dispatch_designer_first && main)
|
||||
return main->on_event(&e) == kEventResult::Consumed;
|
||||
if (auto* main = layout[main_id])
|
||||
if (auto* main = layout[main_id]; plan.dispatch_main_if_not_consumed && main)
|
||||
ret = main->on_event(&e);
|
||||
return ret == kEventResult::Consumed;
|
||||
}
|
||||
bool App::gesture_start(const glm::vec2& p0, const glm::vec2& p1)
|
||||
{
|
||||
redraw = true;
|
||||
const auto plan = pp::app::plan_app_gesture_dispatch(
|
||||
p0.x,
|
||||
p0.y,
|
||||
p1.x,
|
||||
p1.y,
|
||||
p0.x,
|
||||
p0.y,
|
||||
p1.x,
|
||||
p1.y,
|
||||
zoom,
|
||||
layout.get(main_id) != nullptr);
|
||||
if (!plan) {
|
||||
LOG("Gesture start dispatch plan failed: %s", plan.status().message);
|
||||
return false;
|
||||
}
|
||||
|
||||
redraw = plan.value().request_redraw;
|
||||
GestureEvent e;
|
||||
glm::vec2 p = glm::lerp(p0, p1, 0.5f);
|
||||
e.m_type = kEventType::GestureStart;
|
||||
e.m_pos = p / glm::vec2(zoom);
|
||||
e.m_distance = glm::distance(p0, p1);
|
||||
e.m_pos = { plan.value().normalized_x, plan.value().normalized_y };
|
||||
e.m_distance = plan.value().distance;
|
||||
gesture_p0 = p0;
|
||||
gesture_p1 = p1;
|
||||
kEventResult ret = kEventResult::Available;
|
||||
if (auto* main = layout[main_id])
|
||||
if (auto* main = layout[main_id]; plan.value().dispatch_main && main)
|
||||
ret = main->on_event(&e);
|
||||
return ret == kEventResult::Consumed;
|
||||
}
|
||||
bool App::gesture_move(const glm::vec2& p0, const glm::vec2& p1)
|
||||
{
|
||||
redraw = true;
|
||||
const auto plan = pp::app::plan_app_gesture_dispatch(
|
||||
p0.x,
|
||||
p0.y,
|
||||
p1.x,
|
||||
p1.y,
|
||||
gesture_p0.x,
|
||||
gesture_p0.y,
|
||||
gesture_p1.x,
|
||||
gesture_p1.y,
|
||||
zoom,
|
||||
layout.get(main_id) != nullptr);
|
||||
if (!plan) {
|
||||
LOG("Gesture move dispatch plan failed: %s", plan.status().message);
|
||||
return false;
|
||||
}
|
||||
|
||||
redraw = plan.value().request_redraw;
|
||||
GestureEvent e;
|
||||
glm::vec2 p = glm::lerp(p0, p1, 0.5f);
|
||||
e.m_type = kEventType::GestureMove;
|
||||
e.m_pos = p / glm::vec2(zoom);
|
||||
e.m_distance = glm::distance(p0, p1);
|
||||
e.m_distance_delta = e.m_distance - glm::distance(gesture_p0, gesture_p1);
|
||||
e.m_pos_delta = p - glm::lerp(gesture_p0, gesture_p1, 0.5f);
|
||||
e.m_pos = { plan.value().normalized_x, plan.value().normalized_y };
|
||||
e.m_distance = plan.value().distance;
|
||||
e.m_distance_delta = plan.value().distance_delta;
|
||||
e.m_pos_delta = { plan.value().position_delta_x, plan.value().position_delta_y };
|
||||
kEventResult ret = kEventResult::Available;
|
||||
if (auto* main = layout[main_id])
|
||||
if (auto* main = layout[main_id]; plan.value().dispatch_main && main)
|
||||
ret = main->on_event(&e);
|
||||
return ret == kEventResult::Consumed;
|
||||
}
|
||||
bool App::gesture_end()
|
||||
{
|
||||
redraw = true;
|
||||
const auto plan = pp::app::plan_app_main_input_dispatch(layout.get(main_id) != nullptr);
|
||||
redraw = plan.request_redraw;
|
||||
GestureEvent e;
|
||||
e.m_type = kEventType::GestureEnd;
|
||||
kEventResult ret = kEventResult::Available;
|
||||
if (auto* main = layout[main_id])
|
||||
if (auto* main = layout[main_id]; plan.dispatch_main && main)
|
||||
ret = main->on_event(&e);
|
||||
return ret == kEventResult::Consumed;
|
||||
}
|
||||
bool App::touch_tap(const glm::vec2& pos, int fingers, int tap_count)
|
||||
{
|
||||
redraw = true;
|
||||
const auto plan = pp::app::plan_app_main_input_dispatch(layout.get(main_id) != nullptr);
|
||||
redraw = plan.request_redraw;
|
||||
TouchEvent e;
|
||||
e.m_type = kEventType::TouchTap;
|
||||
e.m_finger_count = fingers;
|
||||
e.m_tap_count = tap_count;
|
||||
kEventResult ret = kEventResult::Available;
|
||||
if (auto* main = layout[main_id])
|
||||
if (auto* main = layout[main_id]; plan.dispatch_main && main)
|
||||
ret = main->on_event(&e);
|
||||
return ret == kEventResult::Consumed;
|
||||
}
|
||||
bool App::key_down(kKey key)
|
||||
{
|
||||
if (key == kKey::KeySpacebar && vr_active)
|
||||
const auto plan = pp::app::plan_app_key_down_dispatch(
|
||||
layout.get(main_id) != nullptr,
|
||||
key == kKey::KeySpacebar,
|
||||
vr_active);
|
||||
if (plan.sync_vr_camera_rotation)
|
||||
canvas->m_canvas->m_cam_rot = vr_rot;
|
||||
redraw = true;
|
||||
keys[(int)key] = true;
|
||||
redraw = plan.request_redraw;
|
||||
keys[(int)key] = plan.set_key_down;
|
||||
KeyEvent e;
|
||||
e.m_type = kEventType::KeyDown;
|
||||
e.m_key = key;
|
||||
kEventResult ret = kEventResult::Available;
|
||||
if (auto* main = layout[main_id])
|
||||
if (auto* main = layout[main_id]; plan.dispatch_main && main)
|
||||
ret = main->on_event(&e);
|
||||
return ret == kEventResult::Consumed;
|
||||
}
|
||||
bool App::key_up(kKey key)
|
||||
{
|
||||
redraw = true;
|
||||
keys[(int)key] = false;
|
||||
const auto plan = pp::app::plan_app_key_up_dispatch(layout.get(main_id) != nullptr);
|
||||
redraw = plan.request_redraw;
|
||||
keys[(int)key] = plan.set_key_down;
|
||||
KeyEvent e;
|
||||
e.m_type = kEventType::KeyUp;
|
||||
e.m_key = key;
|
||||
kEventResult ret = kEventResult::Available;
|
||||
if (auto* main = layout[main_id])
|
||||
if (auto* main = layout[main_id]; plan.dispatch_main && main)
|
||||
ret = main->on_event(&e);
|
||||
return ret == kEventResult::Consumed;
|
||||
}
|
||||
bool App::key_char(char key)
|
||||
{
|
||||
redraw = true;
|
||||
const auto plan = pp::app::plan_app_main_input_dispatch(layout.get(main_id) != nullptr);
|
||||
redraw = plan.request_redraw;
|
||||
KeyEvent e;
|
||||
e.m_type = kEventType::KeyChar;
|
||||
e.m_char = key;
|
||||
kEventResult ret = kEventResult::Available;
|
||||
if (auto* main = layout[main_id])
|
||||
if (auto* main = layout[main_id]; plan.dispatch_main && main)
|
||||
ret = main->on_event(&e);
|
||||
return ret == kEventResult::Consumed;
|
||||
}
|
||||
|
||||
void App::toggle_ui()
|
||||
{
|
||||
auto m = layout[main_id]->m_children[1];
|
||||
ui_visible = !ui_visible;
|
||||
for (int i = 1; i < m->m_children.size(); i++)
|
||||
m->m_children[i]->m_display = ui_visible;
|
||||
auto* main = layout[main_id];
|
||||
const std::size_t main_child_count = main ? main->m_children.size() : 0U;
|
||||
auto* panel_container = main_child_count > 1U ? main->m_children[1].get() : nullptr;
|
||||
const auto plan = pp::app::plan_app_ui_visibility_toggle(
|
||||
ui_visible,
|
||||
main != nullptr,
|
||||
main_child_count,
|
||||
panel_container ? panel_container->m_children.size() : 0U);
|
||||
if (!plan) {
|
||||
LOG("UI toggle plan failed: %s", plan.status().message);
|
||||
return;
|
||||
}
|
||||
|
||||
ui_visible = plan.value().next_ui_visible;
|
||||
if (!panel_container)
|
||||
return;
|
||||
|
||||
for (std::size_t i = plan.value().first_panel_child_index;
|
||||
i < plan.value().panel_child_count;
|
||||
++i) {
|
||||
panel_container->m_children[i]->m_display = ui_visible;
|
||||
}
|
||||
}
|
||||
|
||||
void App::set_stylus()
|
||||
{
|
||||
has_stylus = true;
|
||||
if (canvas)
|
||||
const auto plan = pp::app::plan_app_stylus_attach(canvas != nullptr);
|
||||
has_stylus = plan.set_has_stylus;
|
||||
if (plan.enable_canvas_touch_lock && canvas)
|
||||
canvas->m_canvas->m_touch_lock = true;
|
||||
}
|
||||
|
||||
1229
src/app_layout.cpp
1229
src/app_layout.cpp
File diff suppressed because it is too large
Load Diff
@@ -1,100 +1,68 @@
|
||||
#include "pch.h"
|
||||
#include "app.h"
|
||||
#include "legacy_gl_runtime_dispatch.h"
|
||||
#include "renderer_api/shader_catalog.h"
|
||||
#include "renderer_gl/opengl_capabilities.h"
|
||||
#include "shader.h"
|
||||
|
||||
namespace {
|
||||
|
||||
void apply_shader_manager_feature_state(pp::renderer::gl::OpenGlFeatureState feature_state) noexcept
|
||||
{
|
||||
ShaderManager::ext_framebuffer_fetch = feature_state.capabilities.framebuffer_fetch;
|
||||
ShaderManager::ext_map_aligned = feature_state.capabilities.map_buffer_alignment;
|
||||
ShaderManager::ext_float32 = feature_state.capabilities.float32_textures;
|
||||
ShaderManager::ext_float32_linear = feature_state.capabilities.float32_linear;
|
||||
ShaderManager::ext_float16 = feature_state.capabilities.float16_textures;
|
||||
ShaderManager::set_render_device_features(feature_state.features);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
void App::initShaders()
|
||||
{
|
||||
#ifdef _DEBUG
|
||||
if (!check_uniform_uniqueness())
|
||||
std::logic_error("check_uniform_uniqueness() failed");
|
||||
LOG("check_uniform_uniqueness() failed");
|
||||
#endif // _DEBUG
|
||||
|
||||
render_task([] {
|
||||
GLint n_exts;
|
||||
glGetIntegerv(GL_NUM_EXTENSIONS, &n_exts);
|
||||
for (int i = 0; i < n_exts; i++)
|
||||
{
|
||||
std::string ext = (const char*)glGetStringi(GL_EXTENSIONS, i);
|
||||
if (ext.find("shader_framebuffer_fetch") != std::string::npos)
|
||||
ShaderManager::ext_framebuffer_fetch = true;
|
||||
if (ext.find("map_buffer_alignment") != std::string::npos)
|
||||
ShaderManager::ext_map_aligned = true;
|
||||
#if __GLES__ && !__WEB__
|
||||
if (ext.find("texture_float") != std::string::npos)
|
||||
ShaderManager::ext_float32 = true;
|
||||
if (ext.find("texture_float_linear") != std::string::npos)
|
||||
ShaderManager::ext_float32_linear = true;
|
||||
if (ext.find("color_buffer_float") != std::string::npos)
|
||||
ShaderManager::ext_float32 = true;
|
||||
if (ext.find("texture_half_float") != std::string::npos)
|
||||
ShaderManager::ext_float16 = true;
|
||||
if (ext.find("color_buffer_half_float") != std::string::npos)
|
||||
ShaderManager::ext_float16 = true;
|
||||
#endif
|
||||
LOG("EXT: %s", ext.c_str());
|
||||
const auto detection_result = pp::renderer::gl::query_opengl_capability_detection(
|
||||
pp::legacy::gl_runtime::extension_query_dispatch(),
|
||||
pp::renderer::gl::opengl_runtime_for_current_build());
|
||||
if (!detection_result.ok()) {
|
||||
LOG("OpenGL capability detection failed: %s", detection_result.status().message);
|
||||
return;
|
||||
}
|
||||
|
||||
const auto& detection = detection_result.value();
|
||||
for (const auto& extension : detection.extensions) {
|
||||
LOG("EXT: %s", extension.c_str());
|
||||
}
|
||||
|
||||
apply_shader_manager_feature_state(detection.feature_state);
|
||||
});
|
||||
|
||||
#if __GL__
|
||||
// In OpenGL 3.3 these should be already available
|
||||
ShaderManager::ext_float32_linear = true;
|
||||
ShaderManager::ext_float32 = true;
|
||||
ShaderManager::ext_float16 = true;
|
||||
#endif
|
||||
apply_shader_manager_feature_state(pp::renderer::gl::detect_opengl_feature_state(
|
||||
std::span<const std::string_view> {},
|
||||
pp::renderer::gl::opengl_runtime_for_current_build()));
|
||||
|
||||
LOG("Shader Extension shader_framebuffer_fetch: %s", ShaderManager::ext_framebuffer_fetch ? "enabled" : "disabled");
|
||||
|
||||
LOG("initializing shaders");
|
||||
if (!ShaderManager::load(kShader::Texture, "data/shaders/texture.glsl"))
|
||||
LOG("Failed to create shader Texture");
|
||||
if (!ShaderManager::load(kShader::TextureAlpha, "data/shaders/texture-alpha.glsl"))
|
||||
LOG("Failed to create shader TextureAlpha");
|
||||
if (!ShaderManager::load(kShader::TextureMask, "data/shaders/texture-mask.glsl"))
|
||||
LOG("Failed to create shader TextureMask");
|
||||
if (!ShaderManager::load(kShader::TextureColorize, "data/shaders/texture-colorize.glsl"))
|
||||
LOG("Failed to create shader TextureColorize");
|
||||
if (!ShaderManager::load(kShader::TextureBlend, "data/shaders/texture-blend.glsl"))
|
||||
LOG("Failed to create shader TextureBlend");
|
||||
if (!ShaderManager::load(kShader::StrokePreview, "data/shaders/stroke-preview.glsl"))
|
||||
LOG("Failed to create shader StrokePreview");
|
||||
if (!ShaderManager::load(kShader::CompErase, "data/shaders/comp-erase.glsl"))
|
||||
LOG("Failed to create shader CompErase");
|
||||
if (!ShaderManager::load(kShader::CompDraw, "data/shaders/comp-draw.glsl"))
|
||||
LOG("Failed to create shader CompDraw");
|
||||
if (!ShaderManager::load(kShader::Color, "data/shaders/color.glsl"))
|
||||
LOG("Failed to create shader Color");
|
||||
if (!ShaderManager::load(kShader::ColorQuad, "data/shaders/color-quad.glsl"))
|
||||
LOG("Failed to create shader ColorQuad");
|
||||
if (!ShaderManager::load(kShader::ColorTri, "data/shaders/color-tri.glsl"))
|
||||
LOG("Failed to create shader ColorTri");
|
||||
if (!ShaderManager::load(kShader::ColorHue, "data/shaders/color-hue.glsl"))
|
||||
LOG("Failed to create shader ColorHue");
|
||||
if (!ShaderManager::load(kShader::UVs, "data/shaders/uvs.glsl"))
|
||||
LOG("Failed to create shader UVs");
|
||||
if (!ShaderManager::load(kShader::Font, "data/shaders/font.glsl"))
|
||||
LOG("Failed to create shader Font");
|
||||
if (!ShaderManager::load(kShader::Atlas, "data/shaders/atlas.glsl"))
|
||||
LOG("Failed to create shader Atlas");
|
||||
if (!ShaderManager::load(kShader::Stroke, "data/shaders/stroke.glsl"))
|
||||
LOG("Failed to create shader Stroke");
|
||||
if (!ShaderManager::load(kShader::StrokePad, "data/shaders/stroke-pad.glsl"))
|
||||
LOG("Failed to create shader StrokePad");
|
||||
if (!ShaderManager::load(kShader::StrokeDilate, "data/shaders/stroke-dilate.glsl"))
|
||||
LOG("Failed to create shader StrokeDilate");
|
||||
if (!ShaderManager::load(kShader::Checkerboard, "data/shaders/checkerboard.glsl"))
|
||||
LOG("Failed to create shader Checkerboard");
|
||||
if (!ShaderManager::load(kShader::Equirect, "data/shaders/equirect.glsl"))
|
||||
LOG("Failed to create shader Equirect");
|
||||
if (!ShaderManager::load(kShader::BrushStroke, "data/shaders/stroke-instanced.glsl"))
|
||||
LOG("Failed to create shader BrushStroke");
|
||||
if (!ShaderManager::load(kShader::VertexColor, "data/shaders/vertex-color.glsl"))
|
||||
LOG("Failed to create shader VertexColor");
|
||||
if (!ShaderManager::load(kShader::Lambert, "data/shaders/lambert.glsl"))
|
||||
LOG("Failed to create shader Lambert");
|
||||
if (!ShaderManager::load(kShader::LambertLightmap, "data/shaders/lightmap.glsl"))
|
||||
LOG("Failed to create shader LambertLightmap");
|
||||
if (!ShaderManager::load(kShader::BakeUV, "data/shaders/bake-uv.glsl"))
|
||||
LOG("Failed to create shader BakeUV");
|
||||
const auto shader_catalog = pp::renderer::panopainter_shader_catalog();
|
||||
const auto catalog_status = pp::renderer::validate_shader_catalog(shader_catalog);
|
||||
if (!catalog_status.ok())
|
||||
{
|
||||
LOG("Shader catalog validation failed: %s", catalog_status.message);
|
||||
return;
|
||||
}
|
||||
|
||||
for (const auto& shader : shader_catalog)
|
||||
{
|
||||
if (!ShaderManager::load(static_cast<kShader>(const_hash(shader.name)), shader.path))
|
||||
LOG("Failed to create shader %s", shader.name);
|
||||
}
|
||||
LOG("shaders initialized");
|
||||
}
|
||||
|
||||
|
||||
169
src/app_vr.cpp
169
src/app_vr.cpp
@@ -1,12 +1,88 @@
|
||||
#include "pch.h"
|
||||
|
||||
#include <cstdint>
|
||||
|
||||
#include "app.h"
|
||||
#include "legacy_ui_gl_dispatch.h"
|
||||
#include "node_panel_grid.h"
|
||||
#include "util.h"
|
||||
#include "shape.h"
|
||||
#include "renderer_gl/opengl_capabilities.h"
|
||||
|
||||
#ifdef _WIN32
|
||||
bool win32_vr_start();
|
||||
void win32_vr_stop();
|
||||
#endif
|
||||
namespace {
|
||||
|
||||
void set_active_texture_unit(std::uint32_t unit_index)
|
||||
{
|
||||
pp::legacy::ui_gl::activate_texture_unit(unit_index, "OpenGL VR");
|
||||
}
|
||||
|
||||
void unbind_texture_2d()
|
||||
{
|
||||
pp::legacy::ui_gl::unbind_texture_2d("OpenGL VR");
|
||||
}
|
||||
|
||||
void apply_vr_ui_viewport(pp::renderer::gl::OpenGlViewportRect viewport)
|
||||
{
|
||||
const auto status = pp::renderer::gl::apply_opengl_viewport(
|
||||
viewport,
|
||||
pp::renderer::gl::OpenGlViewportDispatch {
|
||||
.viewport = pp::legacy::ui_gl::set_opengl_viewport,
|
||||
});
|
||||
if (!status.ok())
|
||||
LOG("OpenGL VR UI viewport failed: %s", status.message);
|
||||
}
|
||||
|
||||
void apply_vr_ui_scissor_test(bool enabled)
|
||||
{
|
||||
const auto status = pp::renderer::gl::apply_opengl_scissor_test(
|
||||
enabled,
|
||||
pp::renderer::gl::OpenGlScissorTestDispatch {
|
||||
.enable = pp::legacy::ui_gl::enable_opengl_state,
|
||||
.disable = pp::legacy::ui_gl::disable_opengl_state,
|
||||
});
|
||||
if (!status.ok())
|
||||
LOG("OpenGL VR UI scissor test failed: %s", status.message);
|
||||
}
|
||||
|
||||
void apply_vr_render_capability(std::uint32_t state, bool enabled)
|
||||
{
|
||||
const auto status = pp::renderer::gl::apply_opengl_capability(
|
||||
state,
|
||||
enabled,
|
||||
pp::renderer::gl::OpenGlCapabilityDispatch {
|
||||
.enable = pp::legacy::ui_gl::enable_opengl_state,
|
||||
.disable = pp::legacy::ui_gl::disable_opengl_state,
|
||||
});
|
||||
if (!status.ok())
|
||||
LOG("OpenGL VR render state failed: %s", status.message);
|
||||
}
|
||||
|
||||
bool query_vr_render_capability(std::uint32_t state)
|
||||
{
|
||||
const auto result = pp::renderer::gl::query_opengl_capability_state(
|
||||
state,
|
||||
pp::renderer::gl::OpenGlCapabilityStateQueryDispatch {
|
||||
.is_enabled = pp::legacy::ui_gl::is_opengl_state_enabled,
|
||||
});
|
||||
if (!result.ok()) {
|
||||
LOG("OpenGL VR render state query failed: %s", result.status().message);
|
||||
return false;
|
||||
}
|
||||
return result.value();
|
||||
}
|
||||
|
||||
void clear_vr_depth_buffer()
|
||||
{
|
||||
const auto status = pp::renderer::gl::clear_opengl_buffers(
|
||||
pp::renderer::gl::framebuffer_depth_buffer_mask(),
|
||||
pp::renderer::gl::OpenGlBufferClearDispatch {
|
||||
.clear = pp::legacy::ui_gl::clear_opengl_buffer,
|
||||
});
|
||||
if (!status.ok())
|
||||
LOG("OpenGL VR depth clear failed: %s", status.message);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
bool trigger_down = false;
|
||||
cbuffer<glm::vec3> controller_points(10);
|
||||
@@ -19,31 +95,28 @@ Sphere controller_ray;
|
||||
|
||||
bool App::vr_start()
|
||||
{
|
||||
#ifdef _WIN32
|
||||
return win32_vr_start();
|
||||
#else
|
||||
return false;
|
||||
#endif
|
||||
return start_platform_vr_mode();
|
||||
}
|
||||
|
||||
void App::vr_stop()
|
||||
{
|
||||
#ifdef _WIN32
|
||||
win32_vr_stop();
|
||||
#endif
|
||||
stop_platform_vr_mode();
|
||||
}
|
||||
|
||||
void App::vr_draw_ui()
|
||||
{
|
||||
uirtt.bindFramebuffer();
|
||||
uirtt.clear();
|
||||
glViewport(0, 0, uirtt.getWidth(), uirtt.getHeight());
|
||||
glEnable(GL_SCISSOR_TEST);
|
||||
apply_vr_ui_viewport(pp::renderer::gl::OpenGlViewportRect {
|
||||
.width = static_cast<std::int32_t>(uirtt.getWidth()),
|
||||
.height = static_cast<std::int32_t>(uirtt.getHeight()),
|
||||
});
|
||||
apply_vr_ui_scissor_test(true);
|
||||
auto observer = std::bind(&App::update_ui_observer, this, std::placeholders::_1);
|
||||
for (int i = 1; i < layout[main_id]->m_children.size(); i++)
|
||||
layout[main_id]->m_children[i]->watch(observer);
|
||||
//msgbox->watch(observer);
|
||||
glDisable(GL_SCISSOR_TEST);
|
||||
apply_vr_ui_scissor_test(false);
|
||||
uirtt.unbindFramebuffer();
|
||||
}
|
||||
|
||||
@@ -185,12 +258,12 @@ void App::vr_draw(const glm::mat4& proj, const glm::mat4& camera, const glm::mat
|
||||
glm::vec3 origin = glm::vec3(0, 0, -1) * glm::transpose(glm::mat3(pose));
|
||||
vr_rot = glm::lookAt({ 0, 0, 0 }, origin, { 0, 1, 0 });
|
||||
|
||||
auto blend = glIsEnabled(GL_BLEND);
|
||||
auto depth = glIsEnabled(GL_DEPTH_TEST);
|
||||
const bool blend = query_vr_render_capability(pp::renderer::gl::blend_state());
|
||||
const bool depth = query_vr_render_capability(pp::renderer::gl::depth_test_state());
|
||||
|
||||
glDisable(GL_BLEND);
|
||||
glDisable(GL_DEPTH_TEST);
|
||||
glClear(GL_DEPTH_BUFFER_BIT);
|
||||
apply_vr_render_capability(pp::renderer::gl::blend_state(), false);
|
||||
apply_vr_render_capability(pp::renderer::gl::depth_test_state(), false);
|
||||
clear_vr_depth_buffer();
|
||||
|
||||
for (int plane_index = 0; plane_index < 6; plane_index++)
|
||||
{
|
||||
@@ -205,9 +278,9 @@ void App::vr_draw(const glm::mat4& proj, const glm::mat4& camera, const glm::mat
|
||||
m_face_plane.draw_fill();
|
||||
}
|
||||
|
||||
glEnable(GL_BLEND);
|
||||
glEnable(GL_DEPTH_TEST);
|
||||
glClear(GL_DEPTH_BUFFER_BIT);
|
||||
apply_vr_render_capability(pp::renderer::gl::blend_state(), true);
|
||||
apply_vr_render_capability(pp::renderer::gl::depth_test_state(), true);
|
||||
clear_vr_depth_buffer();
|
||||
|
||||
for (size_t i = 0; i < canvas->m_canvas->m_layers.size(); i++)
|
||||
{
|
||||
@@ -241,17 +314,17 @@ void App::vr_draw(const glm::mat4& proj, const glm::mat4& camera, const glm::mat
|
||||
//ShaderManager::u_int(kShaderUniform::Lock, m_canvas->m_layers[layer_index]->m_alpha_locked);
|
||||
ShaderManager::u_int(kShaderUniform::Mask, canvas->m_canvas->m_smask_active);
|
||||
ShaderManager::u_mat4(kShaderUniform::MVP, plane_mvp_z);
|
||||
glActiveTexture(GL_TEXTURE0);
|
||||
set_active_texture_unit(0);
|
||||
canvas->m_canvas->m_layers[layer_index]->rtt(plane_index).bindTexture();
|
||||
glActiveTexture(GL_TEXTURE1);
|
||||
set_active_texture_unit(1);
|
||||
canvas->m_canvas->m_tmp[plane_index].bindTexture();
|
||||
glActiveTexture(GL_TEXTURE2);
|
||||
set_active_texture_unit(2);
|
||||
canvas->m_canvas->m_smask.rtt(plane_index).bindTexture();
|
||||
m_face_plane.draw_fill();
|
||||
canvas->m_canvas->m_smask.rtt(plane_index).unbindTexture();
|
||||
glActiveTexture(GL_TEXTURE1);
|
||||
set_active_texture_unit(1);
|
||||
canvas->m_canvas->m_tmp[plane_index].unbindTexture();
|
||||
glActiveTexture(GL_TEXTURE0);
|
||||
set_active_texture_unit(0);
|
||||
canvas->m_canvas->m_layers[layer_index]->rtt(plane_index).unbindTexture();
|
||||
}
|
||||
else if (canvas->m_canvas->m_show_tmp && canvas->m_canvas->m_current_layer_idx == layer_index)
|
||||
@@ -292,28 +365,28 @@ void App::vr_draw(const glm::mat4& proj, const glm::mat4& camera, const glm::mat
|
||||
ShaderManager::u_int(kShaderUniform::PatternBlendMode, b->m_pattern_blend_mode);
|
||||
ShaderManager::u_vec2(kShaderUniform::PatternOffset, Canvas::I->m_pattern_offset);
|
||||
|
||||
glActiveTexture(GL_TEXTURE0);
|
||||
set_active_texture_unit(0);
|
||||
canvas->m_canvas->m_layers[layer_index]->rtt(plane_index).bindTexture();
|
||||
glActiveTexture(GL_TEXTURE1);
|
||||
set_active_texture_unit(1);
|
||||
canvas->m_canvas->m_tmp[plane_index].bindTexture();
|
||||
glActiveTexture(GL_TEXTURE2);
|
||||
set_active_texture_unit(2);
|
||||
canvas->m_canvas->m_smask.rtt(plane_index).bindTexture();
|
||||
glActiveTexture(GL_TEXTURE3);
|
||||
set_active_texture_unit(3);
|
||||
if (b->m_dual_enabled)
|
||||
canvas->m_canvas->m_tmp_dual[plane_index].bindTexture();
|
||||
glActiveTexture(GL_TEXTURE4);
|
||||
set_active_texture_unit(4);
|
||||
b->m_pattern_texture ?
|
||||
b->m_pattern_texture->bind() :
|
||||
glBindTexture(GL_TEXTURE_2D, 0);
|
||||
unbind_texture_2d();
|
||||
m_face_plane.draw_fill();
|
||||
glActiveTexture(GL_TEXTURE3);
|
||||
set_active_texture_unit(3);
|
||||
if (b->m_dual_enabled)
|
||||
canvas->m_canvas->m_tmp_dual[plane_index].unbindTexture();
|
||||
glActiveTexture(GL_TEXTURE2);
|
||||
set_active_texture_unit(2);
|
||||
canvas->m_canvas->m_smask.rtt(plane_index).unbindTexture();
|
||||
glActiveTexture(GL_TEXTURE1);
|
||||
set_active_texture_unit(1);
|
||||
canvas->m_canvas->m_tmp[plane_index].unbindTexture();
|
||||
glActiveTexture(GL_TEXTURE0);
|
||||
set_active_texture_unit(0);
|
||||
canvas->m_canvas->m_layers[layer_index]->rtt(plane_index).unbindTexture();
|
||||
}
|
||||
else
|
||||
@@ -325,7 +398,7 @@ void App::vr_draw(const glm::mat4& proj, const glm::mat4& camera, const glm::mat
|
||||
ShaderManager::u_int(kShaderUniform::Highlight, canvas->m_canvas->m_layers[layer_index]->m_hightlight);
|
||||
ShaderManager::u_mat4(kShaderUniform::MVP, plane_mvp_z);
|
||||
|
||||
glActiveTexture(GL_TEXTURE0);
|
||||
set_active_texture_unit(0);
|
||||
canvas->m_canvas->m_layers[layer_index]->rtt(plane_index).bindTexture();
|
||||
m_face_plane.draw_fill();
|
||||
canvas->m_canvas->m_layers[layer_index]->rtt(plane_index).unbindTexture();
|
||||
@@ -352,7 +425,7 @@ void App::vr_draw(const glm::mat4& proj, const glm::mat4& camera, const glm::mat
|
||||
m_face_plane.draw_stroke();
|
||||
}
|
||||
|
||||
glDisable(GL_DEPTH_TEST);
|
||||
apply_vr_render_capability(pp::renderer::gl::depth_test_state(), false);
|
||||
// draw the brush
|
||||
/*
|
||||
auto mode = dynamic_cast<CanvasModePen*>(canvas->m_canvas->modes[(int)canvas->m_canvas->m_current_mode][0]);
|
||||
@@ -377,8 +450,8 @@ void App::vr_draw(const glm::mat4& proj, const glm::mat4& camera, const glm::mat
|
||||
glm::scale(glm::vec3(canvas->m_canvas->m_current_brush->m_tip_size / height)) *
|
||||
glm::eulerAngleZ(canvas->m_canvas->m_current_brush->m_tip_angle * (float)(M_PI * 2.0))
|
||||
);
|
||||
glEnable(GL_BLEND);
|
||||
glActiveTexture(GL_TEXTURE0);
|
||||
apply_vr_render_capability(pp::renderer::gl::blend_state(), true);
|
||||
set_active_texture_unit(0);
|
||||
auto& tex = *canvas->m_canvas->m_current_brush->m_tip_texture;
|
||||
tex.bind();
|
||||
sampler_linear.bind(0);
|
||||
@@ -399,7 +472,7 @@ void App::vr_draw(const glm::mat4& proj, const glm::mat4& camera, const glm::mat
|
||||
ShaderManager::use(kShader::Texture);
|
||||
ShaderManager::u_int(kShaderUniform::Tex, 0);
|
||||
ShaderManager::u_mat4(kShaderUniform::MVP, mvp);
|
||||
glActiveTexture(GL_TEXTURE0);
|
||||
set_active_texture_unit(0);
|
||||
uirtt.bindTexture();
|
||||
m_face_plane.draw_fill();
|
||||
uirtt.unbindTexture();
|
||||
@@ -451,8 +524,8 @@ void App::vr_draw(const glm::mat4& proj, const glm::mat4& camera, const glm::mat
|
||||
glm::scale(glm::vec3(canvas->m_canvas->m_current_brush->m_tip_size * 100.f / height)) *
|
||||
glm::eulerAngleZ(canvas->m_canvas->m_current_brush->m_tip_angle * (float)(M_PI * 2.0))
|
||||
);
|
||||
glEnable(GL_BLEND);
|
||||
glActiveTexture(GL_TEXTURE0);
|
||||
apply_vr_render_capability(pp::renderer::gl::blend_state(), true);
|
||||
set_active_texture_unit(0);
|
||||
auto& tex = *canvas->m_canvas->m_current_brush->m_tip_texture;
|
||||
tex.bind();
|
||||
sampler_linear.bind(0);
|
||||
@@ -466,7 +539,7 @@ void App::vr_draw(const glm::mat4& proj, const glm::mat4& camera, const glm::mat
|
||||
for (auto& mode : *canvas->m_canvas->m_mode)
|
||||
mode->on_Draw(ortho_proj, proj, camera);
|
||||
|
||||
glDisable(GL_DEPTH_TEST);
|
||||
glDisable(pp::renderer::gl::depth_test_state());
|
||||
if (canvas->m_canvas->m_smask_active)
|
||||
{
|
||||
canvas->m_canvas->modes[(int)kCanvasMode::MaskFree][0]->on_Draw(ortho_proj, proj, camera);
|
||||
@@ -479,8 +552,8 @@ void App::vr_draw(const glm::mat4& proj, const glm::mat4& camera, const glm::mat
|
||||
mode->on_Draw(ortho_proj, proj, camera);
|
||||
*/
|
||||
|
||||
blend ? glEnable(GL_BLEND) : glDisable(GL_BLEND);
|
||||
depth ? glEnable(GL_DEPTH_TEST) : glDisable(GL_DEPTH_TEST);
|
||||
apply_vr_render_capability(pp::renderer::gl::blend_state(), blend);
|
||||
apply_vr_render_capability(pp::renderer::gl::depth_test_state(), depth);
|
||||
sampler.unbind();
|
||||
}
|
||||
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
#include "pch.h"
|
||||
#include "log.h"
|
||||
#include "asset.h"
|
||||
#include "platform_api/network_tls_policy.h"
|
||||
|
||||
#ifdef __APPLE__
|
||||
#include <Foundation/Foundation.h>
|
||||
@@ -8,9 +9,24 @@
|
||||
#endif
|
||||
|
||||
#ifdef __ANDROID__
|
||||
#include <android/asset_manager.h>
|
||||
#include <dirent.h>
|
||||
AAssetManager* Asset::m_am;
|
||||
void* Asset::m_android_asset_manager;
|
||||
bool android_create_dir(const std::string& path);
|
||||
|
||||
namespace {
|
||||
|
||||
[[nodiscard]] AAsset* android_asset_handle(void* asset)
|
||||
{
|
||||
return static_cast<AAsset*>(asset);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
void Asset::set_android_asset_manager(void* asset_manager)
|
||||
{
|
||||
m_android_asset_manager = asset_manager;
|
||||
}
|
||||
#endif
|
||||
|
||||
bool Asset::delete_file(const std::string& path)
|
||||
@@ -65,7 +81,7 @@ std::vector<std::string> Asset::list_files(std::string folder, const std::string
|
||||
#elif __ANDROID__
|
||||
if (is_asset)
|
||||
{
|
||||
AAssetDir* dir = AAssetManager_openDir(Asset::m_am, folder.c_str());
|
||||
AAssetDir* dir = AAssetManager_openDir(static_cast<AAssetManager*>(Asset::m_android_asset_manager), folder.c_str());
|
||||
while (const char* name = AAssetDir_getNextFileName(dir))
|
||||
{
|
||||
//LOG("asset: %s", name);
|
||||
@@ -187,9 +203,8 @@ bool Asset::open_url(const std::string& url, std::function<bool(float)> progress
|
||||
curl_easy_setopt(curl, CURLOPT_WRITEDATA, &tmp_data);
|
||||
curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, curl_data_handler_asset);
|
||||
curl_easy_setopt(curl, CURLOPT_FAILONERROR, 1L);
|
||||
#ifdef __ANDROID__
|
||||
curl_easy_setopt(curl, CURLOPT_SSL_VERIFYPEER, 0L);
|
||||
#endif
|
||||
if (pp::platform::default_disables_network_tls_verification())
|
||||
curl_easy_setopt(curl, CURLOPT_SSL_VERIFYPEER, 0L);
|
||||
if (progress)
|
||||
{
|
||||
on_progress = progress;
|
||||
@@ -237,7 +252,10 @@ bool Asset::open(const char* path)
|
||||
#ifdef __ANDROID__
|
||||
if (is_asset(path))
|
||||
{
|
||||
if (!(m_asset = AAssetManager_open(m_am, path, AASSET_MODE_RANDOM)))
|
||||
if (!(m_android_asset = AAssetManager_open(
|
||||
static_cast<AAssetManager*>(m_android_asset_manager),
|
||||
path,
|
||||
AASSET_MODE_RANDOM)))
|
||||
{
|
||||
LOG("AAssetManager_open failed %s", path);
|
||||
return false;
|
||||
@@ -283,8 +301,8 @@ glm::uint8_t* Asset::read_all()
|
||||
{
|
||||
if (is_asset(m_current_path))
|
||||
{
|
||||
m_len = (int)AAsset_getLength(m_asset);
|
||||
m_data = (uint8_t*)AAsset_getBuffer(m_asset);
|
||||
m_len = (int)AAsset_getLength(android_asset_handle(m_android_asset));
|
||||
m_data = (uint8_t*)AAsset_getBuffer(android_asset_handle(m_android_asset));
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -316,9 +334,9 @@ glm::uint8_t* Asset::read_all()
|
||||
void Asset::close()
|
||||
{
|
||||
#ifdef __ANDROID__
|
||||
if (m_asset)
|
||||
AAsset_close(m_asset);
|
||||
m_asset = nullptr;
|
||||
if (m_android_asset)
|
||||
AAsset_close(android_asset_handle(m_android_asset));
|
||||
m_android_asset = nullptr;
|
||||
#else
|
||||
if (m_fp)
|
||||
fclose(m_fp);
|
||||
|
||||
@@ -4,8 +4,13 @@ class Asset
|
||||
{
|
||||
public:
|
||||
#ifdef __ANDROID__
|
||||
static AAssetManager* m_am;
|
||||
AAsset* m_asset = nullptr;
|
||||
static void set_android_asset_manager(void* asset_manager);
|
||||
|
||||
private:
|
||||
static void* m_android_asset_manager;
|
||||
void* m_android_asset = nullptr;
|
||||
|
||||
public:
|
||||
#endif
|
||||
static std::vector<std::string> list_files(std::string folder, const std::string& filter_regex);
|
||||
static bool exist(std::string path);
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user