added on-screen dev-console button option under advanced settings

This commit is contained in:
Eric 2023-09-07 18:07:52 -07:00
parent cd098866ea
commit 58cec911a6
No known key found for this signature in database
GPG Key ID: 89C93F0F8D6D5A98
69 changed files with 889 additions and 698 deletions

100
.efrocachemap generated
View File

@ -421,7 +421,7 @@
"build/assets/ba_data/audio/zoeOw.ogg": "74befe45a8417e95b6a2233c51992a26", "build/assets/ba_data/audio/zoeOw.ogg": "74befe45a8417e95b6a2233c51992a26",
"build/assets/ba_data/audio/zoePickup01.ogg": "48ab8cddfcde36a750856f3f81dd20c8", "build/assets/ba_data/audio/zoePickup01.ogg": "48ab8cddfcde36a750856f3f81dd20c8",
"build/assets/ba_data/audio/zoeScream01.ogg": "2b468aedfa8741090247f04eb9e6df55", "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/arabic.json": "db961f7fe0541a31880929e1c17ea957",
"build/assets/ba_data/data/languages/belarussian.json": "5e373ddcfa6e1f771b74c02298a6599a", "build/assets/ba_data/data/languages/belarussian.json": "5e373ddcfa6e1f771b74c02298a6599a",
"build/assets/ba_data/data/languages/chinese.json": "6520f793066c95773002b4e9a920fd1d", "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/czech.json": "f3ce219840946cb8f9aa6d3e25927ab3",
"build/assets/ba_data/data/languages/danish.json": "3fd69080783d5c9dcc0af737f02b6f1e", "build/assets/ba_data/data/languages/danish.json": "3fd69080783d5c9dcc0af737f02b6f1e",
"build/assets/ba_data/data/languages/dutch.json": "22b44a33bf81142ba2befad14eb5746e", "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/esperanto.json": "0e397cfa5f3fb8cef5f4a64f21cda880",
"build/assets/ba_data/data/languages/filipino.json": "58f363cfd8a3ccf0c904ab753d95789b", "build/assets/ba_data/data/languages/filipino.json": "58f363cfd8a3ccf0c904ab753d95789b",
"build/assets/ba_data/data/languages/french.json": "6057b18878ad8379e51b507fa94958d8", "build/assets/ba_data/data/languages/french.json": "6057b18878ad8379e51b507fa94958d8",
"build/assets/ba_data/data/languages/german.json": "549754d2a530d825200c6126be56df5c", "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/greek.json": "a65d78f912e9a89f98de004405167a6a",
"build/assets/ba_data/data/languages/hindi.json": "88ee0cda537bab9ac827def5e236fe1a", "build/assets/ba_data/data/languages/hindi.json": "88ee0cda537bab9ac827def5e236fe1a",
"build/assets/ba_data/data/languages/hungarian.json": "796a290a8c44a1e7635208c2ff5fdc6e", "build/assets/ba_data/data/languages/hungarian.json": "796a290a8c44a1e7635208c2ff5fdc6e",
"build/assets/ba_data/data/languages/indonesian.json": "583bae1ecc04375cee089a82359110b7", "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/korean.json": "ca1122a9ee551da3f75ae632012bd0e2",
"build/assets/ba_data/data/languages/malay.json": "832562ce997fc70704b9234c95fb2e38", "build/assets/ba_data/data/languages/malay.json": "832562ce997fc70704b9234c95fb2e38",
"build/assets/ba_data/data/languages/persian.json": "5119aec9cbb2f8d00f2afaccf5fd5410", "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/portuguese.json": "99b27c598c90fd522132af3536aef0ee",
"build/assets/ba_data/data/languages/romanian.json": "aeebdd54f65939c2facc6ac50c117826", "build/assets/ba_data/data/languages/romanian.json": "aeebdd54f65939c2facc6ac50c117826",
"build/assets/ba_data/data/languages/russian.json": "aa99f9f597787fe4e09c8ab53fe2e081", "build/assets/ba_data/data/languages/russian.json": "aa99f9f597787fe4e09c8ab53fe2e081",
"build/assets/ba_data/data/languages/serbian.json": "d7452dd72ac0e51680cb39b5ebaa1c69", "build/assets/ba_data/data/languages/serbian.json": "d7452dd72ac0e51680cb39b5ebaa1c69",
"build/assets/ba_data/data/languages/slovak.json": "27962d53dc3f7dd4e877cd40faafeeef", "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/swedish.json": "77d671f10613291ebf9c71da66f18a18",
"build/assets/ba_data/data/languages/tamil.json": "b9d4b4e107456ea6420ee0f9d9d7a03e", "build/assets/ba_data/data/languages/tamil.json": "b9d4b4e107456ea6420ee0f9d9d7a03e",
"build/assets/ba_data/data/languages/thai.json": "33f63753c9af9a5b238d229a0bf23fbc", "build/assets/ba_data/data/languages/thai.json": "33f63753c9af9a5b238d229a0bf23fbc",
@ -4064,50 +4064,50 @@
"build/assets/windows/Win32/ucrtbased.dll": "2def5335207d41b21b9823f6805997f1", "build/assets/windows/Win32/ucrtbased.dll": "2def5335207d41b21b9823f6805997f1",
"build/assets/windows/Win32/vc_redist.x86.exe": "b08a55e2e77623fe657bea24f223a3ae", "build/assets/windows/Win32/vc_redist.x86.exe": "b08a55e2e77623fe657bea24f223a3ae",
"build/assets/windows/Win32/vcruntime140d.dll": "865b2af4d1e26a1a8073c89acb06e599", "build/assets/windows/Win32/vcruntime140d.dll": "865b2af4d1e26a1a8073c89acb06e599",
"build/prefab/full/linux_arm64_gui/debug/ballisticakit": "2ceb16e09034aa4e3213e8fb69dd209b", "build/prefab/full/linux_arm64_gui/debug/ballisticakit": "c0e542e25f839b32d91a07046bd56333",
"build/prefab/full/linux_arm64_gui/release/ballisticakit": "7ecdc512653325abef8e66e9c3e0e479", "build/prefab/full/linux_arm64_gui/release/ballisticakit": "f159a31dcd46f144cc796a6f2cbb0a2a",
"build/prefab/full/linux_arm64_server/debug/dist/ballisticakit_headless": "70a1c3300cb0610a85449d08b3483a54", "build/prefab/full/linux_arm64_server/debug/dist/ballisticakit_headless": "f0a7e6c9ad568b229b8299ac15a485b4",
"build/prefab/full/linux_arm64_server/release/dist/ballisticakit_headless": "000738cc726938ff5282d211629739e3", "build/prefab/full/linux_arm64_server/release/dist/ballisticakit_headless": "4c453da417e3e2ab696009f3efb0cbc5",
"build/prefab/full/linux_x86_64_gui/debug/ballisticakit": "e4d0b9fdcb16f2bef3891f28b950daac", "build/prefab/full/linux_x86_64_gui/debug/ballisticakit": "56b10a97ac10e4ff9ae854616f3d58de",
"build/prefab/full/linux_x86_64_gui/release/ballisticakit": "9cc960c0de9e10dd300561dc9ae045e2", "build/prefab/full/linux_x86_64_gui/release/ballisticakit": "e6a5ecde216f1c180321eec53de8e54d",
"build/prefab/full/linux_x86_64_server/debug/dist/ballisticakit_headless": "be453afedfb331abe39e4940755b34e7", "build/prefab/full/linux_x86_64_server/debug/dist/ballisticakit_headless": "3170eb1b194b77204b5b3454d271e10d",
"build/prefab/full/linux_x86_64_server/release/dist/ballisticakit_headless": "d478af40d922a68e4deab6dd452b0e51", "build/prefab/full/linux_x86_64_server/release/dist/ballisticakit_headless": "368f1eb9a45f3e0d7452e5770f67bd65",
"build/prefab/full/mac_arm64_gui/debug/ballisticakit": "5fdb7d8752f9a8315a5f94a32b5db368", "build/prefab/full/mac_arm64_gui/debug/ballisticakit": "4376bee2a3d9b1ac98f418e981bd6b5e",
"build/prefab/full/mac_arm64_gui/release/ballisticakit": "433dae3372f05c7faf75658c6f962be1", "build/prefab/full/mac_arm64_gui/release/ballisticakit": "241bcdfb4a528a4f9714efa9c086a6a6",
"build/prefab/full/mac_arm64_server/debug/dist/ballisticakit_headless": "d66d0201decc90f677520f41ac227a71", "build/prefab/full/mac_arm64_server/debug/dist/ballisticakit_headless": "ed0ae25a5332c9e90bc576a41b0e6397",
"build/prefab/full/mac_arm64_server/release/dist/ballisticakit_headless": "37efebdbd1866b71ac324f5ca949151a", "build/prefab/full/mac_arm64_server/release/dist/ballisticakit_headless": "b0fc89d99bfacf8326a313c0c9d89065",
"build/prefab/full/mac_x86_64_gui/debug/ballisticakit": "5ccfadd91168c07956328dd11b118b28", "build/prefab/full/mac_x86_64_gui/debug/ballisticakit": "ab540c6524dfdc31ef55be4726f6a978",
"build/prefab/full/mac_x86_64_gui/release/ballisticakit": "404d9db4de7089346264f3f62dbb6d88", "build/prefab/full/mac_x86_64_gui/release/ballisticakit": "d0e6d409f0f5bfa339060bd6024b10f7",
"build/prefab/full/mac_x86_64_server/debug/dist/ballisticakit_headless": "ef3dda575b711a0fa515f93ebd0a7379", "build/prefab/full/mac_x86_64_server/debug/dist/ballisticakit_headless": "287d4e643461591bb897d26f9b24a600",
"build/prefab/full/mac_x86_64_server/release/dist/ballisticakit_headless": "8ee2be9a35e12158c0ace56b04a3ea99", "build/prefab/full/mac_x86_64_server/release/dist/ballisticakit_headless": "8f53ad04b1de065d104b7fd28da0e79c",
"build/prefab/full/windows_x86_gui/debug/BallisticaKit.exe": "1c305bde639f5fc76e908a3a3fb9203f", "build/prefab/full/windows_x86_gui/debug/BallisticaKit.exe": "74fa35f559c38176ff91c794b78d06e2",
"build/prefab/full/windows_x86_gui/release/BallisticaKit.exe": "e58ce9a499068e682295dd3d88a0801d", "build/prefab/full/windows_x86_gui/release/BallisticaKit.exe": "2cb71ce71f4ac5d8d2bda2af1ebc87b5",
"build/prefab/full/windows_x86_server/debug/dist/BallisticaKitHeadless.exe": "38a2c503c8d892d7e94cd53fb4da5b48", "build/prefab/full/windows_x86_server/debug/dist/BallisticaKitHeadless.exe": "53ac1a5d801ae8ae82de21ec9d9a6b8d",
"build/prefab/full/windows_x86_server/release/dist/BallisticaKitHeadless.exe": "deea2cf3afb5598ed9eb6ce4798b1cef", "build/prefab/full/windows_x86_server/release/dist/BallisticaKitHeadless.exe": "113c82cf8b6c95a2c940c45b97e4b894",
"build/prefab/lib/linux_arm64_gui/debug/libballisticaplus.a": "a3607fd941915ab11503f82acfc392b5", "build/prefab/lib/linux_arm64_gui/debug/libballisticaplus.a": "6ccd6f2bd0e20520063d4bf8e2c016d0",
"build/prefab/lib/linux_arm64_gui/release/libballisticaplus.a": "b5a129d83796c9e7015ab5e319d2c22f", "build/prefab/lib/linux_arm64_gui/release/libballisticaplus.a": "82e76d58eab4962ee7567fbc655072d6",
"build/prefab/lib/linux_arm64_server/debug/libballisticaplus.a": "a3607fd941915ab11503f82acfc392b5", "build/prefab/lib/linux_arm64_server/debug/libballisticaplus.a": "6ccd6f2bd0e20520063d4bf8e2c016d0",
"build/prefab/lib/linux_arm64_server/release/libballisticaplus.a": "b5a129d83796c9e7015ab5e319d2c22f", "build/prefab/lib/linux_arm64_server/release/libballisticaplus.a": "82e76d58eab4962ee7567fbc655072d6",
"build/prefab/lib/linux_x86_64_gui/debug/libballisticaplus.a": "13405a4a16a71d073b6b3cabbbcd9666", "build/prefab/lib/linux_x86_64_gui/debug/libballisticaplus.a": "e2ca657abc7945934c4b33602ecfbace",
"build/prefab/lib/linux_x86_64_gui/release/libballisticaplus.a": "86b26dc84cc7fa7095e51cfcae759c0b", "build/prefab/lib/linux_x86_64_gui/release/libballisticaplus.a": "5b24b2e91fb5c6eca673b0c35bbaf4ca",
"build/prefab/lib/linux_x86_64_server/debug/libballisticaplus.a": "13405a4a16a71d073b6b3cabbbcd9666", "build/prefab/lib/linux_x86_64_server/debug/libballisticaplus.a": "e2ca657abc7945934c4b33602ecfbace",
"build/prefab/lib/linux_x86_64_server/release/libballisticaplus.a": "86b26dc84cc7fa7095e51cfcae759c0b", "build/prefab/lib/linux_x86_64_server/release/libballisticaplus.a": "5b24b2e91fb5c6eca673b0c35bbaf4ca",
"build/prefab/lib/mac_arm64_gui/debug/libballisticaplus.a": "678e09ecd5da367ce290ca7318617b61", "build/prefab/lib/mac_arm64_gui/debug/libballisticaplus.a": "cfa1c3ca813c3974316cc0abbb56277b",
"build/prefab/lib/mac_arm64_gui/release/libballisticaplus.a": "a9cdc9dd029dabc6dfa5b61d33de7927", "build/prefab/lib/mac_arm64_gui/release/libballisticaplus.a": "73a49adbf5e205d927eda1a2272a3e98",
"build/prefab/lib/mac_arm64_server/debug/libballisticaplus.a": "678e09ecd5da367ce290ca7318617b61", "build/prefab/lib/mac_arm64_server/debug/libballisticaplus.a": "cfa1c3ca813c3974316cc0abbb56277b",
"build/prefab/lib/mac_arm64_server/release/libballisticaplus.a": "a9cdc9dd029dabc6dfa5b61d33de7927", "build/prefab/lib/mac_arm64_server/release/libballisticaplus.a": "73a49adbf5e205d927eda1a2272a3e98",
"build/prefab/lib/mac_x86_64_gui/debug/libballisticaplus.a": "4811585805942428ddb217917e4ad843", "build/prefab/lib/mac_x86_64_gui/debug/libballisticaplus.a": "a882153cd74bdb5c1b84d2c46a290527",
"build/prefab/lib/mac_x86_64_gui/release/libballisticaplus.a": "c5c40967e63471c9c4abd6dfbef892df", "build/prefab/lib/mac_x86_64_gui/release/libballisticaplus.a": "6b00cce1baf5f95d36ae911cdcb23dba",
"build/prefab/lib/mac_x86_64_server/debug/libballisticaplus.a": "d34c0a142e7d391a109a33ea3cc77c08", "build/prefab/lib/mac_x86_64_server/debug/libballisticaplus.a": "8708149fb6208e4e5889b4742784623d",
"build/prefab/lib/mac_x86_64_server/release/libballisticaplus.a": "c5c40967e63471c9c4abd6dfbef892df", "build/prefab/lib/mac_x86_64_server/release/libballisticaplus.a": "6b00cce1baf5f95d36ae911cdcb23dba",
"build/prefab/lib/windows/Debug_Win32/BallisticaKitGenericPlus.lib": "9d23a52a0c270710332bf35297db9f36", "build/prefab/lib/windows/Debug_Win32/BallisticaKitGenericPlus.lib": "dc9d6facd1062a48245d5fcb603fe5d6",
"build/prefab/lib/windows/Debug_Win32/BallisticaKitGenericPlus.pdb": "61d84134e4088e5138ceb200ba20960b", "build/prefab/lib/windows/Debug_Win32/BallisticaKitGenericPlus.pdb": "987b189ddac1f90808357749dd44fb2c",
"build/prefab/lib/windows/Debug_Win32/BallisticaKitHeadlessPlus.lib": "a7a9854fb6f114ac3f3f35c451170328", "build/prefab/lib/windows/Debug_Win32/BallisticaKitHeadlessPlus.lib": "bf8be09124840f7af212918fa98a34ec",
"build/prefab/lib/windows/Debug_Win32/BallisticaKitHeadlessPlus.pdb": "f5a2d196943141917b1ee0f3beb1f5eb", "build/prefab/lib/windows/Debug_Win32/BallisticaKitHeadlessPlus.pdb": "33ad88b1557e2828c8e0d8be10d9a5ca",
"build/prefab/lib/windows/Release_Win32/BallisticaKitGenericPlus.lib": "ec0052c95df6ea5cb3bdcaabcb1ea55c", "build/prefab/lib/windows/Release_Win32/BallisticaKitGenericPlus.lib": "3cec3a2d11567ff3dda36ace808c6082",
"build/prefab/lib/windows/Release_Win32/BallisticaKitGenericPlus.pdb": "d21984e95164c4e7df91b35a60828fc7", "build/prefab/lib/windows/Release_Win32/BallisticaKitGenericPlus.pdb": "b69a204fea2b6fcfee2cbce63a8edd9a",
"build/prefab/lib/windows/Release_Win32/BallisticaKitHeadlessPlus.lib": "b613c7699c66c9a2a17bf9360015ade0", "build/prefab/lib/windows/Release_Win32/BallisticaKitHeadlessPlus.lib": "59874804f88d67858d988cbc746ca601",
"build/prefab/lib/windows/Release_Win32/BallisticaKitHeadlessPlus.pdb": "6f907f7940d2f5e44874e24f9a1856a0", "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/__init__.py": "f885fed7f2ed98ff2ba271f9dbe3391c",
"src/assets/ba_data/python/babase/_mgen/enums.py": "f8cd3af311ac63147882590123b78318", "src/assets/ba_data/python/babase/_mgen/enums.py": "f8cd3af311ac63147882590123b78318",
"src/ballistica/base/mgen/pyembed/binding_base.inc": "ad347097a38e0d7ede9eb6dec6a80ee9", "src/ballistica/base/mgen/pyembed/binding_base.inc": "ad347097a38e0d7ede9eb6dec6a80ee9",

