diff --git a/.efrocachemap b/.efrocachemap index cbc9a7de..dd26dc63 100644 --- a/.efrocachemap +++ b/.efrocachemap @@ -421,7 +421,7 @@ "build/assets/ba_data/audio/zoeOw.ogg": "74befe45a8417e95b6a2233c51992a26", "build/assets/ba_data/audio/zoePickup01.ogg": "48ab8cddfcde36a750856f3f81dd20c8", "build/assets/ba_data/audio/zoeScream01.ogg": "2b468aedfa8741090247f04eb9e6df55", - "build/assets/ba_data/data/langdata.json": "7be961f6a6b9742859638cbd5a747e18", + "build/assets/ba_data/data/langdata.json": "0f4630cc7c78222e782da9cedd4df284", "build/assets/ba_data/data/languages/arabic.json": "db961f7fe0541a31880929e1c17ea957", "build/assets/ba_data/data/languages/belarussian.json": "5e373ddcfa6e1f771b74c02298a6599a", "build/assets/ba_data/data/languages/chinese.json": "6520f793066c95773002b4e9a920fd1d", @@ -430,27 +430,27 @@ "build/assets/ba_data/data/languages/czech.json": "f3ce219840946cb8f9aa6d3e25927ab3", "build/assets/ba_data/data/languages/danish.json": "3fd69080783d5c9dcc0af737f02b6f1e", "build/assets/ba_data/data/languages/dutch.json": "22b44a33bf81142ba2befad14eb5746e", - "build/assets/ba_data/data/languages/english.json": "d16e8899211693c20d3b00fc198f58c6", + "build/assets/ba_data/data/languages/english.json": "6d261a19b40a27eca92f6199a26f5779", "build/assets/ba_data/data/languages/esperanto.json": "0e397cfa5f3fb8cef5f4a64f21cda880", "build/assets/ba_data/data/languages/filipino.json": "58f363cfd8a3ccf0c904ab753d95789b", "build/assets/ba_data/data/languages/french.json": "6057b18878ad8379e51b507fa94958d8", "build/assets/ba_data/data/languages/german.json": "549754d2a530d825200c6126be56df5c", - "build/assets/ba_data/data/languages/gibberish.json": "236f8547ba09722b7c7f5b8333986984", + "build/assets/ba_data/data/languages/gibberish.json": "d23fe0936bb6177443f4b74bf5981b13", "build/assets/ba_data/data/languages/greek.json": "a65d78f912e9a89f98de004405167a6a", "build/assets/ba_data/data/languages/hindi.json": "88ee0cda537bab9ac827def5e236fe1a", "build/assets/ba_data/data/languages/hungarian.json": "796a290a8c44a1e7635208c2ff5fdc6e", "build/assets/ba_data/data/languages/indonesian.json": "583bae1ecc04375cee089a82359110b7", - "build/assets/ba_data/data/languages/italian.json": "ce99027a5d1af17689b4311eafa5ff24", + "build/assets/ba_data/data/languages/italian.json": "8d9332d461fa5b84780818bf6c2978b5", "build/assets/ba_data/data/languages/korean.json": "ca1122a9ee551da3f75ae632012bd0e2", "build/assets/ba_data/data/languages/malay.json": "832562ce997fc70704b9234c95fb2e38", "build/assets/ba_data/data/languages/persian.json": "5119aec9cbb2f8d00f2afaccf5fd5410", - "build/assets/ba_data/data/languages/polish.json": "336eeb0028af5f3c7b9c12d3a051db2c", + "build/assets/ba_data/data/languages/polish.json": "826c5b0402c2f0bcc29bc6f48b833545", "build/assets/ba_data/data/languages/portuguese.json": "99b27c598c90fd522132af3536aef0ee", "build/assets/ba_data/data/languages/romanian.json": "aeebdd54f65939c2facc6ac50c117826", "build/assets/ba_data/data/languages/russian.json": "aa99f9f597787fe4e09c8ab53fe2e081", "build/assets/ba_data/data/languages/serbian.json": "d7452dd72ac0e51680cb39b5ebaa1c69", "build/assets/ba_data/data/languages/slovak.json": "27962d53dc3f7dd4e877cd40faafeeef", - "build/assets/ba_data/data/languages/spanish.json": "90d7408d07d9445630aecbe7efaa722d", + "build/assets/ba_data/data/languages/spanish.json": "b5390c76f3475c8b6dd64ab9f170b4d8", "build/assets/ba_data/data/languages/swedish.json": "77d671f10613291ebf9c71da66f18a18", "build/assets/ba_data/data/languages/tamil.json": "b9d4b4e107456ea6420ee0f9d9d7a03e", "build/assets/ba_data/data/languages/thai.json": "33f63753c9af9a5b238d229a0bf23fbc", @@ -4064,50 +4064,50 @@ "build/assets/windows/Win32/ucrtbased.dll": "2def5335207d41b21b9823f6805997f1", "build/assets/windows/Win32/vc_redist.x86.exe": "b08a55e2e77623fe657bea24f223a3ae", "build/assets/windows/Win32/vcruntime140d.dll": "865b2af4d1e26a1a8073c89acb06e599", - "build/prefab/full/linux_arm64_gui/debug/ballisticakit": "2ceb16e09034aa4e3213e8fb69dd209b", - "build/prefab/full/linux_arm64_gui/release/ballisticakit": "7ecdc512653325abef8e66e9c3e0e479", - "build/prefab/full/linux_arm64_server/debug/dist/ballisticakit_headless": "70a1c3300cb0610a85449d08b3483a54", - "build/prefab/full/linux_arm64_server/release/dist/ballisticakit_headless": "000738cc726938ff5282d211629739e3", - "build/prefab/full/linux_x86_64_gui/debug/ballisticakit": "e4d0b9fdcb16f2bef3891f28b950daac", - "build/prefab/full/linux_x86_64_gui/release/ballisticakit": "9cc960c0de9e10dd300561dc9ae045e2", - "build/prefab/full/linux_x86_64_server/debug/dist/ballisticakit_headless": "be453afedfb331abe39e4940755b34e7", - "build/prefab/full/linux_x86_64_server/release/dist/ballisticakit_headless": "d478af40d922a68e4deab6dd452b0e51", - "build/prefab/full/mac_arm64_gui/debug/ballisticakit": "5fdb7d8752f9a8315a5f94a32b5db368", - "build/prefab/full/mac_arm64_gui/release/ballisticakit": "433dae3372f05c7faf75658c6f962be1", - "build/prefab/full/mac_arm64_server/debug/dist/ballisticakit_headless": "d66d0201decc90f677520f41ac227a71", - "build/prefab/full/mac_arm64_server/release/dist/ballisticakit_headless": "37efebdbd1866b71ac324f5ca949151a", - "build/prefab/full/mac_x86_64_gui/debug/ballisticakit": "5ccfadd91168c07956328dd11b118b28", - "build/prefab/full/mac_x86_64_gui/release/ballisticakit": "404d9db4de7089346264f3f62dbb6d88", - "build/prefab/full/mac_x86_64_server/debug/dist/ballisticakit_headless": "ef3dda575b711a0fa515f93ebd0a7379", - "build/prefab/full/mac_x86_64_server/release/dist/ballisticakit_headless": "8ee2be9a35e12158c0ace56b04a3ea99", - "build/prefab/full/windows_x86_gui/debug/BallisticaKit.exe": "1c305bde639f5fc76e908a3a3fb9203f", - "build/prefab/full/windows_x86_gui/release/BallisticaKit.exe": "e58ce9a499068e682295dd3d88a0801d", - "build/prefab/full/windows_x86_server/debug/dist/BallisticaKitHeadless.exe": "38a2c503c8d892d7e94cd53fb4da5b48", - "build/prefab/full/windows_x86_server/release/dist/BallisticaKitHeadless.exe": "deea2cf3afb5598ed9eb6ce4798b1cef", - "build/prefab/lib/linux_arm64_gui/debug/libballisticaplus.a": "a3607fd941915ab11503f82acfc392b5", - "build/prefab/lib/linux_arm64_gui/release/libballisticaplus.a": "b5a129d83796c9e7015ab5e319d2c22f", - "build/prefab/lib/linux_arm64_server/debug/libballisticaplus.a": "a3607fd941915ab11503f82acfc392b5", - "build/prefab/lib/linux_arm64_server/release/libballisticaplus.a": "b5a129d83796c9e7015ab5e319d2c22f", - "build/prefab/lib/linux_x86_64_gui/debug/libballisticaplus.a": "13405a4a16a71d073b6b3cabbbcd9666", - "build/prefab/lib/linux_x86_64_gui/release/libballisticaplus.a": "86b26dc84cc7fa7095e51cfcae759c0b", - "build/prefab/lib/linux_x86_64_server/debug/libballisticaplus.a": "13405a4a16a71d073b6b3cabbbcd9666", - "build/prefab/lib/linux_x86_64_server/release/libballisticaplus.a": "86b26dc84cc7fa7095e51cfcae759c0b", - "build/prefab/lib/mac_arm64_gui/debug/libballisticaplus.a": "678e09ecd5da367ce290ca7318617b61", - "build/prefab/lib/mac_arm64_gui/release/libballisticaplus.a": "a9cdc9dd029dabc6dfa5b61d33de7927", - "build/prefab/lib/mac_arm64_server/debug/libballisticaplus.a": "678e09ecd5da367ce290ca7318617b61", - "build/prefab/lib/mac_arm64_server/release/libballisticaplus.a": "a9cdc9dd029dabc6dfa5b61d33de7927", - "build/prefab/lib/mac_x86_64_gui/debug/libballisticaplus.a": "4811585805942428ddb217917e4ad843", - "build/prefab/lib/mac_x86_64_gui/release/libballisticaplus.a": "c5c40967e63471c9c4abd6dfbef892df", - "build/prefab/lib/mac_x86_64_server/debug/libballisticaplus.a": "d34c0a142e7d391a109a33ea3cc77c08", - "build/prefab/lib/mac_x86_64_server/release/libballisticaplus.a": "c5c40967e63471c9c4abd6dfbef892df", - "build/prefab/lib/windows/Debug_Win32/BallisticaKitGenericPlus.lib": "9d23a52a0c270710332bf35297db9f36", - "build/prefab/lib/windows/Debug_Win32/BallisticaKitGenericPlus.pdb": "61d84134e4088e5138ceb200ba20960b", - "build/prefab/lib/windows/Debug_Win32/BallisticaKitHeadlessPlus.lib": "a7a9854fb6f114ac3f3f35c451170328", - "build/prefab/lib/windows/Debug_Win32/BallisticaKitHeadlessPlus.pdb": "f5a2d196943141917b1ee0f3beb1f5eb", - "build/prefab/lib/windows/Release_Win32/BallisticaKitGenericPlus.lib": "ec0052c95df6ea5cb3bdcaabcb1ea55c", - "build/prefab/lib/windows/Release_Win32/BallisticaKitGenericPlus.pdb": "d21984e95164c4e7df91b35a60828fc7", - "build/prefab/lib/windows/Release_Win32/BallisticaKitHeadlessPlus.lib": "b613c7699c66c9a2a17bf9360015ade0", - "build/prefab/lib/windows/Release_Win32/BallisticaKitHeadlessPlus.pdb": "6f907f7940d2f5e44874e24f9a1856a0", + "build/prefab/full/linux_arm64_gui/debug/ballisticakit": "c0e542e25f839b32d91a07046bd56333", + "build/prefab/full/linux_arm64_gui/release/ballisticakit": "f159a31dcd46f144cc796a6f2cbb0a2a", + "build/prefab/full/linux_arm64_server/debug/dist/ballisticakit_headless": "f0a7e6c9ad568b229b8299ac15a485b4", + "build/prefab/full/linux_arm64_server/release/dist/ballisticakit_headless": "4c453da417e3e2ab696009f3efb0cbc5", + "build/prefab/full/linux_x86_64_gui/debug/ballisticakit": "56b10a97ac10e4ff9ae854616f3d58de", + "build/prefab/full/linux_x86_64_gui/release/ballisticakit": "e6a5ecde216f1c180321eec53de8e54d", + "build/prefab/full/linux_x86_64_server/debug/dist/ballisticakit_headless": "3170eb1b194b77204b5b3454d271e10d", + "build/prefab/full/linux_x86_64_server/release/dist/ballisticakit_headless": "368f1eb9a45f3e0d7452e5770f67bd65", + "build/prefab/full/mac_arm64_gui/debug/ballisticakit": "4376bee2a3d9b1ac98f418e981bd6b5e", + "build/prefab/full/mac_arm64_gui/release/ballisticakit": "241bcdfb4a528a4f9714efa9c086a6a6", + "build/prefab/full/mac_arm64_server/debug/dist/ballisticakit_headless": "ed0ae25a5332c9e90bc576a41b0e6397", + "build/prefab/full/mac_arm64_server/release/dist/ballisticakit_headless": "b0fc89d99bfacf8326a313c0c9d89065", + "build/prefab/full/mac_x86_64_gui/debug/ballisticakit": "ab540c6524dfdc31ef55be4726f6a978", + "build/prefab/full/mac_x86_64_gui/release/ballisticakit": "d0e6d409f0f5bfa339060bd6024b10f7", + "build/prefab/full/mac_x86_64_server/debug/dist/ballisticakit_headless": "287d4e643461591bb897d26f9b24a600", + "build/prefab/full/mac_x86_64_server/release/dist/ballisticakit_headless": "8f53ad04b1de065d104b7fd28da0e79c", + "build/prefab/full/windows_x86_gui/debug/BallisticaKit.exe": "74fa35f559c38176ff91c794b78d06e2", + "build/prefab/full/windows_x86_gui/release/BallisticaKit.exe": "2cb71ce71f4ac5d8d2bda2af1ebc87b5", + "build/prefab/full/windows_x86_server/debug/dist/BallisticaKitHeadless.exe": "53ac1a5d801ae8ae82de21ec9d9a6b8d", + "build/prefab/full/windows_x86_server/release/dist/BallisticaKitHeadless.exe": "113c82cf8b6c95a2c940c45b97e4b894", + "build/prefab/lib/linux_arm64_gui/debug/libballisticaplus.a": "6ccd6f2bd0e20520063d4bf8e2c016d0", + "build/prefab/lib/linux_arm64_gui/release/libballisticaplus.a": "82e76d58eab4962ee7567fbc655072d6", + "build/prefab/lib/linux_arm64_server/debug/libballisticaplus.a": "6ccd6f2bd0e20520063d4bf8e2c016d0", + "build/prefab/lib/linux_arm64_server/release/libballisticaplus.a": "82e76d58eab4962ee7567fbc655072d6", + "build/prefab/lib/linux_x86_64_gui/debug/libballisticaplus.a": "e2ca657abc7945934c4b33602ecfbace", + "build/prefab/lib/linux_x86_64_gui/release/libballisticaplus.a": "5b24b2e91fb5c6eca673b0c35bbaf4ca", + "build/prefab/lib/linux_x86_64_server/debug/libballisticaplus.a": "e2ca657abc7945934c4b33602ecfbace", + "build/prefab/lib/linux_x86_64_server/release/libballisticaplus.a": "5b24b2e91fb5c6eca673b0c35bbaf4ca", + "build/prefab/lib/mac_arm64_gui/debug/libballisticaplus.a": "cfa1c3ca813c3974316cc0abbb56277b", + "build/prefab/lib/mac_arm64_gui/release/libballisticaplus.a": "73a49adbf5e205d927eda1a2272a3e98", + "build/prefab/lib/mac_arm64_server/debug/libballisticaplus.a": "cfa1c3ca813c3974316cc0abbb56277b", + "build/prefab/lib/mac_arm64_server/release/libballisticaplus.a": "73a49adbf5e205d927eda1a2272a3e98", + "build/prefab/lib/mac_x86_64_gui/debug/libballisticaplus.a": "a882153cd74bdb5c1b84d2c46a290527", + "build/prefab/lib/mac_x86_64_gui/release/libballisticaplus.a": "6b00cce1baf5f95d36ae911cdcb23dba", + "build/prefab/lib/mac_x86_64_server/debug/libballisticaplus.a": "8708149fb6208e4e5889b4742784623d", + "build/prefab/lib/mac_x86_64_server/release/libballisticaplus.a": "6b00cce1baf5f95d36ae911cdcb23dba", + "build/prefab/lib/windows/Debug_Win32/BallisticaKitGenericPlus.lib": "dc9d6facd1062a48245d5fcb603fe5d6", + "build/prefab/lib/windows/Debug_Win32/BallisticaKitGenericPlus.pdb": "987b189ddac1f90808357749dd44fb2c", + "build/prefab/lib/windows/Debug_Win32/BallisticaKitHeadlessPlus.lib": "bf8be09124840f7af212918fa98a34ec", + "build/prefab/lib/windows/Debug_Win32/BallisticaKitHeadlessPlus.pdb": "33ad88b1557e2828c8e0d8be10d9a5ca", + "build/prefab/lib/windows/Release_Win32/BallisticaKitGenericPlus.lib": "3cec3a2d11567ff3dda36ace808c6082", + "build/prefab/lib/windows/Release_Win32/BallisticaKitGenericPlus.pdb": "b69a204fea2b6fcfee2cbce63a8edd9a", + "build/prefab/lib/windows/Release_Win32/BallisticaKitHeadlessPlus.lib": "59874804f88d67858d988cbc746ca601", + "build/prefab/lib/windows/Release_Win32/BallisticaKitHeadlessPlus.pdb": "d9cb15a56ce8f6e3870cb2a5c83defda", "src/assets/ba_data/python/babase/_mgen/__init__.py": "f885fed7f2ed98ff2ba271f9dbe3391c", "src/assets/ba_data/python/babase/_mgen/enums.py": "f8cd3af311ac63147882590123b78318", "src/ballistica/base/mgen/pyembed/binding_base.inc": "ad347097a38e0d7ede9eb6dec6a80ee9", diff --git a/.idea/dictionaries/ericf.xml b/.idea/dictionaries/ericf.xml index 23ee3503..28006291 100644 --- a/.idea/dictionaries/ericf.xml +++ b/.idea/dictionaries/ericf.xml @@ -2318,6 +2318,7 @@ projpath projprefix projroot + projrootstr projs projsrc projtxt diff --git a/CHANGELOG.md b/CHANGELOG.md index 4ff47c8f..9773b95c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,11 @@ -### 1.7.28 (build 21306, api 8, 2023-09-06) +### 1.7.28 (build 21322, api 8, 2023-09-07) +- Renamed Console to DevConsole, and added an option under advanced settings to + always show an ugly 'dev' button onscreen which can be used to toggle it. The + backtick key still works also for anyone with a keyboard. I plan to add more + functionality besides just the Python console to the dev-console, and perhaps + improve the Python console a bit too (add support for on-screen keyboards, + etc.) - Added some high level functionality for copying and deleting feature-sets to the `spinoff` tool. For example, to create your own `poo` feature-set based on the existing `template_fs` one, do `tools/spinoff fset-copy template_fs poo`. @@ -16,6 +22,10 @@ significantly faster & more efficient. - Updated internal Python builds for Apple & iOS to 3.11.5, and updated a few dependent libraries as well (OpenSSL bumped from 3.0.8 to 3.0.10, etc.). +- Cleaned up the `babase.quit()` mechanism a bit. The default for the 'soft' arg + is now true, so a raw `babase.quit()` should now be a good citizen on mobile + platforms. Also added the `g_base->QuitApp()` call which gives the C++ layer + an equivalent to the Python call. ### 1.7.27 (build 21282, api 8, 2023-08-30) diff --git a/ballisticakit-cmake/.idea/dictionaries/ericf.xml b/ballisticakit-cmake/.idea/dictionaries/ericf.xml index 22391a37..5db2c3e1 100644 --- a/ballisticakit-cmake/.idea/dictionaries/ericf.xml +++ b/ballisticakit-cmake/.idea/dictionaries/ericf.xml @@ -1378,6 +1378,7 @@ projname projpath projprefix + projrootstr projsrc projtxt prolly diff --git a/ballisticakit-cmake/CMakeLists.txt b/ballisticakit-cmake/CMakeLists.txt index 7bc72301..b9892c57 100644 --- a/ballisticakit-cmake/CMakeLists.txt +++ b/ballisticakit-cmake/CMakeLists.txt @@ -424,8 +424,8 @@ set(BALLISTICA_SOURCES ${BA_SRC_ROOT}/ballistica/base/support/stress_test.cc ${BA_SRC_ROOT}/ballistica/base/support/stress_test.h ${BA_SRC_ROOT}/ballistica/base/support/ui_v1_soft.h - ${BA_SRC_ROOT}/ballistica/base/ui/console.cc - ${BA_SRC_ROOT}/ballistica/base/ui/console.h + ${BA_SRC_ROOT}/ballistica/base/ui/dev_console.cc + ${BA_SRC_ROOT}/ballistica/base/ui/dev_console.h ${BA_SRC_ROOT}/ballistica/base/ui/ui.cc ${BA_SRC_ROOT}/ballistica/base/ui/ui.h ${BA_SRC_ROOT}/ballistica/base/ui/widget_message.h diff --git a/ballisticakit-windows/Generic/BallisticaKitGeneric.vcxproj b/ballisticakit-windows/Generic/BallisticaKitGeneric.vcxproj index 25b311dc..c0352e10 100644 --- a/ballisticakit-windows/Generic/BallisticaKitGeneric.vcxproj +++ b/ballisticakit-windows/Generic/BallisticaKitGeneric.vcxproj @@ -410,8 +410,8 @@ - - + + diff --git a/ballisticakit-windows/Generic/BallisticaKitGeneric.vcxproj.filters b/ballisticakit-windows/Generic/BallisticaKitGeneric.vcxproj.filters index 82b2e114..6cc20782 100644 --- a/ballisticakit-windows/Generic/BallisticaKitGeneric.vcxproj.filters +++ b/ballisticakit-windows/Generic/BallisticaKitGeneric.vcxproj.filters @@ -664,10 +664,10 @@ ballistica\base\support - + ballistica\base\ui - + ballistica\base\ui diff --git a/ballisticakit-windows/Headless/BallisticaKitHeadless.vcxproj b/ballisticakit-windows/Headless/BallisticaKitHeadless.vcxproj index 12edaef7..77d01538 100644 --- a/ballisticakit-windows/Headless/BallisticaKitHeadless.vcxproj +++ b/ballisticakit-windows/Headless/BallisticaKitHeadless.vcxproj @@ -405,8 +405,8 @@ - - + + diff --git a/ballisticakit-windows/Headless/BallisticaKitHeadless.vcxproj.filters b/ballisticakit-windows/Headless/BallisticaKitHeadless.vcxproj.filters index 82b2e114..6cc20782 100644 --- a/ballisticakit-windows/Headless/BallisticaKitHeadless.vcxproj.filters +++ b/ballisticakit-windows/Headless/BallisticaKitHeadless.vcxproj.filters @@ -664,10 +664,10 @@ ballistica\base\support - + ballistica\base\ui - + ballistica\base\ui diff --git a/config/featuresets/featureset_base.py b/config/featuresets/featureset_base.py index 0704a845..aee9ba99 100644 --- a/config/featuresets/featureset_base.py +++ b/config/featuresets/featureset_base.py @@ -9,7 +9,7 @@ from __future__ import annotations from batools.featureset import FeatureSet -# Grab the FeatureSet we should apply to. +# Grab the FeatureSet we're defining here. fset = FeatureSet.get_active() fset.requirements = {'core'} diff --git a/config/featuresets/featureset_classic.py b/config/featuresets/featureset_classic.py index ff121279..96767276 100644 --- a/config/featuresets/featureset_classic.py +++ b/config/featuresets/featureset_classic.py @@ -9,7 +9,7 @@ from __future__ import annotations from batools.featureset import FeatureSet -# Grab the FeatureSet we should apply to. +# Grab the FeatureSet we're defining here. fset = FeatureSet.get_active() fset.requirements = { @@ -27,7 +27,7 @@ fset.soft_requirements = {'plus'} # We provide 'babase.app.classic'. fset.has_python_app_subsystem = True -# If 'plus' is present, our subsystem should be inited after it +# If 'plus' is present, our subsystem should be inited *after* it # (classic accounts key off of plus's v2 accounts) fset.python_app_subsystem_dependencies = {'plus'} diff --git a/config/featuresets/featureset_core.py b/config/featuresets/featureset_core.py index f83ad5c7..70ca40e3 100644 --- a/config/featuresets/featureset_core.py +++ b/config/featuresets/featureset_core.py @@ -9,7 +9,7 @@ from __future__ import annotations from batools.featureset import FeatureSet -# Grab the FeatureSet we should apply to. +# Grab the FeatureSet we're defining here. fset = FeatureSet.get_active() fset.requirements = set() diff --git a/config/featuresets/featureset_plus.py b/config/featuresets/featureset_plus.py index 72526b07..be5f6b9c 100644 --- a/config/featuresets/featureset_plus.py +++ b/config/featuresets/featureset_plus.py @@ -9,7 +9,7 @@ from __future__ import annotations from batools.featureset import FeatureSet -# Grab the FeatureSet we should apply to. +# Grab the FeatureSet we're defining here. fset = FeatureSet.get_active() fset.requirements = {'core', 'base'} diff --git a/config/featuresets/featureset_scene_v1.py b/config/featuresets/featureset_scene_v1.py index 594a555e..3109d22d 100644 --- a/config/featuresets/featureset_scene_v1.py +++ b/config/featuresets/featureset_scene_v1.py @@ -9,7 +9,7 @@ from __future__ import annotations from batools.featureset import FeatureSet -# Grab the FeatureSet we should apply to. +# Grab the FeatureSet we're defining here. fset = FeatureSet.get_active() fset.requirements = {'core', 'base', 'classic', 'scene_v1_lib'} diff --git a/config/featuresets/featureset_scene_v1_lib.py b/config/featuresets/featureset_scene_v1_lib.py index 9d79d77b..a0e47e61 100644 --- a/config/featuresets/featureset_scene_v1_lib.py +++ b/config/featuresets/featureset_scene_v1_lib.py @@ -9,9 +9,10 @@ from __future__ import annotations from batools.featureset import FeatureSet -# Grab the FeatureSet we should apply to. +# Grab the FeatureSet we're defining here. fset = FeatureSet.get_active() +# We're just a library of Python stuff; no C++ here. fset.has_python_binary_module = False fset.requirements = {'core', 'base', 'scene_v1'} diff --git a/config/featuresets/featureset_template_fs.py b/config/featuresets/featureset_template_fs.py index 2368ea6d..c5ce9d08 100644 --- a/config/featuresets/featureset_template_fs.py +++ b/config/featuresets/featureset_template_fs.py @@ -9,7 +9,7 @@ from __future__ import annotations from batools.featureset import FeatureSet -# Grab the FeatureSet we should apply to. +# Grab the FeatureSet we're defining here. fset = FeatureSet.get_active() fset.requirements = {'core', 'base'} diff --git a/config/featuresets/featureset_ui_v1.py b/config/featuresets/featureset_ui_v1.py index ea22ddbf..6bbe600c 100644 --- a/config/featuresets/featureset_ui_v1.py +++ b/config/featuresets/featureset_ui_v1.py @@ -10,7 +10,7 @@ from __future__ import annotations from batools.featureset import FeatureSet from batools.dummymodule import DummyModuleDef -# Grab the FeatureSet we should apply to. +# Grab the FeatureSet we're defining here. fset = FeatureSet.get_active() fset.requirements = {'core', 'base'} diff --git a/config/featuresets/featureset_ui_v1_lib.py b/config/featuresets/featureset_ui_v1_lib.py index 16112371..0f78438f 100644 --- a/config/featuresets/featureset_ui_v1_lib.py +++ b/config/featuresets/featureset_ui_v1_lib.py @@ -9,9 +9,10 @@ from __future__ import annotations from batools.featureset import FeatureSet -# Grab the FeatureSet we should apply to. +# Grab the FeatureSet we're defining here. fset = FeatureSet.get_active() +# We're just a library of Python stuff; no C++. fset.has_python_binary_module = False fset.requirements = {'core', 'base', 'ui_v1', 'classic'} diff --git a/config/spinoffconfig.py b/config/spinoffconfig.py index 64dac935..be3fae88 100644 --- a/config/spinoffconfig.py +++ b/config/spinoffconfig.py @@ -3,31 +3,30 @@ # pylint: disable=missing-module-docstring, invalid-name from __future__ import annotations -# This file is exec'ed by tools/spinoff, allowing us to customize -# how this src project gits filtered into dst projects. +# This file is exec'ed by tools/spinoff, allowing us to customize how +# this src project gits filtered into dst projects. from batools.spinoff import SpinoffContext # Grab the context we should apply to. ctx = SpinoffContext.get_active() -# As a src project, we set up a baseline set of rules based on what -# we contain. The dst project config (exec'ed after us) is then free -# to override based on what they want of ours or what they add -# themselves. +# As a src project, we set up a baseline set of rules based on what we +# contain. The dst project config (exec'ed after us) is then free to +# override based on what they want of ours or what they add themselves. -# Any files/dirs with these base names will be ignored by spinoff -# on both src and dst. +# Any files/dirs with these base names will be ignored by spinoff on +# both src and dst. ctx.ignore_names = { '__pycache__', '.git', '.mypy_cache', } -# Special set of paths managed by spinoff but ALSO stored in git in -# the dst project. This is for bare minimum stuff needed to be always -# present in dst for bootstrapping, indexing by github, etc). Changes -# to these files in dst will be silently and happily overwritten by +# Special set of paths managed by spinoff but ALSO stored in git in the +# dst project. This is for bare minimum stuff needed to be always +# present in dst for bootstrapping, indexing by github, etc). Changes to +# these files in dst will be silently and happily overwritten by # spinoff, so tread carefully. ctx.git_mirrored_paths = { '.gitignore', @@ -36,16 +35,16 @@ ctx.git_mirrored_paths = { 'config/jenkins', } -# File names that can be quietly ignored or cleared out when found. -# This should encompass things like .DS_Store files created by the -# Mac Finder when browsing directories. This helps spinoff remove -# empty directories when doing a 'clean', etc. +# File names that can be quietly ignored or cleared out when found. This +# should encompass things like .DS_Store files created by the Mac Finder +# when browsing directories. This helps spinoff remove empty directories +# when doing a 'clean', etc. ctx.cruft_file_names = {'.DS_Store'} # These paths in the src project will be skipped over during updates and -# not synced into the dst project. The dst project can use this to -# trim out parts of the src project that it doesn't want or that it -# intends to 'override' with its own versions. +# not synced into the dst project. The dst project can use this to trim +# out parts of the src project that it doesn't want or that it intends +# to 'override' with its own versions. ctx.src_omit_paths = { '.gitignore', 'config/spinoffconfig.py', @@ -63,27 +62,27 @@ ctx.src_omit_paths = { # within it from being synced by spinoff; it just means that each of # those individual spinoff-managed files will have their own gitignore # entry since there is no longer one covering the whole dir. So to keep -# things tidy, carve out the minimal set of exact file/dir paths that you -# need. +# things tidy, carve out the minimal set of exact file/dir paths that +# you need. ctx.src_write_paths = { 'tools/spinoff', 'config/spinoffconfig.py', } -# Normally spinoff errors if it finds any files in its managed dirs -# that it did not put there. This is to prevent accidentally working -# in these parts of a dst project; since these sections are git-ignored, -# git itself won't raise any warnings in such cases and it would be easy -# to accidentally lose work otherwise. +# Normally spinoff errors if it finds any files in its managed dirs that +# it did not put there. This is to prevent accidentally working in these +# parts of a dst project; since these sections are git-ignored, git +# itself won't raise any warnings in such cases and it would be easy to +# accidentally lose work otherwise. +# # This list can be used to suppress spinoff's errors for specific # locations. This is generally used to allow build output or other # dynamically generated files to exist within spinoff-managed # directories. It is possible to use src_write_paths for such purposes, -# but this has the side-effect of greatly complicating the dst -# project's gitignore list; selectively marking a few dirs as -# unchecked makes for a cleaner setup. Just be careful to not set -# excessively broad regions as unchecked; you don't want to mask -# actual useful error messages. +# but this has the side-effect of greatly complicating the dst project's +# gitignore list; selectively marking a few dirs as unchecked makes for +# a cleaner setup. Just be careful to not set excessively broad regions +# as unchecked; you don't want to mask actual useful error messages. ctx.src_unchecked_paths = { 'src/ballistica/mgen', 'src/ballistica/*/mgen', @@ -102,12 +101,12 @@ ctx.src_unchecked_paths = { 'ballisticakit-android/BallisticaKit/.cxx', } -# Paths/names/suffixes we consider 'project' files. -# These files are synced after all other files and go through -# batools.project.Updater class as part of their filtering. -# This allows them to update themselves in the same way as they -# do when running 'make update' for the project; adding the final -# filtered set of project source files to themself, etc. +# Paths/names/suffixes we consider 'project' files. These files are +# synced after all other files and go through batools.project.Updater +# class as part of their filtering. This allows them to update +# themselves in the same way as they do when running 'make update' for +# the project; adding the final filtered set of project source files to +# themself, etc. ctx.project_file_paths = {'src/assets/ba_data/python/babase/_app.py'} ctx.project_file_names = { 'Makefile', @@ -124,15 +123,16 @@ ctx.project_file_suffixes = { '.pbxproj', } -# Everything actually synced into dst will use the following filter rules: +# Everything actually synced into dst will use the following filter +# rules: -# If files are 'filtered' it means they will have all instances -# of BallisticaKit in their names and contents replaced with their -# project name. Other custom filtering can also be applied. Obviously -# filtering should not be run on certain files (binary data, etc.) -# and disabling it where not needed can improve efficiency and make -# backporting easier (editing spinoff-managed files in dst and getting -# those changes back into src). +# If files are 'filtered' it means they will have all instances of +# BallisticaKit in their names and contents replaced with their project +# name. Other custom filtering can also be applied. Obviously filtering +# should not be run on certain files (binary data, etc.) and disabling +# it where not needed can improve efficiency and make backporting easier +# (editing spinoff-managed files in dst and getting those changes back +# into src). # Anything under these dirs WILL be filtered. ctx.filter_dirs = { @@ -153,8 +153,8 @@ ctx.no_filter_dirs = { 'src/assets/windows', } -# ELSE files matching these exact base names WILL be filtered -# (so FOO matches a/b/FOO as well as just FOO). +# ELSE files matching these exact base names WILL be filtered (so FOO +# matches a/b/FOO as well as just FOO). ctx.filter_file_names = { 'Makefile', '.gitignore', diff --git a/src/assets/ba_data/python/babase/_app.py b/src/assets/ba_data/python/babase/_app.py index bb2c2fd1..593e444b 100644 --- a/src/assets/ba_data/python/babase/_app.py +++ b/src/assets/ba_data/python/babase/_app.py @@ -118,15 +118,16 @@ class App: # This section generated by batools.appmodule; do not edit. # Ask our default app modes to handle it. - # (based on 'default_app_modes' in projectconfig). + # (generated from 'default_app_modes' in projectconfig). import bascenev1 import babase - if bascenev1.SceneV1AppMode.can_handle_intent(intent): - return bascenev1.SceneV1AppMode - - if babase.EmptyAppMode.can_handle_intent(intent): - return babase.EmptyAppMode + for appmode in [ + bascenev1.SceneV1AppMode, + babase.EmptyAppMode, + ]: + if appmode.can_handle_intent(intent): + return appmode return None @@ -1057,6 +1058,7 @@ class App: @property def protocol_version(self) -> int: """(internal).""" + # pylint: disable=cyclic-import import bascenev1 warnings.warn( diff --git a/src/assets/ba_data/python/baenv.py b/src/assets/ba_data/python/baenv.py index 3413bd9b..682944b4 100644 --- a/src/assets/ba_data/python/baenv.py +++ b/src/assets/ba_data/python/baenv.py @@ -52,7 +52,7 @@ if TYPE_CHECKING: # Build number and version of the ballistica binary we expect to be # using. -TARGET_BALLISTICA_BUILD = 21306 +TARGET_BALLISTICA_BUILD = 21322 TARGET_BALLISTICA_VERSION = '1.7.28' diff --git a/src/assets/ba_data/python/bauiv1lib/coop/browser.py b/src/assets/ba_data/python/bauiv1lib/coop/browser.py index 215af697..20760381 100644 --- a/src/assets/ba_data/python/bauiv1lib/coop/browser.py +++ b/src/assets/ba_data/python/bauiv1lib/coop/browser.py @@ -350,7 +350,7 @@ class CoopBrowserWindow(bui.Window): # noinspection PyUnresolvedReferences @staticmethod def _preload_modules() -> None: - """Preload modules we use (called in bg thread).""" + """Preload modules we use; avoids hitches (called in bg thread).""" import bauiv1lib.purchase as _unused1 import bauiv1lib.coop.gamebutton as _unused2 import bauiv1lib.confirm as _unused3 diff --git a/src/assets/ba_data/python/bauiv1lib/mainmenu.py b/src/assets/ba_data/python/bauiv1lib/mainmenu.py index 69f5406c..73dc0ccf 100644 --- a/src/assets/ba_data/python/bauiv1lib/mainmenu.py +++ b/src/assets/ba_data/python/bauiv1lib/mainmenu.py @@ -92,7 +92,7 @@ class MainMenuWindow(bui.Window): # noinspection PyUnresolvedReferences @staticmethod def _preload_modules() -> None: - """Preload modules we use (called in bg thread).""" + """Preload modules we use; avoids hitches (called in bg thread).""" import bauiv1lib.getremote as _unused import bauiv1lib.confirm as _unused2 import bauiv1lib.store.button as _unused3 diff --git a/src/assets/ba_data/python/bauiv1lib/play.py b/src/assets/ba_data/python/bauiv1lib/play.py index 27d5bca5..ef1ca896 100644 --- a/src/assets/ba_data/python/bauiv1lib/play.py +++ b/src/assets/ba_data/python/bauiv1lib/play.py @@ -513,7 +513,7 @@ class PlayWindow(bui.Window): # noinspection PyUnresolvedReferences @staticmethod def _preload_modules() -> None: - """Preload modules we use (called in bg thread).""" + """Preload modules we use; avoids hitches (called in bg thread).""" import bauiv1lib.mainmenu as _unused1 import bauiv1lib.account as _unused2 import bauiv1lib.coop.browser as _unused3 diff --git a/src/assets/ba_data/python/bauiv1lib/settings/advanced.py b/src/assets/ba_data/python/bauiv1lib/settings/advanced.py index 58d730c7..aa00b581 100644 --- a/src/assets/ba_data/python/bauiv1lib/settings/advanced.py +++ b/src/assets/ba_data/python/bauiv1lib/settings/advanced.py @@ -16,7 +16,7 @@ if TYPE_CHECKING: class AdvancedSettingsWindow(bui.Window): - """Window for editing advanced game settings.""" + """Window for editing advanced app settings.""" def __init__( self, @@ -61,6 +61,7 @@ class AdvancedSettingsWindow(bui.Window): self._spacing = 32 self._menu_open = False top_extra = 10 if uiscale is bui.UIScale.SMALL else 0 + super().__init__( root_widget=bui.containerwidget( size=(self._width, self._height + top_extra), @@ -93,7 +94,7 @@ class AdvancedSettingsWindow(bui.Window): self._scroll_width = self._width - (100 + 2 * x_inset) self._scroll_height = self._height - 115.0 self._sub_width = self._scroll_width * 0.95 - self._sub_height = 724.0 + self._sub_height = 766.0 if self._show_always_use_internal_keyboard: self._sub_height += 62 @@ -185,7 +186,7 @@ class AdvancedSettingsWindow(bui.Window): # noinspection PyUnresolvedReferences @staticmethod def _preload_modules() -> None: - """Preload modules we use (called in bg thread).""" + """Preload modules we use; avoids hitches (called in bg thread).""" from babase import modutils as _unused2 from bauiv1lib import config as _unused1 from bauiv1lib.settings import vrtesting as _unused3 @@ -474,6 +475,19 @@ class AdvancedSettingsWindow(bui.Window): maxwidth=430, ) + v -= 42 + self._show_dev_console_button_check_box = ConfigCheckBox( + parent=self._subcontainer, + position=(50, v), + size=(self._sub_width - 100, 30), + configkey='Show Dev Console Button', + displayname=bui.Lstr( + resource=f'{self._r}.showDevConsoleButtonText' + ), + scale=1.0, + maxwidth=430, + ) + v -= 42 self._disable_camera_shake_check_box = ConfigCheckBox( parent=self._subcontainer, diff --git a/src/assets/ba_data/python/bauiv1lib/settings/allsettings.py b/src/assets/ba_data/python/bauiv1lib/settings/allsettings.py index 42a8d8c2..d6d18b5f 100644 --- a/src/assets/ba_data/python/bauiv1lib/settings/allsettings.py +++ b/src/assets/ba_data/python/bauiv1lib/settings/allsettings.py @@ -224,7 +224,7 @@ class AllSettingsWindow(bui.Window): # noinspection PyUnresolvedReferences @staticmethod def _preload_modules() -> None: - """Preload modules we use (called in bg thread).""" + """Preload modules we use; avoids hitches (called in bg thread).""" import bauiv1lib.mainmenu as _unused1 import bauiv1lib.settings.controls as _unused2 import bauiv1lib.settings.graphics as _unused3 diff --git a/src/ballistica/base/app_mode/app_mode.cc b/src/ballistica/base/app_mode/app_mode.cc index 1aeaf6b2..13dbb5f8 100644 --- a/src/ballistica/base/app_mode/app_mode.cc +++ b/src/ballistica/base/app_mode/app_mode.cc @@ -66,6 +66,6 @@ void AppMode::LanguageChanged() {} auto AppMode::LastClientJoinTime() const -> millisecs_t { return -1; } -auto AppMode::InMainMenu() const -> bool { return false; } +auto AppMode::InClassicMainMenuSession() const -> bool { return false; } } // namespace ballistica::base diff --git a/src/ballistica/base/app_mode/app_mode.h b/src/ballistica/base/app_mode/app_mode.h index d1f6ca60..2bb7e69c 100644 --- a/src/ballistica/base/app_mode/app_mode.h +++ b/src/ballistica/base/app_mode/app_mode.h @@ -79,8 +79,8 @@ class AppMode { /// Called when language changes. virtual void LanguageChanged(); - /// Are we currently in a 'main menu'? - virtual auto InMainMenu() const -> bool; + /// Are we currently in a classic 'main menu' session? + virtual auto InClassicMainMenuSession() const -> bool; /// Get current party size (for legacy parties). virtual auto GetPartySize() const -> int; diff --git a/src/ballistica/base/app_mode/app_mode_empty.cc b/src/ballistica/base/app_mode/app_mode_empty.cc index 6ac648b2..415e6af9 100644 --- a/src/ballistica/base/app_mode/app_mode_empty.cc +++ b/src/ballistica/base/app_mode/app_mode_empty.cc @@ -50,8 +50,10 @@ void AppModeEmpty::DrawWorld(base::FrameDef* frame_def) { sinf(static_cast(frame_def->display_time_millisecs()) / 600.0f); auto yoffs = cosf(static_cast(frame_def->display_time_millisecs()) / 600.0f); + + // Z value -1 will draw us under most everything. c.Translate(pass->virtual_width() * 0.5f - 70.0f + xoffs * 200.0f, - pass->virtual_height() * 0.5f - 20.0f + yoffs * 200.0f); + pass->virtual_height() * 0.5f - 20.0f + yoffs * 200.0f, -1.0f); c.Scale(2.0, 2.0); int text_elem_count = grp.GetElementCount(); diff --git a/src/ballistica/base/base.cc b/src/ballistica/base/base.cc index 0d931da8..8701ea85 100644 --- a/src/ballistica/base/base.cc +++ b/src/ballistica/base/base.cc @@ -24,7 +24,7 @@ #include "ballistica/base/support/plus_soft.h" #include "ballistica/base/support/stdio_console.h" #include "ballistica/base/support/stress_test.h" -#include "ballistica/base/ui/console.h" +#include "ballistica/base/ui/dev_console.h" #include "ballistica/base/ui/ui.h" #include "ballistica/core/python/core_python.h" #include "ballistica/shared/foundation/event_loop.h" @@ -156,7 +156,7 @@ void BaseFeatureSet::OnAssetsAvailable() { // Spin up the in-app console. if (!g_core->HeadlessMode()) { - console_ = new Console(); + console_ = new DevConsole(); // Print any messages that have built up. if (!console_startup_messages_.empty()) { @@ -234,7 +234,7 @@ void BaseFeatureSet::OnAppShutdownComplete() { if (app_adapter->ManagesEventLoop()) { g_core->main_event_loop()->Quit(); } else { - platform->QuitApp(); + platform->TerminateApp(); } } @@ -747,4 +747,18 @@ void BaseFeatureSet::ShutdownSuppressDisallow() { auto BaseFeatureSet::GetReturnValue() const -> int { return return_value(); } +void BaseFeatureSet::QuitApp(QuitType quit_type) { + // If they ask for 'back' and we support that, do it. + // Otherwise if they want 'back' or 'soft' and we support soft, do it. + // Otherwise go with a regular app shutdown. + if (quit_type == QuitType::kBack && g_base->platform->CanBackQuit()) { + logic->event_loop()->PushCall([this] { platform->DoBackQuit(); }); + } else if ((quit_type == QuitType::kBack || quit_type == QuitType::kSoft) + && g_base->platform->CanSoftQuit()) { + logic->event_loop()->PushCall([this] { platform->DoSoftQuit(); }); + } else { + logic->event_loop()->PushCall([this] { logic->Shutdown(); }); + } +} + } // namespace ballistica::base diff --git a/src/ballistica/base/base.h b/src/ballistica/base/base.h index c828b325..cbe74a24 100644 --- a/src/ballistica/base/base.h +++ b/src/ballistica/base/base.h @@ -53,7 +53,7 @@ class Camera; class ClassicSoftInterface; class CollisionMeshAsset; class CollisionCache; -class Console; +class DevConsole; class Context; class ContextRef; class DataAsset; @@ -121,6 +121,18 @@ class UIV1SoftInterface; class AppAdapterVR; class GraphicsVR; +enum class QuitType { + /// Leads to the process exiting. + kHard, + /// May hide/reset the app but keep the process running. Generally how + /// mobile apps behave. + kSoft, + /// Same as kSoft but gives 'back-button-pressed' behavior which may + /// differ depending on the OS (returning to a previous Activity in + /// Android instead of dumping to the home screen, etc.) + kBack, +}; + enum class AssetType { kTexture, kCollisionMesh, @@ -601,6 +613,14 @@ class BaseFeatureSet : public FeatureSetNativeComponent, /// Start app systems in motion. void StartApp() override; + /// Issue a high level app quit request. Can be called from any thread. + /// 'soft' means the app can simply reset/hide itself instead of actually + /// exiting the process (common behavior on mobile platforms). 'back' + /// means that a soft-quit should behave as if a back-button was pressed, + /// whic may trigger different behavior in the OS than a standard soft + /// quit. + void QuitApp(QuitType quit_type = QuitType::kSoft); + /// Called when app shutdown process completes. Sets app to exit. void OnAppShutdownComplete(); @@ -759,7 +779,7 @@ class BaseFeatureSet : public FeatureSetNativeComponent, void PrintContextUnavailable_(); AppMode* app_mode_; - Console* console_{}; + DevConsole* console_{}; PlusSoftInterface* plus_soft_{}; ClassicSoftInterface* classic_soft_{}; UIV1SoftInterface* ui_v1_soft_{}; diff --git a/src/ballistica/base/graphics/component/render_component.h b/src/ballistica/base/graphics/component/render_component.h index d2a829b5..f16953fa 100644 --- a/src/ballistica/base/graphics/component/render_component.h +++ b/src/ballistica/base/graphics/component/render_component.h @@ -11,6 +11,17 @@ namespace ballistica::base { class RenderComponent { public: + class ScopedTransformObj { + public: + explicit ScopedTransformObj(RenderComponent* c) : c_{c} { + c_->PushTransform(); + } + ~ScopedTransformObj() { c_->PopTransform(); } + + private: + RenderComponent* c_; + }; + explicit RenderComponent(RenderPass* pass) : state_(State::kConfiguring), pass_(pass), cmd_buffer_(nullptr) {} ~RenderComponent() { @@ -82,6 +93,9 @@ class RenderComponent { EnsureDrawing(); cmd_buffer_->PutCommand(RenderCommandBuffer::Command::kPopTransform); } + auto ScopedTransform() -> ScopedTransformObj { + return ScopedTransformObj(this); + } void Translate(float x, float y) { EnsureDrawing(); cmd_buffer_->PutCommand(RenderCommandBuffer::Command::kTranslate2); diff --git a/src/ballistica/base/graphics/graphics.cc b/src/ballistica/base/graphics/graphics.cc index 5a4c1655..7c5fbcf5 100644 --- a/src/ballistica/base/graphics/graphics.cc +++ b/src/ballistica/base/graphics/graphics.cc @@ -20,7 +20,7 @@ #include "ballistica/base/logic/logic.h" #include "ballistica/base/python/support/python_context_call.h" #include "ballistica/base/support/app_config.h" -#include "ballistica/base/ui/console.h" +#include "ballistica/base/ui/dev_console.h" #include "ballistica/base/ui/ui.h" #include "ballistica/core/core.h" #include "ballistica/shared/foundation/event_loop.h" @@ -34,7 +34,6 @@ const float kScreenMeshZDepth{-0.05f}; const float kProgressBarZDepth{0.0f}; const int kProgressBarFadeTime{500}; const float kDebugImgZDepth{-0.04f}; -const float kCursorZDepth{-0.1f}; auto Graphics::IsShaderTransparent(ShadingType c) -> bool { switch (c) { @@ -105,8 +104,8 @@ void Graphics::OnAppShutdown() { assert(g_base->InLogicThread()); } void Graphics::DoApplyAppConfig() { assert(g_base->InLogicThread()); - // Not relevant for fullscreen anymore - // since we're fullscreen windows everywhere. + // Not relevant for fullscreen anymore since we use fullscreen windows. + // everywhere. int width = 800; int height = 600; @@ -158,28 +157,20 @@ void Graphics::DoApplyAppConfig() { // Note: when the graphics-thread applies the first set-screen event it will // trigger the remainder of startup such as media-loading; make sure nothing - // below this will affect that. + // below this point will affect that. g_base->graphics_server->PushSetScreenCall( fullscreen, width, height, texture_quality_requested, graphics_quality_requested, android_res); - set_show_fps(g_base->app_config->Resolve(AppConfig::BoolID::kShowFPS)); - set_show_ping(g_base->app_config->Resolve(AppConfig::BoolID::kShowPing)); + show_fps_ = g_base->app_config->Resolve(AppConfig::BoolID::kShowFPS); + show_ping_ = g_base->app_config->Resolve(AppConfig::BoolID::kShowPing); + tv_border_ = g_base->app_config->Resolve(AppConfig::BoolID::kEnableTVBorder); g_base->graphics_server->PushSetScreenGammaCall( g_base->app_config->Resolve(AppConfig::FloatID::kScreenGamma)); g_base->graphics_server->PushSetScreenPixelScaleCall( g_base->app_config->Resolve(AppConfig::FloatID::kScreenPixelScale)); - // Set tv border (for both client and server). - // FIXME: this should exist either on the client or the server; not both. - // (and should be communicated via frameldefs/etc.) - bool tv_border = - g_base->app_config->Resolve(AppConfig::BoolID::kEnableTVBorder); - g_base->graphics_server->event_loop()->PushCall( - [tv_border] { g_base->graphics_server->set_tv_border(tv_border); }); - set_tv_border(tv_border); - // V-sync setting. std::string v_sync = g_base->app_config->Resolve(AppConfig::StringID::kVerticalSync); @@ -313,7 +304,7 @@ void Graphics::SetShadowRange(float lower_bottom, float lower_top, } auto Graphics::GetShadowDensity(float x, float y, float z) -> float { - if (y < shadow_lower_bottom_) { // NOLINT(bugprone-branch-clone) + if (y < shadow_lower_bottom_) { return 0.0f; } else if (y < shadow_lower_top_) { float amt = @@ -1108,9 +1099,16 @@ void Graphics::DrawUI(FrameDef* frame_def) { g_base->ui->Draw(frame_def); } +void Graphics::DrawDevUI(FrameDef* frame_def) { + // Just do generic thing in our default implementation. + // Special variants like GraphicsVR may do fancier stuff here. + g_base->ui->DrawDev(frame_def); +} + void Graphics::BuildAndPushFrameDef() { assert(g_base->InLogicThread()); assert(camera_.Exists()); + assert(!g_core->HeadlessMode()); // Keep track of when we're in here; can be useful for making sure stuff // doesn't muck with our lists/etc. while we're using them. @@ -1167,16 +1165,14 @@ void Graphics::BuildAndPushFrameDef() { DrawUI(frame_def); - // Let input draw anything it needs to. (touch input graphics, etc) + // Let input draw anything it needs to (touch input graphics, etc). g_base->input->Draw(frame_def); RenderPass* overlay_pass = frame_def->overlay_pass(); DrawMiscOverlays(overlay_pass); - // Draw console. - if (!g_core->HeadlessMode() && g_base->console()) { - g_base->console()->Draw(overlay_pass); - } + // Let UI draw dev console and whatever else. + DrawDevUI(frame_def); DrawCursor(overlay_pass, app_time_millisecs); diff --git a/src/ballistica/base/graphics/graphics.h b/src/ballistica/base/graphics/graphics.h index 79d9630a..cb289e09 100644 --- a/src/ballistica/base/graphics/graphics.h +++ b/src/ballistica/base/graphics/graphics.h @@ -19,30 +19,32 @@ namespace ballistica::base { // Light/shadow res is divided by this to get pure light res. -const int kLightResDiv = 4; +const int kLightResDiv{4}; // How we divide up our z depth spectrum: -const float kBackingDepth5 = 1.0f; +const float kBackingDepth5{1.0f}; // Background // blit-shapes (with cam buffer) -const float kBackingDepth4 = 0.9f; +const float kBackingDepth4{0.9f}; // World (without cam buffer) or overlay-3d (with cam buffer) -const float kBackingDepth3C = 0.65f; -const float kBackingDepth3B = 0.4f; -const float kBackingDepth3 = 0.15f; +const float kBackingDepth3C{0.65f}; +const float kBackingDepth3B{0.4f}; +const float kBackingDepth3{0.15f}; // Overlay-3d (without cam buffer) / overlay(vr) -const float kBackingDepth2C = 0.147f; -const float kBackingDepth2B = 0.143f; -const float kBackingDepth2 = 0.14f; +const float kBackingDepth2C{0.147f}; +const float kBackingDepth2B{0.143f}; +const float kBackingDepth2{0.14f}; // Overlay(non-vr) // cover (vr) -const float kBackingDepth1B = 0.01f; -const float kBackingDepth1 = 0.0f; +const float kBackingDepth1B{0.01f}; +const float kBackingDepth1{0.0f}; -const float kShadowNeutral = 0.5f; +const float kShadowNeutral{0.5f}; + +const float kCursorZDepth{-0.1f}; // Client class for graphics operations (used from the logic thread). class Graphics { @@ -218,13 +220,7 @@ class Graphics { void SetShadowRange(float lower_bottom, float lower_top, float upper_bottom, float upper_top); void ReleaseFadeEndCommand(); - void set_show_fps(bool val) { show_fps_ = val; } - void set_show_ping(bool val) { show_ping_ = val; } - // FIXME - move to graphics_server - void set_tv_border(bool val) { - assert(g_base->InLogicThread()); - tv_border_ = val; - } + auto tv_border() const -> bool { assert(g_base->InLogicThread()); return tv_border_; @@ -297,8 +293,13 @@ class Graphics { void set_drawing_transparent_only(bool val) { drawing_transparent_only_ = val; } + + /// Draw regular UI. virtual void DrawUI(FrameDef* frame_def); + /// Draw dev console or whatever else on top of normal stuff. + virtual void DrawDevUI(FrameDef* frame_def); + auto drawing_opaque_only() const -> bool { return drawing_opaque_only_; } void set_drawing_opaque_only(bool val) { drawing_opaque_only_ = val; } diff --git a/src/ballistica/base/graphics/graphics_server.cc b/src/ballistica/base/graphics/graphics_server.cc index 572b7804..e262296e 100644 --- a/src/ballistica/base/graphics/graphics_server.cc +++ b/src/ballistica/base/graphics/graphics_server.cc @@ -117,6 +117,11 @@ auto GraphicsServer::GetRenderFrameDef() -> FrameDef* { return nullptr; } +void GraphicsServer::ApplyFrameDefSettings(FrameDef* frame_def) { + assert(g_base->InGraphicsThread()); + tv_border_ = frame_def->tv_border(); +} + // Runs any mesh updates contained in the frame-def. void GraphicsServer::RunFrameDefMeshUpdates(FrameDef* frame_def) { assert(g_base->InGraphicsThread()); @@ -170,6 +175,9 @@ void GraphicsServer::TryRender() { assert(g_base->InGraphicsThread()); if (FrameDef* frame_def = GetRenderFrameDef()) { + // Apply settings such as tv-mode passed along on the frame-def. + ApplyFrameDefSettings(frame_def); + // Note: we always run mesh updates contained in the framedef // even if we don't actually render it. // (Hmm this seems flaky; will TryRender always get called diff --git a/src/ballistica/base/graphics/graphics_server.h b/src/ballistica/base/graphics/graphics_server.h index 91ae7a8e..10fd4fcb 100644 --- a/src/ballistica/base/graphics/graphics_server.h +++ b/src/ballistica/base/graphics/graphics_server.h @@ -52,6 +52,8 @@ class GraphicsServer { // of using the RenderFrameDef* calls auto GetRenderFrameDef() -> FrameDef*; + void ApplyFrameDefSettings(FrameDef* frame_def); + void RunFrameDefMeshUpdates(FrameDef* frame_def); // renders shadow passes and other common parts of a frame_def @@ -215,10 +217,6 @@ class GraphicsServer { assert(g_base->InGraphicsThread()); return res_y_virtual_; } - void set_tv_border(bool val) { - assert(g_base->InGraphicsThread()); - tv_border_ = val; - } auto tv_border() const { assert(g_base->InGraphicsThread()); return tv_border_; @@ -302,8 +300,8 @@ class GraphicsServer { EventLoop* event_loop_{}; float res_x_{}; float res_y_{}; - float res_x_virtual_{0.0f}; - float res_y_virtual_{0.0f}; + float res_x_virtual_{}; + float res_y_virtual_{}; bool tv_border_{}; bool renderer_context_lost_{}; uint32_t texture_compression_types_{}; diff --git a/src/ballistica/base/graphics/support/frame_def.cc b/src/ballistica/base/graphics/support/frame_def.cc index 3b4ba002..479a5506 100644 --- a/src/ballistica/base/graphics/support/frame_def.cc +++ b/src/ballistica/base/graphics/support/frame_def.cc @@ -72,6 +72,7 @@ void FrameDef::Reset() { assert(g_base->graphics->has_supports_high_quality_graphics_value()); orbiting_ = (g_base->graphics->camera()->mode() == CameraMode::kOrbit); + tv_border_ = g_base->graphics->tv_border(); shadow_offset_ = g_base->graphics->shadow_offset(); shadow_scale_ = g_base->graphics->shadow_scale(); diff --git a/src/ballistica/base/graphics/support/frame_def.h b/src/ballistica/base/graphics/support/frame_def.h index d4743b55..2f790f94 100644 --- a/src/ballistica/base/graphics/support/frame_def.h +++ b/src/ballistica/base/graphics/support/frame_def.h @@ -154,6 +154,7 @@ class FrameDef { auto media_components() const -> const std::vector>& { return media_components_; } + auto tv_border() const { return tv_border_; } void set_camera_mode(CameraMode val) { camera_mode_ = val; } void set_rendering(bool val) { rendering_ = val; } @@ -205,6 +206,7 @@ class FrameDef { std::unique_ptr blit_pass_; GraphicsQuality quality_{GraphicsQuality::kLow}; bool orbiting_{}; + bool tv_border_{}; millisecs_t app_time_millisecs_{}; millisecs_t display_time_millisecs_{}; millisecs_t display_time_elapsed_millisecs_{}; diff --git a/src/ballistica/base/input/input.cc b/src/ballistica/base/input/input.cc index 9fa0b25c..ca8fe1a8 100644 --- a/src/ballistica/base/input/input.cc +++ b/src/ballistica/base/input/input.cc @@ -12,7 +12,7 @@ #include "ballistica/base/logic/logic.h" #include "ballistica/base/python/base_python.h" #include "ballistica/base/support/app_config.h" -#include "ballistica/base/ui/console.h" +#include "ballistica/base/ui/dev_console.h" #include "ballistica/base/ui/ui.h" #include "ballistica/shared/buildconfig/buildconfig_common.h" #include "ballistica/shared/foundation/event_loop.h" @@ -23,26 +23,14 @@ namespace ballistica::base { Input::Input() = default; template -void SafePushLogicCall(const char* desc, const F& lambda) { - // Note: originally this call was created to silently ignore early events - // coming in before app stuff was up and running, but that was a bad idea, - // as it caused us to ignore device-create messages sometimes which lead - // to other issues later. So now I'm trying to fix those problems at the - // source, but am leaving this intact for now as a clean way to catch - // anything that needs fixing. - if (!g_base) { - FatalError(std::string(desc) + " called with null g_base."); - return; - } - if (auto* loop = g_base->logic->event_loop()) { - loop->PushCall(lambda); - } else { - FatalError(std::string(desc) + " called before logic event loop created."); - } +void PushLogicCall(const F& lambda) { + assert(g_base); + assert(g_base->logic->event_loop()); + g_base->logic->event_loop()->PushCall(lambda); } void Input::PushCreateKeyboardInputDevices() { - SafePushLogicCall(__func__, [this] { CreateKeyboardInputDevices(); }); + PushLogicCall([this] { CreateKeyboardInputDevices(); }); } void Input::CreateKeyboardInputDevices() { @@ -59,7 +47,7 @@ void Input::CreateKeyboardInputDevices() { } void Input::PushDestroyKeyboardInputDevices() { - SafePushLogicCall(__func__, [this] { DestroyKeyboardInputDevices(); }); + PushLogicCall([this] { DestroyKeyboardInputDevices(); }); } void Input::DestroyKeyboardInputDevices() { @@ -171,13 +159,14 @@ void Input::CreateTouchInput() { void Input::AnnounceConnects() { static bool first_print = true; - // For the first announcement just say "X controllers detected" and don't have - // a sound. + // For the first announcement just say "X controllers detected" and don't + // have a sound. if (first_print && g_core->GetAppTimeMillisecs() < 10000) { first_print = false; // Disabling this completely for now; being more lenient with devices - // allowed on android means this will often come back with large numbers. + // allowed on Android means this will often come back with large + // numbers. bool do_print{false}; // If there's been several connected, just give a number. @@ -203,7 +192,7 @@ void Input::AnnounceConnects() { &s, "${COUNT}", std::to_string(newly_connected_controllers_.size())); ScreenMessage(s); } else { - // If its just one, name it. + // If its just one, give its name. std::string s = g_base->assets->GetResourceString("controllerConnectedText"); Utils::StringReplaceOne(&s, "${CONTROLLER}", @@ -276,7 +265,7 @@ void Input::ShowStandardInputDeviceDisconnectedMessage(InputDevice* j) { void Input::PushAddInputDeviceCall(InputDevice* input_device, bool standard_message) { - SafePushLogicCall(__func__, [this, input_device, standard_message] { + PushLogicCall([this, input_device, standard_message] { AddInputDevice(input_device, standard_message); }); } @@ -362,7 +351,7 @@ void Input::AddInputDevice(InputDevice* device, bool standard_message) { void Input::PushRemoveInputDeviceCall(InputDevice* input_device, bool standard_message) { - SafePushLogicCall(__func__, [this, input_device, standard_message] { + PushLogicCall([this, input_device, standard_message] { RemoveInputDevice(input_device, standard_message); }); } @@ -390,8 +379,6 @@ void Input::RemoveInputDevice(InputDevice* input, bool standard_message) { device->DetachFromPlayer(); // This should kill the device. - // FIXME: since many devices get allocated in the main thread, - // should we not kill it there too?... device.Clear(); UpdateInputDeviceCounts(); return; @@ -411,10 +398,10 @@ void Input::UpdateInputDeviceCounts() { int total = 0; int controller_count = 0; for (auto& input_device : input_devices_) { - // Ok, we now limit non-keyboard non-touchscreen devices to ones that have - // been active recently.. (we're starting to get lots of virtual devices and - // other cruft on android; don't wanna show controller UIs just due to - // those) + // Ok, we now limit non-keyboard non-touchscreen devices to ones that + // have been active recently.. (we're starting to get lots of virtual + // devices and other cruft on android; don't wanna show controller UIs + // just due to those) if (input_device.Exists() && ((*input_device).IsTouchScreen() || (*input_device).IsKeyboard() || ((*input_device).last_input_time_millisecs() != 0 @@ -480,7 +467,6 @@ auto Input::GetLocalActiveInputDeviceCount() -> int { auto Input::HaveControllerWithPlayer() -> bool { assert(g_base->InLogicThread()); - // NOLINTNEXTLINE(readability-use-anyofallof) for (auto& input_device : input_devices_) { if (input_device.Exists() && (*input_device).IsController() && (*input_device).AttachedToPlayer()) { @@ -492,7 +478,6 @@ auto Input::HaveControllerWithPlayer() -> bool { auto Input::HaveRemoteAppController() -> bool { assert(g_base->InLogicThread()); - // NOLINTNEXTLINE(readability-use-anyofallof) for (auto& input_device : input_devices_) { if (input_device.Exists() && (*input_device).IsRemoteApp()) { return true; @@ -546,8 +531,8 @@ auto Input::ShouldCompletelyIgnoreInputDevice(InputDevice* input_device) void Input::UpdateEnabledControllerSubsystems() { assert(g_base); - // First off, on mac, let's update whether we want to completely ignore either - // the classic or the iOS/Mac controller subsystems. + // First off, on mac, let's update whether we want to completely ignore + // either the classic or the iOS/Mac controller subsystems. if (g_buildconfig.ostype_macos()) { std::string sys = g_base->app_config->Resolve( AppConfig::StringID::kMacControllerSubsystem); @@ -581,9 +566,9 @@ void Input::DoApplyAppConfig() { UpdateEnabledControllerSubsystems(); - // It's technically possible that updating these controls will add or remove - // devices, thus changing the input_devices_ list, so lets work with a copy of - // it. + // It's technically possible that updating these controls will add or + // remove devices, thus changing the input_devices_ list, so lets work + // with a copy of it. std::vector > input_devices = input_devices_; for (auto& input_device : input_devices) { if (input_device.Exists()) { @@ -819,7 +804,7 @@ void Input::ProcessStressTesting(int player_count) { } void Input::PushTextInputEvent(const std::string& text) { - SafePushLogicCall(__func__, [this, text] { + PushLogicCall([this, text] { MarkInputActive(); // Ignore if input is locked. @@ -837,7 +822,7 @@ void Input::PushTextInputEvent(const std::string& text) { void Input::PushJoystickEvent(const SDL_Event& event, InputDevice* input_device) { - SafePushLogicCall(__func__, [this, event, input_device] { + PushLogicCall([this, event, input_device] { HandleJoystickEvent(event, input_device); }); } @@ -871,11 +856,11 @@ void Input::HandleJoystickEvent(const SDL_Event& event, } void Input::PushKeyPressEvent(const SDL_Keysym& keysym) { - SafePushLogicCall(__func__, [this, keysym] { HandleKeyPress(&keysym); }); + PushLogicCall([this, keysym] { HandleKeyPress(&keysym); }); } void Input::PushKeyReleaseEvent(const SDL_Keysym& keysym) { - SafePushLogicCall(__func__, [this, keysym] { HandleKeyRelease(&keysym); }); + PushLogicCall([this, keysym] { HandleKeyRelease(&keysym); }); } void Input::CaptureKeyboardInput(HandleKeyPressCall* press_call, @@ -947,10 +932,9 @@ void Input::HandleKeyPress(const SDL_Keysym* keysym) { case SDLK_KP_ENTER: case SDLK_BACKSPACE: { // FIXME: I don't remember what this was put here for, but now that - // we - // have hardware keyboards it crashes text fields by sending them a - // TEXT_INPUT message with no string.. I made them resistant to - // that case but wondering if we can take this out?... + // we have hardware keyboards it crashes text fields by sending + // them a TEXT_INPUT message with no string.. I made them resistant + // to that case but wondering if we can take this out? g_base->ui->SendWidgetMessage( WidgetMessage(WidgetMessage::Type::kTextInput, keysym)); break; @@ -971,7 +955,7 @@ void Input::HandleKeyPress(const SDL_Keysym* keysym) { return; } - // Control-Q quits. On mac, the usual cmd-q gets handled by SDL/etc. + // Control-Q quits. On Mac, the usual cmd-q gets handled by SDL/etc. // implicitly. if (!repeat_press && keysym->sym == SDLK_q && (keysym->mod & KMOD_CTRL)) { g_base->ui->ConfirmQuit(); @@ -1027,14 +1011,12 @@ void Input::HandleKeyPress(const SDL_Keysym* keysym) { } case SDLK_F7: - SafePushLogicCall(__func__, - [] { g_base->graphics->ToggleManualCamera(); }); + PushLogicCall([] { g_base->graphics->ToggleManualCamera(); }); handled = true; break; case SDLK_F8: - SafePushLogicCall( - __func__, [] { g_base->graphics->ToggleNetworkDebugDisplay(); }); + PushLogicCall([] { g_base->graphics->ToggleNetworkDebugDisplay(); }); handled = true; break; @@ -1045,8 +1027,7 @@ void Input::HandleKeyPress(const SDL_Keysym* keysym) { break; case SDLK_F10: - SafePushLogicCall(__func__, - [] { g_base->graphics->ToggleDebugDraw(); }); + PushLogicCall([] { g_base->graphics->ToggleDebugDraw(); }); handled = true; break; @@ -1080,49 +1061,38 @@ void Input::HandleKeyPress(const SDL_Keysym* keysym) { } void Input::HandleKeyRelease(const SDL_Keysym* keysym) { + assert(g_base); assert(g_base->InLogicThread()); - // Note: we want to let these through even if input is locked. + // Note: we want to let releases through even if input is locked. MarkInputActive(); - // If someone is capturing these events, give them a crack at it. - if (keyboard_input_capture_release_) { - if (keyboard_input_capture_release_(*keysym)) { - return; - } - } - - // Regardless of what else we do, keep track of mod key states. - // (for things like manual camera moves. For individual key presses - // ideally we should use the modifiers bundled with the key presses) - UpdateModKeyStates(keysym, false); - - // In some cases we may receive duplicate key-release events - // (if a keyboard reset was run it deals out key releases but then the - // keyboard driver issues them as well) + // In some cases we may receive duplicate key-release events (if a + // keyboard reset was run, it deals out key releases, but then the + // keyboard driver issues them as well). if (keys_held_.count(keysym->sym) == 0) { return; } + // If someone is capturing these events, give them a crack at it. + if (keyboard_input_capture_release_) { + (keyboard_input_capture_release_(*keysym)); + } + + // Keep track of mod key states for things like manual camera moves. For + // individual key presses ideally we should instead use modifiers bundled + // with the key press events. + UpdateModKeyStates(keysym, false); + keys_held_.erase(keysym->sym); - if (IsInputLocked()) { - return; + if (g_base->console() != nullptr) { + g_base->console()->HandleKeyRelease(keysym); } - bool handled = false; - - if (g_base && g_base->console() != nullptr - && g_base->console()->HandleKeyRelease(keysym)) { - handled = true; - } - - // If we haven't claimed it, pass it along as potential player input. - if (!handled) { - if (keyboard_input_) { - keyboard_input_->HandleKey(keysym, false, false); - } + if (keyboard_input_) { + keyboard_input_->HandleKey(keysym, false, false); } } @@ -1155,15 +1125,17 @@ void Input::UpdateModKeyStates(const SDL_Keysym* keysym, bool press) { } void Input::PushMouseScrollEvent(const Vector2f& amount) { - SafePushLogicCall(__func__, [this, amount] { HandleMouseScroll(amount); }); + PushLogicCall([this, amount] { HandleMouseScroll(amount); }); } void Input::HandleMouseScroll(const Vector2f& amount) { assert(g_base->InLogicThread()); + + // If input is locked, allow it to mark us active but nothing more. + MarkInputActive(); if (IsInputLocked()) { return; } - MarkInputActive(); if (std::abs(amount.y) > 0.0001f) { g_base->ui->SendWidgetMessage( @@ -1187,17 +1159,19 @@ void Input::HandleMouseScroll(const Vector2f& amount) { void Input::PushSmoothMouseScrollEvent(const Vector2f& velocity, bool momentum) { - SafePushLogicCall(__func__, [this, velocity, momentum] { + PushLogicCall([this, velocity, momentum] { HandleSmoothMouseScroll(velocity, momentum); }); } void Input::HandleSmoothMouseScroll(const Vector2f& velocity, bool momentum) { assert(g_base->InLogicThread()); + + // If input is locked, allow it to mark us active but nothing more. + MarkInputActive(); if (IsInputLocked()) { return; } - MarkInputActive(); bool handled = false; handled = g_base->ui->SendWidgetMessage( @@ -1219,15 +1193,19 @@ void Input::HandleSmoothMouseScroll(const Vector2f& velocity, bool momentum) { } void Input::PushMouseMotionEvent(const Vector2f& position) { - SafePushLogicCall(__func__, - [this, position] { HandleMouseMotion(position); }); + PushLogicCall([this, position] { HandleMouseMotion(position); }); } void Input::HandleMouseMotion(const Vector2f& position) { - assert(g_base->graphics); + assert(g_base); assert(g_base->InLogicThread()); + MarkInputActive(); + if (IsInputLocked()) { + return; + } + float old_cursor_pos_x = cursor_pos_x_; float old_cursor_pos_y = cursor_pos_y_; @@ -1240,8 +1218,6 @@ void Input::HandleMouseMotion(const Vector2f& position) { last_mouse_move_time_ = g_core->GetAppTimeMillisecs(); mouse_move_count_++; - bool handled{}; - // If we have a touch-input in editing mode, pass along events to it. // (it usually handles its own events but here we want it to play nice // with stuff under it by blocking touches, etc) @@ -1250,48 +1226,35 @@ void Input::HandleMouseMotion(const Vector2f& position) { cursor_pos_y_); } - // UI interaction. - if (!IsInputLocked()) { - handled = g_base->ui->SendWidgetMessage( - WidgetMessage(WidgetMessage::Type::kMouseMove, nullptr, cursor_pos_x_, - cursor_pos_y_)); - } + // Let any UI stuff handle it. + g_base->ui->HandleMouseMotion(cursor_pos_x_, cursor_pos_y_); // Manual camera motion. Camera* camera = g_base->graphics->camera(); - if (!handled && camera && camera->manual()) { + if (camera && camera->manual()) { float move_h = (cursor_pos_x_ - old_cursor_pos_x) / g_base->graphics->screen_virtual_width(); float move_v = (cursor_pos_y_ - old_cursor_pos_y) / g_base->graphics->screen_virtual_width(); camera->ManualHandleMouseMove(move_h, move_v); } - - // Old screen edge UI. - g_base->ui->HandleLegacyRootUIMouseMotion(cursor_pos_x_, cursor_pos_y_); } void Input::PushMouseDownEvent(int button, const Vector2f& position) { - SafePushLogicCall(__func__, [this, button, position] { - HandleMouseDown(button, position); - }); + PushLogicCall( + [this, button, position] { HandleMouseDown(button, position); }); } void Input::HandleMouseDown(int button, const Vector2f& position) { assert(g_base); - assert(g_base->graphics); assert(g_base->InLogicThread()); + MarkInputActive(); + if (IsInputLocked()) { return; } - // if (!g_base->ui->MainMenuVisible()) { - // return; - // } - - MarkInputActive(); - last_mouse_move_time_ = g_core->GetAppTimeMillisecs(); mouse_move_count_++; @@ -1306,7 +1269,6 @@ void Input::HandleMouseDown(int button, const Vector2f& position) { last_click_time_ = click_time; bool handled{}; - // auto* root_widget = g_base->ui->root_widget(); // If we have a touch-input in editing mode, pass along events to it. // (it usually handles its own events but here we want it to play nice @@ -1317,15 +1279,8 @@ void Input::HandleMouseDown(int button, const Vector2f& position) { } if (!handled) { - if (g_base->ui->HandleLegacyRootUIMouseDown(cursor_pos_x_, cursor_pos_y_)) { - handled = true; - } - } - - if (!handled) { - handled = g_base->ui->SendWidgetMessage( - WidgetMessage(WidgetMessage::Type::kMouseDown, nullptr, cursor_pos_x_, - cursor_pos_y_, double_click ? 2 : 1)); + handled = g_base->ui->HandleMouseDown(button, cursor_pos_x_, cursor_pos_y_, + double_click); } // Manual camera input. @@ -1349,8 +1304,7 @@ void Input::HandleMouseDown(int button, const Vector2f& position) { } void Input::PushMouseUpEvent(int button, const Vector2f& position) { - SafePushLogicCall( - __func__, [this, button, position] { HandleMouseUp(button, position); }); + PushLogicCall([this, button, position] { HandleMouseUp(button, position); }); } void Input::HandleMouseUp(int button, const Vector2f& position) { @@ -1363,8 +1317,6 @@ void Input::HandleMouseUp(int button, const Vector2f& position) { cursor_pos_y_ = g_base->graphics->PixelToVirtualY( position.y * g_base->graphics->screen_pixel_height()); - bool handled{}; - // If we have a touch-input in editing mode, pass along events to it. // (it usually handles its own events but here we want it to play nice // with stuff under it by blocking touches, etc) @@ -1373,14 +1325,7 @@ void Input::HandleMouseUp(int button, const Vector2f& position) { cursor_pos_y_); } - // ui_v1::Widget* root_widget = g_base->ui->root_widget(); - // if (root_widget) { - handled = g_base->ui->SendWidgetMessage(WidgetMessage( - WidgetMessage::Type::kMouseUp, nullptr, cursor_pos_x_, cursor_pos_y_)); - // } - - Camera* camera = g_base->graphics->camera(); - if (!handled && camera) { + if (Camera* camera = g_base->graphics->camera()) { switch (button) { case SDL_BUTTON_LEFT: camera->set_mouse_left_down(false); @@ -1397,11 +1342,11 @@ void Input::HandleMouseUp(int button, const Vector2f& position) { camera->UpdateManualMode(); } - g_base->ui->HandleLegacyRootUIMouseUp(cursor_pos_x_, cursor_pos_y_); + g_base->ui->HandleMouseUp(button, cursor_pos_x_, cursor_pos_y_); } void Input::PushTouchEvent(const TouchEvent& e) { - SafePushLogicCall(__func__, [e, this] { HandleTouchEvent(e); }); + PushLogicCall([e, this] { HandleTouchEvent(e); }); } void Input::HandleTouchEvent(const TouchEvent& e) { @@ -1501,19 +1446,19 @@ void Input::Draw(FrameDef* frame_def) { } auto Input::IsCursorVisible() const -> bool { - assert(g_base->InLogicThread()); - if (!g_base->ui) { + if (!g_base) { return false; } + assert(g_base->InLogicThread()); - // Keeps mouse hidden to start with.. + // Keeps mouse hidden to start with. if (mouse_move_count_ < 2) { return false; } bool val; - // Show our cursor if any dialogs/windows are up or else if its been - // moved very recently. + // Show our cursor if any dialogs/windows are up or else if its been moved + // very recently. if (g_base->ui->MainMenuVisible()) { val = (g_core->GetAppTimeMillisecs() - last_mouse_move_time_ < 5000); } else { diff --git a/src/ballistica/base/logic/logic.cc b/src/ballistica/base/logic/logic.cc index 884c8190..21bfc619 100644 --- a/src/ballistica/base/logic/logic.cc +++ b/src/ballistica/base/logic/logic.cc @@ -11,7 +11,7 @@ #include "ballistica/base/python/base_python.h" #include "ballistica/base/support/plus_soft.h" #include "ballistica/base/support/stdio_console.h" -#include "ballistica/base/ui/console.h" +#include "ballistica/base/ui/dev_console.h" #include "ballistica/base/ui/ui.h" #include "ballistica/shared/foundation/event_loop.h" #include "ballistica/shared/python/python_sys.h" diff --git a/src/ballistica/base/platform/apple/base_platform_apple.cc b/src/ballistica/base/platform/apple/base_platform_apple.cc index e955fdc9..14c05758 100644 --- a/src/ballistica/base/platform/apple/base_platform_apple.cc +++ b/src/ballistica/base/platform/apple/base_platform_apple.cc @@ -63,11 +63,11 @@ void BasePlatformApple::DoOpenURL(const std::string& url) { #endif } -void BasePlatformApple::QuitApp() { +void BasePlatformApple::TerminateApp() { #if BA_OSTYPE_MACOS && BA_XCODE_BUILD && !BA_HEADLESS_BUILD - core::AppleUtils::Quit(); // will post a cocoa terminate + core::AppleUtils::TerminateApp(); #else - BasePlatform::QuitApp(); + BasePlatform::TerminateApp(); #endif } diff --git a/src/ballistica/base/platform/apple/base_platform_apple.h b/src/ballistica/base/platform/apple/base_platform_apple.h index c885e052..a9907425 100644 --- a/src/ballistica/base/platform/apple/base_platform_apple.h +++ b/src/ballistica/base/platform/apple/base_platform_apple.h @@ -16,7 +16,7 @@ class BasePlatformApple : public BasePlatform { void RestorePurchases() override; void PurchaseAck(const std::string& purchase, const std::string& order_id) override; - void QuitApp() override; + void TerminateApp() override; void DoOpenURL(const std::string& url) override; diff --git a/src/ballistica/base/platform/base_platform.cc b/src/ballistica/base/platform/base_platform.cc index a17d09df..7ca4032f 100644 --- a/src/ballistica/base/platform/base_platform.cc +++ b/src/ballistica/base/platform/base_platform.cc @@ -321,6 +321,11 @@ void BasePlatform::OnAppShutdown() { assert(g_base->InLogicThread()); } void BasePlatform::OnScreenSizeChange() { assert(g_base->InLogicThread()); } void BasePlatform::DoApplyAppConfig() { assert(g_base->InLogicThread()); } -void BasePlatform::QuitApp() { exit(g_base->return_value()); } +void BasePlatform::TerminateApp() { exit(g_base->return_value()); } + +auto BasePlatform::CanSoftQuit() -> bool { return false; } +auto BasePlatform::CanBackQuit() -> bool { return false; } +void BasePlatform::DoBackQuit() {} +void BasePlatform::DoSoftQuit() {} } // namespace ballistica::base diff --git a/src/ballistica/base/platform/base_platform.h b/src/ballistica/base/platform/base_platform.h index fc2c53a1..bf262e19 100644 --- a/src/ballistica/base/platform/base_platform.h +++ b/src/ballistica/base/platform/base_platform.h @@ -33,8 +33,35 @@ class BasePlatform { virtual void OnScreenSizeChange(); virtual void DoApplyAppConfig(); - /// Quit the app (can be immediate or via posting some high level event). - virtual void QuitApp(); + /// Return whether this platform supports soft-quit. A soft quit is + /// when the app is reset/backgrounded/etc. but remains running in case + /// needed again. Generally this is the behavior on mobile apps. + virtual auto CanSoftQuit() -> bool; + + /// Implement soft-quit behavior. Will always be called in the logic + /// thread. Make sure to also override CanBackQuit to reflect this being + /// present. Note that when quitting the app yourself, you should use + /// g_base->QuitApp(); do not call this directly. + virtual void DoSoftQuit(); + + /// Return whether this platform supports back-quit. A back quit is a + /// variation of soft-quit generally triggered by a back button, which may + /// give different results in the OS. For example on Android this may + /// result in jumping back to the previous Android activity instead of + /// just ending the current one and dumping to the home screen as normal + /// soft quit might do. + virtual auto CanBackQuit() -> bool; + + /// Implement back-quit behavior. Will always be called in the logic + /// thread. Make sure to also override CanBackQuit to reflect this being + /// present. Note that when quitting the app yourself, you should use + /// g_base->QuitApp(); do not call this directly. + virtual void DoBackQuit(); + + /// Terminate the app. This can be immediate or by posting some high + /// level event. There should be nothing left to do in the engine at + /// this point. + virtual void TerminateApp(); #pragma mark IN APP PURCHASES -------------------------------------------------- diff --git a/src/ballistica/base/python/methods/python_methods_app.cc b/src/ballistica/base/python/methods/python_methods_app.cc index 18110551..14b0aa8f 100644 --- a/src/ballistica/base/python/methods/python_methods_app.cc +++ b/src/ballistica/base/python/methods/python_methods_app.cc @@ -5,6 +5,7 @@ #include "ballistica/base/app_mode/app_mode_empty.h" #include "ballistica/base/graphics/graphics_server.h" #include "ballistica/base/logic/logic.h" +#include "ballistica/base/platform/base_platform.h" #include "ballistica/base/python/base_python.h" #include "ballistica/base/python/support/python_context_call_runnable.h" #include "ballistica/base/support/stress_test.h" @@ -511,45 +512,23 @@ static auto PyQuit(PyObject* self, PyObject* args, PyObject* keywds) static const char* kwlist[] = {"soft", "back", nullptr}; int soft = 0; int back = 0; - if (!PyArg_ParseTupleAndKeywords(args, keywds, "|ii", + if (!PyArg_ParseTupleAndKeywords(args, keywds, "|pp", const_cast(kwlist), &soft, &back)) { return nullptr; } - - // Log(LogLevel::kDebug, - // "QUIT soft=" + std::to_string(soft) + " back=" + std::to_string(back)); - - // FIXME this should all just go through platform and/or app-adapter. - - if (g_buildconfig.ostype_ios_tvos()) { - // This should never be called on iOS - Log(LogLevel::kError, "Quit called."); - } - - bool handled = false; - - // A few types get handled specially on Android. - if (g_buildconfig.ostype_android()) { - if (!handled && back) { - // Back-quit simply synthesizes a back press. - // Note to self: I remember this behaved slightly differently than - // doing a soft quit but I should remind myself how. - g_core->platform->AndroidSynthesizeBackPress(); - handled = true; - } - - if (!handled && soft) { - // Soft-quit just kills our activity but doesn't run app shutdown. - // Thus we'll be able to spin back up (reset to the main menu) - // if the user re-launches us. - g_core->platform->AndroidQuitActivity(); - handled = true; + QuitType quit_type{}; + if (back) { + if (!soft) { + Log(LogLevel::kWarning, + "Got soft=False back=True in quit() which is ambiguous."); } + quit_type = QuitType::kBack; + } else if (soft) { + quit_type = QuitType::kSoft; + } else { + quit_type = QuitType::kHard; } - // In all other cases, kick off a standard app shutdown. - if (!handled) { - g_base->logic->event_loop()->PushCall([] { g_base->logic->Shutdown(); }); - } + g_base->QuitApp(quit_type); Py_RETURN_NONE; BA_PYTHON_CATCH; } @@ -559,15 +538,18 @@ static PyMethodDef PyQuitDef = { (PyCFunction)PyQuit, // method METH_VARARGS | METH_KEYWORDS, // flags - "quit(soft: bool = False, back: bool = False) -> None\n" + "quit(soft: bool = True, back: bool = False) -> None\n" "\n" - "Quit the game.\n" + "Quit the app.\n" "\n" "Category: **General Utility Functions**\n" "\n" - "On systems like Android, 'soft' will end the activity but keep the\n" - "app running.", -}; + "On platforms such as mobile, a 'soft' quit may background and/or reset\n" + "the app but keep it running. A 'back' quit is a special form of soft\n" + "quit that may trigger different behavior in the OS. On Android, for\n" + "example, a back-quit may simply jump to the previous Android activity,\n" + "while a regular soft quit may just exit the current activity and dump\n" + "the user at their home screen."}; // ----------------------------- apply_config ---------------------------------- diff --git a/src/ballistica/base/support/app_config.cc b/src/ballistica/base/support/app_config.cc index df3f85de..1c04f0b4 100644 --- a/src/ballistica/base/support/app_config.cc +++ b/src/ballistica/base/support/app_config.cc @@ -218,6 +218,8 @@ void AppConfig::SetupEntries() { BoolEntry("Always Use Internal Keyboard", false); bool_entries_[BoolID::kShowFPS] = BoolEntry("Show FPS", false); bool_entries_[BoolID::kShowPing] = BoolEntry("Show Ping", false); + bool_entries_[BoolID::kShowDevConsoleButton] = + BoolEntry("Show Dev Console Button", false); bool_entries_[BoolID::kEnableTVBorder] = BoolEntry("TV Border", g_core->platform->IsRunningOnTV()); bool_entries_[BoolID::kKeyboardP2Enabled] = diff --git a/src/ballistica/base/support/app_config.h b/src/ballistica/base/support/app_config.h index b819b3e5..e8d183e6 100644 --- a/src/ballistica/base/support/app_config.h +++ b/src/ballistica/base/support/app_config.h @@ -64,6 +64,7 @@ class AppConfig { kAlwaysUseInternalKeyboard, kShowFPS, kShowPing, + kShowDevConsoleButton, kEnableTVBorder, kKeyboardP2Enabled, kEnablePackageMods, diff --git a/src/ballistica/base/support/ui_v1_soft.h b/src/ballistica/base/support/ui_v1_soft.h index e1a51170..62fce014 100644 --- a/src/ballistica/base/support/ui_v1_soft.h +++ b/src/ballistica/base/support/ui_v1_soft.h @@ -6,6 +6,7 @@ #include "ballistica/base/ui/ui.h" // Predeclare some types we use. + namespace ballistica::ui_v1 { class RootUI; class Widget; @@ -13,10 +14,9 @@ class Widget; namespace ballistica::base { -/// 'Soft' interface to the ui_v1 feature-set, managed by base. -/// Feature-sets listing ui_v1 as a soft requirement must limit their use of -/// it to these methods and should be prepared to handle the not-present -/// case. +/// 'Soft' interface to the ui_v1 feature-set, managed by base. Feature-sets +/// listing ui_v1 as a soft requirement must limit their use of it to these +/// methods and should be prepared to handle the not-present case. class UIV1SoftInterface { public: virtual void DoHandleDeviceMenuPress(base::InputDevice* device) = 0; diff --git a/src/ballistica/base/ui/console.cc b/src/ballistica/base/ui/dev_console.cc similarity index 78% rename from src/ballistica/base/ui/console.cc rename to src/ballistica/base/ui/dev_console.cc index 9809c92c..fd36c5f7 100644 --- a/src/ballistica/base/ui/console.cc +++ b/src/ballistica/base/ui/dev_console.cc @@ -1,6 +1,6 @@ // Released under the MIT License. See LICENSE for details. -#include "ballistica/base/ui/console.h" +#include "ballistica/base/ui/dev_console.h" #include "ballistica/base/app_mode/app_mode.h" #include "ballistica/base/audio/audio.h" @@ -19,14 +19,14 @@ namespace ballistica::base { // How much of the screen the console covers when it is at full size. -const float kConsoleSize = 0.9f; -const float kConsoleZDepth = 0.0f; -const int kConsoleLineLimit = 80; -const int kStringBreakUpSize = 1950; -const int kActivateKey1 = SDLK_BACKQUOTE; -const int kActivateKey2 = SDLK_F2; +const float kDevConsoleSize = 0.9f; +const float kDevConsoleZDepth = 0.0f; +const int kDevConsoleLineLimit = 80; +const int kDevConsoleStringBreakUpSize = 1950; +const int kDevConsoleActivateKey1 = SDLK_BACKQUOTE; +const int kDevConsoleActivateKey2 = SDLK_F2; -Console::Console() { +DevConsole::DevConsole() { assert(g_base->InLogicThread()); std::string title = std::string("BallisticaKit ") + kEngineVersion + " (" + std::to_string(kEngineBuildNumber) + ")"; @@ -42,15 +42,15 @@ Console::Console() { prompt_text_group_.set_text(">"); } -Console::~Console() = default; +DevConsole::~DevConsole() = default; -auto Console::HandleKeyPress(const SDL_Keysym* keysym) -> bool { +auto DevConsole::HandleKeyPress(const SDL_Keysym* keysym) -> bool { assert(g_base->InLogicThread()); // Handle our toggle buttons no matter whether we're active. switch (keysym->sym) { - case kActivateKey1: - case kActivateKey2: { + case kDevConsoleActivateKey1: + case kDevConsoleActivateKey2: { if (!g_buildconfig.demo_build() && !g_buildconfig.arcade_build()) { // (reset input so characters don't continue walking and stuff) g_base->input->ResetHoldStates(); @@ -111,9 +111,7 @@ auto Console::HandleKeyPress(const SDL_Keysym* keysym) -> bool { case SDLK_KP_ENTER: case SDLK_RETURN: { if (!input_enabled_) { - Log(LogLevel::kWarning, - "Console input is not allowed until the app reaches the 'running' " - "state."); + Log(LogLevel::kWarning, "Console input is not allowed yet."); break; } input_history_position_ = 0; @@ -121,7 +119,7 @@ auto Console::HandleKeyPress(const SDL_Keysym* keysym) -> bool { last_line_.clear(); lines_.clear(); } else { - PushCommand(input_string_); + SubmitCommand_(input_string_); } input_history_.push_front(input_string_); if (input_history_.size() > 100) { @@ -151,7 +149,7 @@ auto Console::HandleKeyPress(const SDL_Keysym* keysym) -> bool { return true; } -void Console::PushCommand(const std::string& command) { +void DevConsole::SubmitCommand_(const std::string& command) { assert(g_base); g_base->logic->event_loop()->PushCall([command] { // These are always run in whichever context is 'visible'. @@ -172,13 +170,25 @@ void Console::PushCommand(const std::string& command) { }); } -void Console::EnableInput() { +void DevConsole::EnableInput() { assert(g_base->InLogicThread()); input_enabled_ = true; } -void Console::ToggleState() { +void DevConsole::Dismiss() { assert(g_base->InLogicThread()); + if (state_ == State::kInactive) { + return; + } + + state_prev_ = state_; + state_ = State::kInactive; + transition_start_ = g_core->GetAppTimeMillisecs(); +} + +void DevConsole::ToggleState() { + assert(g_base->InLogicThread()); + state_prev_ = state_; switch (state_) { case State::kInactive: state_ = State::kMini; @@ -194,7 +204,7 @@ void Console::ToggleState() { transition_start_ = g_core->GetAppTimeMillisecs(); } -auto Console::HandleTextEditing(const std::string& text) -> bool { +auto DevConsole::HandleTextEditing(const std::string& text) -> bool { assert(g_base->InLogicThread()); if (state_ == State::kInactive) { return false; @@ -209,31 +219,32 @@ auto Console::HandleTextEditing(const std::string& text) -> bool { return true; } -auto Console::HandleKeyRelease(const SDL_Keysym* keysym) -> bool { +auto DevConsole::HandleKeyRelease(const SDL_Keysym* keysym) -> bool { // Always absorb our activate keys. - if (keysym->sym == kActivateKey1 || keysym->sym == kActivateKey2) { + if (keysym->sym == kDevConsoleActivateKey1 + || keysym->sym == kDevConsoleActivateKey2) { return true; } - // Otherwise simply absorb all key-ups if we're active. + // Otherwise absorb *all* key-ups when we're active. return state_ != State::kInactive; } #pragma clang diagnostic push #pragma ide diagnostic ignored "LocalValueEscapesScope" -void Console::Print(const std::string& s_in) { +void DevConsole::Print(const std::string& s_in) { assert(g_base->InLogicThread()); std::string s = Utils::GetValidUTF8(s_in.c_str(), "cspr"); last_line_ += s; std::vector broken_up; - g_base->text_graphics->BreakUpString(last_line_.c_str(), kStringBreakUpSize, - &broken_up); + g_base->text_graphics->BreakUpString( + last_line_.c_str(), kDevConsoleStringBreakUpSize, &broken_up); // Spit out all completed lines and keep the last one as lastline. for (size_t i = 0; i < broken_up.size() - 1; i++) { lines_.emplace_back(broken_up[i], g_core->GetAppTimeMillisecs()); - if (lines_.size() > kConsoleLineLimit) { + if (lines_.size() > kDevConsoleLineLimit) { lines_.pop_front(); } } @@ -243,7 +254,7 @@ void Console::Print(const std::string& s_in) { #pragma clang diagnostic pop -void Console::Draw(RenderPass* pass) { +void DevConsole::Draw(RenderPass* pass) { millisecs_t transition_ticks = 100; if ((transition_start_ != 0) && (state_ != State::kInactive @@ -257,27 +268,37 @@ void Console::Draw(RenderPass* pass) { if (state_ == State::kMini) { bottom = pass->virtual_height() - mini_size; } else { - bottom = pass->virtual_height() - pass->virtual_height() * kConsoleSize; + bottom = + pass->virtual_height() - pass->virtual_height() * kDevConsoleSize; } if (g_core->GetAppTimeMillisecs() - transition_start_ < transition_ticks) { - if (state_ == State::kMini) { - bottom = pass->virtual_height() * (1.0f - ratio) + bottom * (ratio); - } else if (state_ == State::kFull) { - bottom = - (pass->virtual_height() - pass->virtual_height() * kConsoleSize) - * (ratio) - + (pass->virtual_height() - mini_size) * (1.0f - ratio); + float from_height; + if (state_prev_ == State::kMini) { + from_height = pass->virtual_height() - mini_size; + } else if (state_prev_ == State::kFull) { + from_height = + pass->virtual_height() - pass->virtual_height() * kDevConsoleSize; } else { - bottom = pass->virtual_height() * ratio + bottom * (1.0f - ratio); + from_height = pass->virtual_height(); } + float to_height; + if (state_ == State::kMini) { + to_height = pass->virtual_height() - mini_size; + } else if (state_ == State::kFull) { + to_height = + pass->virtual_height() - pass->virtual_height() * kDevConsoleSize; + } else { + to_height = pass->virtual_height(); + } + bottom = to_height * ratio + from_height * (1.0 - ratio); } { - bg_mesh_.SetPositionAndSize(0, bottom, kConsoleZDepth, + bg_mesh_.SetPositionAndSize(0, bottom, kDevConsoleZDepth, pass->virtual_width(), (pass->virtual_height() - bottom)); - stripe_mesh_.SetPositionAndSize(0, bottom + 15, kConsoleZDepth, + stripe_mesh_.SetPositionAndSize(0, bottom + 15, kDevConsoleZDepth, pass->virtual_width(), 15); - shadow_mesh_.SetPositionAndSize(0, bottom - 7, kConsoleZDepth, + shadow_mesh_.SetPositionAndSize(0, bottom - 7, kDevConsoleZDepth, pass->virtual_width(), 7); SimpleComponent c(pass); c.SetTransparent(true); @@ -304,7 +325,8 @@ void Console::Draw(RenderPass* pass) { for (int e = 0; e < elem_count; e++) { c.SetTexture(built_text_group_.GetElementTexture(e)); c.PushTransform(); - c.Translate(pass->virtual_width() - 175.0f, bottom + 0, kConsoleZDepth); + c.Translate(pass->virtual_width() - 175.0f, bottom + 0, + kDevConsoleZDepth); c.Scale(0.5f, 0.5f, 0.5f); c.DrawMesh(built_text_group_.GetElementMesh(e)); c.PopTransform(); @@ -313,7 +335,7 @@ void Console::Draw(RenderPass* pass) { for (int e = 0; e < elem_count; e++) { c.SetTexture(title_text_group_.GetElementTexture(e)); c.PushTransform(); - c.Translate(20.0f, bottom + 0, kConsoleZDepth); + c.Translate(20.0f, bottom + 0, kDevConsoleZDepth); c.Scale(0.5f, 0.5f, 0.5f); c.DrawMesh(title_text_group_.GetElementMesh(e)); c.PopTransform(); @@ -323,7 +345,7 @@ void Console::Draw(RenderPass* pass) { c.SetTexture(prompt_text_group_.GetElementTexture(e)); c.SetColor(1, 1, 1, 1); c.PushTransform(); - c.Translate(5.0f, bottom + 15.0f, kConsoleZDepth); + c.Translate(5.0f, bottom + 15.0f, kDevConsoleZDepth); c.Scale(0.5f, 0.5f, 0.5f); c.DrawMesh(prompt_text_group_.GetElementMesh(e)); c.PopTransform(); @@ -332,7 +354,7 @@ void Console::Draw(RenderPass* pass) { for (int e = 0; e < elem_count; e++) { c.SetTexture(input_text_group_.GetElementTexture(e)); c.PushTransform(); - c.Translate(15.0f, bottom + 15.0f, kConsoleZDepth); + c.Translate(15.0f, bottom + 15.0f, kDevConsoleZDepth); c.Scale(0.5f, 0.5f, 0.5f); c.DrawMesh(input_text_group_.GetElementMesh(e)); c.PopTransform(); @@ -350,7 +372,7 @@ void Console::Draw(RenderPass* pass) { c.PushTransform(); c.Translate( 19.0f + g_base->text_graphics->GetStringWidth(input_string_) * 0.5f, - bottom + 23.0f, kConsoleZDepth); + bottom + 23.0f, kDevConsoleZDepth); c.Scale(5, 11, 1.0f); c.DrawMeshAsset(g_base->assets->SysMesh(SysMeshID::kImage1x1)); c.PopTransform(); @@ -365,7 +387,7 @@ void Console::Draw(RenderPass* pass) { c.SetColor(1, 1, 1, 1); float h = 0.5f * (g_base->graphics->screen_virtual_width() - - (kStringBreakUpSize * draw_scale)); + - (kDevConsoleStringBreakUpSize * draw_scale)); float v = bottom + 32.0f; if (!last_line_.empty()) { if (last_line_mesh_dirty_) { @@ -379,7 +401,7 @@ void Console::Draw(RenderPass* pass) { for (int e = 0; e < elem_count; e++) { c.SetTexture(last_line_mesh_group_->GetElementTexture(e)); c.PushTransform(); - c.Translate(h, v + 2, kConsoleZDepth); + c.Translate(h, v + 2, kDevConsoleZDepth); c.Scale(draw_scale, draw_scale); c.DrawMesh(last_line_mesh_group_->GetElementMesh(e)); c.PopTransform(); @@ -391,7 +413,7 @@ void Console::Draw(RenderPass* pass) { for (int e = 0; e < elem_count; e++) { c.SetTexture(i->GetText().GetElementTexture(e)); c.PushTransform(); - c.Translate(h, v + 2, kConsoleZDepth); + c.Translate(h, v + 2, kDevConsoleZDepth); c.Scale(draw_scale, draw_scale); c.DrawMesh(i->GetText().GetElementMesh(e)); c.PopTransform(); diff --git a/src/ballistica/base/ui/console.h b/src/ballistica/base/ui/dev_console.h similarity index 74% rename from src/ballistica/base/ui/console.h rename to src/ballistica/base/ui/dev_console.h index c55c7522..d0d951ec 100644 --- a/src/ballistica/base/ui/console.h +++ b/src/ballistica/base/ui/dev_console.h @@ -1,7 +1,7 @@ // Released under the MIT License. See LICENSE for details. -#ifndef BALLISTICA_BASE_UI_CONSOLE_H_ -#define BALLISTICA_BASE_UI_CONSOLE_H_ +#ifndef BALLISTICA_BASE_UI_DEV_CONSOLE_H_ +#define BALLISTICA_BASE_UI_DEV_CONSOLE_H_ #include #include @@ -12,22 +12,31 @@ namespace ballistica::base { -class Console { +class DevConsole { public: - Console(); - ~Console(); - auto active() const -> bool { return (state_ != State::kInactive); } - auto transition_start() const -> millisecs_t { return transition_start_; } + DevConsole(); + ~DevConsole(); + auto IsActive() const -> bool { return (state_ != State::kInactive); } auto HandleTextEditing(const std::string& text) -> bool; auto HandleKeyPress(const SDL_Keysym* keysym) -> bool; auto HandleKeyRelease(const SDL_Keysym* keysym) -> bool; + auto transition_start() const -> millisecs_t { return transition_start_; } + + /// Toggle between mini, fullscreen, and inactive. void ToggleState(); + + /// Tell the console to quietly go away no matter what state it is in. + void Dismiss(); + + /// Print text to the console. void Print(const std::string& s_in); void Draw(RenderPass* pass); + + /// Called when the console should start accepting Python command input. void EnableInput(); private: - void PushCommand(const std::string& command); + void SubmitCommand_(const std::string& command); enum class State { kInactive, kMini, kFull }; ImageMesh bg_mesh_; ImageMesh stripe_mesh_; @@ -40,6 +49,7 @@ class Console { bool input_text_dirty_{true}; millisecs_t transition_start_{}; State state_{State::kInactive}; + State state_prev_{State::kInactive}; class Message { public: @@ -71,4 +81,4 @@ class Console { } // namespace ballistica::base -#endif // BALLISTICA_BASE_UI_CONSOLE_H_ +#endif // BALLISTICA_BASE_UI_DEV_CONSOLE_H_ diff --git a/src/ballistica/base/ui/ui.cc b/src/ballistica/base/ui/ui.cc index 53801a2f..28a55a66 100644 --- a/src/ballistica/base/ui/ui.cc +++ b/src/ballistica/base/ui/ui.cc @@ -3,13 +3,16 @@ #include "ballistica/base/ui/ui.h" #include "ballistica/base/audio/audio.h" +#include "ballistica/base/graphics/component/simple_component.h" #include "ballistica/base/input/device/keyboard_input.h" #include "ballistica/base/input/input.h" #include "ballistica/base/logic/logic.h" #include "ballistica/base/python/base_python.h" +#include "ballistica/base/support/app_config.h" #include "ballistica/base/support/ui_v1_soft.h" -#include "ballistica/base/ui/console.h" +#include "ballistica/base/ui/dev_console.h" #include "ballistica/shared/foundation/event_loop.h" +#include "ballistica/shared/foundation/inline.h" #include "ballistica/shared/generic/utils.h" namespace ballistica::base { @@ -17,9 +20,10 @@ namespace ballistica::base { static const int kUIOwnerTimeoutSeconds = 30; UI::UI() { - // Figure out our interface type. assert(g_core); + // Figure out our interface scale. + // Allow overriding via an environment variable. auto* ui_override = getenv("BA_UI_SCALE"); if (ui_override) { @@ -37,7 +41,7 @@ UI::UI() { if (!force_scale_) { // Use automatic val. if (g_core->IsVRMode() || g_core->platform->IsRunningOnTV()) { - // VR and tv builds always use medium. + // VR and TV modes always use medium. scale_ = UIScale::kMedium; } else { scale_ = g_core->platform->GetUIScale(); @@ -85,6 +89,8 @@ void UI::DoApplyAppConfig() { if (g_base->HaveUIV1()) { g_base->ui_v1()->DoApplyAppConfig(); } + show_dev_console_button_ = + g_base->app_config->Resolve(AppConfig::BoolID::kShowDevConsoleButton); } auto UI::MainMenuVisible() const -> bool { @@ -114,36 +120,69 @@ auto UI::PartyWindowOpen() -> bool { return false; } -void UI::HandleLegacyRootUIMouseMotion(float x, float y) { - if (g_base->HaveUIV1()) { - g_base->ui_v1()->HandleLegacyRootUIMouseMotion(x, y); +auto UI::HandleMouseDown(int button, float x, float y, bool double_click) + -> bool { + bool handled{}; + + if (show_dev_console_button_ && button == 1) { + float vx = g_base->graphics->screen_virtual_width(); + float vy = g_base->graphics->screen_virtual_height(); + if (InDevConsoleButton_(x, y)) { + dev_console_button_pressed_ = true; + } } + + if (!handled && g_base->HaveUIV1()) { + handled = g_base->ui_v1()->HandleLegacyRootUIMouseDown(x, y); + } + + if (!handled) { + handled = SendWidgetMessage(WidgetMessage( + WidgetMessage::Type::kMouseDown, nullptr, x, y, double_click ? 2 : 1)); + } + + return handled; } -auto UI::HandleLegacyRootUIMouseDown(float x, float y) -> bool { - if (g_base->HaveUIV1()) { - return g_base->ui_v1()->HandleLegacyRootUIMouseDown(x, y); - } - return false; -} +void UI::HandleMouseUp(int button, float x, float y) { + assert(g_base->InLogicThread()); + + SendWidgetMessage( + WidgetMessage(WidgetMessage::Type::kMouseUp, nullptr, x, y)); + + if (dev_console_button_pressed_) { + if (InDevConsoleButton_(x, y)) { + if (auto* console = g_base->console()) { + console->ToggleState(); + } + } + dev_console_button_pressed_ = false; + } -void UI::HandleLegacyRootUIMouseUp(float x, float y) { if (g_base->HaveUIV1()) { g_base->ui_v1()->HandleLegacyRootUIMouseUp(x, y); } } +void UI::HandleMouseMotion(float x, float y) { + SendWidgetMessage( + WidgetMessage(WidgetMessage::Type::kMouseMove, nullptr, x, y)); + + if (g_base->HaveUIV1()) { + g_base->ui_v1()->HandleLegacyRootUIMouseMotion(x, y); + } +} + void UI::PushBackButtonCall(InputDevice* input_device) { g_base->logic->event_loop()->PushCall([this, input_device] { assert(g_base->InLogicThread()); // If there's a UI up, send along a cancel message. - if (g_base->ui->MainMenuVisible()) { - g_base->ui->SendWidgetMessage( - WidgetMessage(WidgetMessage::Type::kCancel)); + if (MainMenuVisible()) { + SendWidgetMessage(WidgetMessage(WidgetMessage::Type::kCancel)); } else { - // If there's no main screen or overlay windows, ask for a menu owned by - // this device. + // If there's no main screen or overlay windows, ask for a menu owned + // by this device. MainMenuPress_(input_device); } }); @@ -173,24 +212,6 @@ void UI::SetUIInputDevice(InputDevice* input_device) { last_input_device_use_time_ = g_core->GetAppTimeMillisecs(); } -UI::UILock::UILock(bool write) { - assert(g_base->ui); - assert(g_base->InLogicThread()); - - if (write && g_base->ui->ui_lock_count_ != 0) { - BA_LOG_ERROR_TRACE_ONCE("Illegal operation: UI is locked"); - } - g_base->ui->ui_lock_count_++; -} - -UI::UILock::~UILock() { - g_base->ui->ui_lock_count_--; - if (g_base->ui->ui_lock_count_ < 0) { - BA_LOG_ERROR_TRACE_ONCE("ui_lock_count_ < 0"); - g_base->ui->ui_lock_count_ = 0; - } -} - void UI::Reset() { if (g_base->HaveUIV1()) { g_base->ui_v1()->Reset(); @@ -198,9 +219,9 @@ void UI::Reset() { } auto UI::ShouldHighlightWidgets() const -> bool { - // Show selection highlights only if we've got controllers connected and only - // when the main UI is visible (dont want a selection highlight for toolbar - // buttons during a game). + // Show selection highlights only if we've got controllers connected and + // only when the main UI is visible (dont want a selection highlight for + // toolbar buttons during a game). return g_base->input->have_non_touch_inputs() && MainMenuVisible(); } @@ -236,22 +257,24 @@ auto UI::GetWidgetForInput(InputDevice* input_device) -> ui_v1::Widget* { assert(input_device); assert(g_base->InLogicThread()); - // We only allow input-devices to control the UI when there's a window/dialog - // on the screen (even though our top/bottom bars still exist). + // We only allow input-devices to control the UI when there's a + // window/dialog on the screen (even though our top/bottom bars still + // exist). if (!MainMenuVisible()) { return nullptr; } millisecs_t time = g_core->GetAppTimeMillisecs(); - bool print_menu_owner = false; + bool print_menu_owner{}; ui_v1::Widget* ret_val; // Ok here's the deal: - // Because having 10 controllers attached to the UI is pure chaos, - // we only allow one input device at a time to control the menu. - // However, if no events are received by that device for a long time, - // it is up for grabs to the next device that requests it. + // + // Because having 10 controllers attached to the UI is pure chaos, we only + // allow one input device at a time to control the menu. However, if no + // events are received by that device for a long time, it is up for grabs + // to the next device that requests it. if (!g_base->HaveUIV1()) { ret_val = nullptr; @@ -265,7 +288,6 @@ auto UI::GetWidgetForInput(InputDevice* input_device) -> ui_v1::Widget* { // seconds ago to automatically own a newly created widget). last_input_device_use_time_ = time; ui_input_device_ = input_device; - // ret_val = screen_root_widget_.Get(); ret_val = g_base->ui_v1()->GetRootWidget(); } else { // For rejected input devices, play error sounds sometimes so they know @@ -301,8 +323,8 @@ auto UI::GetWidgetForInput(InputDevice* input_device) -> ui_v1::Widget* { } else if (input->GetDeviceName() == "TouchScreen") { name = g_base->assets->GetResourceString("touchScreenText"); } else { - // We used to use player names here, but that's kinda sloppy and random; - // lets just go with device names/numbers. + // We used to use player names here, but that's kinda sloppy and + // random; lets just go with device names/numbers. auto devicesWithName = g_base->input->GetInputDevicesWithName(input->GetDeviceName()); if (devicesWithName.size() == 1) { @@ -328,6 +350,82 @@ void UI::Draw(FrameDef* frame_def) { } } +void UI::DrawDev(FrameDef* frame_def) { + // Draw dev console. + if (g_base->console()) { + g_base->console()->Draw(frame_def->overlay_pass()); + } + + // Draw dev console button. + if (show_dev_console_button_) { + DrawDevConsoleButton_(frame_def); + } +} + +auto UI::DevConsoleButtonSize_() const -> float { + if (scale_ == UIScale::kLarge) { + return 25.0f; + } else if (scale_ == UIScale::kMedium) { + return 40.0f; + } + return 60.0f; +} + +auto UI::InDevConsoleButton_(float x, float y) const -> bool { + float vwidth = g_base->graphics->screen_virtual_width(); + float vheight = g_base->graphics->screen_virtual_height(); + float bsz = DevConsoleButtonSize_(); + float bszh = bsz * 0.5f; + float centerx = vwidth - bsz * 0.5f; + float centery = vheight * 0.5f - bsz * 0.5f; + float diffx = ::std::abs(centerx - x); + float diffy = ::std::abs(centery - y); + return diffx <= bszh && diffy <= bszh; +} + +void UI::DrawDevConsoleButton_(FrameDef* frame_def) { + if (!dev_console_button_txt_.Exists()) { + dev_console_button_txt_ = Object::New(); + dev_console_button_txt_->set_text("dev"); + } + auto& grp(*dev_console_button_txt_); + float vwidth = g_base->graphics->screen_virtual_width(); + float vheight = g_base->graphics->screen_virtual_height(); + float bsz = DevConsoleButtonSize_(); + + SimpleComponent c(frame_def->overlay_pass()); + c.SetTransparent(true); + if (dev_console_button_pressed_) { + c.SetColor(1.0f, 1.0f, 1.0f, 0.8f); + } else { + c.SetColor(0.5f, 0.5f, 0.5f, 0.8f); + } + { + auto xf = c.ScopedTransform(); + c.Translate(vwidth - bsz * 0.5f, vheight * 0.5f - bsz * 0.5f, + kCursorZDepth - 0.01f); + c.Scale(bsz, bsz, 1.0f); + c.DrawMeshAsset(g_base->assets->SysMesh(SysMeshID::kImage1x1)); + { + auto xf = c.ScopedTransform(); + c.Scale(0.02f, 0.02f, 1.0f); + c.Translate(-20.0f, -15.0f, 0.0f); + int text_elem_count = grp.GetElementCount(); + if (dev_console_button_pressed_) { + c.SetColor(1.0f, 1.0f, 1.0f, 1.0f); + } else { + c.SetColor(0.0f, 0.0f, 0.0f, 1.0f); + } + for (int e = 0; e < text_elem_count; e++) { + c.SetTexture(grp.GetElementTexture(e)); + c.SetFlatness(1.0f); + c.DrawMesh(grp.GetElementMesh(e)); + } + } + } + c.Submit(); +} + void UI::ShowURL(const std::string& url) { if (g_base->HaveUIV1()) { g_base->ui_v1()->DoShowURL(url); @@ -339,15 +437,17 @@ void UI::ShowURL(const std::string& url) { void UI::ConfirmQuit() { g_base->logic->event_loop()->PushCall([] { + // If the in-app console is active, dismiss it. + if (g_base->console() != nullptr && g_base->console()->IsActive()) { + g_base->console()->Dismiss(); + } + assert(g_base->InLogicThread()); - // If we're headless or input is locked or the in-app-console is up or - // we don't have ui-v1, just quit immediately; a confirm screen - // wouldn't work anyway. + // If we're headless or we don't have ui-v1, just quit immediately; a + // confirm screen wouldn't work anyway. if (g_core->HeadlessMode() || g_base->input->IsInputLocked() - || !g_base->HaveUIV1() - || (g_base->console() != nullptr && g_base->console()->active())) { - g_base->logic->Shutdown(); - // g_base->python->objs().Get(BasePython::ObjID::kQuitCall).Call(); + || !g_base->HaveUIV1()) { + g_base->QuitApp(); return; } else { ScopedSetContext ssc(nullptr); diff --git a/src/ballistica/base/ui/ui.h b/src/ballistica/base/ui/ui.h index 932660e0..92245ad5 100644 --- a/src/ballistica/base/ui/ui.h +++ b/src/ballistica/base/ui/ui.h @@ -10,19 +10,6 @@ #include "ballistica/base/ui/widget_message.h" #include "ballistica/shared/generic/timer_list.h" -// UI-Locks: make sure widget-lists don't change under you. Use a read-lock -// if you just need to ensure lists remain intact but won't be changing -// anything. Use a write-lock whenever modifying a list. -#if BA_DEBUG_BUILD -#define BA_DEBUG_UI_READ_LOCK ::ballistica::base::UI::UILock ui_lock(false) -#define BA_DEBUG_UI_WRITE_LOCK ::ballistica::base::UI::UILock ui_lock(true) -#else -#define BA_DEBUG_UI_READ_LOCK -#define BA_DEBUG_UI_WRITE_LOCK -#endif -#define BA_UI_READ_LOCK UI::UILock ui_lock(false) -#define BA_UI_WRITE_LOCK UI::UILock ui_lock(true) - // Predeclare a few things from ui_v1. namespace ballistica::ui_v1 { class Widget; @@ -46,14 +33,17 @@ class UI { void LanguageChanged(); + /// Reset all UI to a default state. Generally should be called when + /// switching app-modes or when resetting things within an app mode. void Reset(); - /// Pop up an in-app window to show a url (NOT in a browser). Can be - /// called from any thread. + /// Pop up an in-app window to display a URL (NOT to open the URL in a + /// browser). Can be called from any thread. void ShowURL(const std::string& url); - /// High level call to request a quit ui. When a UI can't be shown, - /// triggers an immediate shutdown. This can be called from any thread. + /// High level call to request a quit; ideally with a confirmation ui. + /// When a UI can't be shown, triggers an immediate shutdown. This can be + /// called from any thread. void ConfirmQuit(); /// Return whether there is UI present in either the main or overlay @@ -61,66 +51,65 @@ class UI { auto MainMenuVisible() const -> bool; auto PartyIconVisible() -> bool; - void ActivatePartyIcon(); - void HandleLegacyRootUIMouseMotion(float x, float y); - auto HandleLegacyRootUIMouseDown(float x, float y) -> bool; - void HandleLegacyRootUIMouseUp(float x, float y); auto PartyWindowOpen() -> bool; + void ActivatePartyIcon(); + auto HandleMouseDown(int button, float x, float y, bool double_click) -> bool; + void HandleMouseUp(int button, float x, float y); + void HandleMouseMotion(float x, float y); + + /// Draw regular UI. void Draw(FrameDef* frame_def); - // Returns the widget an input should send commands to, if any. Also - // potentially locks other inputs out of controlling the UI, so only call - // this if you intend on sending a message to that widget. + /// Draw dev UI on top. + void DrawDev(FrameDef* frame_def); + + /// Return the widget an input-device should send commands to, if any. + /// Potentially assigns UI control to the provide device, so only call + /// this if you intend on actually sending a message to that widget. auto GetWidgetForInput(InputDevice* input_device) -> ui_v1::Widget*; - // Send message to the active widget. + /// Send a message to the active widget. auto SendWidgetMessage(const WidgetMessage& msg) -> int; + /// Set the device controlling the UI. void SetUIInputDevice(InputDevice* input_device); - // Returns the input-device that currently owns the menu; otherwise - // nullptr. + /// Return the input-device that currently owns the UI; otherwise nullptr. auto GetUIInputDevice() const -> InputDevice*; + /// Schedule a back button press. Can be called from any thread. void PushBackButtonCall(InputDevice* input_device); - // Returns whether currently selected widgets should flash. This will be - // false in some situations such as when only touch screen control is - // active. + /// Return whether currently selected widgets should flash. This will be + /// false in some situations such as when only touch screen control is + /// present. auto ShouldHighlightWidgets() const -> bool; - // Same except for button shortcuts; these generally only get shown if a - // joystick of some form is present. + /// Return whether currently selected widget should show button shortcuts. + /// These generally only get shown if a joystick of some form is present. auto ShouldShowButtonShortcuts() const -> bool; - // Used to ensure widgets are not created or destroyed at certain times - // (while traversing widget hierarchy, etc). - class UILock { - public: - explicit UILock(bool write); - ~UILock(); - - private: - BA_DISALLOW_CLASS_COPIES(UILock); - }; - + /// Overall ui scale for the app. auto scale() const { return scale_; } - /// Push a generic 'menu press' event, optionally associated with an - /// input device (nullptr to specify none). Note: caller must ensure - /// a RemoveInputDevice() call does not arrive at the logic thread - /// before this one. + /// Push a generic 'menu press' event, optionally associated with an input + /// device (nullptr to specify none). Can be called from any thread. void PushMainMenuPressCall(InputDevice* device); private: void MainMenuPress_(InputDevice* device); + auto DevConsoleButtonSize_() const -> float; + auto InDevConsoleButton_(float x, float y) const -> bool; + void DrawDevConsoleButton_(FrameDef* frame_def); Object::WeakRef ui_input_device_; millisecs_t last_input_device_use_time_{}; millisecs_t last_widget_input_reject_err_sound_time_{}; - int ui_lock_count_{}; UIScale scale_{UIScale::kLarge}; bool force_scale_{}; + bool show_dev_console_button_{}; + bool dev_console_button_pressed_{}; + Object::Ref dev_console_button_txt_; }; } // namespace ballistica::base diff --git a/src/ballistica/core/platform/core_platform.cc b/src/ballistica/core/platform/core_platform.cc index 899aaff5..8bdcf9d9 100644 --- a/src/ballistica/core/platform/core_platform.cc +++ b/src/ballistica/core/platform/core_platform.cc @@ -682,14 +682,6 @@ void CorePlatform::AndroidSetResString(const std::string& res) { throw Exception(); } -void CorePlatform::AndroidSynthesizeBackPress() { - Log(LogLevel::kError, "AndroidSynthesizeBackPress() unimplemented"); -} - -void CorePlatform::AndroidQuitActivity() { - Log(LogLevel::kError, "AndroidQuitActivity() unimplemented"); -} - auto CorePlatform::GetDeviceV1AccountID() -> std::string { if (g_core->HeadlessMode()) { return "S-" + GetLegacyDeviceUUID(); @@ -758,12 +750,6 @@ void CorePlatform::MusicPlayerSetVolume(float volume) { auto CorePlatform::IsOSPlayingMusic() -> bool { return false; } -void CorePlatform::AndroidShowAppInvite(const std::string& title, - const std::string& message, - const std::string& code) { - Log(LogLevel::kError, "AndroidShowAppInvite() unimplemented"); -} - void CorePlatform::IncrementAnalyticsCount(const std::string& name, int increment) {} diff --git a/src/ballistica/core/platform/core_platform.h b/src/ballistica/core/platform/core_platform.h index 83d9e269..e476ca28 100644 --- a/src/ballistica/core/platform/core_platform.h +++ b/src/ballistica/core/platform/core_platform.h @@ -217,11 +217,6 @@ class CorePlatform { virtual auto GetAndroidExecArg() -> std::string; virtual void AndroidSetResString(const std::string& res); - virtual void AndroidSynthesizeBackPress(); - virtual void AndroidQuitActivity(); - virtual void AndroidShowAppInvite(const std::string& title, - const std::string& message, - const std::string& code); virtual auto AndroidGetExternalFilesDir() -> std::string; #pragma mark PERMISSIONS ------------------------------------------------------- diff --git a/src/ballistica/scene_v1/README.md b/src/ballistica/scene_v1/README.md index 75c3cdb0..8e1ff316 100644 --- a/src/ballistica/scene_v1/README.md +++ b/src/ballistica/scene_v1/README.md @@ -2,3 +2,8 @@ Gameplay code for classic BombSquad, as well as app-modes and other support classes required to use it. + +Note that, ideally, gameplay code (scene version) should live in its own feature +set and app-mode code should live in another. That way a single app-mode can +wrangle multiple scene versions. But for historical reasons they're all mixed up +in this case. diff --git a/src/ballistica/scene_v1/node/image_node.cc b/src/ballistica/scene_v1/node/image_node.cc index fd7734fa..5162f5a8 100644 --- a/src/ballistica/scene_v1/node/image_node.cc +++ b/src/ballistica/scene_v1/node/image_node.cc @@ -71,7 +71,9 @@ auto ImageNode::InitType() -> NodeType* { ImageNode::ImageNode(Scene* scene) : Node(scene, node_type) {} ImageNode::~ImageNode() { - if (fill_screen_) scene()->decrement_bg_cover_count(); + if (fill_screen_) { + scene()->decrement_bg_cover_count(); + } } auto ImageNode::GetAttach() const -> std::string { @@ -209,11 +211,11 @@ void ImageNode::Draw(base::FrameDef* frame_def) { if (host_only_ && !context_ref().GetHostSession()) { return; } - bool vr = (g_core->IsVRMode()); + bool vr = g_core->IsVRMode(); // In vr mode we use the fixed overlay position if our scene // is set for that. - bool vr_use_fixed = (scene()->use_fixed_vr_overlay()); + bool vr_use_fixed = scene()->use_fixed_vr_overlay(); // Currently front and vr-fixed are mutually-exclusive.. need to fix. if (front_) { diff --git a/src/ballistica/scene_v1/support/scene_v1_app_mode.cc b/src/ballistica/scene_v1/support/scene_v1_app_mode.cc index 306df4da..cb6dfbf9 100644 --- a/src/ballistica/scene_v1/support/scene_v1_app_mode.cc +++ b/src/ballistica/scene_v1/support/scene_v1_app_mode.cc @@ -39,11 +39,10 @@ const int kKickVoteFailRetryDelay = 60000; /// Extra delay for the initiator of a failed vote. const int kKickVoteFailRetryDelayInitiatorExtra = 120000; -// Minimum clients that must be present for a kick vote to count. -// (for non-headless builds we require more votes since the host doesn't count -// but may be playing (in a 2on2 with 3 clients, don't want 2 clients able to -// kick). -// NOLINTNEXTLINE(cert-err58-cpp) +// Minimum clients that must be present for a kick vote to count. (for +// non-headless builds we require more votes since the host doesn't count +// but may be playing (in a 2on2 with 3 clients, don't want 2 clients able +// to kick). const int kKickVoteMinimumClients = (g_buildconfig.headless_build() ? 3 : 4); struct SceneV1AppMode::ScanResultsEntryPriv { @@ -68,7 +67,7 @@ base::InputDeviceDelegate* SceneV1AppMode::CreateInputDeviceDelegate( // Go with 5 minute ban. const int kKickBanSeconds = 5 * 60; -bool SceneV1AppMode::InMainMenu() const { +bool SceneV1AppMode::InClassicMainMenuSession() const { HostSession* hostsession = ContextRefSceneV1::FromAppForegroundContext().GetHostSession(); return (hostsession && hostsession->is_main_menu()); diff --git a/src/ballistica/scene_v1/support/scene_v1_app_mode.h b/src/ballistica/scene_v1/support/scene_v1_app_mode.h index 9d0b2b54..34952109 100644 --- a/src/ballistica/scene_v1/support/scene_v1_app_mode.h +++ b/src/ballistica/scene_v1/support/scene_v1_app_mode.h @@ -150,15 +150,15 @@ class SceneV1AppMode : public base::AppMode { void OnAppStart() override; void OnAppPause() override; void OnAppResume() override; - auto InMainMenu() const -> bool override; + auto InClassicMainMenuSession() const -> bool override; auto CreateInputDeviceDelegate(base::InputDevice* device) -> base::InputDeviceDelegate* override; void SetInternalMusic(base::SoundAsset* music, float volume = 1.0, bool loop = true); - // Run a cycle of host scanning (basically sending out a broadcast packet to - // see who's out there). + // Run a cycle of host scanning (basically sending out a broadcast packet + // to see who's out there). void HostScanCycle(); void EndHostScanning(); diff --git a/src/ballistica/scene_v1/support/scene_v1_context.h b/src/ballistica/scene_v1/support/scene_v1_context.h index e792a1e7..16414ecd 100644 --- a/src/ballistica/scene_v1/support/scene_v1_context.h +++ b/src/ballistica/scene_v1/support/scene_v1_context.h @@ -8,7 +8,7 @@ namespace ballistica::scene_v1 { -/// Wraps a weak-ref to a context_ref with functionality specific to scene_v1. +/// A context-ref specific to SceneV1. class ContextRefSceneV1 : public base::ContextRef { public: ContextRefSceneV1() : ContextRef() {} @@ -23,8 +23,8 @@ class ContextRefSceneV1 : public base::ContextRef { static auto FromAppForegroundContext() -> ContextRefSceneV1; // If the current Context is (or is part of) a HostSession, return it; - // otherwise return nullptr. be aware that this will return a session if the - // context is *either* a host-activity or a host-session + // otherwise return nullptr. be aware that this will return a session if + // the context is *either* a host-activity or a host-session auto GetHostSession() const -> HostSession*; // Return the current context as an HostActivity if it is one; otherwise @@ -32,15 +32,14 @@ class ContextRefSceneV1 : public base::ContextRef { auto GetHostActivity() const -> HostActivity*; // If the current context contains a scene that can be manipulated by - // standard commands, this returns it. This includes host-sessions, + // standard commands, this returns it. This includes host-sessions, // host-activities, and the UI context. auto GetMutableScene() const -> Scene*; }; -/// Object containing some sort of context_ref. -/// App-modes can subclass this to provide the actual context_ref they desire, -/// and then code can use GetTyped() to safely retrieve context_ref as that -/// type. +/// Object containing some sort of context_ref. App-modes can subclass this +/// to provide the actual context_ref they desire, and then code can use +/// GetTyped() to safely retrieve context_ref as that type. class SceneV1Context : public base::Context { public: static auto Current() -> SceneV1Context& { diff --git a/src/ballistica/shared/ballistica.cc b/src/ballistica/shared/ballistica.cc index f2c2308c..f353c40a 100644 --- a/src/ballistica/shared/ballistica.cc +++ b/src/ballistica/shared/ballistica.cc @@ -39,7 +39,7 @@ auto main(int argc, char** argv) -> int { namespace ballistica { // These are set automatically via script; don't modify them here. -const int kEngineBuildNumber = 21306; +const int kEngineBuildNumber = 21322; const char* kEngineVersion = "1.7.28"; const int kEngineApiVersion = 8; diff --git a/src/ballistica/ui_v1/python/methods/python_methods_ui_v1.cc b/src/ballistica/ui_v1/python/methods/python_methods_ui_v1.cc index f0147a40..50542ee6 100644 --- a/src/ballistica/ui_v1/python/methods/python_methods_ui_v1.cc +++ b/src/ballistica/ui_v1/python/methods/python_methods_ui_v1.cc @@ -2472,45 +2472,6 @@ static PyMethodDef PyShowAd2Def = { "(internal)", }; -// --------------------------- show_app_invite --------------------------------- - -static auto PyShowAppInvite(PyObject* self, PyObject* args, PyObject* keywds) - -> PyObject* { - BA_PYTHON_TRY; - std::string title; - std::string message; - std::string code; - PyObject* title_obj; - PyObject* message_obj; - PyObject* code_obj; - static const char* kwlist[] = {"title", "message", "code", nullptr}; - if (!PyArg_ParseTupleAndKeywords(args, keywds, "OOO", - const_cast(kwlist), &title_obj, - &message_obj, &code_obj)) { - return nullptr; - } - title = g_base->python->GetPyLString(title_obj); - message = g_base->python->GetPyLString(message_obj); - code = g_base->python->GetPyLString(code_obj); - g_core->platform->AndroidShowAppInvite(title, message, code); - Py_RETURN_NONE; - BA_PYTHON_CATCH; -} - -static PyMethodDef PyShowAppInviteDef = { - "show_app_invite", // name - (PyCFunction)PyShowAppInvite, // method - METH_VARARGS | METH_KEYWORDS, // flags - - "show_app_invite(title: str | bauiv1.Lstr,\n" - " message: str | bauiv1.Lstr,\n" - " code: str) -> None\n" - "\n" - "(internal)\n" - "\n" - "Category: **General Utility Functions**", -}; - // --------------------- set_party_icon_always_visible ------------------------- static auto PySetPartyIconAlwaysVisible(PyObject* self, PyObject* args, @@ -2882,7 +2843,6 @@ auto PythonMethodsUIV1::GetMethods() -> std::vector { PyGetSpecialWidgetDef, PySetPartyWindowOpenDef, PySetPartyIconAlwaysVisibleDef, - PyShowAppInviteDef, PyShowAdDef, PyShowAd2Def, PyShowOnlineScoreUIDef, diff --git a/src/ballistica/ui_v1/support/root_ui.cc b/src/ballistica/ui_v1/support/root_ui.cc index cd82b389..5e58f9db 100644 --- a/src/ballistica/ui_v1/support/root_ui.cc +++ b/src/ballistica/ui_v1/support/root_ui.cc @@ -223,7 +223,7 @@ void RootUI::Draw(base::FrameDef* frame_def) { // Flash and show a message if we're in the main menu instructing the // player to start a game. bool flash = false; - bool in_main_menu = g_base->app_mode()->InMainMenu(); + bool in_main_menu = g_base->app_mode()->InClassicMainMenuSession(); if (in_main_menu && party_size > 0 && show_client_joined) flash = true; @@ -255,8 +255,8 @@ void RootUI::Draw(base::FrameDef* frame_def) { c.PopTransform(); c.Submit(); - // Based on who has menu control, we may show a key/button below the party - // icon. + // Based on who has menu control, we may show a key/button below the + // party icon. if (!active) { if (base::InputDevice* uiid = g_base->ui->GetUIInputDevice()) { std::string party_button_name = uiid->GetPartyButtonName(); @@ -304,8 +304,8 @@ void RootUI::Draw(base::FrameDef* frame_def) { party_size_text_group_->set_text( std::to_string(party_size_text_group_num_)); - // ..we also may want to update our 'someone joined' message if we're - // host + // ..we also may want to update our 'someone joined' message if + // we're host if (is_host) { if (!start_a_game_text_group_.Exists()) { start_a_game_text_group_ = Object::New(); diff --git a/src/ballistica/ui_v1/ui_v1.cc b/src/ballistica/ui_v1/ui_v1.cc index f38e0aae..0d5ff65d 100644 --- a/src/ballistica/ui_v1/ui_v1.cc +++ b/src/ballistica/ui_v1/ui_v1.cc @@ -134,9 +134,8 @@ void UIV1FeatureSet::Draw(base::FrameDef* frame_def) { auto* root_widget = root_widget_.Get(); if (root_widget && root_widget->HasChildren()) { - // Draw our opaque and transparent parts separately. - // This way we can draw front-to-back for opaque and back-to-front for - // transparent. + // Draw our opaque and transparent parts separately. This way we can + // draw front-to-back for opaque and back-to-front for transparent. g_base->graphics->set_drawing_opaque_only(true); @@ -281,4 +280,22 @@ void UIV1FeatureSet::DoApplyAppConfig() { base::AppConfig::BoolID::kAlwaysUseInternalKeyboard)); } +UIV1FeatureSet::UILock::UILock(bool write) { + assert(g_base->ui); + assert(g_base->InLogicThread()); + + if (write && g_ui_v1->ui_lock_count_ != 0) { + BA_LOG_ERROR_TRACE_ONCE("Illegal operation: UI is locked"); + } + g_ui_v1->ui_lock_count_++; +} + +UIV1FeatureSet::UILock::~UILock() { + g_ui_v1->ui_lock_count_--; + if (g_ui_v1->ui_lock_count_ < 0) { + BA_LOG_ERROR_TRACE_ONCE("ui_lock_count_ < 0"); + g_ui_v1->ui_lock_count_ = 0; + } +} + } // namespace ballistica::ui_v1 diff --git a/src/ballistica/ui_v1/ui_v1.h b/src/ballistica/ui_v1/ui_v1.h index f2607e4a..7a6410c8 100644 --- a/src/ballistica/ui_v1/ui_v1.h +++ b/src/ballistica/ui_v1/ui_v1.h @@ -15,6 +15,19 @@ // BA 2.0 UI testing. #define BA_UI_V1_TOOLBAR_TEST 0 +// UI-Locks: make sure widget-lists don't change under you. Use a read-lock +// if you just need to ensure lists remain intact but won't be changing +// anything. Use a write-lock whenever modifying a list. +#if BA_DEBUG_BUILD +#define BA_DEBUG_UI_READ_LOCK \ + ::ballistica::ui_v1::UIV1FeatureSet::UILock ui_lock(false) +#define BA_DEBUG_UI_WRITE_LOCK \ + ::ballistica::ui_v1::UIV1FeatureSet::UILock ui_lock(true) +#else +#define BA_DEBUG_UI_READ_LOCK +#define BA_DEBUG_UI_WRITE_LOCK +#endif + // Predeclared types from other feature sets that we use. namespace ballistica::core { class CoreFeatureSet; @@ -57,6 +70,17 @@ class UIV1FeatureSet : public FeatureSetNativeComponent, /// it. Basically a Python import statement. static auto Import() -> UIV1FeatureSet*; + /// Used to ensure widgets are not created or destroyed at certain times + /// (while traversing widget hierarchy, etc). + class UILock { + public: + explicit UILock(bool write); + ~UILock(); + + private: + BA_DISALLOW_CLASS_COPIES(UILock); + }; + /// Called when our associated Python module is instantiated. static void OnModuleExec(PyObject* module); void DoHandleDeviceMenuPress(base::InputDevice* device) override; @@ -113,6 +137,7 @@ class UIV1FeatureSet : public FeatureSetNativeComponent, Object::Ref screen_root_widget_; Object::Ref overlay_root_widget_; Object::Ref root_widget_; + int ui_lock_count_{}; }; } // namespace ballistica::ui_v1 diff --git a/src/ballistica/ui_v1/widget/root_widget.cc b/src/ballistica/ui_v1/widget/root_widget.cc index 12233ad3..99e47e61 100644 --- a/src/ballistica/ui_v1/widget/root_widget.cc +++ b/src/ballistica/ui_v1/widget/root_widget.cc @@ -98,9 +98,9 @@ struct RootWidget::Text { }; RootWidget::RootWidget() { - // we enable a special 'single-depth-root' mode - // in which we use most of our depth range for our first child - // (our screen stack) and the small remaining bit for the rest + // We enable a special 'single-depth-root' mode in which we use most of + // our depth range for our first child (our screen stack) and the small + // remaining bit for the rest. set_single_depth(true); set_single_depth_root(true); set_background(false); @@ -110,7 +110,7 @@ RootWidget::~RootWidget() = default; auto RootWidget::AddCover(float h_align, VAlign v_align, float x, float y, float w, float h, float o) -> RootWidget::Button* { - // currently just not doing these in vr mode + // Currently just not doing these in vr mode. if (g_core->IsVRMode()) { return nullptr; } @@ -132,9 +132,9 @@ auto RootWidget::AddCover(float h_align, VAlign v_align, float x, float y, bd.visibility_mask = static_cast(Widget::ToolbarVisibility::kMenuFullRoot); - // when the user specifies no backing it means they intend to cover the screen - // with a flat-ish window texture.. however this only applies to phone-size; - // for other sizes we always draw a backing. + // When the user specifies no backing it means they intend to cover the + // screen with a flat-ish window texture.. however this only applies to + // phone-size; for other sizes we always draw a backing. if (g_base->ui->scale() != UIScale::kSmall) { bd.visibility_mask |= static_cast(Widget::ToolbarVisibility::kMenuFull); @@ -352,7 +352,7 @@ void RootWidget::Setup() { } } - // widen this a bit in small mode so it just covers most of the top + // Widen this a bit in small mode so it just covers most of the top // - that looks funny in medium/large mode though // if (g_ui->scale() == UIScale::kSmall) { // AddCover(0.5f, VAlign::kTop, 0.0f, 320.0f, @@ -838,8 +838,8 @@ auto RootWidget::AddButton(const ButtonDef& def) -> RootWidget::Button* { } else { b.widget->set_up_widget(screen_stack_widget_); } - // We wanna prevent anyone from redirecting these to point to outside widgets - // since we'll probably outlive those outside widgets. + // We wanna prevent anyone from redirecting these to point to outside + // widgets since we'll probably outlive those outside widgets. b.widget->set_neighbors_locked(true); if (!def.img.empty()) { @@ -894,7 +894,7 @@ void RootWidget::UpdateForFocusedWindow() { void RootWidget::UpdateForFocusedWindow(Widget* widget) { // Take note if the current session is the main menu; we do a few things // differently there. - in_main_menu_ = g_base->app_mode()->InMainMenu(); + in_main_menu_ = g_base->app_mode()->InClassicMainMenuSession(); if (widget == nullptr) { toolbar_visibility_ = ToolbarVisibility::kInGame; @@ -929,8 +929,8 @@ void RootWidget::StepPositions(float dt) { static_cast(static_cast(toolbar_visibility_) & static_cast(b.visibility_mask)); - // When we're in the main menu, always disable the menu button - // and shift the party button a bit to the right + // When we're in the main menu, always disable the menu button and shift + // the party button a bit to the right if (in_main_menu_) { if (&b == menu_button_) { enable_button = false; @@ -940,13 +940,13 @@ void RootWidget::StepPositions(float dt) { } } if (&b == back_button_) { - // back button is always disabled in medium/large UI + // Back button is always disabled in medium/large UI. if (g_base->ui->scale() != UIScale::kSmall) { enable_button = false; } - // whenever back button is enabled, left on account button should go to - // it; otherwise it goes nowhere. + // Whenever back button is enabled, left on account button should go + // to it; otherwise it goes nowhere. Widget* ab = account_button_->widget.Get(); ab->set_neighbors_locked(false); ab->set_left_widget(enable_button ? back_button_->widget.Get() : ab); @@ -957,9 +957,10 @@ void RootWidget::StepPositions(float dt) { b.y_target += disable_offset; } - // special case: we shift buttons on the top right to the right if the menu - // button is hidden (and also if the button is hidden; otherwise things come - // in diagonally) + // special case: we shift buttons on the top right to the right if the + // menu button is hidden (and also if the button is hidden; otherwise + // things come in diagonally) + // // if (b.h_align == HAlign::kRight and b.v_align == VAlign::kTop // if (b.h_align >= 1.0f and b.v_align == VAlign::kTop // and (toolbar_visibility_ != ToolbarVisibility::kInGame or not @@ -971,15 +972,15 @@ void RootWidget::StepPositions(float dt) { b.x_smoothed += (b.x_target - b.x_smoothed) * 0.015f * dt; b.y_smoothed += (b.y_target - b.y_smoothed) * 0.015f * dt; - // Snap in place once we reach the target; otherwise note - // that we need to keep going. + // Snap in place once we reach the target; otherwise note that we need + // to keep going. if (std::abs(b.x_target - b.x_smoothed) < 0.1f && std::abs(b.y_target - b.y_smoothed) < 0.1f) { b.x_smoothed = b.x_target; b.y_smoothed = b.y_target; - // Also flip off visibility if we're moving offscreen and have reached our - // target. + // Also flip off visibility if we're moving offscreen and have reached + // our target. if (!enable_button) { b.widget->set_visible_in_container(false); } @@ -989,7 +990,8 @@ void RootWidget::StepPositions(float dt) { b.widget->set_visible_in_container(true); } - // Now calc final abs x and y based on screen size, smoothed positions, etc. + // Now calc final abs x and y based on screen size, smoothed positions, + // etc. float x, y; x = width() * b.h_align + base_scale_ * (b.x_smoothed - b.width * b.scale * 0.5f); diff --git a/src/ballistica/ui_v1/widget/text_widget.cc b/src/ballistica/ui_v1/widget/text_widget.cc index 0f7ab87e..89cc059b 100644 --- a/src/ballistica/ui_v1/widget/text_widget.cc +++ b/src/ballistica/ui_v1/widget/text_widget.cc @@ -531,9 +531,9 @@ auto TextWidget::ShouldUseStringEditDialog() const -> bool { return true; } - // On most platforms we always want to do this. - // on mac/pc, however, we use inline editing if the current UI input-device - // is the mouse or keyboard + // On most platforms we always want to do this. On desktop, however, we + // use inline editing if the current UI input-device is the mouse or + // keyboard. if (g_buildconfig.ostype_macos() || g_buildconfig.ostype_windows() || g_buildconfig.ostype_linux()) { base::InputDevice* ui_input_device = g_base->ui->GetUIInputDevice(); diff --git a/tools/batools/appmodule.py b/tools/batools/appmodule.py index c0cebc3f..5e6dc721 100755 --- a/tools/batools/appmodule.py +++ b/tools/batools/appmodule.py @@ -15,7 +15,7 @@ if TYPE_CHECKING: def generate_app_module( - feature_sets: dict[str, FeatureSet], existing_data: str + projroot: str, feature_sets: dict[str, FeatureSet], existing_data: str ) -> str: """Generate babase._app.py based on its existing version.""" @@ -24,7 +24,7 @@ def generate_app_module( # pylint: disable=too-many-statements import textwrap - from efrotools import replace_section + from efrotools import replace_section, getprojectconfig out = '' @@ -156,31 +156,50 @@ def generate_app_module( # Generate default app-mode-selection logic. # TODO - make this customizable via project settings or whatnot. + default_app_modes: list[str] | None = getprojectconfig(projroot).get( + 'default_app_modes' + ) + if not isinstance(default_app_modes, list) or not all( + isinstance(x, str) for x in default_app_modes + ): + raise RuntimeError( + 'Could not load default_app_modes from projectconfig' + ) + + def _module_for_app_mode(amode: str) -> str: + return '.'.join(amode.split('.')[:-1]) + + def _is_valid_app_mode(amode: str) -> bool: + # Consider the app mode valid if it comes from a Python + # package provided by one of our feature-sets. + module = _module_for_app_mode(amode) + for featureset in feature_sets.values(): + if featureset.name_python_package == module: + return True + return False + + default_app_modes = [m for m in default_app_modes if _is_valid_app_mode(m)] contents = ( '# Ask our default app modes to handle it.\n' - "# (based on 'default_app_modes' in projectconfig).\n" + "# (generated from 'default_app_modes' in projectconfig).\n" ) - imports: list[str] = [] - if 'scene_v1' in fsets: - imports.append('bascenev1') - if 'base' in fsets: - imports.append('babase') - - for imp in imports: - contents += f'import {imp}\n' + if not default_app_modes: + raise RuntimeError('No valid default_app_modes specified.') + for mode in default_app_modes: + contents += f'import {_module_for_app_mode(mode)}\n' contents += '\n' - if 'scene_v1' in fsets: - contents += ( - 'if bascenev1.SceneV1AppMode.can_handle_intent(intent):\n' - ' return bascenev1.SceneV1AppMode\n\n' - ) - if 'base' in fsets: - contents += ( - 'if babase.EmptyAppMode.can_handle_intent(intent):\n' - ' return babase.EmptyAppMode\n\n' - ) + contents += 'for appmode in [\n' + for mode in default_app_modes: + contents += f' {mode},\n' + contents += ( + ']:\n' + ' if appmode.can_handle_intent(intent):\n' + ' return appmode\n' + '\n' + ) + contents += 'return None\n' indent = ' ' diff --git a/tools/batools/project/_updater.py b/tools/batools/project/_updater.py index bfa193c8..a3192397 100755 --- a/tools/batools/project/_updater.py +++ b/tools/batools/project/_updater.py @@ -699,7 +699,7 @@ class ProjectUpdater: from batools.appmodule import generate_app_module self._generated_files[path] = generate_app_module( - self.feature_sets, existing_data + self.projroot, self.feature_sets, existing_data ) def _update_meta_makefile(self) -> None: diff --git a/tools/efrotools/__init__.py b/tools/efrotools/__init__.py index 93ae8e54..1c3bbd45 100644 --- a/tools/efrotools/__init__.py +++ b/tools/efrotools/__init__.py @@ -28,6 +28,10 @@ PYVER = '3.11' # Update; just using the same executable used to launch us. PYTHON_BIN = sys.executable +# Cache these since we may repeatedly fetch these in batch mode. +_g_project_configs: dict[str, dict[str, Any]] = {} +_g_local_configs: dict[str, dict[str, Any]] = {} + def explicit_bool(value: bool) -> bool: """Simply return input value; can avoid unreachable-code type warnings.""" @@ -36,36 +40,45 @@ def explicit_bool(value: bool) -> bool: def getlocalconfig(projroot: Path | str) -> dict[str, Any]: """Return a project's localconfig contents (or default if missing).""" - localconfig: dict[str, Any] + projrootstr = str(projroot) + if projrootstr not in _g_local_configs: + localconfig: dict[str, Any] - # Allow overriding path via env var. - path = os.environ.get('EFRO_LOCALCONFIG_PATH') - if path is None: - path = 'config/localconfig.json' + # Allow overriding path via env var. + path = os.environ.get('EFRO_LOCALCONFIG_PATH') + if path is None: + path = 'config/localconfig.json' - try: - with open(Path(projroot, path), encoding='utf-8') as infile: - localconfig = json.loads(infile.read()) - except FileNotFoundError: - localconfig = {} - return localconfig + try: + with open(Path(projroot, path), encoding='utf-8') as infile: + localconfig = json.loads(infile.read()) + except FileNotFoundError: + localconfig = {} + _g_local_configs[projrootstr] = localconfig + + return _g_local_configs[projrootstr] def getprojectconfig(projroot: Path | str) -> dict[str, Any]: """Return a project's projectconfig contents (or default if missing).""" - config: dict[str, Any] - try: - with open( - Path(projroot, 'config/projectconfig.json'), encoding='utf-8' - ) as infile: - config = json.loads(infile.read()) - except FileNotFoundError: - config = {} - return config + projrootstr = str(projroot) + if projrootstr not in _g_project_configs: + config: dict[str, Any] + try: + with open( + Path(projroot, 'config/projectconfig.json'), encoding='utf-8' + ) as infile: + config = json.loads(infile.read()) + except FileNotFoundError: + config = {} + _g_project_configs[projrootstr] = config + return _g_project_configs[projrootstr] def setprojectconfig(projroot: Path | str, config: dict[str, Any]) -> None: """Set the project config contents.""" + projrootstr = str(projroot) + _g_project_configs[projrootstr] = config os.makedirs(Path(projroot, 'config'), exist_ok=True) with Path(projroot, 'config/projectconfig.json').open( 'w', encoding='utf-8'