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'