View File

@ -2318,6 +2318,7 @@
<w>projpath</w> <w>projpath</w>
<w>projprefix</w> <w>projprefix</w>
<w>projroot</w> <w>projroot</w>
<w>projrootstr</w>
<w>projs</w> <w>projs</w>
<w>projsrc</w> <w>projsrc</w>
<w>projtxt</w> <w>projtxt</w>

View File

@ -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 - 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 `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`. the existing `template_fs` one, do `tools/spinoff fset-copy template_fs poo`.
@ -16,6 +22,10 @@
significantly faster & more efficient. significantly faster & more efficient.
- Updated internal Python builds for Apple & iOS to 3.11.5, and updated a few - 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.). 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) ### 1.7.27 (build 21282, api 8, 2023-08-30)

View File

@ -1378,6 +1378,7 @@
<w>projname</w> <w>projname</w>
<w>projpath</w> <w>projpath</w>
<w>projprefix</w> <w>projprefix</w>
<w>projrootstr</w>
<w>projsrc</w> <w>projsrc</w>
<w>projtxt</w> <w>projtxt</w>
<w>prolly</w> <w>prolly</w>

View File

@ -424,8 +424,8 @@ set(BALLISTICA_SOURCES
${BA_SRC_ROOT}/ballistica/base/support/stress_test.cc ${BA_SRC_ROOT}/ballistica/base/support/stress_test.cc
${BA_SRC_ROOT}/ballistica/base/support/stress_test.h ${BA_SRC_ROOT}/ballistica/base/support/stress_test.h
${BA_SRC_ROOT}/ballistica/base/support/ui_v1_soft.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/dev_console.cc
${BA_SRC_ROOT}/ballistica/base/ui/console.h ${BA_SRC_ROOT}/ballistica/base/ui/dev_console.h
${BA_SRC_ROOT}/ballistica/base/ui/ui.cc ${BA_SRC_ROOT}/ballistica/base/ui/ui.cc
${BA_SRC_ROOT}/ballistica/base/ui/ui.h ${BA_SRC_ROOT}/ballistica/base/ui/ui.h
${BA_SRC_ROOT}/ballistica/base/ui/widget_message.h ${BA_SRC_ROOT}/ballistica/base/ui/widget_message.h

View File

@ -410,8 +410,8 @@
<ClCompile Include="..\..\src\ballistica\base\support\stress_test.cc" /> <ClCompile Include="..\..\src\ballistica\base\support\stress_test.cc" />
<ClInclude Include="..\..\src\ballistica\base\support\stress_test.h" /> <ClInclude Include="..\..\src\ballistica\base\support\stress_test.h" />
<ClInclude Include="..\..\src\ballistica\base\support\ui_v1_soft.h" /> <ClInclude Include="..\..\src\ballistica\base\support\ui_v1_soft.h" />
<ClCompile Include="..\..\src\ballistica\base\ui\console.cc" /> <ClCompile Include="..\..\src\ballistica\base\ui\dev_console.cc" />
<ClInclude Include="..\..\src\ballistica\base\ui\console.h" /> <ClInclude Include="..\..\src\ballistica\base\ui\dev_console.h" />
<ClCompile Include="..\..\src\ballistica\base\ui\ui.cc" /> <ClCompile Include="..\..\src\ballistica\base\ui\ui.cc" />
<ClInclude Include="..\..\src\ballistica\base\ui\ui.h" /> <ClInclude Include="..\..\src\ballistica\base\ui\ui.h" />
<ClInclude Include="..\..\src\ballistica\base\ui\widget_message.h" /> <ClInclude Include="..\..\src\ballistica\base\ui\widget_message.h" />

View File

@ -664,10 +664,10 @@
<ClInclude Include="..\..\src\ballistica\base\support\ui_v1_soft.h"> <ClInclude Include="..\..\src\ballistica\base\support\ui_v1_soft.h">
<Filter>ballistica\base\support</Filter> <Filter>ballistica\base\support</Filter>
</ClInclude> </ClInclude>
<ClCompile Include="..\..\src\ballistica\base\ui\console.cc"> <ClCompile Include="..\..\src\ballistica\base\ui\dev_console.cc">
<Filter>ballistica\base\ui</Filter> <Filter>ballistica\base\ui</Filter>
</ClCompile> </ClCompile>
<ClInclude Include="..\..\src\ballistica\base\ui\console.h"> <ClInclude Include="..\..\src\ballistica\base\ui\dev_console.h">
<Filter>ballistica\base\ui</Filter> <Filter>ballistica\base\ui</Filter>
</ClInclude> </ClInclude>
<ClCompile Include="..\..\src\ballistica\base\ui\ui.cc"> <ClCompile Include="..\..\src\ballistica\base\ui\ui.cc">

View File

@ -405,8 +405,8 @@
<ClCompile Include="..\..\src\ballistica\base\support\stress_test.cc" /> <ClCompile Include="..\..\src\ballistica\base\support\stress_test.cc" />
<ClInclude Include="..\..\src\ballistica\base\support\stress_test.h" /> <ClInclude Include="..\..\src\ballistica\base\support\stress_test.h" />
<ClInclude Include="..\..\src\ballistica\base\support\ui_v1_soft.h" /> <ClInclude Include="..\..\src\ballistica\base\support\ui_v1_soft.h" />
<ClCompile Include="..\..\src\ballistica\base\ui\console.cc" /> <ClCompile Include="..\..\src\ballistica\base\ui\dev_console.cc" />
<ClInclude Include="..\..\src\ballistica\base\ui\console.h" /> <ClInclude Include="..\..\src\ballistica\base\ui\dev_console.h" />
<ClCompile Include="..\..\src\ballistica\base\ui\ui.cc" /> <ClCompile Include="..\..\src\ballistica\base\ui\ui.cc" />
<ClInclude Include="..\..\src\ballistica\base\ui\ui.h" /> <ClInclude Include="..\..\src\ballistica\base\ui\ui.h" />
<ClInclude Include="..\..\src\ballistica\base\ui\widget_message.h" /> <ClInclude Include="..\..\src\ballistica\base\ui\widget_message.h" />

View File

@ -664,10 +664,10 @@
<ClInclude Include="..\..\src\ballistica\base\support\ui_v1_soft.h"> <ClInclude Include="..\..\src\ballistica\base\support\ui_v1_soft.h">
<Filter>ballistica\base\support</Filter> <Filter>ballistica\base\support</Filter>
</ClInclude> </ClInclude>
<ClCompile Include="..\..\src\ballistica\base\ui\console.cc"> <ClCompile Include="..\..\src\ballistica\base\ui\dev_console.cc">
<Filter>ballistica\base\ui</Filter> <Filter>ballistica\base\ui</Filter>
</ClCompile> </ClCompile>
<ClInclude Include="..\..\src\ballistica\base\ui\console.h"> <ClInclude Include="..\..\src\ballistica\base\ui\dev_console.h">
<Filter>ballistica\base\ui</Filter> <Filter>ballistica\base\ui</Filter>
</ClInclude> </ClInclude>
<ClCompile Include="..\..\src\ballistica\base\ui\ui.cc"> <ClCompile Include="..\..\src\ballistica\base\ui\ui.cc">

View File

@ -9,7 +9,7 @@ from __future__ import annotations
from batools.featureset import FeatureSet from batools.featureset import FeatureSet
# Grab the FeatureSet we should apply to. # Grab the FeatureSet we're defining here.
fset = FeatureSet.get_active() fset = FeatureSet.get_active()
fset.requirements = {'core'} fset.requirements = {'core'}

View File

@ -9,7 +9,7 @@ from __future__ import annotations
from batools.featureset import FeatureSet from batools.featureset import FeatureSet
# Grab the FeatureSet we should apply to. # Grab the FeatureSet we're defining here.
fset = FeatureSet.get_active() fset = FeatureSet.get_active()
fset.requirements = { fset.requirements = {
@ -27,7 +27,7 @@ fset.soft_requirements = {'plus'}
# We provide 'babase.app.classic'. # We provide 'babase.app.classic'.
fset.has_python_app_subsystem = True 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) # (classic accounts key off of plus's v2 accounts)
fset.python_app_subsystem_dependencies = {'plus'} fset.python_app_subsystem_dependencies = {'plus'}

View File

@ -9,7 +9,7 @@ from __future__ import annotations
from batools.featureset import FeatureSet from batools.featureset import FeatureSet
# Grab the FeatureSet we should apply to. # Grab the FeatureSet we're defining here.
fset = FeatureSet.get_active() fset = FeatureSet.get_active()
fset.requirements = set() fset.requirements = set()

View File

@ -9,7 +9,7 @@ from __future__ import annotations
from batools.featureset import FeatureSet from batools.featureset import FeatureSet
# Grab the FeatureSet we should apply to. # Grab the FeatureSet we're defining here.
fset = FeatureSet.get_active() fset = FeatureSet.get_active()
fset.requirements = {'core', 'base'} fset.requirements = {'core', 'base'}

View File

@ -9,7 +9,7 @@ from __future__ import annotations
from batools.featureset import FeatureSet from batools.featureset import FeatureSet
# Grab the FeatureSet we should apply to. # Grab the FeatureSet we're defining here.
fset = FeatureSet.get_active() fset = FeatureSet.get_active()
fset.requirements = {'core', 'base', 'classic', 'scene_v1_lib'} fset.requirements = {'core', 'base', 'classic', 'scene_v1_lib'}

View File

@ -9,9 +9,10 @@ from __future__ import annotations
from batools.featureset import FeatureSet from batools.featureset import FeatureSet
# Grab the FeatureSet we should apply to. # Grab the FeatureSet we're defining here.
fset = FeatureSet.get_active() fset = FeatureSet.get_active()
# We're just a library of Python stuff; no C++ here.
fset.has_python_binary_module = False fset.has_python_binary_module = False
fset.requirements = {'core', 'base', 'scene_v1'} fset.requirements = {'core', 'base', 'scene_v1'}

View File

@ -9,7 +9,7 @@ from __future__ import annotations
from batools.featureset import FeatureSet from batools.featureset import FeatureSet
# Grab the FeatureSet we should apply to. # Grab the FeatureSet we're defining here.
fset = FeatureSet.get_active() fset = FeatureSet.get_active()
fset.requirements = {'core', 'base'} fset.requirements = {'core', 'base'}

View File

@ -10,7 +10,7 @@ from __future__ import annotations
from batools.featureset import FeatureSet from batools.featureset import FeatureSet
from batools.dummymodule import DummyModuleDef from batools.dummymodule import DummyModuleDef
# Grab the FeatureSet we should apply to. # Grab the FeatureSet we're defining here.
fset = FeatureSet.get_active() fset = FeatureSet.get_active()
fset.requirements = {'core', 'base'} fset.requirements = {'core', 'base'}

View File

@ -9,9 +9,10 @@ from __future__ import annotations
from batools.featureset import FeatureSet from batools.featureset import FeatureSet
# Grab the FeatureSet we should apply to. # Grab the FeatureSet we're defining here.
fset = FeatureSet.get_active() fset = FeatureSet.get_active()
# We're just a library of Python stuff; no C++.
fset.has_python_binary_module = False fset.has_python_binary_module = False
fset.requirements = {'core', 'base', 'ui_v1', 'classic'} fset.requirements = {'core', 'base', 'ui_v1', 'classic'}

View File

@ -3,31 +3,30 @@
# pylint: disable=missing-module-docstring, invalid-name # pylint: disable=missing-module-docstring, invalid-name
from __future__ import annotations from __future__ import annotations
# This file is exec'ed by tools/spinoff, allowing us to customize # This file is exec'ed by tools/spinoff, allowing us to customize how
# how this src project gits filtered into dst projects. # this src project gits filtered into dst projects.
from batools.spinoff import SpinoffContext from batools.spinoff import SpinoffContext
# Grab the context we should apply to. # Grab the context we should apply to.
ctx = SpinoffContext.get_active() ctx = SpinoffContext.get_active()
# As a src project, we set up a baseline set of rules based on what # As a src project, we set up a baseline set of rules based on what we
# we contain. The dst project config (exec'ed after us) is then free # contain. The dst project config (exec'ed after us) is then free to
# to override based on what they want of ours or what they add # override based on what they want of ours or what they add themselves.
# themselves.
# Any files/dirs with these base names will be ignored by spinoff # Any files/dirs with these base names will be ignored by spinoff on
# on both src and dst. # both src and dst.
ctx.ignore_names = { ctx.ignore_names = {
'__pycache__', '__pycache__',
'.git', '.git',
'.mypy_cache', '.mypy_cache',
} }
# Special set of paths managed by spinoff but ALSO stored in git in # Special set of paths managed by spinoff but ALSO stored in git in the
# the dst project. This is for bare minimum stuff needed to be always # dst project. This is for bare minimum stuff needed to be always
# present in dst for bootstrapping, indexing by github, etc). Changes # present in dst for bootstrapping, indexing by github, etc). Changes to
# to these files in dst will be silently and happily overwritten by # these files in dst will be silently and happily overwritten by
# spinoff, so tread carefully. # spinoff, so tread carefully.
ctx.git_mirrored_paths = { ctx.git_mirrored_paths = {
'.gitignore', '.gitignore',
@ -36,16 +35,16 @@ ctx.git_mirrored_paths = {
'config/jenkins', 'config/jenkins',
} }
# File names that can be quietly ignored or cleared out when found. # File names that can be quietly ignored or cleared out when found. This
# This should encompass things like .DS_Store files created by the # should encompass things like .DS_Store files created by the Mac Finder
# Mac Finder when browsing directories. This helps spinoff remove # when browsing directories. This helps spinoff remove empty directories
# empty directories when doing a 'clean', etc. # when doing a 'clean', etc.
ctx.cruft_file_names = {'.DS_Store'} ctx.cruft_file_names = {'.DS_Store'}
# These paths in the src project will be skipped over during updates and # 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 # not synced into the dst project. The dst project can use this to trim
# trim out parts of the src project that it doesn't want or that it # out parts of the src project that it doesn't want or that it intends
# intends to 'override' with its own versions. # to 'override' with its own versions.
ctx.src_omit_paths = { ctx.src_omit_paths = {
'.gitignore', '.gitignore',
'config/spinoffconfig.py', 'config/spinoffconfig.py',
@ -63,27 +62,27 @@ ctx.src_omit_paths = {
# within it from being synced by spinoff; it just means that each of # within it from being synced by spinoff; it just means that each of
# those individual spinoff-managed files will have their own gitignore # those individual spinoff-managed files will have their own gitignore
# entry since there is no longer one covering the whole dir. So to keep # 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 # things tidy, carve out the minimal set of exact file/dir paths that
# need. # you need.
ctx.src_write_paths = { ctx.src_write_paths = {
'tools/spinoff', 'tools/spinoff',
'config/spinoffconfig.py', 'config/spinoffconfig.py',
} }
# Normally spinoff errors if it finds any files in its managed dirs # Normally spinoff errors if it finds any files in its managed dirs that
# that it did not put there. This is to prevent accidentally working # it did not put there. This is to prevent accidentally working in these
# in these parts of a dst project; since these sections are git-ignored, # parts of a dst project; since these sections are git-ignored, git
# git itself won't raise any warnings in such cases and it would be easy # itself won't raise any warnings in such cases and it would be easy to
# to accidentally lose work otherwise. # accidentally lose work otherwise.
#
# This list can be used to suppress spinoff's errors for specific # This list can be used to suppress spinoff's errors for specific
# locations. This is generally used to allow build output or other # locations. This is generally used to allow build output or other
# dynamically generated files to exist within spinoff-managed # dynamically generated files to exist within spinoff-managed
# directories. It is possible to use src_write_paths for such purposes, # directories. It is possible to use src_write_paths for such purposes,
# but this has the side-effect of greatly complicating the dst # but this has the side-effect of greatly complicating the dst project's
# project's gitignore list; selectively marking a few dirs as # gitignore list; selectively marking a few dirs as unchecked makes for
# unchecked makes for a cleaner setup. Just be careful to not set # a cleaner setup. Just be careful to not set excessively broad regions
# excessively broad regions as unchecked; you don't want to mask # as unchecked; you don't want to mask actual useful error messages.
# actual useful error messages.
ctx.src_unchecked_paths = { ctx.src_unchecked_paths = {
'src/ballistica/mgen', 'src/ballistica/mgen',
'src/ballistica/*/mgen', 'src/ballistica/*/mgen',
@ -102,12 +101,12 @@ ctx.src_unchecked_paths = {
'ballisticakit-android/BallisticaKit/.cxx', 'ballisticakit-android/BallisticaKit/.cxx',
} }
# Paths/names/suffixes we consider 'project' files. # Paths/names/suffixes we consider 'project' files. These files are
# These files are synced after all other files and go through # synced after all other files and go through batools.project.Updater
# batools.project.Updater class as part of their filtering. # class as part of their filtering. This allows them to update
# This allows them to update themselves in the same way as they # themselves in the same way as they do when running 'make update' for
# do when running 'make update' for the project; adding the final # the project; adding the final filtered set of project source files to
# filtered set of project source files to themself, etc. # themself, etc.
ctx.project_file_paths = {'src/assets/ba_data/python/babase/_app.py'} ctx.project_file_paths = {'src/assets/ba_data/python/babase/_app.py'}
ctx.project_file_names = { ctx.project_file_names = {
'Makefile', 'Makefile',
@ -124,15 +123,16 @@ ctx.project_file_suffixes = {
'.pbxproj', '.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 # If files are 'filtered' it means they will have all instances of
# of BallisticaKit in their names and contents replaced with their # BallisticaKit in their names and contents replaced with their project
# project name. Other custom filtering can also be applied. Obviously # name. Other custom filtering can also be applied. Obviously filtering
# filtering should not be run on certain files (binary data, etc.) # should not be run on certain files (binary data, etc.) and disabling
# and disabling it where not needed can improve efficiency and make # it where not needed can improve efficiency and make backporting easier
# backporting easier (editing spinoff-managed files in dst and getting # (editing spinoff-managed files in dst and getting those changes back
# those changes back into src). # into src).
# Anything under these dirs WILL be filtered. # Anything under these dirs WILL be filtered.
ctx.filter_dirs = { ctx.filter_dirs = {
@ -153,8 +153,8 @@ ctx.no_filter_dirs = {
'src/assets/windows', 'src/assets/windows',
} }
# ELSE files matching these exact base names WILL be filtered # ELSE files matching these exact base names WILL be filtered (so FOO
# (so FOO matches a/b/FOO as well as just FOO). # matches a/b/FOO as well as just FOO).
ctx.filter_file_names = { ctx.filter_file_names = {
'Makefile', 'Makefile',
'.gitignore', '.gitignore',

View File

@ -118,15 +118,16 @@ class App:
# This section generated by batools.appmodule; do not edit. # This section generated by batools.appmodule; do not edit.
# Ask our default app modes to handle it. # 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 bascenev1
import babase import babase
if bascenev1.SceneV1AppMode.can_handle_intent(intent): for appmode in [
return bascenev1.SceneV1AppMode bascenev1.SceneV1AppMode,
babase.EmptyAppMode,
if babase.EmptyAppMode.can_handle_intent(intent): ]:
return babase.EmptyAppMode if appmode.can_handle_intent(intent):
return appmode
return None return None
@ -1057,6 +1058,7 @@ class App:
@property @property
def protocol_version(self) -> int: def protocol_version(self) -> int:
"""(internal).""" """(internal)."""
# pylint: disable=cyclic-import
import bascenev1 import bascenev1
warnings.warn( warnings.warn(

View File

@ -52,7 +52,7 @@ if TYPE_CHECKING:
# Build number and version of the ballistica binary we expect to be # Build number and version of the ballistica binary we expect to be
# using. # using.
TARGET_BALLISTICA_BUILD = 21306 TARGET_BALLISTICA_BUILD = 21322
TARGET_BALLISTICA_VERSION = '1.7.28' TARGET_BALLISTICA_VERSION = '1.7.28'

View File

@ -350,7 +350,7 @@ class CoopBrowserWindow(bui.Window):
# noinspection PyUnresolvedReferences # noinspection PyUnresolvedReferences
@staticmethod @staticmethod
def _preload_modules() -> None: 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.purchase as _unused1
import bauiv1lib.coop.gamebutton as _unused2 import bauiv1lib.coop.gamebutton as _unused2
import bauiv1lib.confirm as _unused3 import bauiv1lib.confirm as _unused3

View File

@ -92,7 +92,7 @@ class MainMenuWindow(bui.Window):
# noinspection PyUnresolvedReferences # noinspection PyUnresolvedReferences
@staticmethod @staticmethod
def _preload_modules() -> None: 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.getremote as _unused
import bauiv1lib.confirm as _unused2 import bauiv1lib.confirm as _unused2
import bauiv1lib.store.button as _unused3 import bauiv1lib.store.button as _unused3

View File

@ -513,7 +513,7 @@ class PlayWindow(bui.Window):
# noinspection PyUnresolvedReferences # noinspection PyUnresolvedReferences
@staticmethod @staticmethod
def _preload_modules() -> None: 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.mainmenu as _unused1
import bauiv1lib.account as _unused2 import bauiv1lib.account as _unused2
import bauiv1lib.coop.browser as _unused3 import bauiv1lib.coop.browser as _unused3

View File

@ -16,7 +16,7 @@ if TYPE_CHECKING:
class AdvancedSettingsWindow(bui.Window): class AdvancedSettingsWindow(bui.Window):
"""Window for editing advanced game settings.""" """Window for editing advanced app settings."""
def __init__( def __init__(
self, self,
@ -61,6 +61,7 @@ class AdvancedSettingsWindow(bui.Window):
self._spacing = 32 self._spacing = 32
self._menu_open = False self._menu_open = False
top_extra = 10 if uiscale is bui.UIScale.SMALL else 0 top_extra = 10 if uiscale is bui.UIScale.SMALL else 0
super().__init__( super().__init__(
root_widget=bui.containerwidget( root_widget=bui.containerwidget(
size=(self._width, self._height + top_extra), 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_width = self._width - (100 + 2 * x_inset)
self._scroll_height = self._height - 115.0 self._scroll_height = self._height - 115.0
self._sub_width = self._scroll_width * 0.95 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: if self._show_always_use_internal_keyboard:
self._sub_height += 62 self._sub_height += 62
@ -185,7 +186,7 @@ class AdvancedSettingsWindow(bui.Window):
# noinspection PyUnresolvedReferences # noinspection PyUnresolvedReferences
@staticmethod @staticmethod
def _preload_modules() -> None: 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 babase import modutils as _unused2
from bauiv1lib import config as _unused1 from bauiv1lib import config as _unused1
from bauiv1lib.settings import vrtesting as _unused3 from bauiv1lib.settings import vrtesting as _unused3
@ -474,6 +475,19 @@ class AdvancedSettingsWindow(bui.Window):
maxwidth=430, 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 v -= 42
self._disable_camera_shake_check_box = ConfigCheckBox( self._disable_camera_shake_check_box = ConfigCheckBox(
parent=self._subcontainer, parent=self._subcontainer,

View File

@ -224,7 +224,7 @@ class AllSettingsWindow(bui.Window):
# noinspection PyUnresolvedReferences # noinspection PyUnresolvedReferences
@staticmethod @staticmethod
def _preload_modules() -> None: 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.mainmenu as _unused1
import bauiv1lib.settings.controls as _unused2 import bauiv1lib.settings.controls as _unused2
import bauiv1lib.settings.graphics as _unused3 import bauiv1lib.settings.graphics as _unused3

View File

@ -66,6 +66,6 @@ void AppMode::LanguageChanged() {}
auto AppMode::LastClientJoinTime() const -> millisecs_t { return -1; } 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 } // namespace ballistica::base

View File

@ -79,8 +79,8 @@ class AppMode {
/// Called when language changes. /// Called when language changes.
virtual void LanguageChanged(); virtual void LanguageChanged();
/// Are we currently in a 'main menu'? /// Are we currently in a classic 'main menu' session?
virtual auto InMainMenu() const -> bool; virtual auto InClassicMainMenuSession() const -> bool;
/// Get current party size (for legacy parties). /// Get current party size (for legacy parties).
virtual auto GetPartySize() const -> int; virtual auto GetPartySize() const -> int;

View File

@ -50,8 +50,10 @@ void AppModeEmpty::DrawWorld(base::FrameDef* frame_def) {
sinf(static_cast<float>(frame_def->display_time_millisecs()) / 600.0f); sinf(static_cast<float>(frame_def->display_time_millisecs()) / 600.0f);
auto yoffs = auto yoffs =
cosf(static_cast<float>(frame_def->display_time_millisecs()) / 600.0f); cosf(static_cast<float>(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, 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); c.Scale(2.0, 2.0);
int text_elem_count = grp.GetElementCount(); int text_elem_count = grp.GetElementCount();

View File

@ -24,7 +24,7 @@
#include "ballistica/base/support/plus_soft.h" #include "ballistica/base/support/plus_soft.h"
#include "ballistica/base/support/stdio_console.h" #include "ballistica/base/support/stdio_console.h"
#include "ballistica/base/support/stress_test.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/base/ui/ui.h"
#include "ballistica/core/python/core_python.h" #include "ballistica/core/python/core_python.h"
#include "ballistica/shared/foundation/event_loop.h" #include "ballistica/shared/foundation/event_loop.h"
@ -156,7 +156,7 @@ void BaseFeatureSet::OnAssetsAvailable() {
// Spin up the in-app console. // Spin up the in-app console.
if (!g_core->HeadlessMode()) { if (!g_core->HeadlessMode()) {
console_ = new Console(); console_ = new DevConsole();
// Print any messages that have built up. // Print any messages that have built up.
if (!console_startup_messages_.empty()) { if (!console_startup_messages_.empty()) {
@ -234,7 +234,7 @@ void BaseFeatureSet::OnAppShutdownComplete() {
if (app_adapter->ManagesEventLoop()) { if (app_adapter->ManagesEventLoop()) {
g_core->main_event_loop()->Quit(); g_core->main_event_loop()->Quit();
} else { } else {
platform->QuitApp(); platform->TerminateApp();
} }
} }
@ -747,4 +747,18 @@ void BaseFeatureSet::ShutdownSuppressDisallow() {
auto BaseFeatureSet::GetReturnValue() const -> int { return return_value(); } 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 } // namespace ballistica::base

View File

@ -53,7 +53,7 @@ class Camera;
class ClassicSoftInterface; class ClassicSoftInterface;
class CollisionMeshAsset; class CollisionMeshAsset;
class CollisionCache; class CollisionCache;
class Console; class DevConsole;
class Context; class Context;
class ContextRef; class ContextRef;
class DataAsset; class DataAsset;
@ -121,6 +121,18 @@ class UIV1SoftInterface;
class AppAdapterVR; class AppAdapterVR;
class GraphicsVR; 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 { enum class AssetType {
kTexture, kTexture,
kCollisionMesh, kCollisionMesh,
@ -601,6 +613,14 @@ class BaseFeatureSet : public FeatureSetNativeComponent,
/// Start app systems in motion. /// Start app systems in motion.
void StartApp() override; 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. /// Called when app shutdown process completes. Sets app to exit.
void OnAppShutdownComplete(); void OnAppShutdownComplete();
@ -759,7 +779,7 @@ class BaseFeatureSet : public FeatureSetNativeComponent,
void PrintContextUnavailable_(); void PrintContextUnavailable_();
AppMode* app_mode_; AppMode* app_mode_;
Console* console_{}; DevConsole* console_{};
PlusSoftInterface* plus_soft_{}; PlusSoftInterface* plus_soft_{};
ClassicSoftInterface* classic_soft_{}; ClassicSoftInterface* classic_soft_{};
UIV1SoftInterface* ui_v1_soft_{}; UIV1SoftInterface* ui_v1_soft_{};

View File

@ -11,6 +11,17 @@ namespace ballistica::base {
class RenderComponent { class RenderComponent {
public: public:
class ScopedTransformObj {
public:
explicit ScopedTransformObj(RenderComponent* c) : c_{c} {
c_->PushTransform();
}
~ScopedTransformObj() { c_->PopTransform(); }
private:
RenderComponent* c_;
};
explicit RenderComponent(RenderPass* pass) explicit RenderComponent(RenderPass* pass)
: state_(State::kConfiguring), pass_(pass), cmd_buffer_(nullptr) {} : state_(State::kConfiguring), pass_(pass), cmd_buffer_(nullptr) {}
~RenderComponent() { ~RenderComponent() {
@ -82,6 +93,9 @@ class RenderComponent {
EnsureDrawing(); EnsureDrawing();
cmd_buffer_->PutCommand(RenderCommandBuffer::Command::kPopTransform); cmd_buffer_->PutCommand(RenderCommandBuffer::Command::kPopTransform);
} }
auto ScopedTransform() -> ScopedTransformObj {
return ScopedTransformObj(this);
}
void Translate(float x, float y) { void Translate(float x, float y) {
EnsureDrawing(); EnsureDrawing();
cmd_buffer_->PutCommand(RenderCommandBuffer::Command::kTranslate2); cmd_buffer_->PutCommand(RenderCommandBuffer::Command::kTranslate2);

View File

@ -20,7 +20,7 @@
#include "ballistica/base/logic/logic.h" #include "ballistica/base/logic/logic.h"
#include "ballistica/base/python/support/python_context_call.h" #include "ballistica/base/python/support/python_context_call.h"
#include "ballistica/base/support/app_config.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/base/ui/ui.h"
#include "ballistica/core/core.h" #include "ballistica/core/core.h"
#include "ballistica/shared/foundation/event_loop.h" #include "ballistica/shared/foundation/event_loop.h"
@ -34,7 +34,6 @@ const float kScreenMeshZDepth{-0.05f};
const float kProgressBarZDepth{0.0f}; const float kProgressBarZDepth{0.0f};
const int kProgressBarFadeTime{500}; const int kProgressBarFadeTime{500};
const float kDebugImgZDepth{-0.04f}; const float kDebugImgZDepth{-0.04f};
const float kCursorZDepth{-0.1f};
auto Graphics::IsShaderTransparent(ShadingType c) -> bool { auto Graphics::IsShaderTransparent(ShadingType c) -> bool {
switch (c) { switch (c) {
@ -105,8 +104,8 @@ void Graphics::OnAppShutdown() { assert(g_base->InLogicThread()); }
void Graphics::DoApplyAppConfig() { void Graphics::DoApplyAppConfig() {
assert(g_base->InLogicThread()); assert(g_base->InLogicThread());
// Not relevant for fullscreen anymore // Not relevant for fullscreen anymore since we use fullscreen windows.
// since we're fullscreen windows everywhere. // everywhere.
int width = 800; int width = 800;
int height = 600; int height = 600;
@ -158,28 +157,20 @@ void Graphics::DoApplyAppConfig() {
// Note: when the graphics-thread applies the first set-screen event it will // 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 // 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( g_base->graphics_server->PushSetScreenCall(
fullscreen, width, height, texture_quality_requested, fullscreen, width, height, texture_quality_requested,
graphics_quality_requested, android_res); graphics_quality_requested, android_res);
set_show_fps(g_base->app_config->Resolve(AppConfig::BoolID::kShowFPS)); show_fps_ = g_base->app_config->Resolve(AppConfig::BoolID::kShowFPS);
set_show_ping(g_base->app_config->Resolve(AppConfig::BoolID::kShowPing)); 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->graphics_server->PushSetScreenGammaCall(
g_base->app_config->Resolve(AppConfig::FloatID::kScreenGamma)); g_base->app_config->Resolve(AppConfig::FloatID::kScreenGamma));
g_base->graphics_server->PushSetScreenPixelScaleCall( g_base->graphics_server->PushSetScreenPixelScaleCall(
g_base->app_config->Resolve(AppConfig::FloatID::kScreenPixelScale)); 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. // V-sync setting.
std::string v_sync = std::string v_sync =
g_base->app_config->Resolve(AppConfig::StringID::kVerticalSync); 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 { 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; return 0.0f;
} else if (y < shadow_lower_top_) { } else if (y < shadow_lower_top_) {
float amt = float amt =
@ -1108,9 +1099,16 @@ void Graphics::DrawUI(FrameDef* frame_def) {
g_base->ui->Draw(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() { void Graphics::BuildAndPushFrameDef() {
assert(g_base->InLogicThread()); assert(g_base->InLogicThread());
assert(camera_.Exists()); assert(camera_.Exists());
assert(!g_core->HeadlessMode());
// Keep track of when we're in here; can be useful for making sure stuff // 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. // doesn't muck with our lists/etc. while we're using them.
@ -1167,16 +1165,14 @@ void Graphics::BuildAndPushFrameDef() {
DrawUI(frame_def); 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); g_base->input->Draw(frame_def);
RenderPass* overlay_pass = frame_def->overlay_pass(); RenderPass* overlay_pass = frame_def->overlay_pass();
DrawMiscOverlays(overlay_pass); DrawMiscOverlays(overlay_pass);
// Draw console. // Let UI draw dev console and whatever else.
if (!g_core->HeadlessMode() && g_base->console()) { DrawDevUI(frame_def);
g_base->console()->Draw(overlay_pass);
}
DrawCursor(overlay_pass, app_time_millisecs); DrawCursor(overlay_pass, app_time_millisecs);

View File

@ -19,30 +19,32 @@
namespace ballistica::base { namespace ballistica::base {
// Light/shadow res is divided by this to get pure light res. // 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: // How we divide up our z depth spectrum:
const float kBackingDepth5 = 1.0f; const float kBackingDepth5{1.0f};
// Background // Background
// blit-shapes (with cam buffer) // 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) // World (without cam buffer) or overlay-3d (with cam buffer)
const float kBackingDepth3C = 0.65f; const float kBackingDepth3C{0.65f};
const float kBackingDepth3B = 0.4f; const float kBackingDepth3B{0.4f};
const float kBackingDepth3 = 0.15f; const float kBackingDepth3{0.15f};
// Overlay-3d (without cam buffer) / overlay(vr) // Overlay-3d (without cam buffer) / overlay(vr)
const float kBackingDepth2C = 0.147f; const float kBackingDepth2C{0.147f};
const float kBackingDepth2B = 0.143f; const float kBackingDepth2B{0.143f};
const float kBackingDepth2 = 0.14f; const float kBackingDepth2{0.14f};
// Overlay(non-vr) // cover (vr) // Overlay(non-vr) // cover (vr)
const float kBackingDepth1B = 0.01f; const float kBackingDepth1B{0.01f};
const float kBackingDepth1 = 0.0f; 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). // Client class for graphics operations (used from the logic thread).
class Graphics { class Graphics {
@ -218,13 +220,7 @@ class Graphics {
void SetShadowRange(float lower_bottom, float lower_top, float upper_bottom, void SetShadowRange(float lower_bottom, float lower_top, float upper_bottom,
float upper_top); float upper_top);
void ReleaseFadeEndCommand(); 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 { auto tv_border() const -> bool {
assert(g_base->InLogicThread()); assert(g_base->InLogicThread());
return tv_border_; return tv_border_;
@ -297,8 +293,13 @@ class Graphics {
void set_drawing_transparent_only(bool val) { void set_drawing_transparent_only(bool val) {
drawing_transparent_only_ = val; drawing_transparent_only_ = val;
} }
/// Draw regular UI.
virtual void DrawUI(FrameDef* frame_def); 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_; } auto drawing_opaque_only() const -> bool { return drawing_opaque_only_; }
void set_drawing_opaque_only(bool val) { drawing_opaque_only_ = val; } void set_drawing_opaque_only(bool val) { drawing_opaque_only_ = val; }

View File

@ -117,6 +117,11 @@ auto GraphicsServer::GetRenderFrameDef() -> FrameDef* {
return nullptr; 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. // Runs any mesh updates contained in the frame-def.
void GraphicsServer::RunFrameDefMeshUpdates(FrameDef* frame_def) { void GraphicsServer::RunFrameDefMeshUpdates(FrameDef* frame_def) {
assert(g_base->InGraphicsThread()); assert(g_base->InGraphicsThread());
@ -170,6 +175,9 @@ void GraphicsServer::TryRender() {
assert(g_base->InGraphicsThread()); assert(g_base->InGraphicsThread());
if (FrameDef* frame_def = GetRenderFrameDef()) { 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 // Note: we always run mesh updates contained in the framedef
// even if we don't actually render it. // even if we don't actually render it.
// (Hmm this seems flaky; will TryRender always get called // (Hmm this seems flaky; will TryRender always get called

View File

@ -52,6 +52,8 @@ class GraphicsServer {
// of using the RenderFrameDef* calls // of using the RenderFrameDef* calls
auto GetRenderFrameDef() -> FrameDef*; auto GetRenderFrameDef() -> FrameDef*;
void ApplyFrameDefSettings(FrameDef* frame_def);
void RunFrameDefMeshUpdates(FrameDef* frame_def); void RunFrameDefMeshUpdates(FrameDef* frame_def);
// renders shadow passes and other common parts of a frame_def // renders shadow passes and other common parts of a frame_def
@ -215,10 +217,6 @@ class GraphicsServer {
assert(g_base->InGraphicsThread()); assert(g_base->InGraphicsThread());
return res_y_virtual_; return res_y_virtual_;
} }
void set_tv_border(bool val) {
assert(g_base->InGraphicsThread());
tv_border_ = val;
}
auto tv_border() const { auto tv_border() const {
assert(g_base->InGraphicsThread()); assert(g_base->InGraphicsThread());
return tv_border_; return tv_border_;
@ -302,8 +300,8 @@ class GraphicsServer {
EventLoop* event_loop_{}; EventLoop* event_loop_{};
float res_x_{}; float res_x_{};
float res_y_{}; float res_y_{};
float res_x_virtual_{0.0f}; float res_x_virtual_{};
float res_y_virtual_{0.0f}; float res_y_virtual_{};
bool tv_border_{}; bool tv_border_{};
bool renderer_context_lost_{}; bool renderer_context_lost_{};
uint32_t texture_compression_types_{}; uint32_t texture_compression_types_{};

View File

@ -72,6 +72,7 @@ void FrameDef::Reset() {
assert(g_base->graphics->has_supports_high_quality_graphics_value()); assert(g_base->graphics->has_supports_high_quality_graphics_value());
orbiting_ = (g_base->graphics->camera()->mode() == CameraMode::kOrbit); orbiting_ = (g_base->graphics->camera()->mode() == CameraMode::kOrbit);
tv_border_ = g_base->graphics->tv_border();
shadow_offset_ = g_base->graphics->shadow_offset(); shadow_offset_ = g_base->graphics->shadow_offset();
shadow_scale_ = g_base->graphics->shadow_scale(); shadow_scale_ = g_base->graphics->shadow_scale();

View File

@ -154,6 +154,7 @@ class FrameDef {
auto media_components() const -> const std::vector<Object::Ref<Asset>>& { auto media_components() const -> const std::vector<Object::Ref<Asset>>& {
return media_components_; return media_components_;
} }
auto tv_border() const { return tv_border_; }
void set_camera_mode(CameraMode val) { camera_mode_ = val; } void set_camera_mode(CameraMode val) { camera_mode_ = val; }
void set_rendering(bool val) { rendering_ = val; } void set_rendering(bool val) { rendering_ = val; }
@ -205,6 +206,7 @@ class FrameDef {
std::unique_ptr<RenderPass> blit_pass_; std::unique_ptr<RenderPass> blit_pass_;
GraphicsQuality quality_{GraphicsQuality::kLow}; GraphicsQuality quality_{GraphicsQuality::kLow};
bool orbiting_{}; bool orbiting_{};
bool tv_border_{};
millisecs_t app_time_millisecs_{}; millisecs_t app_time_millisecs_{};
millisecs_t display_time_millisecs_{}; millisecs_t display_time_millisecs_{};
millisecs_t display_time_elapsed_millisecs_{}; millisecs_t display_time_elapsed_millisecs_{};

View File

@ -12,7 +12,7 @@
#include "ballistica/base/logic/logic.h" #include "ballistica/base/logic/logic.h"
#include "ballistica/base/python/base_python.h" #include "ballistica/base/python/base_python.h"
#include "ballistica/base/support/app_config.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/base/ui/ui.h"
#include "ballistica/shared/buildconfig/buildconfig_common.h" #include "ballistica/shared/buildconfig/buildconfig_common.h"
#include "ballistica/shared/foundation/event_loop.h" #include "ballistica/shared/foundation/event_loop.h"
@ -23,26 +23,14 @@ namespace ballistica::base {
Input::Input() = default; Input::Input() = default;
template <typename F> template <typename F>
void SafePushLogicCall(const char* desc, const F& lambda) { void PushLogicCall(const F& lambda) {
// Note: originally this call was created to silently ignore early events assert(g_base);
// coming in before app stuff was up and running, but that was a bad idea, assert(g_base->logic->event_loop());
// as it caused us to ignore device-create messages sometimes which lead g_base->logic->event_loop()->PushCall(lambda);
// 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 Input::PushCreateKeyboardInputDevices() { void Input::PushCreateKeyboardInputDevices() {
SafePushLogicCall(__func__, [this] { CreateKeyboardInputDevices(); }); PushLogicCall([this] { CreateKeyboardInputDevices(); });
} }
void Input::CreateKeyboardInputDevices() { void Input::CreateKeyboardInputDevices() {
@ -59,7 +47,7 @@ void Input::CreateKeyboardInputDevices() {
} }
void Input::PushDestroyKeyboardInputDevices() { void Input::PushDestroyKeyboardInputDevices() {
SafePushLogicCall(__func__, [this] { DestroyKeyboardInputDevices(); }); PushLogicCall([this] { DestroyKeyboardInputDevices(); });
} }
void Input::DestroyKeyboardInputDevices() { void Input::DestroyKeyboardInputDevices() {
@ -171,13 +159,14 @@ void Input::CreateTouchInput() {
void Input::AnnounceConnects() { void Input::AnnounceConnects() {
static bool first_print = true; static bool first_print = true;
// For the first announcement just say "X controllers detected" and don't have // For the first announcement just say "X controllers detected" and don't
// a sound. // have a sound.
if (first_print && g_core->GetAppTimeMillisecs() < 10000) { if (first_print && g_core->GetAppTimeMillisecs() < 10000) {
first_print = false; first_print = false;
// Disabling this completely for now; being more lenient with devices // 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}; bool do_print{false};
// If there's been several connected, just give a number. // 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())); &s, "${COUNT}", std::to_string(newly_connected_controllers_.size()));
ScreenMessage(s); ScreenMessage(s);
} else { } else {
// If its just one, name it. // If its just one, give its name.
std::string s = std::string s =
g_base->assets->GetResourceString("controllerConnectedText"); g_base->assets->GetResourceString("controllerConnectedText");
Utils::StringReplaceOne(&s, "${CONTROLLER}", Utils::StringReplaceOne(&s, "${CONTROLLER}",
@ -276,7 +265,7 @@ void Input::ShowStandardInputDeviceDisconnectedMessage(InputDevice* j) {
void Input::PushAddInputDeviceCall(InputDevice* input_device, void Input::PushAddInputDeviceCall(InputDevice* input_device,
bool standard_message) { bool standard_message) {
SafePushLogicCall(__func__, [this, input_device, standard_message] { PushLogicCall([this, input_device, standard_message] {
AddInputDevice(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, void Input::PushRemoveInputDeviceCall(InputDevice* input_device,
bool standard_message) { bool standard_message) {
SafePushLogicCall(__func__, [this, input_device, standard_message] { PushLogicCall([this, input_device, standard_message] {
RemoveInputDevice(input_device, standard_message); RemoveInputDevice(input_device, standard_message);
}); });
} }
@ -390,8 +379,6 @@ void Input::RemoveInputDevice(InputDevice* input, bool standard_message) {
device->DetachFromPlayer(); device->DetachFromPlayer();
// This should kill the device. // This should kill the device.
// FIXME: since many devices get allocated in the main thread,
// should we not kill it there too?...
device.Clear(); device.Clear();
UpdateInputDeviceCounts(); UpdateInputDeviceCounts();
return; return;
@ -411,10 +398,10 @@ void Input::UpdateInputDeviceCounts() {
int total = 0; int total = 0;
int controller_count = 0; int controller_count = 0;
for (auto& input_device : input_devices_) { for (auto& input_device : input_devices_) {
// Ok, we now limit non-keyboard non-touchscreen devices to ones that have // Ok, we now limit non-keyboard non-touchscreen devices to ones that
// been active recently.. (we're starting to get lots of virtual devices and // have been active recently.. (we're starting to get lots of virtual
// other cruft on android; don't wanna show controller UIs just due to // devices and other cruft on android; don't wanna show controller UIs
// those) // just due to those)
if (input_device.Exists() if (input_device.Exists()
&& ((*input_device).IsTouchScreen() || (*input_device).IsKeyboard() && ((*input_device).IsTouchScreen() || (*input_device).IsKeyboard()
|| ((*input_device).last_input_time_millisecs() != 0 || ((*input_device).last_input_time_millisecs() != 0
@ -480,7 +467,6 @@ auto Input::GetLocalActiveInputDeviceCount() -> int {
auto Input::HaveControllerWithPlayer() -> bool { auto Input::HaveControllerWithPlayer() -> bool {
assert(g_base->InLogicThread()); assert(g_base->InLogicThread());
// NOLINTNEXTLINE(readability-use-anyofallof)
for (auto& input_device : input_devices_) { for (auto& input_device : input_devices_) {
if (input_device.Exists() && (*input_device).IsController() if (input_device.Exists() && (*input_device).IsController()
&& (*input_device).AttachedToPlayer()) { && (*input_device).AttachedToPlayer()) {
@ -492,7 +478,6 @@ auto Input::HaveControllerWithPlayer() -> bool {
auto Input::HaveRemoteAppController() -> bool { auto Input::HaveRemoteAppController() -> bool {
assert(g_base->InLogicThread()); assert(g_base->InLogicThread());
// NOLINTNEXTLINE(readability-use-anyofallof)
for (auto& input_device : input_devices_) { for (auto& input_device : input_devices_) {
if (input_device.Exists() && (*input_device).IsRemoteApp()) { if (input_device.Exists() && (*input_device).IsRemoteApp()) {
return true; return true;
@ -546,8 +531,8 @@ auto Input::ShouldCompletelyIgnoreInputDevice(InputDevice* input_device)
void Input::UpdateEnabledControllerSubsystems() { void Input::UpdateEnabledControllerSubsystems() {
assert(g_base); assert(g_base);
// First off, on mac, let's update whether we want to completely ignore either // First off, on mac, let's update whether we want to completely ignore
// the classic or the iOS/Mac controller subsystems. // either the classic or the iOS/Mac controller subsystems.
if (g_buildconfig.ostype_macos()) { if (g_buildconfig.ostype_macos()) {
std::string sys = g_base->app_config->Resolve( std::string sys = g_base->app_config->Resolve(
AppConfig::StringID::kMacControllerSubsystem); AppConfig::StringID::kMacControllerSubsystem);
@ -581,9 +566,9 @@ void Input::DoApplyAppConfig() {
UpdateEnabledControllerSubsystems(); UpdateEnabledControllerSubsystems();
// It's technically possible that updating these controls will add or remove // It's technically possible that updating these controls will add or
// devices, thus changing the input_devices_ list, so lets work with a copy of // remove devices, thus changing the input_devices_ list, so lets work
// it. // with a copy of it.
std::vector<Object::Ref<InputDevice> > input_devices = input_devices_; std::vector<Object::Ref<InputDevice> > input_devices = input_devices_;
for (auto& input_device : input_devices) { for (auto& input_device : input_devices) {
if (input_device.Exists()) { if (input_device.Exists()) {
@ -819,7 +804,7 @@ void Input::ProcessStressTesting(int player_count) {
} }
void Input::PushTextInputEvent(const std::string& text) { void Input::PushTextInputEvent(const std::string& text) {
SafePushLogicCall(__func__, [this, text] { PushLogicCall([this, text] {
MarkInputActive(); MarkInputActive();
// Ignore if input is locked. // Ignore if input is locked.
@ -837,7 +822,7 @@ void Input::PushTextInputEvent(const std::string& text) {
void Input::PushJoystickEvent(const SDL_Event& event, void Input::PushJoystickEvent(const SDL_Event& event,
InputDevice* input_device) { InputDevice* input_device) {
SafePushLogicCall(__func__, [this, event, input_device] { PushLogicCall([this, event, input_device] {
HandleJoystickEvent(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) { 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) { void Input::PushKeyReleaseEvent(const SDL_Keysym& keysym) {
SafePushLogicCall(__func__, [this, keysym] { HandleKeyRelease(&keysym); }); PushLogicCall([this, keysym] { HandleKeyRelease(&keysym); });
} }
void Input::CaptureKeyboardInput(HandleKeyPressCall* press_call, void Input::CaptureKeyboardInput(HandleKeyPressCall* press_call,
@ -947,10 +932,9 @@ void Input::HandleKeyPress(const SDL_Keysym* keysym) {
case SDLK_KP_ENTER: case SDLK_KP_ENTER:
case SDLK_BACKSPACE: { case SDLK_BACKSPACE: {
// FIXME: I don't remember what this was put here for, but now that // FIXME: I don't remember what this was put here for, but now that
// we // we have hardware keyboards it crashes text fields by sending
// have hardware keyboards it crashes text fields by sending them a // them a TEXT_INPUT message with no string.. I made them resistant
// TEXT_INPUT message with no string.. I made them resistant to // to that case but wondering if we can take this out?
// that case but wondering if we can take this out?...
g_base->ui->SendWidgetMessage( g_base->ui->SendWidgetMessage(
WidgetMessage(WidgetMessage::Type::kTextInput, keysym)); WidgetMessage(WidgetMessage::Type::kTextInput, keysym));
break; break;
@ -971,7 +955,7 @@ void Input::HandleKeyPress(const SDL_Keysym* keysym) {
return; 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. // implicitly.
if (!repeat_press && keysym->sym == SDLK_q && (keysym->mod & KMOD_CTRL)) { if (!repeat_press && keysym->sym == SDLK_q && (keysym->mod & KMOD_CTRL)) {
g_base->ui->ConfirmQuit(); g_base->ui->ConfirmQuit();
@ -1027,14 +1011,12 @@ void Input::HandleKeyPress(const SDL_Keysym* keysym) {
} }
case SDLK_F7: case SDLK_F7:
SafePushLogicCall(__func__, PushLogicCall([] { g_base->graphics->ToggleManualCamera(); });
[] { g_base->graphics->ToggleManualCamera(); });
handled = true; handled = true;
break; break;
case SDLK_F8: case SDLK_F8:
SafePushLogicCall( PushLogicCall([] { g_base->graphics->ToggleNetworkDebugDisplay(); });
__func__, [] { g_base->graphics->ToggleNetworkDebugDisplay(); });
handled = true; handled = true;
break; break;
@ -1045,8 +1027,7 @@ void Input::HandleKeyPress(const SDL_Keysym* keysym) {
break; break;
case SDLK_F10: case SDLK_F10:
SafePushLogicCall(__func__, PushLogicCall([] { g_base->graphics->ToggleDebugDraw(); });
[] { g_base->graphics->ToggleDebugDraw(); });
handled = true; handled = true;
break; break;
@ -1080,49 +1061,38 @@ void Input::HandleKeyPress(const SDL_Keysym* keysym) {
} }
void Input::HandleKeyRelease(const SDL_Keysym* keysym) { void Input::HandleKeyRelease(const SDL_Keysym* keysym) {
assert(g_base);
assert(g_base->InLogicThread()); 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(); MarkInputActive();
// If someone is capturing these events, give them a crack at it. // In some cases we may receive duplicate key-release events (if a
if (keyboard_input_capture_release_) { // keyboard reset was run, it deals out key releases, but then the
if (keyboard_input_capture_release_(*keysym)) { // keyboard driver issues them as well).
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)
if (keys_held_.count(keysym->sym) == 0) { if (keys_held_.count(keysym->sym) == 0) {
return; 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); keys_held_.erase(keysym->sym);
if (IsInputLocked()) { if (g_base->console() != nullptr) {
return; g_base->console()->HandleKeyRelease(keysym);
} }
bool handled = false; if (keyboard_input_) {
keyboard_input_->HandleKey(keysym, false, 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);
}
} }
} }
@ -1155,15 +1125,17 @@ void Input::UpdateModKeyStates(const SDL_Keysym* keysym, bool press) {
} }
void Input::PushMouseScrollEvent(const Vector2f& amount) { void Input::PushMouseScrollEvent(const Vector2f& amount) {
SafePushLogicCall(__func__, [this, amount] { HandleMouseScroll(amount); }); PushLogicCall([this, amount] { HandleMouseScroll(amount); });
} }
void Input::HandleMouseScroll(const Vector2f& amount) { void Input::HandleMouseScroll(const Vector2f& amount) {
assert(g_base->InLogicThread()); assert(g_base->InLogicThread());
// If input is locked, allow it to mark us active but nothing more.
MarkInputActive();
if (IsInputLocked()) { if (IsInputLocked()) {
return; return;
} }
MarkInputActive();
if (std::abs(amount.y) > 0.0001f) { if (std::abs(amount.y) > 0.0001f) {
g_base->ui->SendWidgetMessage( g_base->ui->SendWidgetMessage(
@ -1187,17 +1159,19 @@ void Input::HandleMouseScroll(const Vector2f& amount) {
void Input::PushSmoothMouseScrollEvent(const Vector2f& velocity, void Input::PushSmoothMouseScrollEvent(const Vector2f& velocity,
bool momentum) { bool momentum) {
SafePushLogicCall(__func__, [this, velocity, momentum] { PushLogicCall([this, velocity, momentum] {
HandleSmoothMouseScroll(velocity, momentum); HandleSmoothMouseScroll(velocity, momentum);
}); });
} }
void Input::HandleSmoothMouseScroll(const Vector2f& velocity, bool momentum) { void Input::HandleSmoothMouseScroll(const Vector2f& velocity, bool momentum) {
assert(g_base->InLogicThread()); assert(g_base->InLogicThread());
// If input is locked, allow it to mark us active but nothing more.
MarkInputActive();
if (IsInputLocked()) { if (IsInputLocked()) {
return; return;
} }
MarkInputActive();
bool handled = false; bool handled = false;
handled = g_base->ui->SendWidgetMessage( handled = g_base->ui->SendWidgetMessage(
@ -1219,15 +1193,19 @@ void Input::HandleSmoothMouseScroll(const Vector2f& velocity, bool momentum) {
} }
void Input::PushMouseMotionEvent(const Vector2f& position) { void Input::PushMouseMotionEvent(const Vector2f& position) {
SafePushLogicCall(__func__, PushLogicCall([this, position] { HandleMouseMotion(position); });
[this, position] { HandleMouseMotion(position); });
} }
void Input::HandleMouseMotion(const Vector2f& position) { void Input::HandleMouseMotion(const Vector2f& position) {
assert(g_base->graphics); assert(g_base);
assert(g_base->InLogicThread()); assert(g_base->InLogicThread());
MarkInputActive(); MarkInputActive();
if (IsInputLocked()) {
return;
}
float old_cursor_pos_x = cursor_pos_x_; float old_cursor_pos_x = cursor_pos_x_;
float old_cursor_pos_y = cursor_pos_y_; 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(); last_mouse_move_time_ = g_core->GetAppTimeMillisecs();
mouse_move_count_++; mouse_move_count_++;
bool handled{};
// If we have a touch-input in editing mode, pass along events to it. // 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 // (it usually handles its own events but here we want it to play nice
// with stuff under it by blocking touches, etc) // with stuff under it by blocking touches, etc)
@ -1250,48 +1226,35 @@ void Input::HandleMouseMotion(const Vector2f& position) {
cursor_pos_y_); cursor_pos_y_);
} }
// UI interaction. // Let any UI stuff handle it.
if (!IsInputLocked()) { g_base->ui->HandleMouseMotion(cursor_pos_x_, cursor_pos_y_);
handled = g_base->ui->SendWidgetMessage(
WidgetMessage(WidgetMessage::Type::kMouseMove, nullptr, cursor_pos_x_,
cursor_pos_y_));
}
// Manual camera motion. // Manual camera motion.
Camera* camera = g_base->graphics->camera(); 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) float move_h = (cursor_pos_x_ - old_cursor_pos_x)
/ g_base->graphics->screen_virtual_width(); / g_base->graphics->screen_virtual_width();
float move_v = (cursor_pos_y_ - old_cursor_pos_y) float move_v = (cursor_pos_y_ - old_cursor_pos_y)
/ g_base->graphics->screen_virtual_width(); / g_base->graphics->screen_virtual_width();
camera->ManualHandleMouseMove(move_h, move_v); 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) { void Input::PushMouseDownEvent(int button, const Vector2f& position) {
SafePushLogicCall(__func__, [this, button, position] { PushLogicCall(
HandleMouseDown(button, position); [this, button, position] { HandleMouseDown(button, position); });
});
} }
void Input::HandleMouseDown(int button, const Vector2f& position) { void Input::HandleMouseDown(int button, const Vector2f& position) {
assert(g_base); assert(g_base);
assert(g_base->graphics);
assert(g_base->InLogicThread()); assert(g_base->InLogicThread());
MarkInputActive();
if (IsInputLocked()) { if (IsInputLocked()) {
return; return;
} }
// if (!g_base->ui->MainMenuVisible()) {
// return;
// }
MarkInputActive();
last_mouse_move_time_ = g_core->GetAppTimeMillisecs(); last_mouse_move_time_ = g_core->GetAppTimeMillisecs();
mouse_move_count_++; mouse_move_count_++;
@ -1306,7 +1269,6 @@ void Input::HandleMouseDown(int button, const Vector2f& position) {
last_click_time_ = click_time; last_click_time_ = click_time;
bool handled{}; bool handled{};
// auto* root_widget = g_base->ui->root_widget();
// If we have a touch-input in editing mode, pass along events to it. // 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 // (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 (!handled) {
if (g_base->ui->HandleLegacyRootUIMouseDown(cursor_pos_x_, cursor_pos_y_)) { handled = g_base->ui->HandleMouseDown(button, cursor_pos_x_, cursor_pos_y_,
handled = true; double_click);
}
}
if (!handled) {
handled = g_base->ui->SendWidgetMessage(
WidgetMessage(WidgetMessage::Type::kMouseDown, nullptr, cursor_pos_x_,
cursor_pos_y_, double_click ? 2 : 1));
} }
// Manual camera input. // Manual camera input.
@ -1349,8 +1304,7 @@ void Input::HandleMouseDown(int button, const Vector2f& position) {
} }
void Input::PushMouseUpEvent(int button, const Vector2f& position) { void Input::PushMouseUpEvent(int button, const Vector2f& position) {
SafePushLogicCall( PushLogicCall([this, button, position] { HandleMouseUp(button, position); });
__func__, [this, button, position] { HandleMouseUp(button, position); });
} }
void Input::HandleMouseUp(int button, const Vector2f& 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( cursor_pos_y_ = g_base->graphics->PixelToVirtualY(
position.y * g_base->graphics->screen_pixel_height()); position.y * g_base->graphics->screen_pixel_height());
bool handled{};
// If we have a touch-input in editing mode, pass along events to it. // 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 // (it usually handles its own events but here we want it to play nice
// with stuff under it by blocking touches, etc) // with stuff under it by blocking touches, etc)
@ -1373,14 +1325,7 @@ void Input::HandleMouseUp(int button, const Vector2f& position) {
cursor_pos_y_); cursor_pos_y_);
} }
// ui_v1::Widget* root_widget = g_base->ui->root_widget(); if (Camera* camera = g_base->graphics->camera()) {
// 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) {
switch (button) { switch (button) {
case SDL_BUTTON_LEFT: case SDL_BUTTON_LEFT:
camera->set_mouse_left_down(false); camera->set_mouse_left_down(false);
@ -1397,11 +1342,11 @@ void Input::HandleMouseUp(int button, const Vector2f& position) {
camera->UpdateManualMode(); 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) { void Input::PushTouchEvent(const TouchEvent& e) {
SafePushLogicCall(__func__, [e, this] { HandleTouchEvent(e); }); PushLogicCall([e, this] { HandleTouchEvent(e); });
} }
void Input::HandleTouchEvent(const TouchEvent& e) { void Input::HandleTouchEvent(const TouchEvent& e) {
@ -1501,19 +1446,19 @@ void Input::Draw(FrameDef* frame_def) {
} }
auto Input::IsCursorVisible() const -> bool { auto Input::IsCursorVisible() const -> bool {
assert(g_base->InLogicThread()); if (!g_base) {
if (!g_base->ui) {
return false; return false;
} }
assert(g_base->InLogicThread());
// Keeps mouse hidden to start with.. // Keeps mouse hidden to start with.
if (mouse_move_count_ < 2) { if (mouse_move_count_ < 2) {
return false; return false;
} }
bool val; bool val;
// Show our cursor if any dialogs/windows are up or else if its been // Show our cursor if any dialogs/windows are up or else if its been moved
// moved very recently. // very recently.
if (g_base->ui->MainMenuVisible()) { if (g_base->ui->MainMenuVisible()) {
val = (g_core->GetAppTimeMillisecs() - last_mouse_move_time_ < 5000); val = (g_core->GetAppTimeMillisecs() - last_mouse_move_time_ < 5000);
} else { } else {

View File

@ -11,7 +11,7 @@
#include "ballistica/base/python/base_python.h" #include "ballistica/base/python/base_python.h"
#include "ballistica/base/support/plus_soft.h" #include "ballistica/base/support/plus_soft.h"
#include "ballistica/base/support/stdio_console.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/base/ui/ui.h"
#include "ballistica/shared/foundation/event_loop.h" #include "ballistica/shared/foundation/event_loop.h"
#include "ballistica/shared/python/python_sys.h" #include "ballistica/shared/python/python_sys.h"

View File

@ -63,11 +63,11 @@ void BasePlatformApple::DoOpenURL(const std::string& url) {
#endif #endif
} }
void BasePlatformApple::QuitApp() { void BasePlatformApple::TerminateApp() {
#if BA_OSTYPE_MACOS && BA_XCODE_BUILD && !BA_HEADLESS_BUILD #if BA_OSTYPE_MACOS && BA_XCODE_BUILD && !BA_HEADLESS_BUILD
core::AppleUtils::Quit(); // will post a cocoa terminate core::AppleUtils::TerminateApp();
#else #else
BasePlatform::QuitApp(); BasePlatform::TerminateApp();
#endif #endif
} }

View File

@ -16,7 +16,7 @@ class BasePlatformApple : public BasePlatform {
void RestorePurchases() override; void RestorePurchases() override;
void PurchaseAck(const std::string& purchase, void PurchaseAck(const std::string& purchase,
const std::string& order_id) override; const std::string& order_id) override;
void QuitApp() override; void TerminateApp() override;
void DoOpenURL(const std::string& url) override; void DoOpenURL(const std::string& url) override;

View File

@ -321,6 +321,11 @@ void BasePlatform::OnAppShutdown() { assert(g_base->InLogicThread()); }
void BasePlatform::OnScreenSizeChange() { assert(g_base->InLogicThread()); } void BasePlatform::OnScreenSizeChange() { assert(g_base->InLogicThread()); }
void BasePlatform::DoApplyAppConfig() { 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 } // namespace ballistica::base

View File

@ -33,8 +33,35 @@ class BasePlatform {
virtual void OnScreenSizeChange(); virtual void OnScreenSizeChange();
virtual void DoApplyAppConfig(); virtual void DoApplyAppConfig();
/// Quit the app (can be immediate or via posting some high level event). /// Return whether this platform supports soft-quit. A soft quit is
virtual void QuitApp(); /// 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 -------------------------------------------------- #pragma mark IN APP PURCHASES --------------------------------------------------

View File

@ -5,6 +5,7 @@
#include "ballistica/base/app_mode/app_mode_empty.h" #include "ballistica/base/app_mode/app_mode_empty.h"
#include "ballistica/base/graphics/graphics_server.h" #include "ballistica/base/graphics/graphics_server.h"
#include "ballistica/base/logic/logic.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/base_python.h"
#include "ballistica/base/python/support/python_context_call_runnable.h" #include "ballistica/base/python/support/python_context_call_runnable.h"
#include "ballistica/base/support/stress_test.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}; static const char* kwlist[] = {"soft", "back", nullptr};
int soft = 0; int soft = 0;
int back = 0; int back = 0;
if (!PyArg_ParseTupleAndKeywords(args, keywds, "|ii", if (!PyArg_ParseTupleAndKeywords(args, keywds, "|pp",
const_cast<char**>(kwlist), &soft, &back)) { const_cast<char**>(kwlist), &soft, &back)) {
return nullptr; return nullptr;
} }
QuitType quit_type{};
// Log(LogLevel::kDebug, if (back) {
// "QUIT soft=" + std::to_string(soft) + " back=" + std::to_string(back)); if (!soft) {
Log(LogLevel::kWarning,
// FIXME this should all just go through platform and/or app-adapter. "Got soft=False back=True in quit() which is ambiguous.");
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;
} }
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. g_base->QuitApp(quit_type);
if (!handled) {
g_base->logic->event_loop()->PushCall([] { g_base->logic->Shutdown(); });
}
Py_RETURN_NONE; Py_RETURN_NONE;
BA_PYTHON_CATCH; BA_PYTHON_CATCH;
} }
@ -559,15 +538,18 @@ static PyMethodDef PyQuitDef = {
(PyCFunction)PyQuit, // method (PyCFunction)PyQuit, // method
METH_VARARGS | METH_KEYWORDS, // flags METH_VARARGS | METH_KEYWORDS, // flags
"quit(soft: bool = False, back: bool = False) -> None\n" "quit(soft: bool = True, back: bool = False) -> None\n"
"\n" "\n"
"Quit the game.\n" "Quit the app.\n"
"\n" "\n"
"Category: **General Utility Functions**\n" "Category: **General Utility Functions**\n"
"\n" "\n"
"On systems like Android, 'soft' will end the activity but keep the\n" "On platforms such as mobile, a 'soft' quit may background and/or reset\n"
"app running.", "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 ---------------------------------- // ----------------------------- apply_config ----------------------------------

View File

@ -218,6 +218,8 @@ void AppConfig::SetupEntries() {
BoolEntry("Always Use Internal Keyboard", false); BoolEntry("Always Use Internal Keyboard", false);
bool_entries_[BoolID::kShowFPS] = BoolEntry("Show FPS", false); bool_entries_[BoolID::kShowFPS] = BoolEntry("Show FPS", false);
bool_entries_[BoolID::kShowPing] = BoolEntry("Show Ping", false); bool_entries_[BoolID::kShowPing] = BoolEntry("Show Ping", false);
bool_entries_[BoolID::kShowDevConsoleButton] =
BoolEntry("Show Dev Console Button", false);
bool_entries_[BoolID::kEnableTVBorder] = bool_entries_[BoolID::kEnableTVBorder] =
BoolEntry("TV Border", g_core->platform->IsRunningOnTV()); BoolEntry("TV Border", g_core->platform->IsRunningOnTV());
bool_entries_[BoolID::kKeyboardP2Enabled] = bool_entries_[BoolID::kKeyboardP2Enabled] =

View File

@ -64,6 +64,7 @@ class AppConfig {
kAlwaysUseInternalKeyboard, kAlwaysUseInternalKeyboard,
kShowFPS, kShowFPS,
kShowPing, kShowPing,
kShowDevConsoleButton,
kEnableTVBorder, kEnableTVBorder,
kKeyboardP2Enabled, kKeyboardP2Enabled,
kEnablePackageMods, kEnablePackageMods,

View File

@ -6,6 +6,7 @@
#include "ballistica/base/ui/ui.h" #include "ballistica/base/ui/ui.h"
// Predeclare some types we use. // Predeclare some types we use.
namespace ballistica::ui_v1 { namespace ballistica::ui_v1 {
class RootUI; class RootUI;
class Widget; class Widget;
@ -13,10 +14,9 @@ class Widget;
namespace ballistica::base { namespace ballistica::base {
/// 'Soft' interface to the ui_v1 feature-set, managed by base. /// 'Soft' interface to the ui_v1 feature-set, managed by base. Feature-sets
/// Feature-sets listing ui_v1 as a soft requirement must limit their use of /// listing ui_v1 as a soft requirement must limit their use of it to these
/// it to these methods and should be prepared to handle the not-present /// methods and should be prepared to handle the not-present case.
/// case.
class UIV1SoftInterface { class UIV1SoftInterface {
public: public:
virtual void DoHandleDeviceMenuPress(base::InputDevice* device) = 0; virtual void DoHandleDeviceMenuPress(base::InputDevice* device) = 0;

View File

@ -1,6 +1,6 @@
// Released under the MIT License. See LICENSE for details. // 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/app_mode/app_mode.h"
#include "ballistica/base/audio/audio.h" #include "ballistica/base/audio/audio.h"
@ -19,14 +19,14 @@
namespace ballistica::base { namespace ballistica::base {
// How much of the screen the console covers when it is at full size. // How much of the screen the console covers when it is at full size.
const float kConsoleSize = 0.9f; const float kDevConsoleSize = 0.9f;
const float kConsoleZDepth = 0.0f; const float kDevConsoleZDepth = 0.0f;
const int kConsoleLineLimit = 80; const int kDevConsoleLineLimit = 80;
const int kStringBreakUpSize = 1950; const int kDevConsoleStringBreakUpSize = 1950;
const int kActivateKey1 = SDLK_BACKQUOTE; const int kDevConsoleActivateKey1 = SDLK_BACKQUOTE;
const int kActivateKey2 = SDLK_F2; const int kDevConsoleActivateKey2 = SDLK_F2;
Console::Console() { DevConsole::DevConsole() {
assert(g_base->InLogicThread()); assert(g_base->InLogicThread());
std::string title = std::string("BallisticaKit ") + kEngineVersion + " (" std::string title = std::string("BallisticaKit ") + kEngineVersion + " ("
+ std::to_string(kEngineBuildNumber) + ")"; + std::to_string(kEngineBuildNumber) + ")";
@ -42,15 +42,15 @@ Console::Console() {
prompt_text_group_.set_text(">"); 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()); assert(g_base->InLogicThread());
// Handle our toggle buttons no matter whether we're active. // Handle our toggle buttons no matter whether we're active.
switch (keysym->sym) { switch (keysym->sym) {
case kActivateKey1: case kDevConsoleActivateKey1:
case kActivateKey2: { case kDevConsoleActivateKey2: {
if (!g_buildconfig.demo_build() && !g_buildconfig.arcade_build()) { if (!g_buildconfig.demo_build() && !g_buildconfig.arcade_build()) {
// (reset input so characters don't continue walking and stuff) // (reset input so characters don't continue walking and stuff)
g_base->input->ResetHoldStates(); g_base->input->ResetHoldStates();
@ -111,9 +111,7 @@ auto Console::HandleKeyPress(const SDL_Keysym* keysym) -> bool {
case SDLK_KP_ENTER: case SDLK_KP_ENTER:
case SDLK_RETURN: { case SDLK_RETURN: {
if (!input_enabled_) { if (!input_enabled_) {
Log(LogLevel::kWarning, Log(LogLevel::kWarning, "Console input is not allowed yet.");
"Console input is not allowed until the app reaches the 'running' "
"state.");
break; break;
} }
input_history_position_ = 0; input_history_position_ = 0;
@ -121,7 +119,7 @@ auto Console::HandleKeyPress(const SDL_Keysym* keysym) -> bool {
last_line_.clear(); last_line_.clear();
lines_.clear(); lines_.clear();
} else { } else {
PushCommand(input_string_); SubmitCommand_(input_string_);
} }
input_history_.push_front(input_string_); input_history_.push_front(input_string_);
if (input_history_.size() > 100) { if (input_history_.size() > 100) {
@ -151,7 +149,7 @@ auto Console::HandleKeyPress(const SDL_Keysym* keysym) -> bool {
return true; return true;
} }
void Console::PushCommand(const std::string& command) { void DevConsole::SubmitCommand_(const std::string& command) {
assert(g_base); assert(g_base);
g_base->logic->event_loop()->PushCall([command] { g_base->logic->event_loop()->PushCall([command] {
// These are always run in whichever context is 'visible'. // 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()); assert(g_base->InLogicThread());
input_enabled_ = true; input_enabled_ = true;
} }
void Console::ToggleState() { void DevConsole::Dismiss() {
assert(g_base->InLogicThread()); 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_) { switch (state_) {
case State::kInactive: case State::kInactive:
state_ = State::kMini; state_ = State::kMini;
@ -194,7 +204,7 @@ void Console::ToggleState() {
transition_start_ = g_core->GetAppTimeMillisecs(); 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()); assert(g_base->InLogicThread());
if (state_ == State::kInactive) { if (state_ == State::kInactive) {
return false; return false;
@ -209,31 +219,32 @@ auto Console::HandleTextEditing(const std::string& text) -> bool {
return true; return true;
} }
auto Console::HandleKeyRelease(const SDL_Keysym* keysym) -> bool { auto DevConsole::HandleKeyRelease(const SDL_Keysym* keysym) -> bool {
// Always absorb our activate keys. // Always absorb our activate keys.
if (keysym->sym == kActivateKey1 || keysym->sym == kActivateKey2) { if (keysym->sym == kDevConsoleActivateKey1
|| keysym->sym == kDevConsoleActivateKey2) {
return true; 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; return state_ != State::kInactive;
} }
#pragma clang diagnostic push #pragma clang diagnostic push
#pragma ide diagnostic ignored "LocalValueEscapesScope" #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()); assert(g_base->InLogicThread());
std::string s = Utils::GetValidUTF8(s_in.c_str(), "cspr"); std::string s = Utils::GetValidUTF8(s_in.c_str(), "cspr");
last_line_ += s; last_line_ += s;
std::vector<std::string> broken_up; std::vector<std::string> broken_up;
g_base->text_graphics->BreakUpString(last_line_.c_str(), kStringBreakUpSize, g_base->text_graphics->BreakUpString(
&broken_up); last_line_.c_str(), kDevConsoleStringBreakUpSize, &broken_up);
// Spit out all completed lines and keep the last one as lastline. // Spit out all completed lines and keep the last one as lastline.
for (size_t i = 0; i < broken_up.size() - 1; i++) { for (size_t i = 0; i < broken_up.size() - 1; i++) {
lines_.emplace_back(broken_up[i], g_core->GetAppTimeMillisecs()); lines_.emplace_back(broken_up[i], g_core->GetAppTimeMillisecs());
if (lines_.size() > kConsoleLineLimit) { if (lines_.size() > kDevConsoleLineLimit) {
lines_.pop_front(); lines_.pop_front();
} }
} }
@ -243,7 +254,7 @@ void Console::Print(const std::string& s_in) {
#pragma clang diagnostic pop #pragma clang diagnostic pop
void Console::Draw(RenderPass* pass) { void DevConsole::Draw(RenderPass* pass) {
millisecs_t transition_ticks = 100; millisecs_t transition_ticks = 100;
if ((transition_start_ != 0) if ((transition_start_ != 0)
&& (state_ != State::kInactive && (state_ != State::kInactive
@ -257,27 +268,37 @@ void Console::Draw(RenderPass* pass) {
if (state_ == State::kMini) { if (state_ == State::kMini) {
bottom = pass->virtual_height() - mini_size; bottom = pass->virtual_height() - mini_size;
} else { } 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 (g_core->GetAppTimeMillisecs() - transition_start_ < transition_ticks) {
if (state_ == State::kMini) { float from_height;
bottom = pass->virtual_height() * (1.0f - ratio) + bottom * (ratio); if (state_prev_ == State::kMini) {
} else if (state_ == State::kFull) { from_height = pass->virtual_height() - mini_size;
bottom = } else if (state_prev_ == State::kFull) {
(pass->virtual_height() - pass->virtual_height() * kConsoleSize) from_height =
* (ratio) pass->virtual_height() - pass->virtual_height() * kDevConsoleSize;
+ (pass->virtual_height() - mini_size) * (1.0f - ratio);
} else { } 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_width(),
(pass->virtual_height() - bottom)); (pass->virtual_height() - bottom));
stripe_mesh_.SetPositionAndSize(0, bottom + 15, kConsoleZDepth, stripe_mesh_.SetPositionAndSize(0, bottom + 15, kDevConsoleZDepth,
pass->virtual_width(), 15); pass->virtual_width(), 15);
shadow_mesh_.SetPositionAndSize(0, bottom - 7, kConsoleZDepth, shadow_mesh_.SetPositionAndSize(0, bottom - 7, kDevConsoleZDepth,
pass->virtual_width(), 7); pass->virtual_width(), 7);
SimpleComponent c(pass); SimpleComponent c(pass);
c.SetTransparent(true); c.SetTransparent(true);
@ -304,7 +325,8 @@ void Console::Draw(RenderPass* pass) {
for (int e = 0; e < elem_count; e++) { for (int e = 0; e < elem_count; e++) {
c.SetTexture(built_text_group_.GetElementTexture(e)); c.SetTexture(built_text_group_.GetElementTexture(e));
c.PushTransform(); 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.Scale(0.5f, 0.5f, 0.5f);
c.DrawMesh(built_text_group_.GetElementMesh(e)); c.DrawMesh(built_text_group_.GetElementMesh(e));
c.PopTransform(); c.PopTransform();
@ -313,7 +335,7 @@ void Console::Draw(RenderPass* pass) {
for (int e = 0; e < elem_count; e++) { for (int e = 0; e < elem_count; e++) {
c.SetTexture(title_text_group_.GetElementTexture(e)); c.SetTexture(title_text_group_.GetElementTexture(e));
c.PushTransform(); 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.Scale(0.5f, 0.5f, 0.5f);
c.DrawMesh(title_text_group_.GetElementMesh(e)); c.DrawMesh(title_text_group_.GetElementMesh(e));
c.PopTransform(); c.PopTransform();
@ -323,7 +345,7 @@ void Console::Draw(RenderPass* pass) {
c.SetTexture(prompt_text_group_.GetElementTexture(e)); c.SetTexture(prompt_text_group_.GetElementTexture(e));
c.SetColor(1, 1, 1, 1); c.SetColor(1, 1, 1, 1);
c.PushTransform(); 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.Scale(0.5f, 0.5f, 0.5f);
c.DrawMesh(prompt_text_group_.GetElementMesh(e)); c.DrawMesh(prompt_text_group_.GetElementMesh(e));
c.PopTransform(); c.PopTransform();
@ -332,7 +354,7 @@ void Console::Draw(RenderPass* pass) {
for (int e = 0; e < elem_count; e++) { for (int e = 0; e < elem_count; e++) {
c.SetTexture(input_text_group_.GetElementTexture(e)); c.SetTexture(input_text_group_.GetElementTexture(e));
c.PushTransform(); 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.Scale(0.5f, 0.5f, 0.5f);
c.DrawMesh(input_text_group_.GetElementMesh(e)); c.DrawMesh(input_text_group_.GetElementMesh(e));
c.PopTransform(); c.PopTransform();
@ -350,7 +372,7 @@ void Console::Draw(RenderPass* pass) {
c.PushTransform(); c.PushTransform();
c.Translate( c.Translate(
19.0f + g_base->text_graphics->GetStringWidth(input_string_) * 0.5f, 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.Scale(5, 11, 1.0f);
c.DrawMeshAsset(g_base->assets->SysMesh(SysMeshID::kImage1x1)); c.DrawMeshAsset(g_base->assets->SysMesh(SysMeshID::kImage1x1));
c.PopTransform(); c.PopTransform();
@ -365,7 +387,7 @@ void Console::Draw(RenderPass* pass) {
c.SetColor(1, 1, 1, 1); c.SetColor(1, 1, 1, 1);
float h = 0.5f float h = 0.5f
* (g_base->graphics->screen_virtual_width() * (g_base->graphics->screen_virtual_width()
- (kStringBreakUpSize * draw_scale)); - (kDevConsoleStringBreakUpSize * draw_scale));
float v = bottom + 32.0f; float v = bottom + 32.0f;
if (!last_line_.empty()) { if (!last_line_.empty()) {
if (last_line_mesh_dirty_) { if (last_line_mesh_dirty_) {
@ -379,7 +401,7 @@ void Console::Draw(RenderPass* pass) {
for (int e = 0; e < elem_count; e++) { for (int e = 0; e < elem_count; e++) {
c.SetTexture(last_line_mesh_group_->GetElementTexture(e)); c.SetTexture(last_line_mesh_group_->GetElementTexture(e));
c.PushTransform(); c.PushTransform();
c.Translate(h, v + 2, kConsoleZDepth); c.Translate(h, v + 2, kDevConsoleZDepth);
c.Scale(draw_scale, draw_scale); c.Scale(draw_scale, draw_scale);
c.DrawMesh(last_line_mesh_group_->GetElementMesh(e)); c.DrawMesh(last_line_mesh_group_->GetElementMesh(e));
c.PopTransform(); c.PopTransform();
@ -391,7 +413,7 @@ void Console::Draw(RenderPass* pass) {
for (int e = 0; e < elem_count; e++) { for (int e = 0; e < elem_count; e++) {
c.SetTexture(i->GetText().GetElementTexture(e)); c.SetTexture(i->GetText().GetElementTexture(e));
c.PushTransform(); c.PushTransform();
c.Translate(h, v + 2, kConsoleZDepth); c.Translate(h, v + 2, kDevConsoleZDepth);
c.Scale(draw_scale, draw_scale); c.Scale(draw_scale, draw_scale);
c.DrawMesh(i->GetText().GetElementMesh(e)); c.DrawMesh(i->GetText().GetElementMesh(e));
c.PopTransform(); c.PopTransform();

View File

@ -1,7 +1,7 @@
// Released under the MIT License. See LICENSE for details. // Released under the MIT License. See LICENSE for details.
#ifndef BALLISTICA_BASE_UI_CONSOLE_H_ #ifndef BALLISTICA_BASE_UI_DEV_CONSOLE_H_
#define BALLISTICA_BASE_UI_CONSOLE_H_ #define BALLISTICA_BASE_UI_DEV_CONSOLE_H_
#include <list> #include <list>
#include <string> #include <string>
@ -12,22 +12,31 @@
namespace ballistica::base { namespace ballistica::base {
class Console { class DevConsole {
public: public:
Console(); DevConsole();
~Console(); ~DevConsole();
auto active() const -> bool { return (state_ != State::kInactive); } auto IsActive() const -> bool { return (state_ != State::kInactive); }
auto transition_start() const -> millisecs_t { return transition_start_; }
auto HandleTextEditing(const std::string& text) -> bool; auto HandleTextEditing(const std::string& text) -> bool;
auto HandleKeyPress(const SDL_Keysym* keysym) -> bool; auto HandleKeyPress(const SDL_Keysym* keysym) -> bool;
auto HandleKeyRelease(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(); 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 Print(const std::string& s_in);
void Draw(RenderPass* pass); void Draw(RenderPass* pass);
/// Called when the console should start accepting Python command input.
void EnableInput(); void EnableInput();
private: private:
void PushCommand(const std::string& command); void SubmitCommand_(const std::string& command);
enum class State { kInactive, kMini, kFull }; enum class State { kInactive, kMini, kFull };
ImageMesh bg_mesh_; ImageMesh bg_mesh_;
ImageMesh stripe_mesh_; ImageMesh stripe_mesh_;
@ -40,6 +49,7 @@ class Console {
bool input_text_dirty_{true}; bool input_text_dirty_{true};
millisecs_t transition_start_{}; millisecs_t transition_start_{};
State state_{State::kInactive}; State state_{State::kInactive};
State state_prev_{State::kInactive};
class Message { class Message {
public: public:
@ -71,4 +81,4 @@ class Console {
} // namespace ballistica::base } // namespace ballistica::base
#endif // BALLISTICA_BASE_UI_CONSOLE_H_ #endif // BALLISTICA_BASE_UI_DEV_CONSOLE_H_

View File

@ -3,13 +3,16 @@
#include "ballistica/base/ui/ui.h" #include "ballistica/base/ui/ui.h"
#include "ballistica/base/audio/audio.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/device/keyboard_input.h"
#include "ballistica/base/input/input.h" #include "ballistica/base/input/input.h"
#include "ballistica/base/logic/logic.h" #include "ballistica/base/logic/logic.h"
#include "ballistica/base/python/base_python.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/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/event_loop.h"
#include "ballistica/shared/foundation/inline.h"
#include "ballistica/shared/generic/utils.h" #include "ballistica/shared/generic/utils.h"
namespace ballistica::base { namespace ballistica::base {
@ -17,9 +20,10 @@ namespace ballistica::base {
static const int kUIOwnerTimeoutSeconds = 30; static const int kUIOwnerTimeoutSeconds = 30;
UI::UI() { UI::UI() {
// Figure out our interface type.
assert(g_core); assert(g_core);
// Figure out our interface scale.
// Allow overriding via an environment variable. // Allow overriding via an environment variable.
auto* ui_override = getenv("BA_UI_SCALE"); auto* ui_override = getenv("BA_UI_SCALE");
if (ui_override) { if (ui_override) {
@ -37,7 +41,7 @@ UI::UI() {
if (!force_scale_) { if (!force_scale_) {
// Use automatic val. // Use automatic val.
if (g_core->IsVRMode() || g_core->platform->IsRunningOnTV()) { 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; scale_ = UIScale::kMedium;
} else { } else {
scale_ = g_core->platform->GetUIScale(); scale_ = g_core->platform->GetUIScale();
@ -85,6 +89,8 @@ void UI::DoApplyAppConfig() {
if (g_base->HaveUIV1()) { if (g_base->HaveUIV1()) {
g_base->ui_v1()->DoApplyAppConfig(); g_base->ui_v1()->DoApplyAppConfig();
} }
show_dev_console_button_ =
g_base->app_config->Resolve(AppConfig::BoolID::kShowDevConsoleButton);
} }
auto UI::MainMenuVisible() const -> bool { auto UI::MainMenuVisible() const -> bool {
@ -114,36 +120,69 @@ auto UI::PartyWindowOpen() -> bool {
return false; return false;
} }
void UI::HandleLegacyRootUIMouseMotion(float x, float y) { auto UI::HandleMouseDown(int button, float x, float y, bool double_click)
if (g_base->HaveUIV1()) { -> bool {
g_base->ui_v1()->HandleLegacyRootUIMouseMotion(x, y); 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 { void UI::HandleMouseUp(int button, float x, float y) {
if (g_base->HaveUIV1()) { assert(g_base->InLogicThread());
return g_base->ui_v1()->HandleLegacyRootUIMouseDown(x, y);
} SendWidgetMessage(
return false; 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()) { if (g_base->HaveUIV1()) {
g_base->ui_v1()->HandleLegacyRootUIMouseUp(x, y); 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) { void UI::PushBackButtonCall(InputDevice* input_device) {
g_base->logic->event_loop()->PushCall([this, input_device] { g_base->logic->event_loop()->PushCall([this, input_device] {
assert(g_base->InLogicThread()); assert(g_base->InLogicThread());
// If there's a UI up, send along a cancel message. // If there's a UI up, send along a cancel message.
if (g_base->ui->MainMenuVisible()) { if (MainMenuVisible()) {
g_base->ui->SendWidgetMessage( SendWidgetMessage(WidgetMessage(WidgetMessage::Type::kCancel));
WidgetMessage(WidgetMessage::Type::kCancel));
} else { } else {
// If there's no main screen or overlay windows, ask for a menu owned by // If there's no main screen or overlay windows, ask for a menu owned
// this device. // by this device.
MainMenuPress_(input_device); MainMenuPress_(input_device);
} }
}); });
@ -173,24 +212,6 @@ void UI::SetUIInputDevice(InputDevice* input_device) {
last_input_device_use_time_ = g_core->GetAppTimeMillisecs(); 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() { void UI::Reset() {
if (g_base->HaveUIV1()) { if (g_base->HaveUIV1()) {
g_base->ui_v1()->Reset(); g_base->ui_v1()->Reset();
@ -198,9 +219,9 @@ void UI::Reset() {
} }
auto UI::ShouldHighlightWidgets() const -> bool { auto UI::ShouldHighlightWidgets() const -> bool {
// Show selection highlights only if we've got controllers connected and only // Show selection highlights only if we've got controllers connected and
// when the main UI is visible (dont want a selection highlight for toolbar // only when the main UI is visible (dont want a selection highlight for
// buttons during a game). // toolbar buttons during a game).
return g_base->input->have_non_touch_inputs() && MainMenuVisible(); 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(input_device);
assert(g_base->InLogicThread()); assert(g_base->InLogicThread());
// We only allow input-devices to control the UI when there's a window/dialog // We only allow input-devices to control the UI when there's a
// on the screen (even though our top/bottom bars still exist). // window/dialog on the screen (even though our top/bottom bars still
// exist).
if (!MainMenuVisible()) { if (!MainMenuVisible()) {
return nullptr; return nullptr;
} }
millisecs_t time = g_core->GetAppTimeMillisecs(); millisecs_t time = g_core->GetAppTimeMillisecs();
bool print_menu_owner = false; bool print_menu_owner{};
ui_v1::Widget* ret_val; ui_v1::Widget* ret_val;
// Ok here's the deal: // 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. // Because having 10 controllers attached to the UI is pure chaos, we only
// However, if no events are received by that device for a long time, // allow one input device at a time to control the menu. However, if no
// it is up for grabs to the next device that requests it. // 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()) { if (!g_base->HaveUIV1()) {
ret_val = nullptr; 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). // seconds ago to automatically own a newly created widget).
last_input_device_use_time_ = time; last_input_device_use_time_ = time;
ui_input_device_ = input_device; ui_input_device_ = input_device;
// ret_val = screen_root_widget_.Get();
ret_val = g_base->ui_v1()->GetRootWidget(); ret_val = g_base->ui_v1()->GetRootWidget();
} else { } else {
// For rejected input devices, play error sounds sometimes so they know // 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") { } else if (input->GetDeviceName() == "TouchScreen") {
name = g_base->assets->GetResourceString("touchScreenText"); name = g_base->assets->GetResourceString("touchScreenText");
} else { } else {
// We used to use player names here, but that's kinda sloppy and random; // We used to use player names here, but that's kinda sloppy and
// lets just go with device names/numbers. // random; lets just go with device names/numbers.
auto devicesWithName = auto devicesWithName =
g_base->input->GetInputDevicesWithName(input->GetDeviceName()); g_base->input->GetInputDevicesWithName(input->GetDeviceName());
if (devicesWithName.size() == 1) { 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<TextGroup>();
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) { void UI::ShowURL(const std::string& url) {
if (g_base->HaveUIV1()) { if (g_base->HaveUIV1()) {
g_base->ui_v1()->DoShowURL(url); g_base->ui_v1()->DoShowURL(url);
@ -339,15 +437,17 @@ void UI::ShowURL(const std::string& url) {
void UI::ConfirmQuit() { void UI::ConfirmQuit() {
g_base->logic->event_loop()->PushCall([] { 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()); assert(g_base->InLogicThread());
// If we're headless or input is locked or the in-app-console is up or // If we're headless or we don't have ui-v1, just quit immediately; a
// we don't have ui-v1, just quit immediately; a confirm screen // confirm screen wouldn't work anyway.
// wouldn't work anyway.
if (g_core->HeadlessMode() || g_base->input->IsInputLocked() if (g_core->HeadlessMode() || g_base->input->IsInputLocked()
|| !g_base->HaveUIV1() || !g_base->HaveUIV1()) {
|| (g_base->console() != nullptr && g_base->console()->active())) { g_base->QuitApp();
g_base->logic->Shutdown();
// g_base->python->objs().Get(BasePython::ObjID::kQuitCall).Call();
return; return;
} else { } else {
ScopedSetContext ssc(nullptr); ScopedSetContext ssc(nullptr);

View File

@ -10,19 +10,6 @@
#include "ballistica/base/ui/widget_message.h" #include "ballistica/base/ui/widget_message.h"
#include "ballistica/shared/generic/timer_list.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. // Predeclare a few things from ui_v1.
namespace ballistica::ui_v1 { namespace ballistica::ui_v1 {
class Widget; class Widget;
@ -46,14 +33,17 @@ class UI {
void LanguageChanged(); 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(); void Reset();
/// Pop up an in-app window to show a url (NOT in a browser). Can be /// Pop up an in-app window to display a URL (NOT to open the URL in a
/// called from any thread. /// browser). Can be called from any thread.
void ShowURL(const std::string& url); void ShowURL(const std::string& url);
/// High level call to request a quit ui. When a UI can't be shown, /// High level call to request a quit; ideally with a confirmation ui.
/// triggers an immediate shutdown. This can be called from any thread. /// When a UI can't be shown, triggers an immediate shutdown. This can be
/// called from any thread.
void ConfirmQuit(); void ConfirmQuit();
/// Return whether there is UI present in either the main or overlay /// Return whether there is UI present in either the main or overlay
@ -61,66 +51,65 @@ class UI {
auto MainMenuVisible() const -> bool; auto MainMenuVisible() const -> bool;
auto PartyIconVisible() -> 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; 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); void Draw(FrameDef* frame_def);
// Returns the widget an input should send commands to, if any. Also /// Draw dev UI on top.
// potentially locks other inputs out of controlling the UI, so only call void DrawDev(FrameDef* frame_def);
// this if you intend on sending a message to that widget.
/// 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*; 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; auto SendWidgetMessage(const WidgetMessage& msg) -> int;
/// Set the device controlling the UI.
void SetUIInputDevice(InputDevice* input_device); void SetUIInputDevice(InputDevice* input_device);
// Returns the input-device that currently owns the menu; otherwise /// Return the input-device that currently owns the UI; otherwise nullptr.
// nullptr.
auto GetUIInputDevice() const -> InputDevice*; auto GetUIInputDevice() const -> InputDevice*;
/// Schedule a back button press. Can be called from any thread.
void PushBackButtonCall(InputDevice* input_device); void PushBackButtonCall(InputDevice* input_device);
// Returns whether currently selected widgets should flash. This will be /// Return whether currently selected widgets should flash. This will be
// false in some situations such as when only touch screen control is /// false in some situations such as when only touch screen control is
// active. /// present.
auto ShouldHighlightWidgets() const -> bool; auto ShouldHighlightWidgets() const -> bool;
// Same except for button shortcuts; these generally only get shown if a /// Return whether currently selected widget should show button shortcuts.
// joystick of some form is present. /// These generally only get shown if a joystick of some form is present.
auto ShouldShowButtonShortcuts() const -> bool; auto ShouldShowButtonShortcuts() const -> bool;
// Used to ensure widgets are not created or destroyed at certain times /// Overall ui scale for the app.
// (while traversing widget hierarchy, etc).
class UILock {
public:
explicit UILock(bool write);
~UILock();
private:
BA_DISALLOW_CLASS_COPIES(UILock);
};
auto scale() const { return scale_; } auto scale() const { return scale_; }
/// Push a generic 'menu press' event, optionally associated with an /// Push a generic 'menu press' event, optionally associated with an input
/// input device (nullptr to specify none). Note: caller must ensure /// device (nullptr to specify none). Can be called from any thread.
/// a RemoveInputDevice() call does not arrive at the logic thread
/// before this one.
void PushMainMenuPressCall(InputDevice* device); void PushMainMenuPressCall(InputDevice* device);
private: private:
void MainMenuPress_(InputDevice* device); void MainMenuPress_(InputDevice* device);
auto DevConsoleButtonSize_() const -> float;
auto InDevConsoleButton_(float x, float y) const -> bool;
void DrawDevConsoleButton_(FrameDef* frame_def);
Object::WeakRef<InputDevice> ui_input_device_; Object::WeakRef<InputDevice> ui_input_device_;
millisecs_t last_input_device_use_time_{}; millisecs_t last_input_device_use_time_{};
millisecs_t last_widget_input_reject_err_sound_time_{}; millisecs_t last_widget_input_reject_err_sound_time_{};
int ui_lock_count_{};
UIScale scale_{UIScale::kLarge}; UIScale scale_{UIScale::kLarge};
bool force_scale_{}; bool force_scale_{};
bool show_dev_console_button_{};
bool dev_console_button_pressed_{};
Object::Ref<TextGroup> dev_console_button_txt_;
}; };
} // namespace ballistica::base } // namespace ballistica::base

View File

@ -682,14 +682,6 @@ void CorePlatform::AndroidSetResString(const std::string& res) {
throw Exception(); throw Exception();
} }
void CorePlatform::AndroidSynthesizeBackPress() {
Log(LogLevel::kError, "AndroidSynthesizeBackPress() unimplemented");
}
void CorePlatform::AndroidQuitActivity() {
Log(LogLevel::kError, "AndroidQuitActivity() unimplemented");
}
auto CorePlatform::GetDeviceV1AccountID() -> std::string { auto CorePlatform::GetDeviceV1AccountID() -> std::string {
if (g_core->HeadlessMode()) { if (g_core->HeadlessMode()) {
return "S-" + GetLegacyDeviceUUID(); return "S-" + GetLegacyDeviceUUID();
@ -758,12 +750,6 @@ void CorePlatform::MusicPlayerSetVolume(float volume) {
auto CorePlatform::IsOSPlayingMusic() -> bool { return false; } 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, void CorePlatform::IncrementAnalyticsCount(const std::string& name,
int increment) {} int increment) {}

View File

@ -217,11 +217,6 @@ class CorePlatform {
virtual auto GetAndroidExecArg() -> std::string; virtual auto GetAndroidExecArg() -> std::string;
virtual void AndroidSetResString(const std::string& res); 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; virtual auto AndroidGetExternalFilesDir() -> std::string;
#pragma mark PERMISSIONS ------------------------------------------------------- #pragma mark PERMISSIONS -------------------------------------------------------

View File

@ -2,3 +2,8 @@
Gameplay code for classic BombSquad, as well as app-modes and other support Gameplay code for classic BombSquad, as well as app-modes and other support
classes required to use it. 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.

View File

@ -71,7 +71,9 @@ auto ImageNode::InitType() -> NodeType* {
ImageNode::ImageNode(Scene* scene) : Node(scene, node_type) {} ImageNode::ImageNode(Scene* scene) : Node(scene, node_type) {}
ImageNode::~ImageNode() { ImageNode::~ImageNode() {
if (fill_screen_) scene()->decrement_bg_cover_count(); if (fill_screen_) {
scene()->decrement_bg_cover_count();
}
} }
auto ImageNode::GetAttach() const -> std::string { auto ImageNode::GetAttach() const -> std::string {
@ -209,11 +211,11 @@ void ImageNode::Draw(base::FrameDef* frame_def) {
if (host_only_ && !context_ref().GetHostSession()) { if (host_only_ && !context_ref().GetHostSession()) {
return; return;
} }
bool vr = (g_core->IsVRMode()); bool vr = g_core->IsVRMode();
// In vr mode we use the fixed overlay position if our scene // In vr mode we use the fixed overlay position if our scene
// is set for that. // 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. // Currently front and vr-fixed are mutually-exclusive.. need to fix.
if (front_) { if (front_) {

View File

@ -39,11 +39,10 @@ const int kKickVoteFailRetryDelay = 60000;
/// Extra delay for the initiator of a failed vote. /// Extra delay for the initiator of a failed vote.
const int kKickVoteFailRetryDelayInitiatorExtra = 120000; const int kKickVoteFailRetryDelayInitiatorExtra = 120000;
// Minimum clients that must be present for a kick vote to count. // Minimum clients that must be present for a kick vote to count. (for
// (for non-headless builds we require more votes since the host doesn't count // 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 // but may be playing (in a 2on2 with 3 clients, don't want 2 clients able
// kick). // to kick).
// NOLINTNEXTLINE(cert-err58-cpp)
const int kKickVoteMinimumClients = (g_buildconfig.headless_build() ? 3 : 4); const int kKickVoteMinimumClients = (g_buildconfig.headless_build() ? 3 : 4);
struct SceneV1AppMode::ScanResultsEntryPriv { struct SceneV1AppMode::ScanResultsEntryPriv {
@ -68,7 +67,7 @@ base::InputDeviceDelegate* SceneV1AppMode::CreateInputDeviceDelegate(
// Go with 5 minute ban. // Go with 5 minute ban.
const int kKickBanSeconds = 5 * 60; const int kKickBanSeconds = 5 * 60;
bool SceneV1AppMode::InMainMenu() const { bool SceneV1AppMode::InClassicMainMenuSession() const {
HostSession* hostsession = HostSession* hostsession =
ContextRefSceneV1::FromAppForegroundContext().GetHostSession(); ContextRefSceneV1::FromAppForegroundContext().GetHostSession();
return (hostsession && hostsession->is_main_menu()); return (hostsession && hostsession->is_main_menu());

View File

@ -150,15 +150,15 @@ class SceneV1AppMode : public base::AppMode {
void OnAppStart() override; void OnAppStart() override;
void OnAppPause() override; void OnAppPause() override;
void OnAppResume() override; void OnAppResume() override;
auto InMainMenu() const -> bool override; auto InClassicMainMenuSession() const -> bool override;
auto CreateInputDeviceDelegate(base::InputDevice* device) auto CreateInputDeviceDelegate(base::InputDevice* device)
-> base::InputDeviceDelegate* override; -> base::InputDeviceDelegate* override;
void SetInternalMusic(base::SoundAsset* music, float volume = 1.0, void SetInternalMusic(base::SoundAsset* music, float volume = 1.0,
bool loop = true); bool loop = true);
// Run a cycle of host scanning (basically sending out a broadcast packet to // Run a cycle of host scanning (basically sending out a broadcast packet
// see who's out there). // to see who's out there).
void HostScanCycle(); void HostScanCycle();
void EndHostScanning(); void EndHostScanning();

View File

@ -8,7 +8,7 @@
namespace ballistica::scene_v1 { 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 { class ContextRefSceneV1 : public base::ContextRef {
public: public:
ContextRefSceneV1() : ContextRef() {} ContextRefSceneV1() : ContextRef() {}
@ -23,8 +23,8 @@ class ContextRefSceneV1 : public base::ContextRef {
static auto FromAppForegroundContext() -> ContextRefSceneV1; static auto FromAppForegroundContext() -> ContextRefSceneV1;
// If the current Context is (or is part of) a HostSession, return it; // 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 // otherwise return nullptr. be aware that this will return a session if
// context is *either* a host-activity or a host-session // the context is *either* a host-activity or a host-session
auto GetHostSession() const -> HostSession*; auto GetHostSession() const -> HostSession*;
// Return the current context as an HostActivity if it is one; otherwise // 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*; auto GetHostActivity() const -> HostActivity*;
// If the current context contains a scene that can be manipulated by // 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. // host-activities, and the UI context.
auto GetMutableScene() const -> Scene*; auto GetMutableScene() const -> Scene*;
}; };
/// Object containing some sort of context_ref. /// Object containing some sort of context_ref. App-modes can subclass this
/// App-modes can subclass this to provide the actual context_ref they desire, /// to provide the actual context_ref they desire, and then code can use
/// and then code can use GetTyped() to safely retrieve context_ref as that /// GetTyped() to safely retrieve context_ref as that type.
/// type.
class SceneV1Context : public base::Context { class SceneV1Context : public base::Context {
public: public:
static auto Current() -> SceneV1Context& { static auto Current() -> SceneV1Context& {

View File

@ -39,7 +39,7 @@ auto main(int argc, char** argv) -> int {
namespace ballistica { namespace ballistica {
// These are set automatically via script; don't modify them here. // 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 char* kEngineVersion = "1.7.28";
const int kEngineApiVersion = 8; const int kEngineApiVersion = 8;

View File

@ -2472,45 +2472,6 @@ static PyMethodDef PyShowAd2Def = {
"(internal)", "(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<char**>(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 ------------------------- // --------------------- set_party_icon_always_visible -------------------------
static auto PySetPartyIconAlwaysVisible(PyObject* self, PyObject* args, static auto PySetPartyIconAlwaysVisible(PyObject* self, PyObject* args,
@ -2882,7 +2843,6 @@ auto PythonMethodsUIV1::GetMethods() -> std::vector<PyMethodDef> {
PyGetSpecialWidgetDef, PyGetSpecialWidgetDef,
PySetPartyWindowOpenDef, PySetPartyWindowOpenDef,
PySetPartyIconAlwaysVisibleDef, PySetPartyIconAlwaysVisibleDef,
PyShowAppInviteDef,
PyShowAdDef, PyShowAdDef,
PyShowAd2Def, PyShowAd2Def,
PyShowOnlineScoreUIDef, PyShowOnlineScoreUIDef,

View File

@ -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 // Flash and show a message if we're in the main menu instructing the
// player to start a game. // player to start a game.
bool flash = false; 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; 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.PopTransform();
c.Submit(); c.Submit();
// Based on who has menu control, we may show a key/button below the party // Based on who has menu control, we may show a key/button below the
// icon. // party icon.
if (!active) { if (!active) {
if (base::InputDevice* uiid = g_base->ui->GetUIInputDevice()) { if (base::InputDevice* uiid = g_base->ui->GetUIInputDevice()) {
std::string party_button_name = uiid->GetPartyButtonName(); std::string party_button_name = uiid->GetPartyButtonName();
@ -304,8 +304,8 @@ void RootUI::Draw(base::FrameDef* frame_def) {
party_size_text_group_->set_text( party_size_text_group_->set_text(
std::to_string(party_size_text_group_num_)); std::to_string(party_size_text_group_num_));
// ..we also may want to update our 'someone joined' message if we're // ..we also may want to update our 'someone joined' message if
// host // we're host
if (is_host) { if (is_host) {
if (!start_a_game_text_group_.Exists()) { if (!start_a_game_text_group_.Exists()) {
start_a_game_text_group_ = Object::New<base::TextGroup>(); start_a_game_text_group_ = Object::New<base::TextGroup>();

View File

@ -134,9 +134,8 @@ void UIV1FeatureSet::Draw(base::FrameDef* frame_def) {
auto* root_widget = root_widget_.Get(); auto* root_widget = root_widget_.Get();
if (root_widget && root_widget->HasChildren()) { if (root_widget && root_widget->HasChildren()) {
// Draw our opaque and transparent parts separately. // Draw our opaque and transparent parts separately. This way we can
// This way we can draw front-to-back for opaque and back-to-front for // draw front-to-back for opaque and back-to-front for transparent.
// transparent.
g_base->graphics->set_drawing_opaque_only(true); g_base->graphics->set_drawing_opaque_only(true);
@ -281,4 +280,22 @@ void UIV1FeatureSet::DoApplyAppConfig() {
base::AppConfig::BoolID::kAlwaysUseInternalKeyboard)); 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 } // namespace ballistica::ui_v1

View File

@ -15,6 +15,19 @@
// BA 2.0 UI testing. // BA 2.0 UI testing.
#define BA_UI_V1_TOOLBAR_TEST 0 #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. // Predeclared types from other feature sets that we use.
namespace ballistica::core { namespace ballistica::core {
class CoreFeatureSet; class CoreFeatureSet;
@ -57,6 +70,17 @@ class UIV1FeatureSet : public FeatureSetNativeComponent,
/// it. Basically a Python import statement. /// it. Basically a Python import statement.
static auto Import() -> UIV1FeatureSet*; 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. /// Called when our associated Python module is instantiated.
static void OnModuleExec(PyObject* module); static void OnModuleExec(PyObject* module);
void DoHandleDeviceMenuPress(base::InputDevice* device) override; void DoHandleDeviceMenuPress(base::InputDevice* device) override;
@ -113,6 +137,7 @@ class UIV1FeatureSet : public FeatureSetNativeComponent,
Object::Ref<ContainerWidget> screen_root_widget_; Object::Ref<ContainerWidget> screen_root_widget_;
Object::Ref<ContainerWidget> overlay_root_widget_; Object::Ref<ContainerWidget> overlay_root_widget_;
Object::Ref<RootWidget> root_widget_; Object::Ref<RootWidget> root_widget_;
int ui_lock_count_{};
}; };
} // namespace ballistica::ui_v1 } // namespace ballistica::ui_v1

View File

@ -98,9 +98,9 @@ struct RootWidget::Text {
}; };
RootWidget::RootWidget() { RootWidget::RootWidget() {
// we enable a special 'single-depth-root' mode // We enable a special 'single-depth-root' mode in which we use most of
// in which we use most of our depth range for our first child // our depth range for our first child (our screen stack) and the small
// (our screen stack) and the small remaining bit for the rest // remaining bit for the rest.
set_single_depth(true); set_single_depth(true);
set_single_depth_root(true); set_single_depth_root(true);
set_background(false); set_background(false);
@ -110,7 +110,7 @@ RootWidget::~RootWidget() = default;
auto RootWidget::AddCover(float h_align, VAlign v_align, float x, float y, auto RootWidget::AddCover(float h_align, VAlign v_align, float x, float y,
float w, float h, float o) -> RootWidget::Button* { 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()) { if (g_core->IsVRMode()) {
return nullptr; return nullptr;
} }
@ -132,9 +132,9 @@ auto RootWidget::AddCover(float h_align, VAlign v_align, float x, float y,
bd.visibility_mask = bd.visibility_mask =
static_cast<uint32_t>(Widget::ToolbarVisibility::kMenuFullRoot); static_cast<uint32_t>(Widget::ToolbarVisibility::kMenuFullRoot);
// when the user specifies no backing it means they intend to cover the screen // When the user specifies no backing it means they intend to cover the
// with a flat-ish window texture.. however this only applies to phone-size; // screen with a flat-ish window texture.. however this only applies to
// for other sizes we always draw a backing. // phone-size; for other sizes we always draw a backing.
if (g_base->ui->scale() != UIScale::kSmall) { if (g_base->ui->scale() != UIScale::kSmall) {
bd.visibility_mask |= bd.visibility_mask |=
static_cast<uint32_t>(Widget::ToolbarVisibility::kMenuFull); static_cast<uint32_t>(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 // - that looks funny in medium/large mode though
// if (g_ui->scale() == UIScale::kSmall) { // if (g_ui->scale() == UIScale::kSmall) {
// AddCover(0.5f, VAlign::kTop, 0.0f, 320.0f, // AddCover(0.5f, VAlign::kTop, 0.0f, 320.0f,
@ -838,8 +838,8 @@ auto RootWidget::AddButton(const ButtonDef& def) -> RootWidget::Button* {
} else { } else {
b.widget->set_up_widget(screen_stack_widget_); b.widget->set_up_widget(screen_stack_widget_);
} }
// We wanna prevent anyone from redirecting these to point to outside widgets // We wanna prevent anyone from redirecting these to point to outside
// since we'll probably outlive those outside widgets. // widgets since we'll probably outlive those outside widgets.
b.widget->set_neighbors_locked(true); b.widget->set_neighbors_locked(true);
if (!def.img.empty()) { if (!def.img.empty()) {
@ -894,7 +894,7 @@ void RootWidget::UpdateForFocusedWindow() {
void RootWidget::UpdateForFocusedWindow(Widget* widget) { void RootWidget::UpdateForFocusedWindow(Widget* widget) {
// Take note if the current session is the main menu; we do a few things // Take note if the current session is the main menu; we do a few things
// differently there. // differently there.
in_main_menu_ = g_base->app_mode()->InMainMenu(); in_main_menu_ = g_base->app_mode()->InClassicMainMenuSession();
if (widget == nullptr) { if (widget == nullptr) {
toolbar_visibility_ = ToolbarVisibility::kInGame; toolbar_visibility_ = ToolbarVisibility::kInGame;
@ -929,8 +929,8 @@ void RootWidget::StepPositions(float dt) {
static_cast<bool>(static_cast<uint32_t>(toolbar_visibility_) static_cast<bool>(static_cast<uint32_t>(toolbar_visibility_)
& static_cast<uint32_t>(b.visibility_mask)); & static_cast<uint32_t>(b.visibility_mask));
// When we're in the main menu, always disable the menu button // When we're in the main menu, always disable the menu button and shift
// and shift the party button a bit to the right // the party button a bit to the right
if (in_main_menu_) { if (in_main_menu_) {
if (&b == menu_button_) { if (&b == menu_button_) {
enable_button = false; enable_button = false;
@ -940,13 +940,13 @@ void RootWidget::StepPositions(float dt) {
} }
} }
if (&b == back_button_) { 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) { if (g_base->ui->scale() != UIScale::kSmall) {
enable_button = false; enable_button = false;
} }
// whenever back button is enabled, left on account button should go to // Whenever back button is enabled, left on account button should go
// it; otherwise it goes nowhere. // to it; otherwise it goes nowhere.
Widget* ab = account_button_->widget.Get(); Widget* ab = account_button_->widget.Get();
ab->set_neighbors_locked(false); ab->set_neighbors_locked(false);
ab->set_left_widget(enable_button ? back_button_->widget.Get() : ab); 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; b.y_target += disable_offset;
} }
// special case: we shift buttons on the top right to the right if the menu // special case: we shift buttons on the top right to the right if the
// button is hidden (and also if the button is hidden; otherwise things come // menu button is hidden (and also if the button is hidden; otherwise
// in diagonally) // things come in diagonally)
//
// if (b.h_align == HAlign::kRight and b.v_align == VAlign::kTop // if (b.h_align == HAlign::kRight and b.v_align == VAlign::kTop
// if (b.h_align >= 1.0f 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 // 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.x_smoothed += (b.x_target - b.x_smoothed) * 0.015f * dt;
b.y_smoothed += (b.y_target - b.y_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 // Snap in place once we reach the target; otherwise note that we need
// that we need to keep going. // to keep going.
if (std::abs(b.x_target - b.x_smoothed) < 0.1f if (std::abs(b.x_target - b.x_smoothed) < 0.1f
&& std::abs(b.y_target - b.y_smoothed) < 0.1f) { && std::abs(b.y_target - b.y_smoothed) < 0.1f) {
b.x_smoothed = b.x_target; b.x_smoothed = b.x_target;
b.y_smoothed = b.y_target; b.y_smoothed = b.y_target;
// Also flip off visibility if we're moving offscreen and have reached our // Also flip off visibility if we're moving offscreen and have reached
// target. // our target.
if (!enable_button) { if (!enable_button) {
b.widget->set_visible_in_container(false); b.widget->set_visible_in_container(false);
} }
@ -989,7 +990,8 @@ void RootWidget::StepPositions(float dt) {
b.widget->set_visible_in_container(true); 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; float x, y;
x = width() * b.h_align x = width() * b.h_align
+ base_scale_ * (b.x_smoothed - b.width * b.scale * 0.5f); + base_scale_ * (b.x_smoothed - b.width * b.scale * 0.5f);

View File

@ -531,9 +531,9 @@ auto TextWidget::ShouldUseStringEditDialog() const -> bool {
return true; return true;
} }
// On most platforms we always want to do this. // On most platforms we always want to do this. On desktop, however, we
// on mac/pc, however, we use inline editing if the current UI input-device // use inline editing if the current UI input-device is the mouse or
// is the mouse or keyboard // keyboard.
if (g_buildconfig.ostype_macos() || g_buildconfig.ostype_windows() if (g_buildconfig.ostype_macos() || g_buildconfig.ostype_windows()
|| g_buildconfig.ostype_linux()) { || g_buildconfig.ostype_linux()) {
base::InputDevice* ui_input_device = g_base->ui->GetUIInputDevice(); base::InputDevice* ui_input_device = g_base->ui->GetUIInputDevice();

View File

@ -15,7 +15,7 @@ if TYPE_CHECKING:
def generate_app_module( def generate_app_module(
feature_sets: dict[str, FeatureSet], existing_data: str projroot: str, feature_sets: dict[str, FeatureSet], existing_data: str
) -> str: ) -> str:
"""Generate babase._app.py based on its existing version.""" """Generate babase._app.py based on its existing version."""
@ -24,7 +24,7 @@ def generate_app_module(
# pylint: disable=too-many-statements # pylint: disable=too-many-statements
import textwrap import textwrap
from efrotools import replace_section from efrotools import replace_section, getprojectconfig
out = '' out = ''
@ -156,31 +156,50 @@ def generate_app_module(
# Generate default app-mode-selection logic. # Generate default app-mode-selection logic.
# TODO - make this customizable via project settings or whatnot. # 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 = ( contents = (
'# Ask our default app modes to handle it.\n' '# 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 not default_app_modes:
if 'scene_v1' in fsets: raise RuntimeError('No valid default_app_modes specified.')
imports.append('bascenev1')
if 'base' in fsets:
imports.append('babase')
for imp in imports:
contents += f'import {imp}\n'
for mode in default_app_modes:
contents += f'import {_module_for_app_mode(mode)}\n'
contents += '\n' contents += '\n'
if 'scene_v1' in fsets: contents += 'for appmode in [\n'
contents += ( for mode in default_app_modes:
'if bascenev1.SceneV1AppMode.can_handle_intent(intent):\n' contents += f' {mode},\n'
' return bascenev1.SceneV1AppMode\n\n' contents += (
) ']:\n'
if 'base' in fsets: ' if appmode.can_handle_intent(intent):\n'
contents += ( ' return appmode\n'
'if babase.EmptyAppMode.can_handle_intent(intent):\n' '\n'
' return babase.EmptyAppMode\n\n' )
)
contents += 'return None\n' contents += 'return None\n'
indent = ' ' indent = ' '

View File

@ -699,7 +699,7 @@ class ProjectUpdater:
from batools.appmodule import generate_app_module from batools.appmodule import generate_app_module
self._generated_files[path] = 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: def _update_meta_makefile(self) -> None:

View File

@ -28,6 +28,10 @@ PYVER = '3.11'
# Update; just using the same executable used to launch us. # Update; just using the same executable used to launch us.
PYTHON_BIN = sys.executable 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: def explicit_bool(value: bool) -> bool:
"""Simply return input value; can avoid unreachable-code type warnings.""" """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]: def getlocalconfig(projroot: Path | str) -> dict[str, Any]:
"""Return a project's localconfig contents (or default if missing).""" """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. # Allow overriding path via env var.
path = os.environ.get('EFRO_LOCALCONFIG_PATH') path = os.environ.get('EFRO_LOCALCONFIG_PATH')
if path is None: if path is None:
path = 'config/localconfig.json' path = 'config/localconfig.json'
try: try:
with open(Path(projroot, path), encoding='utf-8') as infile: with open(Path(projroot, path), encoding='utf-8') as infile:
localconfig = json.loads(infile.read()) localconfig = json.loads(infile.read())
except FileNotFoundError: except FileNotFoundError:
localconfig = {} localconfig = {}
return localconfig _g_local_configs[projrootstr] = localconfig
return _g_local_configs[projrootstr]
def getprojectconfig(projroot: Path | str) -> dict[str, Any]: def getprojectconfig(projroot: Path | str) -> dict[str, Any]:
"""Return a project's projectconfig contents (or default if missing).""" """Return a project's projectconfig contents (or default if missing)."""
config: dict[str, Any] projrootstr = str(projroot)
try: if projrootstr not in _g_project_configs:
with open( config: dict[str, Any]
Path(projroot, 'config/projectconfig.json'), encoding='utf-8' try:
) as infile: with open(
config = json.loads(infile.read()) Path(projroot, 'config/projectconfig.json'), encoding='utf-8'
except FileNotFoundError: ) as infile:
config = {} config = json.loads(infile.read())
return config except FileNotFoundError:
config = {}
_g_project_configs[projrootstr] = config
return _g_project_configs[projrootstr]
def setprojectconfig(projroot: Path | str, config: dict[str, Any]) -> None: def setprojectconfig(projroot: Path | str, config: dict[str, Any]) -> None:
"""Set the project config contents.""" """Set the project config contents."""
projrootstr = str(projroot)
_g_project_configs[projrootstr] = config
os.makedirs(Path(projroot, 'config'), exist_ok=True) os.makedirs(Path(projroot, 'config'), exist_ok=True)
with Path(projroot, 'config/projectconfig.json').open( with Path(projroot, 'config/projectconfig.json').open(
'w', encoding='utf-8' 'w', encoding='utf-8'