Merge branch 'efroemling:master' into master

This commit is contained in:
Vishal 2023-12-15 16:37:19 +05:30 committed by GitHub
commit add619b080
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
162 changed files with 2780 additions and 1285 deletions

108
.efrocachemap generated
View File

@ -421,21 +421,21 @@
"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": "c6f94f9c1dc833c537d16672d9018b94", "build/assets/ba_data/data/langdata.json": "f200cdf431b9494d8b96cdd47e950dd1",
"build/assets/ba_data/data/languages/arabic.json": "00ba700de6c672a56658a6bd1ad27523", "build/assets/ba_data/data/languages/arabic.json": "00ba700de6c672a56658a6bd1ad27523",
"build/assets/ba_data/data/languages/belarussian.json": "7fe38341815ca6ff4d95224196e7a67e", "build/assets/ba_data/data/languages/belarussian.json": "40883823367f04c5a2403a96525bfcc3",
"build/assets/ba_data/data/languages/chinese.json": "5761468d25f2bd4e79921826cebd572b", "build/assets/ba_data/data/languages/chinese.json": "5761468d25f2bd4e79921826cebd572b",
"build/assets/ba_data/data/languages/chinesetraditional.json": "f858da49be0a5374157c627857751078", "build/assets/ba_data/data/languages/chinesetraditional.json": "f858da49be0a5374157c627857751078",
"build/assets/ba_data/data/languages/croatian.json": "766532c67af5bd0144c2d63cab0516fa", "build/assets/ba_data/data/languages/croatian.json": "766532c67af5bd0144c2d63cab0516fa",
"build/assets/ba_data/data/languages/czech.json": "93c5fe0d884d95435da6c675f64e30e0", "build/assets/ba_data/data/languages/czech.json": "cd21ad8c6b8e9ed700284cf1e1aecbf8",
"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": "bd43b77b1ccca059573acbde148b4767", "build/assets/ba_data/data/languages/english.json": "6a3fab4fb8b2879e00ed9877709bf504",
"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": "afbda3adf14555e1567ee63c32e340e7", "build/assets/ba_data/data/languages/filipino.json": "6f4051ce78861a4666f4978d6f9a0ed2",
"build/assets/ba_data/data/languages/french.json": "49ff6d211537b8003b8241438dca661d", "build/assets/ba_data/data/languages/french.json": "49ff6d211537b8003b8241438dca661d",
"build/assets/ba_data/data/languages/german.json": "450fa41ae264f29a5d1af22143d0d0ad", "build/assets/ba_data/data/languages/german.json": "450fa41ae264f29a5d1af22143d0d0ad",
"build/assets/ba_data/data/languages/gibberish.json": "9aae526303a22372fe9b4cf1781520ef", "build/assets/ba_data/data/languages/gibberish.json": "25fcb5130fae56985bee175aa19f86a2",
"build/assets/ba_data/data/languages/greek.json": "287c0ec437b38772284ef9d3e4fb2fc3", "build/assets/ba_data/data/languages/greek.json": "287c0ec437b38772284ef9d3e4fb2fc3",
"build/assets/ba_data/data/languages/hindi.json": "8848f6b0caec0fcf9d85bc6e683809ec", "build/assets/ba_data/data/languages/hindi.json": "8848f6b0caec0fcf9d85bc6e683809ec",
"build/assets/ba_data/data/languages/hungarian.json": "796a290a8c44a1e7635208c2ff5fdc6e", "build/assets/ba_data/data/languages/hungarian.json": "796a290a8c44a1e7635208c2ff5fdc6e",
@ -445,12 +445,12 @@
"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": "d742f4a6d3c3555031102b21abdcbb5b", "build/assets/ba_data/data/languages/persian.json": "d742f4a6d3c3555031102b21abdcbb5b",
"build/assets/ba_data/data/languages/polish.json": "b9a58b70ed5e99d8b7fa2392b2eb0cda", "build/assets/ba_data/data/languages/polish.json": "b9a58b70ed5e99d8b7fa2392b2eb0cda",
"build/assets/ba_data/data/languages/portuguese.json": "556af4e8170356ad239412e1743e20d5", "build/assets/ba_data/data/languages/portuguese.json": "e3adc6c04486d21e84019a0b03ce11b1",
"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": "e120993371f52edd2d99f2236188933c", "build/assets/ba_data/data/languages/russian.json": "e120993371f52edd2d99f2236188933c",
"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": "80ea58bd3295a0252b7fdac9154aa22f", "build/assets/ba_data/data/languages/spanish.json": "1d14210b4eefb48130608bd0495b7900",
"build/assets/ba_data/data/languages/swedish.json": "5142a96597d17d8344be96a603da64ac", "build/assets/ba_data/data/languages/swedish.json": "5142a96597d17d8344be96a603da64ac",
"build/assets/ba_data/data/languages/tamil.json": "b4de1a2851afe4869c82e9acd94cd89c", "build/assets/ba_data/data/languages/tamil.json": "b4de1a2851afe4869c82e9acd94cd89c",
"build/assets/ba_data/data/languages/thai.json": "77755219bbf5fb7eea0d6b226684f403", "build/assets/ba_data/data/languages/thai.json": "77755219bbf5fb7eea0d6b226684f403",
@ -4060,50 +4060,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": "9fe23e06319e4e256b9fa88814a14afa", "build/prefab/full/linux_arm64_gui/debug/ballisticakit": "6135aeb242afaf9d1114810a67c89cec",
"build/prefab/full/linux_arm64_gui/release/ballisticakit": "4306acae21ce88235f9d1589086866e7", "build/prefab/full/linux_arm64_gui/release/ballisticakit": "bbbbb14d42ed6eb0c5eb56867b7fb870",
"build/prefab/full/linux_arm64_server/debug/dist/ballisticakit_headless": "75e4f7d3a3df67dedd079ec3f4441094", "build/prefab/full/linux_arm64_server/debug/dist/ballisticakit_headless": "cd28f9cc4652736a31c677fc4e5dbaf1",
"build/prefab/full/linux_arm64_server/release/dist/ballisticakit_headless": "bd5eda13f239b81886ac80596d6ade73", "build/prefab/full/linux_arm64_server/release/dist/ballisticakit_headless": "239c608cc52c0320210e56ad6abe57a5",
"build/prefab/full/linux_x86_64_gui/debug/ballisticakit": "0805235a92dd91f96d43ea54575eecac", "build/prefab/full/linux_x86_64_gui/debug/ballisticakit": "e76d67cacf1393d33796d6b6b1bf1413",
"build/prefab/full/linux_x86_64_gui/release/ballisticakit": "07589a61b11cbc5fca0bbc8b7fc1c955", "build/prefab/full/linux_x86_64_gui/release/ballisticakit": "a7eaa8dc4d859ef7a735483b04ccec4a",
"build/prefab/full/linux_x86_64_server/debug/dist/ballisticakit_headless": "f28629761060c8152168b6792b71adae", "build/prefab/full/linux_x86_64_server/debug/dist/ballisticakit_headless": "7a2eef42da34a35ddcc2fd7c66843b1b",
"build/prefab/full/linux_x86_64_server/release/dist/ballisticakit_headless": "1cfd1a33474cdb31834994f626385ed0", "build/prefab/full/linux_x86_64_server/release/dist/ballisticakit_headless": "694599ac6a967b2ed383b27bf8093e5b",
"build/prefab/full/mac_arm64_gui/debug/ballisticakit": "d50879a92d9d344c376f6f196d78d1be", "build/prefab/full/mac_arm64_gui/debug/ballisticakit": "c91cbab6a07affa22e0612210f8b807c",
"build/prefab/full/mac_arm64_gui/release/ballisticakit": "78cd0edf2698f197f2acd80ca364fae7", "build/prefab/full/mac_arm64_gui/release/ballisticakit": "d460f7a3909f92d5dbf752e4521a9fbc",
"build/prefab/full/mac_arm64_server/debug/dist/ballisticakit_headless": "d656f47118ebc3af57c40423cb258bc8", "build/prefab/full/mac_arm64_server/debug/dist/ballisticakit_headless": "0a0abfe75bc987e7b65a3cfa106e8353",
"build/prefab/full/mac_arm64_server/release/dist/ballisticakit_headless": "75df540b27779342a7c696e1bdbe593f", "build/prefab/full/mac_arm64_server/release/dist/ballisticakit_headless": "8f21405b29f2b2ab01323d711492cca0",
"build/prefab/full/mac_x86_64_gui/debug/ballisticakit": "76f0dfacaa9ea67e45e8ccf3bb3bc1c6", "build/prefab/full/mac_x86_64_gui/debug/ballisticakit": "96dc73e819f41f99a1b2dbb45f79d551",
"build/prefab/full/mac_x86_64_gui/release/ballisticakit": "2acc754bed825a9265e0621dc09899e0", "build/prefab/full/mac_x86_64_gui/release/ballisticakit": "c79ac51cd2deabb1c2d0acddeaf81c30",
"build/prefab/full/mac_x86_64_server/debug/dist/ballisticakit_headless": "62c2b6190de8784ea8750ea50e6a2304", "build/prefab/full/mac_x86_64_server/debug/dist/ballisticakit_headless": "f06ec14e8c3106be9df91af7da621dc9",
"build/prefab/full/mac_x86_64_server/release/dist/ballisticakit_headless": "e57358fd9a948a8ce82a54cdd5c766fc", "build/prefab/full/mac_x86_64_server/release/dist/ballisticakit_headless": "f389f9a7b1afc81f76787722340cfa9c",
"build/prefab/full/windows_x86_gui/debug/BallisticaKit.exe": "d36e3303e13049eae5e7ec19861d300e", "build/prefab/full/windows_x86_gui/debug/BallisticaKit.exe": "c7dab78aac11cb1430d8456d5d48107a",
"build/prefab/full/windows_x86_gui/release/BallisticaKit.exe": "46971a2ca1e3021e52ea5d0f4938d2ff", "build/prefab/full/windows_x86_gui/release/BallisticaKit.exe": "67e29852dfee2e63e179cfebf608ef26",
"build/prefab/full/windows_x86_server/debug/dist/BallisticaKitHeadless.exe": "19ebd36613cf62c4bd50e70b93371368", "build/prefab/full/windows_x86_server/debug/dist/BallisticaKitHeadless.exe": "9778f8faf91c9993fbf3015bd4554a87",
"build/prefab/full/windows_x86_server/release/dist/BallisticaKitHeadless.exe": "49ef5905b6e9e1a9caaed3d1c1da4ea5", "build/prefab/full/windows_x86_server/release/dist/BallisticaKitHeadless.exe": "73477bd15b9e3834314fd878c9e108d4",
"build/prefab/lib/linux_arm64_gui/debug/libballisticaplus.a": "c22901e06e88a55cce0b4e08bbf41a4c", "build/prefab/lib/linux_arm64_gui/debug/libballisticaplus.a": "fb9b8443c1b4cccad749df7d6328220f",
"build/prefab/lib/linux_arm64_gui/release/libballisticaplus.a": "a27963487e346338e4c216bd4fbb9e2a", "build/prefab/lib/linux_arm64_gui/release/libballisticaplus.a": "384fb7fd55ad5a6cdbb662da1ec402ab",
"build/prefab/lib/linux_arm64_server/debug/libballisticaplus.a": "c22901e06e88a55cce0b4e08bbf41a4c", "build/prefab/lib/linux_arm64_server/debug/libballisticaplus.a": "fb9b8443c1b4cccad749df7d6328220f",
"build/prefab/lib/linux_arm64_server/release/libballisticaplus.a": "a27963487e346338e4c216bd4fbb9e2a", "build/prefab/lib/linux_arm64_server/release/libballisticaplus.a": "384fb7fd55ad5a6cdbb662da1ec402ab",
"build/prefab/lib/linux_x86_64_gui/debug/libballisticaplus.a": "2663c888aec894656bd8c49932bd7729", "build/prefab/lib/linux_x86_64_gui/debug/libballisticaplus.a": "bc7d0811bcd87156ebf5292a38a1c350",
"build/prefab/lib/linux_x86_64_gui/release/libballisticaplus.a": "5e57d12a3cfcfbc47b0293c3cb9fdca9", "build/prefab/lib/linux_x86_64_gui/release/libballisticaplus.a": "bb32f45054b6999300bf8b41d6a4b402",
"build/prefab/lib/linux_x86_64_server/debug/libballisticaplus.a": "2663c888aec894656bd8c49932bd7729", "build/prefab/lib/linux_x86_64_server/debug/libballisticaplus.a": "bc7d0811bcd87156ebf5292a38a1c350",
"build/prefab/lib/linux_x86_64_server/release/libballisticaplus.a": "5e57d12a3cfcfbc47b0293c3cb9fdca9", "build/prefab/lib/linux_x86_64_server/release/libballisticaplus.a": "bb32f45054b6999300bf8b41d6a4b402",
"build/prefab/lib/mac_arm64_gui/debug/libballisticaplus.a": "0f7dbe6fb3e28a51904aa822b509da0f", "build/prefab/lib/mac_arm64_gui/debug/libballisticaplus.a": "8d9a1505bf397f4902baabed7c1cf438",
"build/prefab/lib/mac_arm64_gui/release/libballisticaplus.a": "081b766945b52460a4f1afc01faa0652", "build/prefab/lib/mac_arm64_gui/release/libballisticaplus.a": "f4d9c115e22dd81e36d1c5baeac8d848",
"build/prefab/lib/mac_arm64_server/debug/libballisticaplus.a": "0f7dbe6fb3e28a51904aa822b509da0f", "build/prefab/lib/mac_arm64_server/debug/libballisticaplus.a": "8d9a1505bf397f4902baabed7c1cf438",
"build/prefab/lib/mac_arm64_server/release/libballisticaplus.a": "081b766945b52460a4f1afc01faa0652", "build/prefab/lib/mac_arm64_server/release/libballisticaplus.a": "f4d9c115e22dd81e36d1c5baeac8d848",
"build/prefab/lib/mac_x86_64_gui/debug/libballisticaplus.a": "ad609c63f68417d5211bbfb23ce4affe", "build/prefab/lib/mac_x86_64_gui/debug/libballisticaplus.a": "fb72c92ec6ec0e1c8f4ced32abd86505",
"build/prefab/lib/mac_x86_64_gui/release/libballisticaplus.a": "852fe46c736082611a831a618923c241", "build/prefab/lib/mac_x86_64_gui/release/libballisticaplus.a": "131aab20cfe77fe89c3f452a855f1e68",
"build/prefab/lib/mac_x86_64_server/debug/libballisticaplus.a": "36fbda7829ed5c2862c34feb09b03402", "build/prefab/lib/mac_x86_64_server/debug/libballisticaplus.a": "ee10cdc9f9a861e2be0f1a208c0ca0fe",
"build/prefab/lib/mac_x86_64_server/release/libballisticaplus.a": "852fe46c736082611a831a618923c241", "build/prefab/lib/mac_x86_64_server/release/libballisticaplus.a": "131aab20cfe77fe89c3f452a855f1e68",
"build/prefab/lib/windows/Debug_Win32/BallisticaKitGenericPlus.lib": "38b4b5b85a9bafdb76222d0f0c962b06", "build/prefab/lib/windows/Debug_Win32/BallisticaKitGenericPlus.lib": "678fabc6dfd6f401ee8942d088ee9181",
"build/prefab/lib/windows/Debug_Win32/BallisticaKitGenericPlus.pdb": "9a8af3d217bcb0bacfaed4c30dd5f42e", "build/prefab/lib/windows/Debug_Win32/BallisticaKitGenericPlus.pdb": "e092d2aed8464a61a623d79ca25308d8",
"build/prefab/lib/windows/Debug_Win32/BallisticaKitHeadlessPlus.lib": "6d10ca306f60d66efb4942636e4955d6", "build/prefab/lib/windows/Debug_Win32/BallisticaKitHeadlessPlus.lib": "6b658f49be396ad645c5e57464739a3b",
"build/prefab/lib/windows/Debug_Win32/BallisticaKitHeadlessPlus.pdb": "1c65d36e4420ed79380dc8c041c94a8b", "build/prefab/lib/windows/Debug_Win32/BallisticaKitHeadlessPlus.pdb": "9d79a56403a6d806ff131a7de664dfa7",
"build/prefab/lib/windows/Release_Win32/BallisticaKitGenericPlus.lib": "9b1b72f3d41c89a6b06288be63e8f40a", "build/prefab/lib/windows/Release_Win32/BallisticaKitGenericPlus.lib": "e831a26d2c28e862d51e24393d158c99",
"build/prefab/lib/windows/Release_Win32/BallisticaKitGenericPlus.pdb": "e0f2eb8ea024bc88e999b9dc16317fd4", "build/prefab/lib/windows/Release_Win32/BallisticaKitGenericPlus.pdb": "46fe1c89bcc75c781729ec9e5491c610",
"build/prefab/lib/windows/Release_Win32/BallisticaKitHeadlessPlus.lib": "42be1225757328f432d91de950444ba0", "build/prefab/lib/windows/Release_Win32/BallisticaKitHeadlessPlus.lib": "9c6278d7df3ce4db2ffe7794a0fd35b7",
"build/prefab/lib/windows/Release_Win32/BallisticaKitHeadlessPlus.pdb": "05bc2832cc0c9fba308668fc1a6d3b0f", "build/prefab/lib/windows/Release_Win32/BallisticaKitHeadlessPlus.pdb": "110c35a17b462864075800756b5e541a",
"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": "28323912b56ec07701eda3d41a6a4101", "src/assets/ba_data/python/babase/_mgen/enums.py": "28323912b56ec07701eda3d41a6a4101",
"src/ballistica/base/mgen/pyembed/binding_base.inc": "72bfed2cce8ff19741989dec28302f3f", "src/ballistica/base/mgen/pyembed/binding_base.inc": "72bfed2cce8ff19741989dec28302f3f",
@ -4112,7 +4112,7 @@
"src/ballistica/core/mgen/pyembed/binding_core.inc": "9d0a3c9636138e35284923e0c8311c69", "src/ballistica/core/mgen/pyembed/binding_core.inc": "9d0a3c9636138e35284923e0c8311c69",
"src/ballistica/core/mgen/pyembed/env.inc": "8be46e5818f360d10b7b0224a9e91d07", "src/ballistica/core/mgen/pyembed/env.inc": "8be46e5818f360d10b7b0224a9e91d07",
"src/ballistica/core/mgen/python_modules_monolithic.h": "fb967ed1c7db0c77d8deb4f00a7103c5", "src/ballistica/core/mgen/python_modules_monolithic.h": "fb967ed1c7db0c77d8deb4f00a7103c5",
"src/ballistica/scene_v1/mgen/pyembed/binding_scene_v1.inc": "d80f970053099b3044204bfe29ddefce", "src/ballistica/scene_v1/mgen/pyembed/binding_scene_v1.inc": "c25b263f2a31fb5ebe057db07d144879",
"src/ballistica/template_fs/mgen/pyembed/binding_template_fs.inc": "44a45492db057bf7f7158c3b0fa11f0f", "src/ballistica/template_fs/mgen/pyembed/binding_template_fs.inc": "44a45492db057bf7f7158c3b0fa11f0f",
"src/ballistica/ui_v1/mgen/pyembed/binding_ui_v1.inc": "8f4c2070174bdc2fbf735180394d7b3a" "src/ballistica/ui_v1/mgen/pyembed/binding_ui_v1.inc": "f5f054050d2b2fcd3763a4833fb32269"
} }

178
.github/workflows/cd.yml vendored Normal file
View File

@ -0,0 +1,178 @@
name: CD
on:
# Run on pushes and pull-requests
push:
pull_request:
jobs:
make_linux_x86_64_gui_debug_build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: Set up Python
uses: actions/setup-python@v4
with:
python-version: '3.11'
- name: Install pip requirements
run: tools/pcommand install_pip_reqs
- name: Make the build
run: make prefab-gui-debug-build
- name: Upload the build
uses: actions/upload-artifact@v3
with:
name: linux_x86_64_gui_(debug)
path: build/prefab/full/linux_x86_64_gui
make_linux_x86_64_server_debug_build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: Set up Python
uses: actions/setup-python@v4
with:
python-version: '3.11'
- name: Install pip requirements
run: tools/pcommand install_pip_reqs
- name: Make the build
run: make prefab-server-debug-build
- name: Upload the build
uses: actions/upload-artifact@v3
with:
name: linux_x86_64_server_(debug)
path: build/prefab/full/linux_x86_64_server
make_linux_arm64_gui_debug_build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: Set up Python
uses: actions/setup-python@v4
with:
python-version: '3.11'
- name: Install pip requirements
run: tools/pcommand install_pip_reqs
- name: Make the build
run: make prefab-linux-arm64-gui-debug-build
- name: Upload the build
uses: actions/upload-artifact@v3
with:
name: linux_arm64_gui_(debug)
path: build/prefab/full/linux_arm64_gui
make_linux_arm64_server_debug_build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: Set up Python
uses: actions/setup-python@v4
with:
python-version: '3.11'
- name: Install pip requirements
run: tools/pcommand install_pip_reqs
- name: Make the build
run: make prefab-linux-arm64-server-debug-build
- name: Upload the build
uses: actions/upload-artifact@v3
with:
name: linux_arm64_server_(debug)
path: build/prefab/full/linux_arm64_server
make_mac_x86_64_gui_debug_build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: Set up Python
uses: actions/setup-python@v4
with:
python-version: '3.11'
- name: Install pip requirements
run: tools/pcommand install_pip_reqs
- name: Make the build
run: make prefab-mac-x86-64-gui-debug-build
- name: Upload the build
uses: actions/upload-artifact@v3
with:
name: mac_x86_64_gui_(debug)
path: build/prefab/full/mac_x86_64_gui
make_mac_x86_64_server_debug_build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: Set up Python
uses: actions/setup-python@v4
with:
python-version: '3.11'
- name: Install pip requirements
run: tools/pcommand install_pip_reqs
- name: Make the build
run: make prefab-mac-x86-64-server-debug-build
- name: Upload the build
uses: actions/upload-artifact@v3
with:
name: mac_x86_64_server_(debug)
path: build/prefab/full/mac_x86_64_server
make_mac_arm64_gui_debug_build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: Set up Python
uses: actions/setup-python@v4
with:
python-version: '3.11'
- name: Install pip requirements
run: tools/pcommand install_pip_reqs
- name: Make the build
run: make prefab-mac-arm64-gui-debug-build
- name: Upload the build
uses: actions/upload-artifact@v3
with:
name: mac_arm64_gui_(debug)
path: build/prefab/full/mac_arm64_gui
make_mac_arm64_server_debug_build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: Set up Python
uses: actions/setup-python@v4
with:
python-version: '3.11'
- name: Install pip requirements
run: tools/pcommand install_pip_reqs
- name: Make the build
run: make prefab-mac-arm64-server-debug-build
- name: Upload the build
uses: actions/upload-artifact@v3
with:
name: mac_arm64_server_(debug)
path: build/prefab/full/mac_arm64_server
make_windows_x86_gui_debug_build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: Set up Python
uses: actions/setup-python@v4
with:
python-version: '3.11'
- name: Install pip requirements
run: tools/pcommand install_pip_reqs
- name: Make the build
run: make prefab-windows-x86-gui-debug-build
- name: Upload the build
uses: actions/upload-artifact@v3
with:
name: windows_x86_gui_(debug)
path: build/prefab/full/windows_x86_gui
make_windows_x86_server_debug_build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: Set up Python
uses: actions/setup-python@v4
with:
python-version: '3.11'
- name: Install pip requirements
run: tools/pcommand install_pip_reqs
- name: Make the build
run: make prefab-windows-x86-server-debug-build
- name: Upload the build
uses: actions/upload-artifact@v3
with:
name: windows_x86_server_(debug)
path: build/prefab/full/windows_x86_server

View File

@ -21,7 +21,6 @@
<excludeFolder url="file://$MODULE_DIR$/ballisticakit-android" /> <excludeFolder url="file://$MODULE_DIR$/ballisticakit-android" />
<excludeFolder url="file://$MODULE_DIR$/ballisticakit-cmake" /> <excludeFolder url="file://$MODULE_DIR$/ballisticakit-cmake" />
<excludeFolder url="file://$MODULE_DIR$/ballisticakit-ios.xcodeproj" /> <excludeFolder url="file://$MODULE_DIR$/ballisticakit-ios.xcodeproj" />
<excludeFolder url="file://$MODULE_DIR$/ballisticakit-mac.xcodeproj" />
<excludeFolder url="file://$MODULE_DIR$/ballisticakit-windows" /> <excludeFolder url="file://$MODULE_DIR$/ballisticakit-windows" />
<excludeFolder url="file://$MODULE_DIR$/ballisticakit-windows-oculus" /> <excludeFolder url="file://$MODULE_DIR$/ballisticakit-windows-oculus" />
<excludeFolder url="file://$MODULE_DIR$/ballisticakit-xcode" /> <excludeFolder url="file://$MODULE_DIR$/ballisticakit-xcode" />

View File

@ -1,4 +1,10 @@
### 1.7.30 (build 21636, api 8, 2023-11-30) ### 1.7.31 (build 21707, api 8, 2023-12-13)
- Added `bascenev1.get_connection_to_host_info_2()` which is an improved
type-safe version of `bascenev1.get_connection_to_host_info()`.
- There is now a link to the official Discord server in the About section
(thanks EraOSBeta!).
### 1.7.30 (build 21697, api 8, 2023-12-08)
- Continued work on the big 1.7.28 update. - Continued work on the big 1.7.28 update.
- Got the Android version back up and running. There's been lots of cleanup and - Got the Android version back up and running. There's been lots of cleanup and
simplification on the Android layer, cleaning out years of cruft. This should simplification on the Android layer, cleaning out years of cruft. This should
@ -15,15 +21,34 @@
- Bundled Android Python has been bumped to version 3.11.6. - Bundled Android Python has been bumped to version 3.11.6.
- Android app suspend behavior has been revamped. The app should stay running - Android app suspend behavior has been revamped. The app should stay running
more often and be quicker to respond when dialogs or other activities more often and be quicker to respond when dialogs or other activities
temporarily pop up in front of it. Please holler if you run into strange side temporarily pop up in front of it. This also allows it to continue playing
music over other activities such as Google Play Games
Achievements/Leaderboards screens. Please holler if you run into strange side
effects such as the app continuing to play audio when it should not be. effects such as the app continuing to play audio when it should not be.
- Modernized the Android fullscreen setup code when running in Android 11 or
newer. The game should now use the whole screen area, including the area
around notches or camera cutouts. Please holler if you are seeing any problems
related to this.
- (build 21626) Fixed a bug where click/tap locations were incorrect on some - (build 21626) Fixed a bug where click/tap locations were incorrect on some
builds when tv-border was on (Thanks for the heads-up Loup(Dliwk's fan)!). builds when tv-border was on (Thanks for the heads-up Loup(Dliwk's fan)!).
- (build 21631) Fixes an issue where '^^^^^^^^^^^^^' lines in stack traces could - (build 21631) Fixes an issue where '^^^^^^^^^^^^^' lines in stack traces could
get chopped into tiny bits each on their own line in the dev console. get chopped into tiny bits each on their own line in the dev console.
- Fixed a longstanding issue where multiple key presses simultaneously could - Hopefully finally fixed a longstanding issue where obscure cases such as
cause multiple windows to pop up where only one is expected. Please holler if multiple key presses simultaneously could cause multiple main menu windows to
you still see this problem happening anywhere. pop up. Please holler if you still see this problem happening anywhere. Also
added a few related safety checks and warnings to help ensure UI code is free
from such problems going forward. To make sure your custom UIs are behaving
well in this system, do the following two things: 1) any time you call
`set_main_menu_window()`, pass your existing main menu window root widget as
`from_window`. 2) In any call that can lead to you switching the main menu
window, check if your root widget is dead or transitioning out first and abort
if it is. See any window in `ui_v1_lib` for examples.
- (build 21691) Fixed a bug causing touches to not register in some cases on
newer Android devices. (Huge thanks to JESWIN A J for helping me track that
down!).
- Temporarily removed the pause-the-game-when-backgrounded behavior for locally
hosted games, mainly due to the code being hacky. Will try to restore this
functionality in a cleaner way soon.
### 1.7.29 (build 21619, api 8, 2023-11-21) ### 1.7.29 (build 21619, api 8, 2023-11-21)

View File

@ -6,7 +6,7 @@ height="50" alt="logo">
***-ica***: collection of things relating to a specific theme. ***-ica***: collection of things relating to a specific theme.
[![](https://github.com/efroemling/ballistica/actions/workflows/ci.yml/badge.svg)](https://github.com/efroemling/ballistica/actions/workflows/ci.yml) [![](https://github.com/efroemling/ballistica/actions/workflows/ci.yml/badge.svg)](https://github.com/efroemling/ballistica/actions/workflows/ci.yml) [![](https://github.com/efroemling/ballistica/actions/workflows/cd.yml/badge.svg)](https://github.com/efroemling/ballistica/actions/workflows/cd.yml)
The Ballistica project is the foundation for The Ballistica project is the foundation for
[BombSquad](https://www.froemling.net/apps/bombsquad) and potentially other [BombSquad](https://www.froemling.net/apps/bombsquad) and potentially other

View File

@ -14,7 +14,6 @@
<file path="$PROJECT_DIR$/../ballisticakit-android" /> <file path="$PROJECT_DIR$/../ballisticakit-android" />
<file path="$PROJECT_DIR$/.idea" /> <file path="$PROJECT_DIR$/.idea" />
<file path="$PROJECT_DIR$/../ballisticakit-ios.xcodeproj" /> <file path="$PROJECT_DIR$/../ballisticakit-ios.xcodeproj" />
<file path="$PROJECT_DIR$/../ballisticakit-mac.xcodeproj" />
<file path="$PROJECT_DIR$/../ballisticakit-windows" /> <file path="$PROJECT_DIR$/../ballisticakit-windows" />
<file path="$PROJECT_DIR$/../ballisticakit-xcode" /> <file path="$PROJECT_DIR$/../ballisticakit-xcode" />
<file path="$PROJECT_DIR$/../build" /> <file path="$PROJECT_DIR$/../build" />

View File

@ -152,7 +152,6 @@ ctx.filter_dirs = {
'ballisticakit-cmake', 'ballisticakit-cmake',
'ballisticakit-xcode/BallisticaKit.xcodeproj', 'ballisticakit-xcode/BallisticaKit.xcodeproj',
'ballisticakit-ios.xcodeproj', 'ballisticakit-ios.xcodeproj',
'ballisticakit-mac.xcodeproj',
'config', 'config',
'src/assets/pdoc', 'src/assets/pdoc',
} }
@ -195,6 +194,7 @@ ctx.filter_file_names = {
'.projectile', '.projectile',
'.editorconfig', '.editorconfig',
'ci.yml', 'ci.yml',
'cd.yml',
'LICENSE', 'LICENSE',
'cloudtool', 'cloudtool',
'bacloud', 'bacloud',

View File

@ -20,7 +20,6 @@
"ba_data/python/babase/__pycache__/_error.cpython-311.opt-1.pyc", "ba_data/python/babase/__pycache__/_error.cpython-311.opt-1.pyc",
"ba_data/python/babase/__pycache__/_general.cpython-311.opt-1.pyc", "ba_data/python/babase/__pycache__/_general.cpython-311.opt-1.pyc",
"ba_data/python/babase/__pycache__/_hooks.cpython-311.opt-1.pyc", "ba_data/python/babase/__pycache__/_hooks.cpython-311.opt-1.pyc",
"ba_data/python/babase/__pycache__/_keyboard.cpython-311.opt-1.pyc",
"ba_data/python/babase/__pycache__/_language.cpython-311.opt-1.pyc", "ba_data/python/babase/__pycache__/_language.cpython-311.opt-1.pyc",
"ba_data/python/babase/__pycache__/_login.cpython-311.opt-1.pyc", "ba_data/python/babase/__pycache__/_login.cpython-311.opt-1.pyc",
"ba_data/python/babase/__pycache__/_math.cpython-311.opt-1.pyc", "ba_data/python/babase/__pycache__/_math.cpython-311.opt-1.pyc",
@ -50,7 +49,6 @@
"ba_data/python/babase/_error.py", "ba_data/python/babase/_error.py",
"ba_data/python/babase/_general.py", "ba_data/python/babase/_general.py",
"ba_data/python/babase/_hooks.py", "ba_data/python/babase/_hooks.py",
"ba_data/python/babase/_keyboard.py",
"ba_data/python/babase/_language.py", "ba_data/python/babase/_language.py",
"ba_data/python/babase/_login.py", "ba_data/python/babase/_login.py",
"ba_data/python/babase/_math.py", "ba_data/python/babase/_math.py",
@ -152,6 +150,7 @@
"ba_data/python/bascenev1/__pycache__/_messages.cpython-311.opt-1.pyc", "ba_data/python/bascenev1/__pycache__/_messages.cpython-311.opt-1.pyc",
"ba_data/python/bascenev1/__pycache__/_multiteamsession.cpython-311.opt-1.pyc", "ba_data/python/bascenev1/__pycache__/_multiteamsession.cpython-311.opt-1.pyc",
"ba_data/python/bascenev1/__pycache__/_music.cpython-311.opt-1.pyc", "ba_data/python/bascenev1/__pycache__/_music.cpython-311.opt-1.pyc",
"ba_data/python/bascenev1/__pycache__/_net.cpython-311.opt-1.pyc",
"ba_data/python/bascenev1/__pycache__/_nodeactor.cpython-311.opt-1.pyc", "ba_data/python/bascenev1/__pycache__/_nodeactor.cpython-311.opt-1.pyc",
"ba_data/python/bascenev1/__pycache__/_player.cpython-311.opt-1.pyc", "ba_data/python/bascenev1/__pycache__/_player.cpython-311.opt-1.pyc",
"ba_data/python/bascenev1/__pycache__/_playlist.cpython-311.opt-1.pyc", "ba_data/python/bascenev1/__pycache__/_playlist.cpython-311.opt-1.pyc",
@ -186,6 +185,7 @@
"ba_data/python/bascenev1/_messages.py", "ba_data/python/bascenev1/_messages.py",
"ba_data/python/bascenev1/_multiteamsession.py", "ba_data/python/bascenev1/_multiteamsession.py",
"ba_data/python/bascenev1/_music.py", "ba_data/python/bascenev1/_music.py",
"ba_data/python/bascenev1/_net.py",
"ba_data/python/bascenev1/_nodeactor.py", "ba_data/python/bascenev1/_nodeactor.py",
"ba_data/python/bascenev1/_player.py", "ba_data/python/bascenev1/_player.py",
"ba_data/python/bascenev1/_playlist.py", "ba_data/python/bascenev1/_playlist.py",
@ -352,10 +352,12 @@
"ba_data/python/bauiv1/__init__.py", "ba_data/python/bauiv1/__init__.py",
"ba_data/python/bauiv1/__pycache__/__init__.cpython-311.opt-1.pyc", "ba_data/python/bauiv1/__pycache__/__init__.cpython-311.opt-1.pyc",
"ba_data/python/bauiv1/__pycache__/_hooks.cpython-311.opt-1.pyc", "ba_data/python/bauiv1/__pycache__/_hooks.cpython-311.opt-1.pyc",
"ba_data/python/bauiv1/__pycache__/_keyboard.cpython-311.opt-1.pyc",
"ba_data/python/bauiv1/__pycache__/_subsystem.cpython-311.opt-1.pyc", "ba_data/python/bauiv1/__pycache__/_subsystem.cpython-311.opt-1.pyc",
"ba_data/python/bauiv1/__pycache__/_uitypes.cpython-311.opt-1.pyc", "ba_data/python/bauiv1/__pycache__/_uitypes.cpython-311.opt-1.pyc",
"ba_data/python/bauiv1/__pycache__/onscreenkeyboard.cpython-311.opt-1.pyc", "ba_data/python/bauiv1/__pycache__/onscreenkeyboard.cpython-311.opt-1.pyc",
"ba_data/python/bauiv1/_hooks.py", "ba_data/python/bauiv1/_hooks.py",
"ba_data/python/bauiv1/_keyboard.py",
"ba_data/python/bauiv1/_subsystem.py", "ba_data/python/bauiv1/_subsystem.py",
"ba_data/python/bauiv1/_uitypes.py", "ba_data/python/bauiv1/_uitypes.py",
"ba_data/python/bauiv1/onscreenkeyboard.py", "ba_data/python/bauiv1/onscreenkeyboard.py",

View File

@ -178,7 +178,6 @@ SCRIPT_TARGETS_PY_PUBLIC = \
$(BUILD_DIR)/ba_data/python/babase/_error.py \ $(BUILD_DIR)/ba_data/python/babase/_error.py \
$(BUILD_DIR)/ba_data/python/babase/_general.py \ $(BUILD_DIR)/ba_data/python/babase/_general.py \
$(BUILD_DIR)/ba_data/python/babase/_hooks.py \ $(BUILD_DIR)/ba_data/python/babase/_hooks.py \
$(BUILD_DIR)/ba_data/python/babase/_keyboard.py \
$(BUILD_DIR)/ba_data/python/babase/_language.py \ $(BUILD_DIR)/ba_data/python/babase/_language.py \
$(BUILD_DIR)/ba_data/python/babase/_login.py \ $(BUILD_DIR)/ba_data/python/babase/_login.py \
$(BUILD_DIR)/ba_data/python/babase/_math.py \ $(BUILD_DIR)/ba_data/python/babase/_math.py \
@ -237,6 +236,7 @@ SCRIPT_TARGETS_PY_PUBLIC = \
$(BUILD_DIR)/ba_data/python/bascenev1/_messages.py \ $(BUILD_DIR)/ba_data/python/bascenev1/_messages.py \
$(BUILD_DIR)/ba_data/python/bascenev1/_multiteamsession.py \ $(BUILD_DIR)/ba_data/python/bascenev1/_multiteamsession.py \
$(BUILD_DIR)/ba_data/python/bascenev1/_music.py \ $(BUILD_DIR)/ba_data/python/bascenev1/_music.py \
$(BUILD_DIR)/ba_data/python/bascenev1/_net.py \
$(BUILD_DIR)/ba_data/python/bascenev1/_nodeactor.py \ $(BUILD_DIR)/ba_data/python/bascenev1/_nodeactor.py \
$(BUILD_DIR)/ba_data/python/bascenev1/_player.py \ $(BUILD_DIR)/ba_data/python/bascenev1/_player.py \
$(BUILD_DIR)/ba_data/python/bascenev1/_playlist.py \ $(BUILD_DIR)/ba_data/python/bascenev1/_playlist.py \
@ -326,6 +326,7 @@ SCRIPT_TARGETS_PY_PUBLIC = \
$(BUILD_DIR)/ba_data/python/batemplatefs/_subsystem.py \ $(BUILD_DIR)/ba_data/python/batemplatefs/_subsystem.py \
$(BUILD_DIR)/ba_data/python/bauiv1/__init__.py \ $(BUILD_DIR)/ba_data/python/bauiv1/__init__.py \
$(BUILD_DIR)/ba_data/python/bauiv1/_hooks.py \ $(BUILD_DIR)/ba_data/python/bauiv1/_hooks.py \
$(BUILD_DIR)/ba_data/python/bauiv1/_keyboard.py \
$(BUILD_DIR)/ba_data/python/bauiv1/_subsystem.py \ $(BUILD_DIR)/ba_data/python/bauiv1/_subsystem.py \
$(BUILD_DIR)/ba_data/python/bauiv1/_uitypes.py \ $(BUILD_DIR)/ba_data/python/bauiv1/_uitypes.py \
$(BUILD_DIR)/ba_data/python/bauiv1/onscreenkeyboard.py \ $(BUILD_DIR)/ba_data/python/bauiv1/onscreenkeyboard.py \
@ -452,7 +453,6 @@ SCRIPT_TARGETS_PYC_PUBLIC = \
$(BUILD_DIR)/ba_data/python/babase/__pycache__/_error.cpython-311.opt-1.pyc \ $(BUILD_DIR)/ba_data/python/babase/__pycache__/_error.cpython-311.opt-1.pyc \
$(BUILD_DIR)/ba_data/python/babase/__pycache__/_general.cpython-311.opt-1.pyc \ $(BUILD_DIR)/ba_data/python/babase/__pycache__/_general.cpython-311.opt-1.pyc \
$(BUILD_DIR)/ba_data/python/babase/__pycache__/_hooks.cpython-311.opt-1.pyc \ $(BUILD_DIR)/ba_data/python/babase/__pycache__/_hooks.cpython-311.opt-1.pyc \
$(BUILD_DIR)/ba_data/python/babase/__pycache__/_keyboard.cpython-311.opt-1.pyc \
$(BUILD_DIR)/ba_data/python/babase/__pycache__/_language.cpython-311.opt-1.pyc \ $(BUILD_DIR)/ba_data/python/babase/__pycache__/_language.cpython-311.opt-1.pyc \
$(BUILD_DIR)/ba_data/python/babase/__pycache__/_login.cpython-311.opt-1.pyc \ $(BUILD_DIR)/ba_data/python/babase/__pycache__/_login.cpython-311.opt-1.pyc \
$(BUILD_DIR)/ba_data/python/babase/__pycache__/_math.cpython-311.opt-1.pyc \ $(BUILD_DIR)/ba_data/python/babase/__pycache__/_math.cpython-311.opt-1.pyc \
@ -511,6 +511,7 @@ SCRIPT_TARGETS_PYC_PUBLIC = \
$(BUILD_DIR)/ba_data/python/bascenev1/__pycache__/_messages.cpython-311.opt-1.pyc \ $(BUILD_DIR)/ba_data/python/bascenev1/__pycache__/_messages.cpython-311.opt-1.pyc \
$(BUILD_DIR)/ba_data/python/bascenev1/__pycache__/_multiteamsession.cpython-311.opt-1.pyc \ $(BUILD_DIR)/ba_data/python/bascenev1/__pycache__/_multiteamsession.cpython-311.opt-1.pyc \
$(BUILD_DIR)/ba_data/python/bascenev1/__pycache__/_music.cpython-311.opt-1.pyc \ $(BUILD_DIR)/ba_data/python/bascenev1/__pycache__/_music.cpython-311.opt-1.pyc \
$(BUILD_DIR)/ba_data/python/bascenev1/__pycache__/_net.cpython-311.opt-1.pyc \
$(BUILD_DIR)/ba_data/python/bascenev1/__pycache__/_nodeactor.cpython-311.opt-1.pyc \ $(BUILD_DIR)/ba_data/python/bascenev1/__pycache__/_nodeactor.cpython-311.opt-1.pyc \
$(BUILD_DIR)/ba_data/python/bascenev1/__pycache__/_player.cpython-311.opt-1.pyc \ $(BUILD_DIR)/ba_data/python/bascenev1/__pycache__/_player.cpython-311.opt-1.pyc \
$(BUILD_DIR)/ba_data/python/bascenev1/__pycache__/_playlist.cpython-311.opt-1.pyc \ $(BUILD_DIR)/ba_data/python/bascenev1/__pycache__/_playlist.cpython-311.opt-1.pyc \
@ -600,6 +601,7 @@ SCRIPT_TARGETS_PYC_PUBLIC = \
$(BUILD_DIR)/ba_data/python/batemplatefs/__pycache__/_subsystem.cpython-311.opt-1.pyc \ $(BUILD_DIR)/ba_data/python/batemplatefs/__pycache__/_subsystem.cpython-311.opt-1.pyc \
$(BUILD_DIR)/ba_data/python/bauiv1/__pycache__/__init__.cpython-311.opt-1.pyc \ $(BUILD_DIR)/ba_data/python/bauiv1/__pycache__/__init__.cpython-311.opt-1.pyc \
$(BUILD_DIR)/ba_data/python/bauiv1/__pycache__/_hooks.cpython-311.opt-1.pyc \ $(BUILD_DIR)/ba_data/python/bauiv1/__pycache__/_hooks.cpython-311.opt-1.pyc \
$(BUILD_DIR)/ba_data/python/bauiv1/__pycache__/_keyboard.cpython-311.opt-1.pyc \
$(BUILD_DIR)/ba_data/python/bauiv1/__pycache__/_subsystem.cpython-311.opt-1.pyc \ $(BUILD_DIR)/ba_data/python/bauiv1/__pycache__/_subsystem.cpython-311.opt-1.pyc \
$(BUILD_DIR)/ba_data/python/bauiv1/__pycache__/_uitypes.cpython-311.opt-1.pyc \ $(BUILD_DIR)/ba_data/python/bauiv1/__pycache__/_uitypes.cpython-311.opt-1.pyc \
$(BUILD_DIR)/ba_data/python/bauiv1/__pycache__/onscreenkeyboard.cpython-311.opt-1.pyc \ $(BUILD_DIR)/ba_data/python/bauiv1/__pycache__/onscreenkeyboard.cpython-311.opt-1.pyc \

View File

@ -154,7 +154,6 @@ from babase._general import (
getclass, getclass,
get_type_name, get_type_name,
) )
from babase._keyboard import Keyboard
from babase._language import Lstr, LanguageSubsystem from babase._language import Lstr, LanguageSubsystem
from babase._login import LoginAdapter, LoginInfo from babase._login import LoginAdapter, LoginInfo
@ -261,7 +260,6 @@ __all__ = [
'is_point_in_box', 'is_point_in_box',
'is_running_on_fire_tv', 'is_running_on_fire_tv',
'is_xcode_build', 'is_xcode_build',
'Keyboard',
'LanguageSubsystem', 'LanguageSubsystem',
'lock_all_input', 'lock_all_input',
'LoginAdapter', 'LoginAdapter',

View File

@ -186,9 +186,10 @@ class AccountV2Subsystem:
cfgkey = 'ImplicitLoginStates' cfgkey = 'ImplicitLoginStates'
cfgdict = _babase.app.config.setdefault(cfgkey, {}) cfgdict = _babase.app.config.setdefault(cfgkey, {})
# Store which (if any) adapter is currently implicitly signed in. # Store which (if any) adapter is currently implicitly signed
# Making the assumption there will only ever be one implicit # in. Making the assumption there will only ever be one implicit
# adapter at a time; may need to update this if that changes. # adapter at a time; may need to revisit this logic if that
# changes.
prev_state = cfgdict.get(login_type.value) prev_state = cfgdict.get(login_type.value)
if state is None: if state is None:
self._implicit_signed_in_adapter = None self._implicit_signed_in_adapter = None
@ -296,9 +297,8 @@ class AccountV2Subsystem:
# Consider this an 'explicit' sign in because the # Consider this an 'explicit' sign in because the
# implicit-login state change presumably was triggered # implicit-login state change presumably was triggered
# by some user action (signing in, signing out, or # by some user action (signing in, signing out, or
# switching accounts via the back-end). # switching accounts via the back-end). NOTE: should
# NOTE: should test case where we don't have # test case where we don't have connectivity here.
# connectivity here.
if plus.cloud.is_connected(): if plus.cloud.is_connected():
if DEBUG_LOG: if DEBUG_LOG:
logging.debug( logging.debug(

View File

@ -229,6 +229,15 @@ class App:
self.lang = LanguageSubsystem() self.lang = LanguageSubsystem()
self.plugins = PluginSubsystem() self.plugins = PluginSubsystem()
@property
def active(self) -> bool:
"""Whether the app is currently front and center.
This will be False when the app is hidden, other activities
are covering it, etc. (depending on the platform).
"""
return _babase.app_is_active()
@property @property
def aioloop(self) -> asyncio.AbstractEventLoop: def aioloop(self) -> asyncio.AbstractEventLoop:
"""The logic thread's asyncio event loop. """The logic thread's asyncio event loop.

View File

@ -31,6 +31,7 @@ class AppMode:
AppExperience associated with the AppMode must be supported by AppExperience associated with the AppMode must be supported by
the current app and runtime environment. the current app and runtime environment.
""" """
# FIXME: check AppExperience.
return cls._supports_intent(intent) return cls._supports_intent(intent)
@classmethod @classmethod

View File

@ -325,7 +325,7 @@ def dump_app_state(
) )
def log_dumped_app_state() -> None: def log_dumped_app_state(from_previous_run: bool = False) -> None:
"""If an app-state dump exists, log it and clear it. No-op otherwise.""" """If an app-state dump exists, log it and clear it. No-op otherwise."""
try: try:
@ -352,8 +352,13 @@ def log_dumped_app_state() -> None:
metadata = dataclass_from_json(DumpedAppStateMetadata, appstatedata) metadata = dataclass_from_json(DumpedAppStateMetadata, appstatedata)
header = (
'Found app state dump from previous app run'
if from_previous_run
else 'App state dump'
)
out += ( out += (
f'App state dump:\nReason: {metadata.reason}\n' f'{header}:\nReason: {metadata.reason}\n'
f'Time: {metadata.app_time:.2f}' f'Time: {metadata.app_time:.2f}'
) )
tbpath = os.path.join( tbpath = os.path.join(
@ -383,7 +388,7 @@ class AppHealthMonitor(AppSubsystem):
def on_app_loading(self) -> None: def on_app_loading(self) -> None:
# If any traceback dumps happened last run, log and clear them. # If any traceback dumps happened last run, log and clear them.
log_dumped_app_state() log_dumped_app_state(from_previous_run=True)
def _app_monitor_thread_main(self) -> None: def _app_monitor_thread_main(self) -> None:
_babase.set_thread_name('ballistica app-monitor') _babase.set_thread_name('ballistica app-monitor')

View File

@ -145,7 +145,7 @@ class LoginAdapter:
is actually being used by the app. It should therefore register is actually being used by the app. It should therefore register
unlocked achievements, leaderboard scores, allow viewing native unlocked achievements, leaderboard scores, allow viewing native
UIs, etc. When not active it should ignore everything and behave UIs, etc. When not active it should ignore everything and behave
as if logged out, even if it technically is still logged in. as if signed out, even if it technically is still signed in.
""" """
assert _babase.in_logic_thread() assert _babase.in_logic_thread()
del active # Unused. del active # Unused.

View File

@ -24,6 +24,8 @@ if TYPE_CHECKING:
# instead of these or to make the meta system aware of arbitrary classes. # instead of these or to make the meta system aware of arbitrary classes.
EXPORT_CLASS_NAME_SHORTCUTS: dict[str, str] = { EXPORT_CLASS_NAME_SHORTCUTS: dict[str, str] = {
'plugin': 'babase.Plugin', 'plugin': 'babase.Plugin',
# DEPRECATED as of 12/2023. Currently am warning if finding these
# but should take this out eventually.
'keyboard': 'babase.Keyboard', 'keyboard': 'babase.Keyboard',
} }
@ -414,30 +416,27 @@ class DirectoryScan:
if export_class_name is not None: if export_class_name is not None:
classname = modulename + '.' + export_class_name classname = modulename + '.' + export_class_name
# Since we'll soon have multiple versions of 'game' # Migrating away from the 'keyboard' name shortcut
# classes we need to migrate people to using base # since it's specific to bauiv1; warn if we find it.
# class names for them. if exporttypestr == 'keyboard':
if exporttypestr == 'game':
logging.warning( logging.warning(
"metascan: %s:%d: '# ba_meta export" "metascan: %s:%d: '# ba_meta export"
" game' tag should be replaced by '# ba_meta" " keyboard' tag should be replaced by '# ba_meta"
" export bascenev1.GameActivity'.", " export bauiv1.Keyboard'.",
subpath, subpath,
lindex + 1, lindex + 1,
) )
self.results.announce_errors_occurred = True self.results.announce_errors_occurred = True
else:
# If export type is one of our shortcuts, sub in the # If export type is one of our shortcuts, sub in the
# actual class path. Otherwise assume its a classpath # actual class path. Otherwise assume its a classpath
# itself. # itself.
exporttype = EXPORT_CLASS_NAME_SHORTCUTS.get( exporttype = EXPORT_CLASS_NAME_SHORTCUTS.get(exporttypestr)
exporttypestr if exporttype is None:
) exporttype = exporttypestr
if exporttype is None: self.results.exports.setdefault(exporttype, []).append(
exporttype = exporttypestr classname
self.results.exports.setdefault(exporttype, []).append( )
classname
)
def _get_export_class_name( def _get_export_class_name(
self, subpath: Path, lines: list[str], lindex: int self, subpath: Path, lines: list[str], lindex: int

View File

@ -4,6 +4,8 @@
from __future__ import annotations from __future__ import annotations
import time import time
import asyncio
import logging
from typing import TYPE_CHECKING from typing import TYPE_CHECKING
import babase import babase
@ -31,6 +33,7 @@ class AdsSubsystem:
self.last_in_game_ad_remove_message_show_time: float | None = None self.last_in_game_ad_remove_message_show_time: float | None = None
self.last_ad_completion_time: float | None = None self.last_ad_completion_time: float | None = None
self.last_ad_was_short = False self.last_ad_was_short = False
self._fallback_task: asyncio.Task | None = None
def do_remove_in_game_ads_message(self) -> None: def do_remove_in_game_ads_message(self) -> None:
"""(internal)""" """(internal)"""
@ -94,7 +97,7 @@ class AdsSubsystem:
show = True show = True
# No ads without net-connections, etc. # No ads without net-connections, etc.
if not bauiv1.can_show_ad(): if not plus.can_show_ad():
show = False show = False
if classic.accounts.have_pro(): if classic.accounts.have_pro():
show = False # Pro disables interstitials. show = False # Pro disables interstitials.
@ -132,7 +135,7 @@ class AdsSubsystem:
# ad-show-threshold and see if we should *actually* show # ad-show-threshold and see if we should *actually* show
# (we reach our threshold faster the longer we've been # (we reach our threshold faster the longer we've been
# playing). # playing).
base = 'ads' if bauiv1.has_video_ads() else 'ads2' base = 'ads' if plus.has_video_ads() else 'ads2'
min_lc = plus.get_v1_account_misc_read_val(base + '.minLC', 0.0) min_lc = plus.get_v1_account_misc_read_val(base + '.minLC', 0.0)
max_lc = plus.get_v1_account_misc_read_val(base + '.maxLC', 5.0) max_lc = plus.get_v1_account_misc_read_val(base + '.maxLC', 5.0)
min_lc_scale = plus.get_v1_account_misc_read_val( min_lc_scale = plus.get_v1_account_misc_read_val(
@ -181,36 +184,53 @@ class AdsSubsystem:
# If we're *still* cleared to show, actually tell the system to show. # If we're *still* cleared to show, actually tell the system to show.
if show: if show:
# As a safety-check, set up an object that will run # As a safety-check, we set up an object that will run the
# the completion callback if we've returned and sat for 10 seconds # completion callback if we've returned and sat for several
# (in case some random ad network doesn't properly deliver its # seconds (in case some random ad network doesn't properly
# completion callback). # deliver its completion callback).
class _Payload: class _Payload:
def __init__(self, pcall: Callable[[], Any]): def __init__(self, pcall: Callable[[], Any]):
self._call = pcall self._call = pcall
self._ran = False self._ran = False
def run(self, fallback: bool = False) -> None: def run(self, fallback: bool = False) -> None:
"""Run fallback call (and issue a warning about it).""" """Run the payload."""
assert app.classic is not None assert app.classic is not None
if not self._ran: if not self._ran:
if fallback: if fallback:
lanst = app.classic.ads.last_ad_network_set_time lanst = app.classic.ads.last_ad_network_set_time
print( logging.error(
'ERROR: relying on fallback ad-callback! ' 'Relying on fallback ad-callback! '
'last network: ' 'last network: %s (set %s seconds ago);'
+ app.classic.ads.last_ad_network ' purpose=%s.',
+ ' (set ' app.classic.ads.last_ad_network,
+ str(int(time.time() - lanst)) time.time() - lanst,
+ 's ago); purpose=' app.classic.ads.last_ad_purpose,
+ app.classic.ads.last_ad_purpose
) )
babase.pushcall(self._call) babase.pushcall(self._call)
self._ran = True self._ran = True
payload = _Payload(call) payload = _Payload(call)
# Set up our backup.
with babase.ContextRef.empty(): with babase.ContextRef.empty():
babase.apptimer(5.0, lambda: payload.run(fallback=True)) # Note to self: Previously this was a simple 5 second
# timer because the app got totally suspended while ads
# were showing (which delayed the timer), but these days
# the app may continue to run, so we need to be more
# careful and only fire the fallback after we see that
# the app has been front-and-center for several seconds.
async def add_fallback_task() -> None:
activesecs = 5
while activesecs > 0:
if babase.app.active:
activesecs -= 1
await asyncio.sleep(1.0)
payload.run(fallback=True)
_fallback_task = babase.app.aioloop.create_task(
add_fallback_task()
)
self.show_ad('between_game', on_completion_call=payload.run) self.show_ad('between_game', on_completion_call=payload.run)
else: else:
babase.pushcall(call) # Just run the callback without the ad. babase.pushcall(call) # Just run the callback without the ad.

View File

@ -41,5 +41,6 @@ class AppDelegate:
sessiontype, sessiontype,
settings, settings,
completion_call=completion_call, completion_call=completion_call,
).get_root_widget() ).get_root_widget(),
from_window=False, # Disable check since we don't know.
) )

View File

@ -800,5 +800,6 @@ class ClassicSubsystem(babase.AppSubsystem):
bauiv1.getsound('swish').play() bauiv1.getsound('swish').play()
babase.app.ui_v1.set_main_menu_window( babase.app.ui_v1.set_main_menu_window(
MainMenuWindow().get_root_widget() MainMenuWindow().get_root_widget(),
from_window=False, # Disable check here.
) )

View File

@ -52,8 +52,8 @@ 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 = 21636 TARGET_BALLISTICA_BUILD = 21707
TARGET_BALLISTICA_VERSION = '1.7.30' TARGET_BALLISTICA_VERSION = '1.7.31'
@dataclass @dataclass

View File

@ -249,3 +249,18 @@ class PlusSubsystem(AppSubsystem):
) -> None: ) -> None:
"""(internal)""" """(internal)"""
return _baplus.tournament_query(callback, args) return _baplus.tournament_query(callback, args)
@staticmethod
def have_incentivized_ad() -> bool:
"""Is an incentivized ad available?"""
return _baplus.have_incentivized_ad()
@staticmethod
def has_video_ads() -> bool:
"""Are video ads available?"""
return _baplus.has_video_ads()
@staticmethod
def can_show_ad() -> bool:
"""Can we show an ad?"""
return _baplus.can_show_ad()

View File

@ -78,6 +78,7 @@ from _bascenev1 import (
end_host_scanning, end_host_scanning,
get_chat_messages, get_chat_messages,
get_connection_to_host_info, get_connection_to_host_info,
get_connection_to_host_info_2,
get_foreground_host_activity, get_foreground_host_activity,
get_foreground_host_session, get_foreground_host_session,
get_game_port, get_game_port,
@ -202,6 +203,7 @@ from bascenev1._multiteamsession import (
DEFAULT_TEAM_NAMES, DEFAULT_TEAM_NAMES,
) )
from bascenev1._music import MusicType, setmusic from bascenev1._music import MusicType, setmusic
from bascenev1._net import HostInfo
from bascenev1._nodeactor import NodeActor from bascenev1._nodeactor import NodeActor
from bascenev1._powerup import get_default_powerup_distribution from bascenev1._powerup import get_default_powerup_distribution
from bascenev1._profile import ( from bascenev1._profile import (
@ -303,6 +305,7 @@ __all__ = [
'GameTip', 'GameTip',
'get_chat_messages', 'get_chat_messages',
'get_connection_to_host_info', 'get_connection_to_host_info',
'get_connection_to_host_info_2',
'get_default_free_for_all_playlist', 'get_default_free_for_all_playlist',
'get_default_teams_playlist', 'get_default_teams_playlist',
'get_default_powerup_distribution', 'get_default_powerup_distribution',
@ -338,6 +341,7 @@ __all__ = [
'have_connected_clients', 'have_connected_clients',
'have_touchscreen_input', 'have_touchscreen_input',
'HitMessage', 'HitMessage',
'HostInfo',
'host_scan_cycle', 'host_scan_cycle',
'ImpactDamageMessage', 'ImpactDamageMessage',
'increment_analytics_count', 'increment_analytics_count',

View File

@ -0,0 +1,24 @@
# Released under the MIT License. See LICENSE for details.
#
"""Functionality related to net play."""
from __future__ import annotations
from typing import TYPE_CHECKING
from dataclasses import dataclass
if TYPE_CHECKING:
pass
@dataclass
class HostInfo:
"""Info about a host."""
name: str
build_number: int
# Note this can be None for non-ip hosts such as bluetooth.
address: str | None
# Note this can be None for non-ip hosts such as bluetooth.
port: int | None

View File

@ -190,7 +190,7 @@ class CoopScoreScreen(bs.Activity[bs.Player, bs.Team]):
super().__del__() super().__del__()
# If our UI is still up, kill it. # If our UI is still up, kill it.
if self._root_ui: if self._root_ui and not self._root_ui.transitioning_out:
with bui.ContextRef.empty(): with bui.ContextRef.empty():
bui.containerwidget(edit=self._root_ui, transition='out_left') bui.containerwidget(edit=self._root_ui, transition='out_left')

View File

@ -317,7 +317,8 @@ class MainMenuActivity(bs.Activity[bs.Player, bs.Team]):
from bauiv1lib.kiosk import KioskWindow from bauiv1lib.kiosk import KioskWindow
bs.app.ui_v1.set_main_menu_window( bs.app.ui_v1.set_main_menu_window(
KioskWindow().get_root_widget() KioskWindow().get_root_widget(),
from_window=False, # Disable check here.
) )
# ..or in normal cases go back to the main menu # ..or in normal cases go back to the main menu
else: else:
@ -326,14 +327,16 @@ class MainMenuActivity(bs.Activity[bs.Player, bs.Team]):
from bauiv1lib.gather import GatherWindow from bauiv1lib.gather import GatherWindow
bs.app.ui_v1.set_main_menu_window( bs.app.ui_v1.set_main_menu_window(
GatherWindow(transition=None).get_root_widget() GatherWindow(transition=None).get_root_widget(),
from_window=False, # Disable check here.
) )
elif main_menu_location == 'Watch': elif main_menu_location == 'Watch':
# pylint: disable=cyclic-import # pylint: disable=cyclic-import
from bauiv1lib.watch import WatchWindow from bauiv1lib.watch import WatchWindow
bs.app.ui_v1.set_main_menu_window( bs.app.ui_v1.set_main_menu_window(
WatchWindow(transition=None).get_root_widget() WatchWindow(transition=None).get_root_widget(),
from_window=False, # Disable check here.
) )
elif main_menu_location == 'Team Game Select': elif main_menu_location == 'Team Game Select':
# pylint: disable=cyclic-import # pylint: disable=cyclic-import
@ -344,7 +347,8 @@ class MainMenuActivity(bs.Activity[bs.Player, bs.Team]):
bs.app.ui_v1.set_main_menu_window( bs.app.ui_v1.set_main_menu_window(
PlaylistBrowserWindow( PlaylistBrowserWindow(
sessiontype=bs.DualTeamSession, transition=None sessiontype=bs.DualTeamSession, transition=None
).get_root_widget() ).get_root_widget(),
from_window=False, # Disable check here.
) )
elif main_menu_location == 'Free-for-All Game Select': elif main_menu_location == 'Free-for-All Game Select':
# pylint: disable=cyclic-import # pylint: disable=cyclic-import
@ -356,28 +360,34 @@ class MainMenuActivity(bs.Activity[bs.Player, bs.Team]):
PlaylistBrowserWindow( PlaylistBrowserWindow(
sessiontype=bs.FreeForAllSession, sessiontype=bs.FreeForAllSession,
transition=None, transition=None,
).get_root_widget() ).get_root_widget(),
from_window=False, # Disable check here.
) )
elif main_menu_location == 'Coop Select': elif main_menu_location == 'Coop Select':
# pylint: disable=cyclic-import # pylint: disable=cyclic-import
from bauiv1lib.coop.browser import CoopBrowserWindow from bauiv1lib.coop.browser import CoopBrowserWindow
bs.app.ui_v1.set_main_menu_window( bs.app.ui_v1.set_main_menu_window(
CoopBrowserWindow(transition=None).get_root_widget() CoopBrowserWindow(
transition=None
).get_root_widget(),
from_window=False, # Disable check here.
) )
elif main_menu_location == 'Benchmarks & Stress Tests': elif main_menu_location == 'Benchmarks & Stress Tests':
# pylint: disable=cyclic-import # pylint: disable=cyclic-import
from bauiv1lib.debug import DebugWindow from bauiv1lib.debug import DebugWindow
bs.app.ui_v1.set_main_menu_window( bs.app.ui_v1.set_main_menu_window(
DebugWindow(transition=None).get_root_widget() DebugWindow(transition=None).get_root_widget(),
from_window=False, # Disable check here.
) )
else: else:
# pylint: disable=cyclic-import # pylint: disable=cyclic-import
from bauiv1lib.mainmenu import MainMenuWindow from bauiv1lib.mainmenu import MainMenuWindow
bs.app.ui_v1.set_main_menu_window( bs.app.ui_v1.set_main_menu_window(
MainMenuWindow(transition=None).get_root_widget() MainMenuWindow(transition=None).get_root_widget(),
from_window=None,
) )
# attempt to show any pending offers immediately. # attempt to show any pending offers immediately.

View File

@ -62,7 +62,6 @@ from babase import (
is_browser_likely_available, is_browser_likely_available,
is_running_on_fire_tv, is_running_on_fire_tv,
is_xcode_build, is_xcode_build,
Keyboard,
lock_all_input, lock_all_input,
LoginAdapter, LoginAdapter,
LoginInfo, LoginInfo,
@ -94,7 +93,6 @@ from babase import (
from _bauiv1 import ( from _bauiv1 import (
buttonwidget, buttonwidget,
can_show_ad,
checkboxwidget, checkboxwidget,
columnwidget, columnwidget,
containerwidget, containerwidget,
@ -103,8 +101,6 @@ from _bauiv1 import (
getmesh, getmesh,
getsound, getsound,
gettexture, gettexture,
has_video_ads,
have_incentivized_ad,
hscrollwidget, hscrollwidget,
imagewidget, imagewidget,
is_party_icon_visible, is_party_icon_visible,
@ -125,6 +121,7 @@ from _bauiv1 import (
Widget, Widget,
widget, widget,
) )
from bauiv1._keyboard import Keyboard
from bauiv1._uitypes import Window, uicleanupcheck from bauiv1._uitypes import Window, uicleanupcheck
from bauiv1._subsystem import UIV1Subsystem from bauiv1._subsystem import UIV1Subsystem
@ -144,7 +141,6 @@ __all__ = [
'AppTimer', 'AppTimer',
'buttonwidget', 'buttonwidget',
'Call', 'Call',
'can_show_ad',
'fullscreen_control_available', 'fullscreen_control_available',
'fullscreen_control_get', 'fullscreen_control_get',
'fullscreen_control_key_shortcut', 'fullscreen_control_key_shortcut',
@ -178,8 +174,6 @@ __all__ = [
'getmesh', 'getmesh',
'getsound', 'getsound',
'gettexture', 'gettexture',
'has_video_ads',
'have_incentivized_ad',
'have_permission', 'have_permission',
'hscrollwidget', 'hscrollwidget',
'imagewidget', 'imagewidget',

View File

@ -6,6 +6,7 @@
from __future__ import annotations from __future__ import annotations
import logging import logging
import inspect
from typing import TYPE_CHECKING from typing import TYPE_CHECKING
import _bauiv1 import _bauiv1
@ -87,3 +88,19 @@ def show_url_window(address: str) -> None:
return return
app.classic.show_url_window(address) app.classic.show_url_window(address)
def double_transition_out_warning() -> None:
"""Called if a widget is set to transition out twice."""
caller_frame = inspect.stack()[1]
caller_filename = caller_frame.filename
caller_line_number = caller_frame.lineno
logging.warning(
'ContainerWidget was set to transition out twice;'
' this often implies buggy code (%s line %s).\n'
' Generally you should check the value of'
' _root_widget.transitioning_out and only kick off transitions'
' when that is False.',
caller_filename,
caller_line_number,
)

View File

@ -5,6 +5,7 @@
from __future__ import annotations from __future__ import annotations
import logging import logging
import inspect
from typing import TYPE_CHECKING from typing import TYPE_CHECKING
import babase import babase
@ -116,21 +117,69 @@ class UIV1Subsystem(babase.AppSubsystem):
# FIXME: Can probably kill this if we do immediate UI death checks. # FIXME: Can probably kill this if we do immediate UI death checks.
self.upkeeptimer = babase.AppTimer(2.6543, ui_upkeep, repeat=True) self.upkeeptimer = babase.AppTimer(2.6543, ui_upkeep, repeat=True)
def set_main_menu_window(self, window: bauiv1.Widget) -> None: def set_main_menu_window(
"""Set the current 'main' window, replacing any existing.""" self,
window: bauiv1.Widget,
from_window: bauiv1.Widget | None | bool = True,
) -> None:
"""Set the current 'main' window, replacing any existing.
If 'from_window' is passed as a bauiv1.Widget or None, a warning
will be issued if it that value does not match the current main
window. This can help clean up flawed code that can lead to bad
UI states. A value of False will disable the check.
"""
existing = self._main_menu_window existing = self._main_menu_window
from inspect import currentframe, getframeinfo
try:
if isinstance(from_window, bool):
# For default val True we warn that the arg wasn't
# passed. False can be explicitly passed to disable this
# check.
if from_window is True:
caller_frame = inspect.stack()[1]
caller_filename = caller_frame.filename
caller_line_number = caller_frame.lineno
logging.warning(
'set_main_menu_window() should be passed a'
" 'from_window' value to help ensure proper UI behavior"
' (%s line %i).',
caller_filename,
caller_line_number,
)
else:
# For everything else, warn if what they passed wasn't
# the previous main menu widget.
if from_window is not existing:
caller_frame = inspect.stack()[1]
caller_filename = caller_frame.filename
caller_line_number = caller_frame.lineno
logging.warning(
"set_main_menu_window() was passed 'from_window' %s"
' but existing main-menu-window is %s. (%s line %i).',
from_window,
existing,
caller_filename,
caller_line_number,
)
except Exception:
# Prevent any bugs in these checks from causing problems.
logging.exception('Error checking from_window')
# Once the above code leads to us fixing all leftover window bugs
# at the source, we can kill the code below.
# Let's grab the location where we were called from to report # Let's grab the location where we were called from to report
# if we have to force-kill the existing window (which normally # if we have to force-kill the existing window (which normally
# should not happen). # should not happen).
frameline = None frameline = None
try: try:
frame = currentframe() frame = inspect.currentframe()
if frame is not None: if frame is not None:
frame = frame.f_back frame = frame.f_back
if frame is not None: if frame is not None:
frameinfo = getframeinfo(frame) frameinfo = inspect.getframeinfo(frame)
frameline = f'{frameinfo.filename} {frameinfo.lineno}' frameline = f'{frameinfo.filename} {frameinfo.lineno}'
except Exception: except Exception:
logging.exception('Error calcing line for set_main_menu_window') logging.exception('Error calcing line for set_main_menu_window')
@ -160,13 +209,18 @@ class UIV1Subsystem(babase.AppSubsystem):
def clear_main_menu_window(self, transition: str | None = None) -> None: def clear_main_menu_window(self, transition: str | None = None) -> None:
"""Clear any existing 'main' window with the provided transition.""" """Clear any existing 'main' window with the provided transition."""
assert transition is None or not transition.endswith('_in')
if self._main_menu_window: if self._main_menu_window:
if transition is not None: if (
transition is not None
and not self._main_menu_window.transitioning_out
):
_bauiv1.containerwidget( _bauiv1.containerwidget(
edit=self._main_menu_window, transition=transition edit=self._main_menu_window, transition=transition
) )
else: else:
self._main_menu_window.delete() self._main_menu_window.delete()
self._main_menu_window = None
def add_main_menu_close_callback(self, call: Callable[[], Any]) -> None: def add_main_menu_close_callback(self, call: Callable[[], Any]) -> None:
"""(internal)""" """(internal)"""

View File

@ -12,6 +12,7 @@ from typing import TYPE_CHECKING
import babase import babase
import _bauiv1 import _bauiv1
from bauiv1._keyboard import Keyboard
from bauiv1._uitypes import Window from bauiv1._uitypes import Window
if TYPE_CHECKING: if TYPE_CHECKING:
@ -252,9 +253,7 @@ class OnScreenKeyboardWindow(Window):
# Show change instructions only if we have more than one # Show change instructions only if we have more than one
# keyboard option. # keyboard option.
keyboards = ( keyboards = (
babase.app.meta.scanresults.exports_of_class( babase.app.meta.scanresults.exports_of_class(Keyboard)
babase.Keyboard
)
if babase.app.meta.scanresults is not None if babase.app.meta.scanresults is not None
else [] else []
) )
@ -286,10 +285,10 @@ class OnScreenKeyboardWindow(Window):
def _get_keyboard(self) -> bui.Keyboard: def _get_keyboard(self) -> bui.Keyboard:
assert babase.app.meta.scanresults is not None assert babase.app.meta.scanresults is not None
classname = babase.app.meta.scanresults.exports_of_class( classname = babase.app.meta.scanresults.exports_of_class(Keyboard)[
babase.Keyboard self._keyboard_index
)[self._keyboard_index] ]
kbclass = babase.getclass(classname, babase.Keyboard) kbclass = babase.getclass(classname, Keyboard)
return kbclass() return kbclass()
def _refresh(self) -> None: def _refresh(self) -> None:
@ -384,9 +383,7 @@ class OnScreenKeyboardWindow(Window):
def _next_keyboard(self) -> None: def _next_keyboard(self) -> None:
assert babase.app.meta.scanresults is not None assert babase.app.meta.scanresults is not None
kbexports = babase.app.meta.scanresults.exports_of_class( kbexports = babase.app.meta.scanresults.exports_of_class(Keyboard)
babase.Keyboard
)
self._keyboard_index = (self._keyboard_index + 1) % len(kbexports) self._keyboard_index = (self._keyboard_index + 1) % len(kbexports)
self._load_keyboard() self._load_keyboard()

View File

@ -1507,9 +1507,18 @@ class AccountSettingsWindow(bui.Window):
# pylint: disable=cyclic-import # pylint: disable=cyclic-import
from bauiv1lib.profile.browser import ProfileBrowserWindow from bauiv1lib.profile.browser import ProfileBrowserWindow
# no-op if our underlying widget is dead or on its way out.
if not self._root_widget or self._root_widget.transitioning_out:
return
self._save_state() self._save_state()
bui.containerwidget(edit=self._root_widget, transition='out_left') bui.containerwidget(edit=self._root_widget, transition='out_left')
ProfileBrowserWindow(origin_widget=self._player_profiles_button) bui.app.ui_v1.set_main_menu_window(
ProfileBrowserWindow(
origin_widget=self._player_profiles_button
).get_root_widget(),
from_window=self._root_widget,
)
def _cancel_sign_in_press(self) -> None: def _cancel_sign_in_press(self) -> None:
# If we're waiting on an adapter to give us credentials, abort. # If we're waiting on an adapter to give us credentials, abort.
@ -1670,6 +1679,10 @@ class AccountSettingsWindow(bui.Window):
# pylint: disable=cyclic-import # pylint: disable=cyclic-import
from bauiv1lib.mainmenu import MainMenuWindow from bauiv1lib.mainmenu import MainMenuWindow
# no-op if our underlying widget is dead or on its way out.
if not self._root_widget or self._root_widget.transitioning_out:
return
self._save_state() self._save_state()
bui.containerwidget( bui.containerwidget(
edit=self._root_widget, transition=self._transition_out edit=self._root_widget, transition=self._transition_out
@ -1678,7 +1691,8 @@ class AccountSettingsWindow(bui.Window):
if not self._modal: if not self._modal:
assert bui.app.classic is not None assert bui.app.classic is not None
bui.app.ui_v1.set_main_menu_window( bui.app.ui_v1.set_main_menu_window(
MainMenuWindow(transition='in_left').get_root_widget() MainMenuWindow(transition='in_left').get_root_widget(),
from_window=self._root_widget,
) )
def _save_state(self) -> None: def _save_state(self) -> None:

View File

@ -415,7 +415,7 @@ class CoopBrowserWindow(bui.Window):
) )
# Decrement time on our tournament buttons. # Decrement time on our tournament buttons.
ads_enabled = bui.have_incentivized_ad() ads_enabled = plus.have_incentivized_ad()
for tbtn in self._tournament_buttons: for tbtn in self._tournament_buttons:
tbtn.time_remaining = max(0, tbtn.time_remaining - 1) tbtn.time_remaining = max(0, tbtn.time_remaining - 1)
if tbtn.time_remaining_value_text is not None: if tbtn.time_remaining_value_text is not None:
@ -430,7 +430,7 @@ class CoopBrowserWindow(bui.Window):
) )
# Also adjust the ad icon visibility. # Also adjust the ad icon visibility.
if tbtn.allow_ads and bui.has_video_ads(): if tbtn.allow_ads and plus.has_video_ads():
bui.imagewidget( bui.imagewidget(
edit=tbtn.entry_fee_ad_image, edit=tbtn.entry_fee_ad_image,
opacity=1.0 if ads_enabled else 0.25, opacity=1.0 if ads_enabled else 0.25,
@ -1019,6 +1019,10 @@ class CoopBrowserWindow(bui.Window):
from bauiv1lib.account import show_sign_in_prompt from bauiv1lib.account import show_sign_in_prompt
from bauiv1lib.league.rankwindow import LeagueRankWindow from bauiv1lib.league.rankwindow import LeagueRankWindow
# no-op if our underlying widget is dead or on its way out.
if not self._root_widget or self._root_widget.transitioning_out:
return
plus = bui.app.plus plus = bui.app.plus
assert plus is not None assert plus is not None
@ -1032,7 +1036,8 @@ class CoopBrowserWindow(bui.Window):
bui.app.ui_v1.set_main_menu_window( bui.app.ui_v1.set_main_menu_window(
LeagueRankWindow( LeagueRankWindow(
origin_widget=self._league_rank_button.get_button() origin_widget=self._league_rank_button.get_button()
).get_root_widget() ).get_root_widget(),
from_window=self._root_widget,
) )
def _switch_to_score( def _switch_to_score(
@ -1043,6 +1048,10 @@ class CoopBrowserWindow(bui.Window):
# pylint: disable=cyclic-import # pylint: disable=cyclic-import
from bauiv1lib.account import show_sign_in_prompt from bauiv1lib.account import show_sign_in_prompt
# no-op if our underlying widget is dead or on its way out.
if not self._root_widget or self._root_widget.transitioning_out:
return
plus = bui.app.plus plus = bui.app.plus
assert plus is not None assert plus is not None
@ -1058,7 +1067,8 @@ class CoopBrowserWindow(bui.Window):
origin_widget=self._store_button.get_button(), origin_widget=self._store_button.get_button(),
show_tab=show_tab, show_tab=show_tab,
back_location='CoopBrowserWindow', back_location='CoopBrowserWindow',
).get_root_widget() ).get_root_widget(),
from_window=self._root_widget,
) )
def is_tourney_data_up_to_date(self) -> bool: def is_tourney_data_up_to_date(self) -> bool:
@ -1218,6 +1228,10 @@ class CoopBrowserWindow(bui.Window):
# pylint: disable=cyclic-import # pylint: disable=cyclic-import
from bauiv1lib.play import PlayWindow from bauiv1lib.play import PlayWindow
# no-op if our underlying widget is dead or on its way out.
if not self._root_widget or self._root_widget.transitioning_out:
return
# If something is selected, store it. # If something is selected, store it.
self._save_state() self._save_state()
bui.containerwidget( bui.containerwidget(
@ -1225,7 +1239,8 @@ class CoopBrowserWindow(bui.Window):
) )
assert bui.app.classic is not None assert bui.app.classic is not None
bui.app.ui_v1.set_main_menu_window( bui.app.ui_v1.set_main_menu_window(
PlayWindow(transition='in_left').get_root_widget() PlayWindow(transition='in_left').get_root_widget(),
from_window=self._root_widget,
) )
def _save_state(self) -> None: def _save_state(self) -> None:

View File

@ -638,8 +638,8 @@ class TournamentButton:
# Now, if this fee allows ads and we support video ads, show # Now, if this fee allows ads and we support video ads, show
# the 'or ad' version. # the 'or ad' version.
if allow_ads and bui.has_video_ads(): if allow_ads and plus.has_video_ads():
ads_enabled = bui.have_incentivized_ad() ads_enabled = plus.have_incentivized_ad()
bui.imagewidget( bui.imagewidget(
edit=self.entry_fee_ad_image, edit=self.entry_fee_ad_image,
opacity=1.0 if ads_enabled else 0.25, opacity=1.0 if ads_enabled else 0.25,

View File

@ -359,10 +359,15 @@ class CreditsListWindow(bui.Window):
def _back(self) -> None: def _back(self) -> None:
from bauiv1lib.mainmenu import MainMenuWindow from bauiv1lib.mainmenu import MainMenuWindow
# no-op if our underlying widget is dead or on its way out.
if not self._root_widget or self._root_widget.transitioning_out:
return
bui.containerwidget( bui.containerwidget(
edit=self._root_widget, transition=self._transition_out edit=self._root_widget, transition=self._transition_out
) )
assert bui.app.classic is not None assert bui.app.classic is not None
bui.app.ui_v1.set_main_menu_window( bui.app.ui_v1.set_main_menu_window(
MainMenuWindow(transition='in_left').get_root_widget() MainMenuWindow(transition='in_left').get_root_widget(),
from_window=self._root_widget,
) )

View File

@ -379,8 +379,13 @@ class DebugWindow(bui.Window):
# pylint: disable=cyclic-import # pylint: disable=cyclic-import
from bauiv1lib.settings.advanced import AdvancedSettingsWindow from bauiv1lib.settings.advanced import AdvancedSettingsWindow
# no-op if our underlying widget is dead or on its way out.
if not self._root_widget or self._root_widget.transitioning_out:
return
bui.containerwidget(edit=self._root_widget, transition='out_right') bui.containerwidget(edit=self._root_widget, transition='out_right')
assert bui.app.classic is not None assert bui.app.classic is not None
bui.app.ui_v1.set_main_menu_window( bui.app.ui_v1.set_main_menu_window(
AdvancedSettingsWindow(transition='in_left').get_root_widget() AdvancedSettingsWindow(transition='in_left').get_root_widget(),
from_window=self._root_widget,
) )

View File

@ -73,6 +73,7 @@ class DiscordWindow(bui.Window):
edit=self._root_widget, cancel_button=self._back_button edit=self._root_widget, cancel_button=self._back_button
) )
# Do we need to translate 'Discord'? Or is that always the name?
self._title_text = bui.textwidget( self._title_text = bui.textwidget(
parent=self._root_widget, parent=self._root_widget,
position=(0, self._height - 52), position=(0, self._height - 52),
@ -91,6 +92,9 @@ class DiscordWindow(bui.Window):
texture=bui.gettexture('discordServer'), texture=bui.gettexture('discordServer'),
) )
# Hmm should we translate this? The discord server is mostly
# English so being able to read this might be a good screening
# process?..
bui.textwidget( bui.textwidget(
parent=self._root_widget, parent=self._root_widget,
position=(self._width / 2 - 60, self._height - 100), position=(self._width / 2 - 60, self._height - 100),
@ -110,7 +114,7 @@ class DiscordWindow(bui.Window):
position=(self._width / 2 - 30, 20), position=(self._width / 2 - 30, 20),
size=(self._width / 2 - 60, 60), size=(self._width / 2 - 60, 60),
autoselect=True, autoselect=True,
label='Join The Discord', label=bui.Lstr(resource='discordJoinText'),
text_scale=1.0, text_scale=1.0,
on_activate_call=bui.Call( on_activate_call=bui.Call(
bui.open_url, 'https://ballistica.net/discord' bui.open_url, 'https://ballistica.net/discord'

View File

@ -270,12 +270,17 @@ class GatherWindow(bui.Window):
"""Called by the private-hosting tab to select a playlist.""" """Called by the private-hosting tab to select a playlist."""
from bauiv1lib.play import PlayWindow from bauiv1lib.play import PlayWindow
# no-op if our underlying widget is dead or on its way out.
if not self._root_widget or self._root_widget.transitioning_out:
return
self._save_state() self._save_state()
bui.containerwidget(edit=self._root_widget, transition='out_left') bui.containerwidget(edit=self._root_widget, transition='out_left')
assert bui.app.classic is not None assert bui.app.classic is not None
bui.app.ui_v1.selecting_private_party_playlist = True bui.app.ui_v1.selecting_private_party_playlist = True
bui.app.ui_v1.set_main_menu_window( bui.app.ui_v1.set_main_menu_window(
PlayWindow(origin_widget=origin_widget).get_root_widget() PlayWindow(origin_widget=origin_widget).get_root_widget(),
from_window=self._root_widget,
) )
def _set_tab(self, tab_id: TabID) -> None: def _set_tab(self, tab_id: TabID) -> None:
@ -383,11 +388,16 @@ class GatherWindow(bui.Window):
def _back(self) -> None: def _back(self) -> None:
from bauiv1lib.mainmenu import MainMenuWindow from bauiv1lib.mainmenu import MainMenuWindow
# no-op if our underlying widget is dead or on its way out.
if not self._root_widget or self._root_widget.transitioning_out:
return
self._save_state() self._save_state()
bui.containerwidget( bui.containerwidget(
edit=self._root_widget, transition=self._transition_out edit=self._root_widget, transition=self._transition_out
) )
assert bui.app.classic is not None assert bui.app.classic is not None
bui.app.ui_v1.set_main_menu_window( bui.app.ui_v1.set_main_menu_window(
MainMenuWindow(transition='in_left').get_root_widget() MainMenuWindow(transition='in_left').get_root_widget(),
from_window=self._root_widget,
) )

View File

@ -16,10 +16,6 @@ if TYPE_CHECKING:
class AboutGatherTab(GatherTab): class AboutGatherTab(GatherTab):
"""The about tab in the gather UI""" """The about tab in the gather UI"""
def __init__(self, window: GatherWindow) -> None:
super().__init__(window)
self._container: bui.Widget | None = None
def on_activate( def on_activate(
self, self,
parent_widget: bui.Widget, parent_widget: bui.Widget,
@ -34,6 +30,40 @@ class AboutGatherTab(GatherTab):
plus = bui.app.plus plus = bui.app.plus
assert plus is not None assert plus is not None
try_tickets = plus.get_v1_account_misc_read_val(
'friendTryTickets', None
)
show_message = True
# Squish message as needed to get things to fit nicely at
# various scales.
uiscale = bui.app.ui_v1.uiscale
message_height = (
210
if uiscale is bui.UIScale.SMALL
else 305
if uiscale is bui.UIScale.MEDIUM
else 370
)
# Let's not talk about sharing in vr-mode; its tricky to fit more
# than one head in a VR-headset.
show_message_extra = not bui.app.env.vr
message_extra_height = 60
show_invite = try_tickets is not None
invite_height = 80
show_discord = True
discord_height = 80
c_height = 0
if show_message:
c_height += message_height
if show_message_extra:
c_height += message_extra_height
if show_invite:
c_height += invite_height
if show_discord:
c_height += discord_height
party_button_label = bui.charstr(bui.SpecialChar.TOP_BUTTON) party_button_label = bui.charstr(bui.SpecialChar.TOP_BUTTON)
message = bui.Lstr( message = bui.Lstr(
resource='gatherWindow.aboutDescriptionText', resource='gatherWindow.aboutDescriptionText',
@ -43,9 +73,7 @@ class AboutGatherTab(GatherTab):
], ],
) )
# Let's not talk about sharing in vr-mode; its tricky to fit more if show_message_extra:
# than one head in a VR-headset ;-)
if not bui.app.env.vr:
message = bui.Lstr( message = bui.Lstr(
value='${A}\n\n${B}', value='${A}\n\n${B}',
subs=[ subs=[
@ -59,46 +87,52 @@ class AboutGatherTab(GatherTab):
), ),
], ],
) )
string_height = 400
include_invite = True
include_discord = False # Need to fix spacing on small first.
msc_scale = 1.1
c_height_2 = min(region_height, string_height * msc_scale + 100)
try_tickets = plus.get_v1_account_misc_read_val(
'friendTryTickets', None
)
if try_tickets is None: scroll_widget = bui.scrollwidget(
include_invite = False
self._container = bui.containerwidget(
parent=parent_widget, parent=parent_widget,
position=(region_left, region_bottom),
size=(region_width, region_height),
highlight=False,
border_opacity=0,
)
msc_scale = 1.1
container = bui.containerwidget(
parent=scroll_widget,
position=( position=(
region_left, region_left,
region_bottom + (region_height - c_height_2) * 0.5, region_bottom + (region_height - c_height) * 0.5,
), ),
size=(region_width, c_height_2), size=(region_width, c_height),
background=False, background=False,
selectable=include_invite or include_discord, selectable=show_invite or show_discord,
) )
bui.widget(edit=self._container, up_widget=tab_button) # Allows escaping if we select the container somehow (though
# shouldn't be possible when buttons are present).
bui.widget(edit=container, up_widget=tab_button)
bui.textwidget( y = c_height - 30
parent=self._container, if show_message:
position=(region_width * 0.5, c_height_2 * 0.58),
color=(0.6, 1.0, 0.6),
scale=msc_scale,
size=(0, 0),
maxwidth=region_width * 0.9,
max_height=c_height_2 * 0.7,
h_align='center',
v_align='center',
text=message,
)
if include_invite:
bui.textwidget( bui.textwidget(
parent=self._container, parent=container,
position=(region_width * 0.57, 35), position=(region_width * 0.5, y),
color=(0.6, 1.0, 0.6),
scale=msc_scale,
size=(0, 0),
maxwidth=region_width * 0.9,
max_height=message_height,
h_align='center',
v_align='top',
text=message,
)
y -= message_height
if show_message_extra:
y -= message_extra_height
if show_invite:
bui.textwidget(
parent=container,
position=(region_width * 0.57, y),
color=(0, 1, 0), color=(0, 1, 0),
scale=0.6, scale=0.6,
size=(0, 0), size=(0, 0),
@ -112,8 +146,8 @@ class AboutGatherTab(GatherTab):
), ),
) )
invite_button = bui.buttonwidget( invite_button = bui.buttonwidget(
parent=self._container, parent=container,
position=(region_width * 0.59, 10), position=(region_width * 0.59, y - 25),
size=(230, 50), size=(230, 50),
color=(0.54, 0.42, 0.56), color=(0.54, 0.42, 0.56),
textcolor=(0, 1, 0), textcolor=(0, 1, 0),
@ -125,13 +159,14 @@ class AboutGatherTab(GatherTab):
on_activate_call=bui.WeakCall(self._invite_to_try_press), on_activate_call=bui.WeakCall(self._invite_to_try_press),
up_widget=tab_button, up_widget=tab_button,
) )
y -= invite_height
else: else:
invite_button = None invite_button = None
if include_discord: if show_discord:
bui.textwidget( bui.textwidget(
parent=self._container, parent=container,
position=(region_width * 0.57, 15 if include_invite else 75), position=(region_width * 0.57, y),
color=(0.6, 0.6, 1), color=(0.6, 0.6, 1),
scale=0.6, scale=0.6,
size=(0, 0), size=(0, 0),
@ -139,26 +174,29 @@ class AboutGatherTab(GatherTab):
h_align='right', h_align='right',
v_align='center', v_align='center',
flatness=1.0, flatness=1.0,
text=( text=bui.Lstr(resource='discordFriendsText'),
'Want to look for new people to play with?\n'
'Join our Discord and find new friends!'
),
) )
bui.buttonwidget( discord_button = bui.buttonwidget(
parent=self._container, parent=container,
position=(region_width * 0.59, -10 if include_invite else 50), position=(region_width * 0.59, y - 25),
size=(230, 50), size=(230, 50),
color=(0.54, 0.42, 0.56), color=(0.54, 0.42, 0.56),
textcolor=(0.6, 0.6, 1), textcolor=(0.6, 0.6, 1),
label='Join The Discord', label=bui.Lstr(resource='discordJoinText'),
autoselect=True, autoselect=True,
on_activate_call=bui.WeakCall(self._join_the_discord_press), on_activate_call=bui.WeakCall(self._join_the_discord_press),
up_widget=( up_widget=(
invite_button if invite_button is not None else tab_button invite_button if invite_button is not None else tab_button
), ),
) )
y -= discord_height
else:
discord_button = None
return self._container if discord_button is not None:
pass
return scroll_widget
def _invite_to_try_press(self) -> None: def _invite_to_try_press(self) -> None:
from bauiv1lib.account import show_sign_in_prompt from bauiv1lib.account import show_sign_in_prompt

View File

@ -334,7 +334,7 @@ class GetCurrencyWindow(bui.Window):
tex_scale=1.2, tex_scale=1.2,
) # 19.99-ish ) # 19.99-ish
self._enable_ad_button = bui.has_video_ads() self._enable_ad_button = plus.has_video_ads()
h = self._width * 0.5 + 110.0 h = self._width * 0.5 + 110.0
v = self._height - b_size[1] - 115.0 v = self._height - b_size[1] - 115.0
@ -561,7 +561,7 @@ class GetCurrencyWindow(bui.Window):
next_reward_ad_time next_reward_ad_time
) )
now = datetime.datetime.utcnow() now = datetime.datetime.utcnow()
if bui.have_incentivized_ad() and ( if plus.have_incentivized_ad() and (
next_reward_ad_time is None or next_reward_ad_time <= now next_reward_ad_time is None or next_reward_ad_time <= now
): ):
self._ad_button_greyed = False self._ad_button_greyed = False
@ -732,8 +732,13 @@ class GetCurrencyWindow(bui.Window):
def _back(self) -> None: def _back(self) -> None:
from bauiv1lib.store import browser from bauiv1lib.store import browser
# no-op if our underlying widget is dead or on its way out.
if not self._root_widget or self._root_widget.transitioning_out:
return
if self._transitioning_out: if self._transitioning_out:
return return
bui.containerwidget( bui.containerwidget(
edit=self._root_widget, transition=self._transition_out edit=self._root_widget, transition=self._transition_out
) )
@ -745,7 +750,9 @@ class GetCurrencyWindow(bui.Window):
).get_root_widget() ).get_root_widget()
if not self._from_modal_store: if not self._from_modal_store:
assert bui.app.classic is not None assert bui.app.classic is not None
bui.app.ui_v1.set_main_menu_window(window) bui.app.ui_v1.set_main_menu_window(
window, from_window=self._root_widget
)
self._transitioning_out = True self._transitioning_out = True

View File

@ -645,11 +645,16 @@ class HelpWindow(bui.Window):
# pylint: disable=cyclic-import # pylint: disable=cyclic-import
from bauiv1lib.mainmenu import MainMenuWindow from bauiv1lib.mainmenu import MainMenuWindow
# no-op if our underlying widget is dead or on its way out.
if not self._root_widget or self._root_widget.transitioning_out:
return
bui.containerwidget( bui.containerwidget(
edit=self._root_widget, transition=self._transition_out edit=self._root_widget, transition=self._transition_out
) )
if self._main_menu: if self._main_menu:
assert bui.app.classic is not None assert bui.app.classic is not None
bui.app.ui_v1.set_main_menu_window( bui.app.ui_v1.set_main_menu_window(
MainMenuWindow(transition='in_left').get_root_widget() MainMenuWindow(transition='in_left').get_root_widget(),
from_window=self._root_widget,
) )

View File

@ -9,7 +9,7 @@ from __future__ import annotations
from typing import TYPE_CHECKING from typing import TYPE_CHECKING
import babase import bauiv1 as bui
if TYPE_CHECKING: if TYPE_CHECKING:
from typing import Iterable from typing import Iterable
@ -33,15 +33,15 @@ def split(chars: Iterable[str], maxlen: int) -> list[list[str]]:
def generate_emojis(maxlen: int) -> list[list[str]]: def generate_emojis(maxlen: int) -> list[list[str]]:
"""Generates a lot of UTF8 emojis prepared for babase.Keyboard pages""" """Generates a lot of UTF8 emojis prepared for bui.Keyboard pages"""
all_emojis = split([chr(i) for i in range(0x1F601, 0x1F650)], maxlen) all_emojis = split([chr(i) for i in range(0x1F601, 0x1F650)], maxlen)
all_emojis += split([chr(i) for i in range(0x2702, 0x27B1)], maxlen) all_emojis += split([chr(i) for i in range(0x2702, 0x27B1)], maxlen)
all_emojis += split([chr(i) for i in range(0x1F680, 0x1F6C1)], maxlen) all_emojis += split([chr(i) for i in range(0x1F680, 0x1F6C1)], maxlen)
return all_emojis return all_emojis
# ba_meta export keyboard # ba_meta export bauiv1.Keyboard
class EnglishKeyboard(babase.Keyboard): class EnglishKeyboard(bui.Keyboard):
"""Default English keyboard.""" """Default English keyboard."""
name = 'English' name = 'English'

View File

@ -501,9 +501,15 @@ class KioskWindow(bui.Window):
def _do_full_menu(self) -> None: def _do_full_menu(self) -> None:
from bauiv1lib.mainmenu import MainMenuWindow from bauiv1lib.mainmenu import MainMenuWindow
# no-op if our underlying widget is dead or on its way out.
if not self._root_widget or self._root_widget.transitioning_out:
return
assert bui.app.classic is not None assert bui.app.classic is not None
self._save_state() self._save_state()
bui.containerwidget(edit=self._root_widget, transition='out_left') bui.containerwidget(edit=self._root_widget, transition='out_left')
bui.app.classic.did_menu_intro = True # prevent delayed transition-in bui.app.classic.did_menu_intro = True # prevent delayed transition-in
bui.app.ui_v1.set_main_menu_window(MainMenuWindow().get_root_widget()) bui.app.ui_v1.set_main_menu_window(
MainMenuWindow().get_root_widget(), from_window=self._root_widget
)

View File

@ -1142,6 +1142,10 @@ class LeagueRankWindow(bui.Window):
def _back(self) -> None: def _back(self) -> None:
from bauiv1lib.coop.browser import CoopBrowserWindow from bauiv1lib.coop.browser import CoopBrowserWindow
# no-op if our underlying widget is dead or on its way out.
if not self._root_widget or self._root_widget.transitioning_out:
return
self._save_state() self._save_state()
bui.containerwidget( bui.containerwidget(
edit=self._root_widget, transition=self._transition_out edit=self._root_widget, transition=self._transition_out
@ -1149,5 +1153,6 @@ class LeagueRankWindow(bui.Window):
if not self._modal: if not self._modal:
assert bui.app.classic is not None assert bui.app.classic is not None
bui.app.ui_v1.set_main_menu_window( bui.app.ui_v1.set_main_menu_window(
CoopBrowserWindow(transition='in_left').get_root_widget() CoopBrowserWindow(transition='in_left').get_root_widget(),
from_window=self._root_widget,
) )

View File

@ -1038,6 +1038,10 @@ class MainMenuWindow(bui.Window):
# pylint: disable=cyclic-import # pylint: disable=cyclic-import
from bauiv1lib.confirm import QuitWindow from bauiv1lib.confirm import QuitWindow
# no-op if our underlying widget is dead or on its way out.
if not self._root_widget or self._root_widget.transitioning_out:
return
# Note: Normally we should go through bui.quit(confirm=True) but # Note: Normally we should go through bui.quit(confirm=True) but
# invoking the window directly lets us scale it up from the # invoking the window directly lets us scale it up from the
# button. # button.
@ -1047,24 +1051,34 @@ class MainMenuWindow(bui.Window):
# pylint: disable=cyclic-import # pylint: disable=cyclic-import
from bauiv1lib.kiosk import KioskWindow from bauiv1lib.kiosk import KioskWindow
# no-op if our underlying widget is dead or on its way out.
if not self._root_widget or self._root_widget.transitioning_out:
return
self._save_state() self._save_state()
bui.containerwidget(edit=self._root_widget, transition='out_right') bui.containerwidget(edit=self._root_widget, transition='out_right')
assert bui.app.classic is not None assert bui.app.classic is not None
bui.app.ui_v1.set_main_menu_window( bui.app.ui_v1.set_main_menu_window(
KioskWindow(transition='in_left').get_root_widget() KioskWindow(transition='in_left').get_root_widget(),
from_window=self._root_widget,
) )
def _show_account_window(self) -> None: def _show_account_window(self) -> None:
# pylint: disable=cyclic-import # pylint: disable=cyclic-import
from bauiv1lib.account.settings import AccountSettingsWindow from bauiv1lib.account.settings import AccountSettingsWindow
# no-op if our underlying widget is dead or on its way out.
if not self._root_widget or self._root_widget.transitioning_out:
return
self._save_state() self._save_state()
bui.containerwidget(edit=self._root_widget, transition='out_left') bui.containerwidget(edit=self._root_widget, transition='out_left')
assert bui.app.classic is not None assert bui.app.classic is not None
bui.app.ui_v1.set_main_menu_window( bui.app.ui_v1.set_main_menu_window(
AccountSettingsWindow( AccountSettingsWindow(
origin_widget=self._account_button origin_widget=self._account_button
).get_root_widget() ).get_root_widget(),
from_window=self._root_widget,
) )
def _on_store_pressed(self) -> None: def _on_store_pressed(self) -> None:
@ -1072,6 +1086,10 @@ class MainMenuWindow(bui.Window):
from bauiv1lib.store.browser import StoreBrowserWindow from bauiv1lib.store.browser import StoreBrowserWindow
from bauiv1lib.account import show_sign_in_prompt from bauiv1lib.account import show_sign_in_prompt
# no-op if our underlying widget is dead or on its way out.
if not self._root_widget or self._root_widget.transitioning_out:
return
plus = bui.app.plus plus = bui.app.plus
assert plus is not None assert plus is not None
@ -1084,7 +1102,8 @@ class MainMenuWindow(bui.Window):
bui.app.ui_v1.set_main_menu_window( bui.app.ui_v1.set_main_menu_window(
StoreBrowserWindow( StoreBrowserWindow(
origin_widget=self._store_button origin_widget=self._store_button
).get_root_widget() ).get_root_widget(),
from_window=self._root_widget,
) )
def _is_benchmark(self) -> bool: def _is_benchmark(self) -> bool:
@ -1149,8 +1168,11 @@ class MainMenuWindow(bui.Window):
def _end_game(self) -> None: def _end_game(self) -> None:
assert bui.app.classic is not None assert bui.app.classic is not None
if not self._root_widget:
# no-op if our underlying widget is dead or on its way out.
if not self._root_widget or self._root_widget.transitioning_out:
return return
bui.containerwidget(edit=self._root_widget, transition='out_left') bui.containerwidget(edit=self._root_widget, transition='out_left')
bui.app.classic.return_to_main_menu_session_gracefully(reset_ui=False) bui.app.classic.return_to_main_menu_session_gracefully(reset_ui=False)
@ -1166,39 +1188,54 @@ class MainMenuWindow(bui.Window):
# pylint: disable=cyclic-import # pylint: disable=cyclic-import
from bauiv1lib.creditslist import CreditsListWindow from bauiv1lib.creditslist import CreditsListWindow
# no-op if our underlying widget is dead or on its way out.
if not self._root_widget or self._root_widget.transitioning_out:
return
self._save_state() self._save_state()
bui.containerwidget(edit=self._root_widget, transition='out_left') bui.containerwidget(edit=self._root_widget, transition='out_left')
assert bui.app.classic is not None assert bui.app.classic is not None
bui.app.ui_v1.set_main_menu_window( bui.app.ui_v1.set_main_menu_window(
CreditsListWindow( CreditsListWindow(
origin_widget=self._credits_button origin_widget=self._credits_button
).get_root_widget() ).get_root_widget(),
from_window=self._root_widget,
) )
def _howtoplay(self) -> None: def _howtoplay(self) -> None:
# pylint: disable=cyclic-import # pylint: disable=cyclic-import
from bauiv1lib.helpui import HelpWindow from bauiv1lib.helpui import HelpWindow
# no-op if our underlying widget is dead or on its way out.
if not self._root_widget or self._root_widget.transitioning_out:
return
self._save_state() self._save_state()
bui.containerwidget(edit=self._root_widget, transition='out_left') bui.containerwidget(edit=self._root_widget, transition='out_left')
assert bui.app.classic is not None assert bui.app.classic is not None
bui.app.ui_v1.set_main_menu_window( bui.app.ui_v1.set_main_menu_window(
HelpWindow( HelpWindow(
main_menu=True, origin_widget=self._how_to_play_button main_menu=True, origin_widget=self._how_to_play_button
).get_root_widget() ).get_root_widget(),
from_window=self._root_widget,
) )
def _settings(self) -> None: def _settings(self) -> None:
# pylint: disable=cyclic-import # pylint: disable=cyclic-import
from bauiv1lib.settings.allsettings import AllSettingsWindow from bauiv1lib.settings.allsettings import AllSettingsWindow
# no-op if our underlying widget is dead or on its way out.
if not self._root_widget or self._root_widget.transitioning_out:
return
self._save_state() self._save_state()
bui.containerwidget(edit=self._root_widget, transition='out_left') bui.containerwidget(edit=self._root_widget, transition='out_left')
assert bui.app.classic is not None assert bui.app.classic is not None
bui.app.ui_v1.set_main_menu_window( bui.app.ui_v1.set_main_menu_window(
AllSettingsWindow( AllSettingsWindow(
origin_widget=self._settings_button origin_widget=self._settings_button
).get_root_widget() ).get_root_widget(),
from_window=self._root_widget,
) )
def _resume_and_call(self, call: Callable[[], Any]) -> None: def _resume_and_call(self, call: Callable[[], Any]) -> None:
@ -1281,35 +1318,50 @@ class MainMenuWindow(bui.Window):
# pylint: disable=cyclic-import # pylint: disable=cyclic-import
from bauiv1lib.gather import GatherWindow from bauiv1lib.gather import GatherWindow
# no-op if our underlying widget is dead or on its way out.
if not self._root_widget or self._root_widget.transitioning_out:
return
self._save_state() self._save_state()
bui.containerwidget(edit=self._root_widget, transition='out_left') bui.containerwidget(edit=self._root_widget, transition='out_left')
assert bui.app.classic is not None assert bui.app.classic is not None
bui.app.ui_v1.set_main_menu_window( bui.app.ui_v1.set_main_menu_window(
GatherWindow(origin_widget=self._gather_button).get_root_widget() GatherWindow(origin_widget=self._gather_button).get_root_widget(),
from_window=self._root_widget,
) )
def _watch_press(self) -> None: def _watch_press(self) -> None:
# pylint: disable=cyclic-import # pylint: disable=cyclic-import
from bauiv1lib.watch import WatchWindow from bauiv1lib.watch import WatchWindow
# no-op if our underlying widget is dead or on its way out.
if not self._root_widget or self._root_widget.transitioning_out:
return
self._save_state() self._save_state()
bui.containerwidget(edit=self._root_widget, transition='out_left') bui.containerwidget(edit=self._root_widget, transition='out_left')
assert bui.app.classic is not None assert bui.app.classic is not None
bui.app.ui_v1.set_main_menu_window( bui.app.ui_v1.set_main_menu_window(
WatchWindow(origin_widget=self._watch_button).get_root_widget() WatchWindow(origin_widget=self._watch_button).get_root_widget(),
from_window=self._root_widget,
) )
def _play_press(self) -> None: def _play_press(self) -> None:
# pylint: disable=cyclic-import # pylint: disable=cyclic-import
from bauiv1lib.play import PlayWindow from bauiv1lib.play import PlayWindow
# no-op if our underlying widget is dead or on its way out.
if not self._root_widget or self._root_widget.transitioning_out:
return
self._save_state() self._save_state()
bui.containerwidget(edit=self._root_widget, transition='out_left') bui.containerwidget(edit=self._root_widget, transition='out_left')
assert bui.app.classic is not None assert bui.app.classic is not None
bui.app.ui_v1.selecting_private_party_playlist = False bui.app.ui_v1.selecting_private_party_playlist = False
bui.app.ui_v1.set_main_menu_window( bui.app.ui_v1.set_main_menu_window(
PlayWindow(origin_widget=self._start_button).get_root_widget() PlayWindow(origin_widget=self._start_button).get_root_widget(),
from_window=self._root_widget,
) )
def _resume(self) -> None: def _resume(self) -> None:

View File

@ -93,9 +93,10 @@ class PartyWindow(bui.Window):
iconscale=1.2, iconscale=1.2,
) )
info = bs.get_connection_to_host_info() info = bs.get_connection_to_host_info_2()
if info.get('name', '') != '':
title = bui.Lstr(value=info['name']) if info is not None and info.name != '':
title = bui.Lstr(value=info.name)
else: else:
title = bui.Lstr(resource=self._r + '.titleText') title = bui.Lstr(resource=self._r + '.titleText')
@ -483,7 +484,8 @@ class PartyWindow(bui.Window):
kick_str = bui.Lstr(resource='kickText') kick_str = bui.Lstr(resource='kickText')
else: else:
# kick-votes appeared in build 14248 # kick-votes appeared in build 14248
if bs.get_connection_to_host_info().get('build_number', 0) < 14248: info = bs.get_connection_to_host_info_2()
if info is None or info.build_number < 14248:
return return
kick_str = bui.Lstr(resource='kickVoteText') kick_str = bui.Lstr(resource='kickVoteText')
assert bui.app.classic is not None assert bui.app.classic is not None

View File

@ -521,13 +521,19 @@ class PlayWindow(bui.Window):
def _back(self) -> None: def _back(self) -> None:
# pylint: disable=cyclic-import # pylint: disable=cyclic-import
# no-op if our underlying widget is dead or on its way out.
if not self._root_widget or self._root_widget.transitioning_out:
return
if self._is_main_menu: if self._is_main_menu:
from bauiv1lib.mainmenu import MainMenuWindow from bauiv1lib.mainmenu import MainMenuWindow
self._save_state() self._save_state()
assert bui.app.classic is not None assert bui.app.classic is not None
bui.app.ui_v1.set_main_menu_window( bui.app.ui_v1.set_main_menu_window(
MainMenuWindow(transition='in_left').get_root_widget() MainMenuWindow(transition='in_left').get_root_widget(),
from_window=self._root_widget,
) )
bui.containerwidget( bui.containerwidget(
edit=self._root_widget, transition=self._transition_out edit=self._root_widget, transition=self._transition_out
@ -538,7 +544,8 @@ class PlayWindow(bui.Window):
self._save_state() self._save_state()
assert bui.app.classic is not None assert bui.app.classic is not None
bui.app.ui_v1.set_main_menu_window( bui.app.ui_v1.set_main_menu_window(
GatherWindow(transition='in_left').get_root_widget() GatherWindow(transition='in_left').get_root_widget(),
from_window=self._root_widget,
) )
bui.containerwidget( bui.containerwidget(
edit=self._root_widget, transition=self._transition_out edit=self._root_widget, transition=self._transition_out
@ -549,6 +556,10 @@ class PlayWindow(bui.Window):
from bauiv1lib.account import show_sign_in_prompt from bauiv1lib.account import show_sign_in_prompt
from bauiv1lib.coop.browser import CoopBrowserWindow from bauiv1lib.coop.browser import CoopBrowserWindow
# no-op if our underlying widget is dead or on its way out.
if not self._root_widget or self._root_widget.transitioning_out:
return
plus = bui.app.plus plus = bui.app.plus
assert plus is not None assert plus is not None
@ -559,26 +570,38 @@ class PlayWindow(bui.Window):
bui.containerwidget(edit=self._root_widget, transition='out_left') bui.containerwidget(edit=self._root_widget, transition='out_left')
assert bui.app.classic is not None assert bui.app.classic is not None
bui.app.ui_v1.set_main_menu_window( bui.app.ui_v1.set_main_menu_window(
CoopBrowserWindow(origin_widget=self._coop_button).get_root_widget() CoopBrowserWindow(
origin_widget=self._coop_button
).get_root_widget(),
from_window=self._root_widget,
) )
def _team_tourney(self) -> None: def _team_tourney(self) -> None:
# pylint: disable=cyclic-import # pylint: disable=cyclic-import
from bauiv1lib.playlist.browser import PlaylistBrowserWindow from bauiv1lib.playlist.browser import PlaylistBrowserWindow
# no-op if our underlying widget is dead or on its way out.
if not self._root_widget or self._root_widget.transitioning_out:
return
self._save_state() self._save_state()
bui.containerwidget(edit=self._root_widget, transition='out_left') bui.containerwidget(edit=self._root_widget, transition='out_left')
assert bui.app.classic is not None assert bui.app.classic is not None
bui.app.ui_v1.set_main_menu_window( bui.app.ui_v1.set_main_menu_window(
PlaylistBrowserWindow( PlaylistBrowserWindow(
origin_widget=self._teams_button, sessiontype=bs.DualTeamSession origin_widget=self._teams_button, sessiontype=bs.DualTeamSession
).get_root_widget() ).get_root_widget(),
from_window=self._root_widget,
) )
def _free_for_all(self) -> None: def _free_for_all(self) -> None:
# pylint: disable=cyclic-import # pylint: disable=cyclic-import
from bauiv1lib.playlist.browser import PlaylistBrowserWindow from bauiv1lib.playlist.browser import PlaylistBrowserWindow
# no-op if our underlying widget is dead or on its way out.
if not self._root_widget or self._root_widget.transitioning_out:
return
self._save_state() self._save_state()
bui.containerwidget(edit=self._root_widget, transition='out_left') bui.containerwidget(edit=self._root_widget, transition='out_left')
assert bui.app.classic is not None assert bui.app.classic is not None
@ -586,7 +609,8 @@ class PlayWindow(bui.Window):
PlaylistBrowserWindow( PlaylistBrowserWindow(
origin_widget=self._free_for_all_button, origin_widget=self._free_for_all_button,
sessiontype=bs.FreeForAllSession, sessiontype=bs.FreeForAllSession,
).get_root_widget() ).get_root_widget(),
from_window=self._root_widget,
) )
def _draw_dude( def _draw_dude(

View File

@ -684,6 +684,10 @@ class PlaylistBrowserWindow(bui.Window):
PlaylistCustomizeBrowserWindow, PlaylistCustomizeBrowserWindow,
) )
# no-op if our underlying widget is dead or on its way out.
if not self._root_widget or self._root_widget.transitioning_out:
return
self._save_state() self._save_state()
bui.containerwidget(edit=self._root_widget, transition='out_left') bui.containerwidget(edit=self._root_widget, transition='out_left')
assert bui.app.classic is not None assert bui.app.classic is not None
@ -691,13 +695,18 @@ class PlaylistBrowserWindow(bui.Window):
PlaylistCustomizeBrowserWindow( PlaylistCustomizeBrowserWindow(
origin_widget=self._customize_button, origin_widget=self._customize_button,
sessiontype=self._sessiontype, sessiontype=self._sessiontype,
).get_root_widget() ).get_root_widget(),
from_window=self._root_widget,
) )
def _on_back_press(self) -> None: def _on_back_press(self) -> None:
# pylint: disable=cyclic-import # pylint: disable=cyclic-import
from bauiv1lib.play import PlayWindow from bauiv1lib.play import PlayWindow
# no-op if our underlying widget is dead or on its way out.
if not self._root_widget or self._root_widget.transitioning_out:
return
# Store our selected playlist if that's changed. # Store our selected playlist if that's changed.
if self._selected_playlist is not None: if self._selected_playlist is not None:
prev_sel = bui.app.config.get( prev_sel = bui.app.config.get(
@ -716,7 +725,8 @@ class PlaylistBrowserWindow(bui.Window):
) )
assert bui.app.classic is not None assert bui.app.classic is not None
bui.app.ui_v1.set_main_menu_window( bui.app.ui_v1.set_main_menu_window(
PlayWindow(transition='in_left').get_root_widget() PlayWindow(transition='in_left').get_root_widget(),
from_window=self._root_widget,
) )
def _save_state(self) -> None: def _save_state(self) -> None:

View File

@ -323,6 +323,10 @@ class PlaylistCustomizeBrowserWindow(bui.Window):
# pylint: disable=cyclic-import # pylint: disable=cyclic-import
from bauiv1lib.playlist import browser from bauiv1lib.playlist import browser
# no-op if our underlying widget is dead or on its way out.
if not self._root_widget or self._root_widget.transitioning_out:
return
if self._selected_playlist_name is not None: if self._selected_playlist_name is not None:
cfg = bui.app.config cfg = bui.app.config
cfg[ cfg[
@ -337,7 +341,8 @@ class PlaylistCustomizeBrowserWindow(bui.Window):
bui.app.ui_v1.set_main_menu_window( bui.app.ui_v1.set_main_menu_window(
browser.PlaylistBrowserWindow( browser.PlaylistBrowserWindow(
transition='in_left', sessiontype=self._sessiontype transition='in_left', sessiontype=self._sessiontype
).get_root_widget() ).get_root_widget(),
from_window=self._root_widget,
) )
def _select(self, name: str, index: int) -> None: def _select(self, name: str, index: int) -> None:

View File

@ -283,6 +283,10 @@ class PlaylistEditWindow(bui.Window):
PlaylistCustomizeBrowserWindow, PlaylistCustomizeBrowserWindow,
) )
# no-op if our underlying widget is dead or on its way out.
if not self._root_widget or self._root_widget.transitioning_out:
return
bui.getsound('powerdown01').play() bui.getsound('powerdown01').play()
bui.containerwidget(edit=self._root_widget, transition='out_right') bui.containerwidget(edit=self._root_widget, transition='out_right')
assert bui.app.classic is not None assert bui.app.classic is not None
@ -293,7 +297,8 @@ class PlaylistEditWindow(bui.Window):
select_playlist=( select_playlist=(
self._editcontroller.get_existing_playlist_name() self._editcontroller.get_existing_playlist_name()
), ),
).get_root_widget() ).get_root_widget(),
from_window=self._root_widget,
) )
def _add(self) -> None: def _add(self) -> None:
@ -315,6 +320,10 @@ class PlaylistEditWindow(bui.Window):
PlaylistCustomizeBrowserWindow, PlaylistCustomizeBrowserWindow,
) )
# no-op if our underlying widget is dead or on its way out.
if not self._root_widget or self._root_widget.transitioning_out:
return
plus = bui.app.plus plus = bui.app.plus
assert plus is not None assert plus is not None
@ -380,7 +389,8 @@ class PlaylistEditWindow(bui.Window):
transition='in_left', transition='in_left',
sessiontype=self._editcontroller.get_session_type(), sessiontype=self._editcontroller.get_session_type(),
select_playlist=new_name, select_playlist=new_name,
).get_root_widget() ).get_root_widget(),
from_window=self._root_widget,
) )
def _save_press_with_sound(self) -> None: def _save_press_with_sound(self) -> None:

View File

@ -92,7 +92,8 @@ class PlaylistEditController:
bui.app.ui_v1.set_main_menu_window( bui.app.ui_v1.set_main_menu_window(
PlaylistEditWindow( PlaylistEditWindow(
editcontroller=self, transition=transition editcontroller=self, transition=transition
).get_root_widget() ).get_root_widget(),
from_window=False, # Disable this check.
) )
def get_config_name(self) -> str: def get_config_name(self) -> str:
@ -150,7 +151,8 @@ class PlaylistEditController:
assert bui.app.classic is not None assert bui.app.classic is not None
bui.app.ui_v1.clear_main_menu_window(transition='out_left') bui.app.ui_v1.clear_main_menu_window(transition='out_left')
bui.app.ui_v1.set_main_menu_window( bui.app.ui_v1.set_main_menu_window(
PlaylistAddGameWindow(editcontroller=self).get_root_widget() PlaylistAddGameWindow(editcontroller=self).get_root_widget(),
from_window=None,
) )
def edit_game_pressed(self) -> None: def edit_game_pressed(self) -> None:
@ -175,7 +177,8 @@ class PlaylistEditController:
bui.app.ui_v1.set_main_menu_window( bui.app.ui_v1.set_main_menu_window(
PlaylistEditWindow( PlaylistEditWindow(
editcontroller=self, transition='in_left' editcontroller=self, transition='in_left'
).get_root_widget() ).get_root_widget(),
from_window=None,
) )
def _show_edit_ui( def _show_edit_ui(
@ -205,7 +208,8 @@ class PlaylistEditController:
bui.app.ui_v1.set_main_menu_window( bui.app.ui_v1.set_main_menu_window(
PlaylistEditWindow( PlaylistEditWindow(
editcontroller=self, transition='in_left' editcontroller=self, transition='in_left'
).get_root_widget() ).get_root_widget(),
from_window=None,
) )
# Otherwise we were adding; go back to the add type choice list. # Otherwise we were adding; go back to the add type choice list.
@ -214,7 +218,8 @@ class PlaylistEditController:
bui.app.ui_v1.set_main_menu_window( bui.app.ui_v1.set_main_menu_window(
PlaylistAddGameWindow( PlaylistAddGameWindow(
editcontroller=self, transition='in_left' editcontroller=self, transition='in_left'
).get_root_widget() ).get_root_widget(),
from_window=None,
) )
else: else:
# Make sure type is in there. # Make sure type is in there.
@ -236,5 +241,6 @@ class PlaylistEditController:
bui.app.ui_v1.set_main_menu_window( bui.app.ui_v1.set_main_menu_window(
PlaylistEditWindow( PlaylistEditWindow(
editcontroller=self, transition='in_left' editcontroller=self, transition='in_left'
).get_root_widget() ).get_root_widget(),
from_window=None,
) )

View File

@ -514,6 +514,10 @@ class PlaylistEditGameWindow(bui.Window):
# pylint: disable=cyclic-import # pylint: disable=cyclic-import
from bauiv1lib.playlist.mapselect import PlaylistMapSelectWindow from bauiv1lib.playlist.mapselect import PlaylistMapSelectWindow
# no-op if our underlying widget is dead or on its way out.
if not self._root_widget or self._root_widget.transitioning_out:
return
# Replace ourself with the map-select UI. # Replace ourself with the map-select UI.
bui.containerwidget(edit=self._root_widget, transition='out_left') bui.containerwidget(edit=self._root_widget, transition='out_left')
assert bui.app.classic is not None assert bui.app.classic is not None
@ -524,7 +528,8 @@ class PlaylistEditGameWindow(bui.Window):
copy.deepcopy(self._getconfig()), copy.deepcopy(self._getconfig()),
self._edit_info, self._edit_info,
self._completion_call, self._completion_call,
).get_root_widget() ).get_root_widget(),
from_window=self._root_widget,
) )
def _choice_inc( def _choice_inc(

View File

@ -273,6 +273,10 @@ class PlaylistMapSelectWindow(bui.Window):
def _select(self, map_name: str) -> None: def _select(self, map_name: str) -> None:
from bauiv1lib.playlist.editgame import PlaylistEditGameWindow from bauiv1lib.playlist.editgame import PlaylistEditGameWindow
# no-op if our underlying widget is dead or on its way out.
if not self._root_widget or self._root_widget.transitioning_out:
return
self._config['settings']['map'] = map_name self._config['settings']['map'] = map_name
bui.containerwidget(edit=self._root_widget, transition='out_right') bui.containerwidget(edit=self._root_widget, transition='out_right')
assert bui.app.classic is not None assert bui.app.classic is not None
@ -285,7 +289,8 @@ class PlaylistMapSelectWindow(bui.Window):
default_selection='map', default_selection='map',
transition='in_left', transition='in_left',
edit_info=self._edit_info, edit_info=self._edit_info,
).get_root_widget() ).get_root_widget(),
from_window=self._root_widget,
) )
def _select_with_delay(self, map_name: str) -> None: def _select_with_delay(self, map_name: str) -> None:
@ -296,6 +301,10 @@ class PlaylistMapSelectWindow(bui.Window):
def _cancel(self) -> None: def _cancel(self) -> None:
from bauiv1lib.playlist.editgame import PlaylistEditGameWindow from bauiv1lib.playlist.editgame import PlaylistEditGameWindow
# no-op if our underlying widget is dead or on its way out.
if not self._root_widget or self._root_widget.transitioning_out:
return
bui.containerwidget(edit=self._root_widget, transition='out_right') bui.containerwidget(edit=self._root_widget, transition='out_right')
assert bui.app.classic is not None assert bui.app.classic is not None
bui.app.ui_v1.set_main_menu_window( bui.app.ui_v1.set_main_menu_window(
@ -307,5 +316,6 @@ class PlaylistMapSelectWindow(bui.Window):
default_selection='map', default_selection='map',
transition='in_left', transition='in_left',
edit_info=self._edit_info, edit_info=self._edit_info,
).get_root_widget() ).get_root_widget(),
from_window=self._root_widget,
) )

View File

@ -140,7 +140,6 @@ class PlayOptionsWindow(PopupWindow):
if show_shuffle_check_box: if show_shuffle_check_box:
self._height += 40 self._height += 40
# Creates our _root_widget.
uiscale = bui.app.ui_v1.uiscale uiscale = bui.app.ui_v1.uiscale
scale = ( scale = (
1.69 1.69
@ -149,6 +148,7 @@ class PlayOptionsWindow(PopupWindow):
if uiscale is bui.UIScale.MEDIUM if uiscale is bui.UIScale.MEDIUM
else 0.85 else 0.85
) )
# Creates our _root_widget.
super().__init__( super().__init__(
position=scale_origin, size=(self._width, self._height), scale=scale position=scale_origin, size=(self._width, self._height), scale=scale
) )
@ -448,6 +448,10 @@ class PlayOptionsWindow(PopupWindow):
self._transition_out() self._transition_out()
def _on_ok_press(self) -> None: def _on_ok_press(self) -> None:
# no-op if our underlying widget is dead or on its way out.
if not self.root_widget or self.root_widget.transitioning_out:
return
# Disallow if our playlist has disappeared. # Disallow if our playlist has disappeared.
if not self._does_target_playlist_exist(): if not self._does_target_playlist_exist():
return return
@ -478,8 +482,12 @@ class PlayOptionsWindow(PopupWindow):
cfg['Private Party Host Session Type'] = typename cfg['Private Party Host Session Type'] = typename
bui.getsound('gunCocking').play() bui.getsound('gunCocking').play()
assert bui.app.classic is not None assert bui.app.classic is not None
# Note: this is a wonky situation where we aren't actually
# the main window but we set it on behalf of the main window
# that popped us up.
bui.app.ui_v1.set_main_menu_window( bui.app.ui_v1.set_main_menu_window(
GatherWindow(transition='in_right').get_root_widget() GatherWindow(transition='in_right').get_root_widget(),
from_window=False, # Disable this test.
) )
self._transition_out(transition='out_left') self._transition_out(transition='out_left')
if self._delegate is not None: if self._delegate is not None:

View File

@ -214,6 +214,10 @@ class ProfileBrowserWindow(bui.Window):
from bauiv1lib.profile.edit import EditProfileWindow from bauiv1lib.profile.edit import EditProfileWindow
from bauiv1lib.purchase import PurchaseWindow from bauiv1lib.purchase import PurchaseWindow
# no-op if our underlying widget is dead or on its way out.
if not self._root_widget or self._root_widget.transitioning_out:
return
plus = bui.app.plus plus = bui.app.plus
assert plus is not None assert plus is not None
@ -254,7 +258,8 @@ class ProfileBrowserWindow(bui.Window):
bui.app.ui_v1.set_main_menu_window( bui.app.ui_v1.set_main_menu_window(
EditProfileWindow( EditProfileWindow(
existing_profile=None, in_main_menu=self._in_main_menu existing_profile=None, in_main_menu=self._in_main_menu
).get_root_widget() ).get_root_widget(),
from_window=self._root_widget if self._in_main_menu else False,
) )
def _delete_profile(self) -> None: def _delete_profile(self) -> None:
@ -303,6 +308,10 @@ class ProfileBrowserWindow(bui.Window):
# pylint: disable=cyclic-import # pylint: disable=cyclic-import
from bauiv1lib.profile.edit import EditProfileWindow from bauiv1lib.profile.edit import EditProfileWindow
# no-op if our underlying widget is dead or on its way out.
if not self._root_widget or self._root_widget.transitioning_out:
return
if self._selected_profile is None: if self._selected_profile is None:
bui.getsound('error').play() bui.getsound('error').play()
bui.screenmessage( bui.screenmessage(
@ -315,7 +324,8 @@ class ProfileBrowserWindow(bui.Window):
bui.app.ui_v1.set_main_menu_window( bui.app.ui_v1.set_main_menu_window(
EditProfileWindow( EditProfileWindow(
self._selected_profile, in_main_menu=self._in_main_menu self._selected_profile, in_main_menu=self._in_main_menu
).get_root_widget() ).get_root_widget(),
from_window=self._root_widget if self._in_main_menu else False,
) )
def _select(self, name: str, index: int) -> None: def _select(self, name: str, index: int) -> None:
@ -326,6 +336,10 @@ class ProfileBrowserWindow(bui.Window):
# pylint: disable=cyclic-import # pylint: disable=cyclic-import
from bauiv1lib.account.settings import AccountSettingsWindow from bauiv1lib.account.settings import AccountSettingsWindow
# no-op if our underlying widget is dead or on its way out.
if not self._root_widget or self._root_widget.transitioning_out:
return
assert bui.app.classic is not None assert bui.app.classic is not None
self._save_state() self._save_state()
@ -335,7 +349,8 @@ class ProfileBrowserWindow(bui.Window):
if self._in_main_menu: if self._in_main_menu:
assert bui.app.classic is not None assert bui.app.classic is not None
bui.app.ui_v1.set_main_menu_window( bui.app.ui_v1.set_main_menu_window(
AccountSettingsWindow(transition='in_left').get_root_widget() AccountSettingsWindow(transition='in_left').get_root_widget(),
from_window=self._root_widget,
) )
# If we're being called up standalone, handle pause/resume ourself. # If we're being called up standalone, handle pause/resume ourself.

View File

@ -18,12 +18,18 @@ class EditProfileWindow(bui.Window):
# FIXME: WILL NEED TO CHANGE THIS FOR UILOCATION. # FIXME: WILL NEED TO CHANGE THIS FOR UILOCATION.
def reload_window(self) -> None: def reload_window(self) -> None:
"""Transitions out and recreates ourself.""" """Transitions out and recreates ourself."""
# no-op if our underlying widget is dead or on its way out.
if not self._root_widget or self._root_widget.transitioning_out:
return
bui.containerwidget(edit=self._root_widget, transition='out_left') bui.containerwidget(edit=self._root_widget, transition='out_left')
assert bui.app.classic is not None assert bui.app.classic is not None
bui.app.ui_v1.set_main_menu_window( bui.app.ui_v1.set_main_menu_window(
EditProfileWindow( EditProfileWindow(
self.getname(), self._in_main_menu self.getname(), self._in_main_menu
).get_root_widget() ).get_root_widget(),
from_window=self._root_widget,
) )
def __init__( def __init__(
@ -672,6 +678,10 @@ class EditProfileWindow(bui.Window):
def _cancel(self) -> None: def _cancel(self) -> None:
from bauiv1lib.profile.browser import ProfileBrowserWindow from bauiv1lib.profile.browser import ProfileBrowserWindow
# no-op if our underlying widget is dead or on its way out.
if not self._root_widget or self._root_widget.transitioning_out:
return
bui.containerwidget(edit=self._root_widget, transition='out_right') bui.containerwidget(edit=self._root_widget, transition='out_right')
assert bui.app.classic is not None assert bui.app.classic is not None
bui.app.ui_v1.set_main_menu_window( bui.app.ui_v1.set_main_menu_window(
@ -679,7 +689,8 @@ class EditProfileWindow(bui.Window):
'in_left', 'in_left',
selected_profile=self._existing_profile, selected_profile=self._existing_profile,
in_main_menu=self._in_main_menu, in_main_menu=self._in_main_menu,
).get_root_widget() ).get_root_widget(),
from_window=self._root_widget,
) )
def _set_color(self, color: tuple[float, float, float]) -> None: def _set_color(self, color: tuple[float, float, float]) -> None:
@ -778,6 +789,10 @@ class EditProfileWindow(bui.Window):
"""Save has been selected.""" """Save has been selected."""
from bauiv1lib.profile.browser import ProfileBrowserWindow from bauiv1lib.profile.browser import ProfileBrowserWindow
# no-op if our underlying widget is dead or on its way out.
if not self._root_widget or self._root_widget.transitioning_out:
return False
plus = bui.app.plus plus = bui.app.plus
assert plus is not None assert plus is not None
@ -827,6 +842,7 @@ class EditProfileWindow(bui.Window):
'in_left', 'in_left',
selected_profile=new_name, selected_profile=new_name,
in_main_menu=self._in_main_menu, in_main_menu=self._in_main_menu,
).get_root_widget() ).get_root_widget(),
from_window=self._root_widget,
) )
return True return True

View File

@ -142,13 +142,18 @@ class PromoCodeWindow(bui.Window):
# pylint: disable=cyclic-import # pylint: disable=cyclic-import
from bauiv1lib.settings.advanced import AdvancedSettingsWindow from bauiv1lib.settings.advanced import AdvancedSettingsWindow
# no-op if our underlying widget is dead or on its way out.
if not self._root_widget or self._root_widget.transitioning_out:
return
bui.containerwidget( bui.containerwidget(
edit=self._root_widget, transition=self._transition_out edit=self._root_widget, transition=self._transition_out
) )
if not self._modal: if not self._modal:
assert bui.app.classic is not None assert bui.app.classic is not None
bui.app.ui_v1.set_main_menu_window( bui.app.ui_v1.set_main_menu_window(
AdvancedSettingsWindow(transition='in_left').get_root_widget() AdvancedSettingsWindow(transition='in_left').get_root_widget(),
from_window=self._root_widget,
) )
def _activate_enter_button(self) -> None: def _activate_enter_button(self) -> None:
@ -158,6 +163,10 @@ class PromoCodeWindow(bui.Window):
# pylint: disable=cyclic-import # pylint: disable=cyclic-import
from bauiv1lib.settings.advanced import AdvancedSettingsWindow from bauiv1lib.settings.advanced import AdvancedSettingsWindow
# no-op if our underlying widget is dead or on its way out.
if not self._root_widget or self._root_widget.transitioning_out:
return
plus = bui.app.plus plus = bui.app.plus
assert plus is not None assert plus is not None
@ -167,7 +176,8 @@ class PromoCodeWindow(bui.Window):
if not self._modal: if not self._modal:
assert bui.app.classic is not None assert bui.app.classic is not None
bui.app.ui_v1.set_main_menu_window( bui.app.ui_v1.set_main_menu_window(
AdvancedSettingsWindow(transition='in_left').get_root_widget() AdvancedSettingsWindow(transition='in_left').get_root_widget(),
from_window=self._root_widget,
) )
plus.add_v1_account_transaction( plus.add_v1_account_transaction(
{ {

View File

@ -682,11 +682,16 @@ class AdvancedSettingsWindow(bui.Window):
def _on_vr_test_press(self) -> None: def _on_vr_test_press(self) -> None:
from bauiv1lib.settings.vrtesting import VRTestingWindow from bauiv1lib.settings.vrtesting import VRTestingWindow
# no-op if our underlying widget is dead or on its way out.
if not self._root_widget or self._root_widget.transitioning_out:
return
self._save_state() self._save_state()
bui.containerwidget(edit=self._root_widget, transition='out_left') bui.containerwidget(edit=self._root_widget, transition='out_left')
assert bui.app.classic is not None assert bui.app.classic is not None
bui.app.ui_v1.set_main_menu_window( bui.app.ui_v1.set_main_menu_window(
VRTestingWindow(transition='in_right').get_root_widget() VRTestingWindow(transition='in_right').get_root_widget(),
from_window=self._root_widget,
) )
def _on_net_test_press(self) -> None: def _on_net_test_press(self) -> None:
@ -694,6 +699,10 @@ class AdvancedSettingsWindow(bui.Window):
assert plus is not None assert plus is not None
from bauiv1lib.settings.nettesting import NetTestingWindow from bauiv1lib.settings.nettesting import NetTestingWindow
# no-op if our underlying widget is dead or on its way out.
if not self._root_widget or self._root_widget.transitioning_out:
return
# Net-testing requires a signed in v1 account. # Net-testing requires a signed in v1 account.
if plus.get_v1_account_state() != 'signed_in': if plus.get_v1_account_state() != 'signed_in':
bui.screenmessage( bui.screenmessage(
@ -706,7 +715,8 @@ class AdvancedSettingsWindow(bui.Window):
bui.containerwidget(edit=self._root_widget, transition='out_left') bui.containerwidget(edit=self._root_widget, transition='out_left')
assert bui.app.classic is not None assert bui.app.classic is not None
bui.app.ui_v1.set_main_menu_window( bui.app.ui_v1.set_main_menu_window(
NetTestingWindow(transition='in_right').get_root_widget() NetTestingWindow(transition='in_right').get_root_widget(),
from_window=self._root_widget,
) )
def _on_friend_promo_code_press(self) -> None: def _on_friend_promo_code_press(self) -> None:
@ -724,17 +734,26 @@ class AdvancedSettingsWindow(bui.Window):
def _on_plugins_button_press(self) -> None: def _on_plugins_button_press(self) -> None:
from bauiv1lib.settings.plugins import PluginWindow from bauiv1lib.settings.plugins import PluginWindow
# no-op if our underlying widget is dead or on its way out.
if not self._root_widget or self._root_widget.transitioning_out:
return
self._save_state() self._save_state()
bui.containerwidget(edit=self._root_widget, transition='out_left') bui.containerwidget(edit=self._root_widget, transition='out_left')
assert bui.app.classic is not None assert bui.app.classic is not None
bui.app.ui_v1.set_main_menu_window( bui.app.ui_v1.set_main_menu_window(
PluginWindow(origin_widget=self._plugins_button).get_root_widget() PluginWindow(origin_widget=self._plugins_button).get_root_widget(),
from_window=self._root_widget,
) )
def _on_promo_code_press(self) -> None: def _on_promo_code_press(self) -> None:
from bauiv1lib.promocode import PromoCodeWindow from bauiv1lib.promocode import PromoCodeWindow
from bauiv1lib.account import show_sign_in_prompt from bauiv1lib.account import show_sign_in_prompt
# no-op if our underlying widget is dead or on its way out.
if not self._root_widget or self._root_widget.transitioning_out:
return
plus = bui.app.plus plus = bui.app.plus
assert plus is not None assert plus is not None
@ -742,23 +761,30 @@ class AdvancedSettingsWindow(bui.Window):
if plus.get_v1_account_state() != 'signed_in': if plus.get_v1_account_state() != 'signed_in':
show_sign_in_prompt() show_sign_in_prompt()
return return
self._save_state() self._save_state()
bui.containerwidget(edit=self._root_widget, transition='out_left') bui.containerwidget(edit=self._root_widget, transition='out_left')
assert bui.app.classic is not None assert bui.app.classic is not None
bui.app.ui_v1.set_main_menu_window( bui.app.ui_v1.set_main_menu_window(
PromoCodeWindow( PromoCodeWindow(
origin_widget=self._promo_code_button origin_widget=self._promo_code_button
).get_root_widget() ).get_root_widget(),
from_window=self._root_widget,
) )
def _on_benchmark_press(self) -> None: def _on_benchmark_press(self) -> None:
from bauiv1lib.debug import DebugWindow from bauiv1lib.debug import DebugWindow
# no-op if our underlying widget is dead or on its way out.
if not self._root_widget or self._root_widget.transitioning_out:
return
self._save_state() self._save_state()
bui.containerwidget(edit=self._root_widget, transition='out_left') bui.containerwidget(edit=self._root_widget, transition='out_left')
assert bui.app.classic is not None assert bui.app.classic is not None
bui.app.ui_v1.set_main_menu_window( bui.app.ui_v1.set_main_menu_window(
DebugWindow(transition='in_right').get_root_widget() DebugWindow(transition='in_right').get_root_widget(),
from_window=self._root_widget,
) )
def _save_state(self) -> None: def _save_state(self) -> None:
@ -908,11 +934,16 @@ class AdvancedSettingsWindow(bui.Window):
def _do_back(self) -> None: def _do_back(self) -> None:
from bauiv1lib.settings.allsettings import AllSettingsWindow from bauiv1lib.settings.allsettings import AllSettingsWindow
# no-op if our underlying widget is dead or on its way out.
if not self._root_widget or self._root_widget.transitioning_out:
return
self._save_state() self._save_state()
bui.containerwidget( bui.containerwidget(
edit=self._root_widget, transition=self._transition_out edit=self._root_widget, transition=self._transition_out
) )
assert bui.app.classic is not None assert bui.app.classic is not None
bui.app.ui_v1.set_main_menu_window( bui.app.ui_v1.set_main_menu_window(
AllSettingsWindow(transition='in_left').get_root_widget() AllSettingsWindow(transition='in_left').get_root_widget(),
from_window=self._root_widget,
) )

View File

@ -235,65 +235,90 @@ class AllSettingsWindow(bui.Window):
# pylint: disable=cyclic-import # pylint: disable=cyclic-import
from bauiv1lib.mainmenu import MainMenuWindow from bauiv1lib.mainmenu import MainMenuWindow
# no-op if our underlying widget is dead or on its way out.
if not self._root_widget or self._root_widget.transitioning_out:
return
self._save_state() self._save_state()
bui.containerwidget( bui.containerwidget(
edit=self._root_widget, transition=self._transition_out edit=self._root_widget, transition=self._transition_out
) )
assert bui.app.classic is not None assert bui.app.classic is not None
bui.app.ui_v1.set_main_menu_window( bui.app.ui_v1.set_main_menu_window(
MainMenuWindow(transition='in_left').get_root_widget() MainMenuWindow(transition='in_left').get_root_widget(),
from_window=self._root_widget,
) )
def _do_controllers(self) -> None: def _do_controllers(self) -> None:
# pylint: disable=cyclic-import # pylint: disable=cyclic-import
from bauiv1lib.settings.controls import ControlsSettingsWindow from bauiv1lib.settings.controls import ControlsSettingsWindow
# no-op if our underlying widget is dead or on its way out.
if not self._root_widget or self._root_widget.transitioning_out:
return
self._save_state() self._save_state()
bui.containerwidget(edit=self._root_widget, transition='out_left') bui.containerwidget(edit=self._root_widget, transition='out_left')
assert bui.app.classic is not None assert bui.app.classic is not None
bui.app.ui_v1.set_main_menu_window( bui.app.ui_v1.set_main_menu_window(
ControlsSettingsWindow( ControlsSettingsWindow(
origin_widget=self._controllers_button origin_widget=self._controllers_button
).get_root_widget() ).get_root_widget(),
from_window=self._root_widget,
) )
def _do_graphics(self) -> None: def _do_graphics(self) -> None:
# pylint: disable=cyclic-import # pylint: disable=cyclic-import
from bauiv1lib.settings.graphics import GraphicsSettingsWindow from bauiv1lib.settings.graphics import GraphicsSettingsWindow
# no-op if our underlying widget is dead or on its way out.
if not self._root_widget or self._root_widget.transitioning_out:
return
self._save_state() self._save_state()
bui.containerwidget(edit=self._root_widget, transition='out_left') bui.containerwidget(edit=self._root_widget, transition='out_left')
assert bui.app.classic is not None assert bui.app.classic is not None
bui.app.ui_v1.set_main_menu_window( bui.app.ui_v1.set_main_menu_window(
GraphicsSettingsWindow( GraphicsSettingsWindow(
origin_widget=self._graphics_button origin_widget=self._graphics_button
).get_root_widget() ).get_root_widget(),
from_window=self._root_widget,
) )
def _do_audio(self) -> None: def _do_audio(self) -> None:
# pylint: disable=cyclic-import # pylint: disable=cyclic-import
from bauiv1lib.settings.audio import AudioSettingsWindow from bauiv1lib.settings.audio import AudioSettingsWindow
# no-op if our underlying widget is dead or on its way out.
if not self._root_widget or self._root_widget.transitioning_out:
return
self._save_state() self._save_state()
bui.containerwidget(edit=self._root_widget, transition='out_left') bui.containerwidget(edit=self._root_widget, transition='out_left')
assert bui.app.classic is not None assert bui.app.classic is not None
bui.app.ui_v1.set_main_menu_window( bui.app.ui_v1.set_main_menu_window(
AudioSettingsWindow( AudioSettingsWindow(
origin_widget=self._audio_button origin_widget=self._audio_button
).get_root_widget() ).get_root_widget(),
from_window=self._root_widget,
) )
def _do_advanced(self) -> None: def _do_advanced(self) -> None:
# pylint: disable=cyclic-import # pylint: disable=cyclic-import
from bauiv1lib.settings.advanced import AdvancedSettingsWindow from bauiv1lib.settings.advanced import AdvancedSettingsWindow
# no-op if our underlying widget is dead or on its way out.
if not self._root_widget or self._root_widget.transitioning_out:
return
self._save_state() self._save_state()
bui.containerwidget(edit=self._root_widget, transition='out_left') bui.containerwidget(edit=self._root_widget, transition='out_left')
assert bui.app.classic is not None assert bui.app.classic is not None
bui.app.ui_v1.set_main_menu_window( bui.app.ui_v1.set_main_menu_window(
AdvancedSettingsWindow( AdvancedSettingsWindow(
origin_widget=self._advanced_button origin_widget=self._advanced_button
).get_root_widget() ).get_root_widget(),
from_window=self._root_widget,
) )
def _save_state(self) -> None: def _save_state(self) -> None:

View File

@ -237,6 +237,10 @@ class AudioSettingsWindow(bui.Window):
# pylint: disable=cyclic-import # pylint: disable=cyclic-import
from bauiv1lib.soundtrack import browser as stb from bauiv1lib.soundtrack import browser as stb
# no-op if our underlying widget is dead or on its way out.
if not self._root_widget or self._root_widget.transitioning_out:
return
# We require disk access for soundtracks; # We require disk access for soundtracks;
# if we don't have it, request it. # if we don't have it, request it.
if not bui.have_permission(bui.Permission.STORAGE): if not bui.have_permission(bui.Permission.STORAGE):
@ -256,13 +260,18 @@ class AudioSettingsWindow(bui.Window):
bui.app.ui_v1.set_main_menu_window( bui.app.ui_v1.set_main_menu_window(
stb.SoundtrackBrowserWindow( stb.SoundtrackBrowserWindow(
origin_widget=self._soundtrack_button origin_widget=self._soundtrack_button
).get_root_widget() ).get_root_widget(),
from_window=self._root_widget,
) )
def _back(self) -> None: def _back(self) -> None:
# pylint: disable=cyclic-import # pylint: disable=cyclic-import
from bauiv1lib.settings import allsettings from bauiv1lib.settings import allsettings
# no-op if our underlying widget is dead or on its way out.
if not self._root_widget or self._root_widget.transitioning_out:
return
self._save_state() self._save_state()
bui.containerwidget( bui.containerwidget(
edit=self._root_widget, transition=self._transition_out edit=self._root_widget, transition=self._transition_out
@ -271,7 +280,8 @@ class AudioSettingsWindow(bui.Window):
bui.app.ui_v1.set_main_menu_window( bui.app.ui_v1.set_main_menu_window(
allsettings.AllSettingsWindow( allsettings.AllSettingsWindow(
transition='in_left' transition='in_left'
).get_root_widget() ).get_root_widget(),
from_window=self._root_widget,
) )
def _save_state(self) -> None: def _save_state(self) -> None:

View File

@ -367,59 +367,84 @@ class ControlsSettingsWindow(bui.Window):
# pylint: disable=cyclic-import # pylint: disable=cyclic-import
from bauiv1lib.settings.keyboard import ConfigKeyboardWindow from bauiv1lib.settings.keyboard import ConfigKeyboardWindow
# no-op if our underlying widget is dead or on its way out.
if not self._root_widget or self._root_widget.transitioning_out:
return
self._save_state() self._save_state()
bui.containerwidget(edit=self._root_widget, transition='out_left') bui.containerwidget(edit=self._root_widget, transition='out_left')
assert bui.app.classic is not None assert bui.app.classic is not None
bui.app.ui_v1.set_main_menu_window( bui.app.ui_v1.set_main_menu_window(
ConfigKeyboardWindow( ConfigKeyboardWindow(
bs.getinputdevice('Keyboard', '#1') bs.getinputdevice('Keyboard', '#1')
).get_root_widget() ).get_root_widget(),
from_window=self._root_widget,
) )
def _config_keyboard2(self) -> None: def _config_keyboard2(self) -> None:
# pylint: disable=cyclic-import # pylint: disable=cyclic-import
from bauiv1lib.settings.keyboard import ConfigKeyboardWindow from bauiv1lib.settings.keyboard import ConfigKeyboardWindow
# no-op if our underlying widget is dead or on its way out.
if not self._root_widget or self._root_widget.transitioning_out:
return
self._save_state() self._save_state()
bui.containerwidget(edit=self._root_widget, transition='out_left') bui.containerwidget(edit=self._root_widget, transition='out_left')
assert bui.app.classic is not None assert bui.app.classic is not None
bui.app.ui_v1.set_main_menu_window( bui.app.ui_v1.set_main_menu_window(
ConfigKeyboardWindow( ConfigKeyboardWindow(
bs.getinputdevice('Keyboard', '#2') bs.getinputdevice('Keyboard', '#2')
).get_root_widget() ).get_root_widget(),
from_window=self._root_widget,
) )
def _do_mobile_devices(self) -> None: def _do_mobile_devices(self) -> None:
# pylint: disable=cyclic-import # pylint: disable=cyclic-import
from bauiv1lib.settings.remoteapp import RemoteAppSettingsWindow from bauiv1lib.settings.remoteapp import RemoteAppSettingsWindow
# no-op if our underlying widget is dead or on its way out.
if not self._root_widget or self._root_widget.transitioning_out:
return
self._save_state() self._save_state()
bui.containerwidget(edit=self._root_widget, transition='out_left') bui.containerwidget(edit=self._root_widget, transition='out_left')
assert bui.app.classic is not None assert bui.app.classic is not None
bui.app.ui_v1.set_main_menu_window( bui.app.ui_v1.set_main_menu_window(
RemoteAppSettingsWindow().get_root_widget() RemoteAppSettingsWindow().get_root_widget(),
from_window=self._root_widget,
) )
def _do_gamepads(self) -> None: def _do_gamepads(self) -> None:
# pylint: disable=cyclic-import # pylint: disable=cyclic-import
from bauiv1lib.settings.gamepadselect import GamepadSelectWindow from bauiv1lib.settings.gamepadselect import GamepadSelectWindow
# no-op if our underlying widget is dead or on its way out.
if not self._root_widget or self._root_widget.transitioning_out:
return
self._save_state() self._save_state()
bui.containerwidget(edit=self._root_widget, transition='out_left') bui.containerwidget(edit=self._root_widget, transition='out_left')
assert bui.app.classic is not None assert bui.app.classic is not None
bui.app.ui_v1.set_main_menu_window( bui.app.ui_v1.set_main_menu_window(
GamepadSelectWindow().get_root_widget() GamepadSelectWindow().get_root_widget(),
from_window=self._root_widget,
) )
def _do_touchscreen(self) -> None: def _do_touchscreen(self) -> None:
# pylint: disable=cyclic-import # pylint: disable=cyclic-import
from bauiv1lib.settings.touchscreen import TouchscreenSettingsWindow from bauiv1lib.settings.touchscreen import TouchscreenSettingsWindow
# no-op if our underlying widget is dead or on its way out.
if not self._root_widget or self._root_widget.transitioning_out:
return
self._save_state() self._save_state()
bui.containerwidget(edit=self._root_widget, transition='out_left') bui.containerwidget(edit=self._root_widget, transition='out_left')
assert bui.app.classic is not None assert bui.app.classic is not None
bui.app.ui_v1.set_main_menu_window( bui.app.ui_v1.set_main_menu_window(
TouchscreenSettingsWindow().get_root_widget() TouchscreenSettingsWindow().get_root_widget(),
from_window=self._root_widget,
) )
def _save_state(self) -> None: def _save_state(self) -> None:
@ -466,11 +491,16 @@ class ControlsSettingsWindow(bui.Window):
# pylint: disable=cyclic-import # pylint: disable=cyclic-import
from bauiv1lib.settings.allsettings import AllSettingsWindow from bauiv1lib.settings.allsettings import AllSettingsWindow
# no-op if our underlying widget is dead or on its way out.
if not self._root_widget or self._root_widget.transitioning_out:
return
self._save_state() self._save_state()
bui.containerwidget( bui.containerwidget(
edit=self._root_widget, transition=self._transition_out edit=self._root_widget, transition=self._transition_out
) )
assert bui.app.classic is not None assert bui.app.classic is not None
bui.app.ui_v1.set_main_menu_window( bui.app.ui_v1.set_main_menu_window(
AllSettingsWindow(transition='in_left').get_root_widget() AllSettingsWindow(transition='in_left').get_root_widget(),
from_window=self._root_widget,
) )

View File

@ -795,19 +795,28 @@ class GamepadSettingsWindow(bui.Window):
def _cancel(self) -> None: def _cancel(self) -> None:
from bauiv1lib.settings.controls import ControlsSettingsWindow from bauiv1lib.settings.controls import ControlsSettingsWindow
# no-op if our underlying widget is dead or on its way out.
if not self._root_widget or self._root_widget.transitioning_out:
return
bui.containerwidget( bui.containerwidget(
edit=self._root_widget, transition=self._transition_out edit=self._root_widget, transition=self._transition_out
) )
if self._is_main_menu: if self._is_main_menu:
assert bui.app.classic is not None assert bui.app.classic is not None
bui.app.ui_v1.set_main_menu_window( bui.app.ui_v1.set_main_menu_window(
ControlsSettingsWindow(transition='in_left').get_root_widget() ControlsSettingsWindow(transition='in_left').get_root_widget(),
from_window=self._root_widget,
) )
def _save(self) -> None: def _save(self) -> None:
classic = bui.app.classic classic = bui.app.classic
assert classic is not None assert classic is not None
# no-op if our underlying widget is dead or on its way out.
if not self._root_widget or self._root_widget.transitioning_out:
return
bui.containerwidget( bui.containerwidget(
edit=self._root_widget, transition=self._transition_out edit=self._root_widget, transition=self._transition_out
) )
@ -852,7 +861,8 @@ class GamepadSettingsWindow(bui.Window):
assert bui.app.classic is not None assert bui.app.classic is not None
bui.app.ui_v1.set_main_menu_window( bui.app.ui_v1.set_main_menu_window(
ControlsSettingsWindow(transition='in_left').get_root_widget() ControlsSettingsWindow(transition='in_left').get_root_widget(),
from_window=self._root_widget,
) )

View File

@ -33,7 +33,8 @@ def gamepad_configure_callback(event: dict[str, Any]) -> None:
assert isinstance(device, bs.InputDevice) assert isinstance(device, bs.InputDevice)
if device.allows_configuring: if device.allows_configuring:
bui.app.ui_v1.set_main_menu_window( bui.app.ui_v1.set_main_menu_window(
gamepad.GamepadSettingsWindow(device).get_root_widget() gamepad.GamepadSettingsWindow(device).get_root_widget(),
from_window=None,
) )
else: else:
width = 700 width = 700
@ -51,7 +52,7 @@ def gamepad_configure_callback(event: dict[str, Any]) -> None:
size=(width, height), size=(width, height),
transition='in_right', transition='in_right',
) )
bui.app.ui_v1.set_main_menu_window(dlg) bui.app.ui_v1.set_main_menu_window(dlg, from_window=None)
if device.allows_configuring_in_system_settings: if device.allows_configuring_in_system_settings:
msg = bui.Lstr( msg = bui.Lstr(
@ -81,12 +82,17 @@ def gamepad_configure_callback(event: dict[str, Any]) -> None:
def _ok() -> None: def _ok() -> None:
from bauiv1lib.settings import controls from bauiv1lib.settings import controls
# no-op if our underlying widget is dead or on its way out.
if not dlg or dlg.transitioning_out:
return
bui.containerwidget(edit=dlg, transition='out_right') bui.containerwidget(edit=dlg, transition='out_right')
assert bui.app.classic is not None assert bui.app.classic is not None
bui.app.ui_v1.set_main_menu_window( bui.app.ui_v1.set_main_menu_window(
controls.ControlsSettingsWindow( controls.ControlsSettingsWindow(
transition='in_left' transition='in_left'
).get_root_widget() ).get_root_widget(),
from_window=dlg,
) )
bui.buttonwidget( bui.buttonwidget(
@ -191,11 +197,16 @@ class GamepadSelectWindow(bui.Window):
def _back(self) -> None: def _back(self) -> None:
from bauiv1lib.settings import controls from bauiv1lib.settings import controls
# no-op if our underlying widget is dead or on its way out.
if not self._root_widget or self._root_widget.transitioning_out:
return
bs.release_gamepad_input() bs.release_gamepad_input()
bui.containerwidget(edit=self._root_widget, transition='out_right') bui.containerwidget(edit=self._root_widget, transition='out_right')
assert bui.app.classic is not None assert bui.app.classic is not None
bui.app.ui_v1.set_main_menu_window( bui.app.ui_v1.set_main_menu_window(
controls.ControlsSettingsWindow( controls.ControlsSettingsWindow(
transition='in_left' transition='in_left'
).get_root_widget() ).get_root_widget(),
from_window=self._root_widget,
) )

View File

@ -436,6 +436,10 @@ class GraphicsSettingsWindow(bui.Window):
def _back(self) -> None: def _back(self) -> None:
from bauiv1lib.settings import allsettings from bauiv1lib.settings import allsettings
# no-op if our underlying widget is dead or on its way out.
if not self._root_widget or self._root_widget.transitioning_out:
return
# Applying max-fps takes a few moments. Apply if it hasn't been # Applying max-fps takes a few moments. Apply if it hasn't been
# yet. # yet.
self._apply_max_fps() self._apply_max_fps()
@ -447,7 +451,8 @@ class GraphicsSettingsWindow(bui.Window):
bui.app.ui_v1.set_main_menu_window( bui.app.ui_v1.set_main_menu_window(
allsettings.AllSettingsWindow( allsettings.AllSettingsWindow(
transition='in_left' transition='in_left'
).get_root_widget() ).get_root_widget(),
from_window=self._root_widget,
) )
def _set_quality(self, quality: str) -> None: def _set_quality(self, quality: str) -> None:

View File

@ -271,15 +271,24 @@ class ConfigKeyboardWindow(bui.Window):
def _cancel(self) -> None: def _cancel(self) -> None:
from bauiv1lib.settings.controls import ControlsSettingsWindow from bauiv1lib.settings.controls import ControlsSettingsWindow
# no-op if our underlying widget is dead or on its way out.
if not self._root_widget or self._root_widget.transitioning_out:
return
bui.containerwidget(edit=self._root_widget, transition='out_right') bui.containerwidget(edit=self._root_widget, transition='out_right')
assert bui.app.classic is not None assert bui.app.classic is not None
bui.app.ui_v1.set_main_menu_window( bui.app.ui_v1.set_main_menu_window(
ControlsSettingsWindow(transition='in_left').get_root_widget() ControlsSettingsWindow(transition='in_left').get_root_widget(),
from_window=self._root_widget,
) )
def _save(self) -> None: def _save(self) -> None:
from bauiv1lib.settings.controls import ControlsSettingsWindow from bauiv1lib.settings.controls import ControlsSettingsWindow
# no-op if our underlying widget is dead or on its way out.
if not self._root_widget or self._root_widget.transitioning_out:
return
assert bui.app.classic is not None assert bui.app.classic is not None
bui.containerwidget(edit=self._root_widget, transition='out_right') bui.containerwidget(edit=self._root_widget, transition='out_right')
bui.getsound('gunCocking').play() bui.getsound('gunCocking').play()
@ -314,7 +323,8 @@ class ConfigKeyboardWindow(bui.Window):
) )
bui.app.config.apply_and_commit() bui.app.config.apply_and_commit()
bui.app.ui_v1.set_main_menu_window( bui.app.ui_v1.set_main_menu_window(
ControlsSettingsWindow(transition='in_left').get_root_widget() ControlsSettingsWindow(transition='in_left').get_root_widget(),
from_window=self._root_widget,
) )

View File

@ -135,8 +135,14 @@ class NetTestingWindow(bui.Window):
def _show_val_testing(self) -> None: def _show_val_testing(self) -> None:
assert bui.app.classic is not None assert bui.app.classic is not None
# no-op if our underlying widget is dead or on its way out.
if not self._root_widget or self._root_widget.transitioning_out:
return
bui.app.ui_v1.set_main_menu_window( bui.app.ui_v1.set_main_menu_window(
NetValTestingWindow().get_root_widget() NetValTestingWindow().get_root_widget(),
from_window=self._root_widget,
) )
bui.containerwidget(edit=self._root_widget, transition='out_left') bui.containerwidget(edit=self._root_widget, transition='out_left')
@ -144,9 +150,14 @@ class NetTestingWindow(bui.Window):
# pylint: disable=cyclic-import # pylint: disable=cyclic-import
from bauiv1lib.settings.advanced import AdvancedSettingsWindow from bauiv1lib.settings.advanced import AdvancedSettingsWindow
# no-op if our underlying widget is dead or on its way out.
if not self._root_widget or self._root_widget.transitioning_out:
return
assert bui.app.classic is not None assert bui.app.classic is not None
bui.app.ui_v1.set_main_menu_window( bui.app.ui_v1.set_main_menu_window(
AdvancedSettingsWindow(transition='in_left').get_root_widget() AdvancedSettingsWindow(transition='in_left').get_root_widget(),
from_window=self._root_widget,
) )
bui.containerwidget(edit=self._root_widget, transition='out_right') bui.containerwidget(edit=self._root_widget, transition='out_right')

View File

@ -232,11 +232,16 @@ class PluginWindow(bui.Window):
# pylint: disable=cyclic-import # pylint: disable=cyclic-import
from bauiv1lib.settings.pluginsettings import PluginSettingsWindow from bauiv1lib.settings.pluginsettings import PluginSettingsWindow
# no-op if our underlying widget is dead or on its way out.
if not self._root_widget or self._root_widget.transitioning_out:
return
self._save_state() self._save_state()
bui.containerwidget(edit=self._root_widget, transition='out_left') bui.containerwidget(edit=self._root_widget, transition='out_left')
assert bui.app.classic is not None assert bui.app.classic is not None
bui.app.ui_v1.set_main_menu_window( bui.app.ui_v1.set_main_menu_window(
PluginSettingsWindow(transition='in_right').get_root_widget() PluginSettingsWindow(transition='in_right').get_root_widget(),
from_window=self._root_widget,
) )
def _show_category_options(self) -> None: def _show_category_options(self) -> None:
@ -449,11 +454,16 @@ class PluginWindow(bui.Window):
# pylint: disable=cyclic-import # pylint: disable=cyclic-import
from bauiv1lib.settings.advanced import AdvancedSettingsWindow from bauiv1lib.settings.advanced import AdvancedSettingsWindow
# no-op if our underlying widget is dead or on its way out.
if not self._root_widget or self._root_widget.transitioning_out:
return
self._save_state() self._save_state()
bui.containerwidget( bui.containerwidget(
edit=self._root_widget, transition=self._transition_out edit=self._root_widget, transition=self._transition_out
) )
assert bui.app.classic is not None assert bui.app.classic is not None
bui.app.ui_v1.set_main_menu_window( bui.app.ui_v1.set_main_menu_window(
AdvancedSettingsWindow(transition='in_left').get_root_widget() AdvancedSettingsWindow(transition='in_left').get_root_widget(),
from_window=self._root_widget,
) )

View File

@ -161,10 +161,15 @@ class PluginSettingsWindow(bui.Window):
# pylint: disable=cyclic-import # pylint: disable=cyclic-import
from bauiv1lib.settings.plugins import PluginWindow from bauiv1lib.settings.plugins import PluginWindow
# no-op if our underlying widget is dead or on its way out.
if not self._root_widget or self._root_widget.transitioning_out:
return
bui.containerwidget( bui.containerwidget(
edit=self._root_widget, transition=self._transition_out edit=self._root_widget, transition=self._transition_out
) )
assert bui.app.classic is not None assert bui.app.classic is not None
bui.app.ui_v1.set_main_menu_window( bui.app.ui_v1.set_main_menu_window(
PluginWindow(transition='in_left').get_root_widget() PluginWindow(transition='in_left').get_root_widget(),
from_window=self._root_widget,
) )

View File

@ -138,10 +138,15 @@ class RemoteAppSettingsWindow(bui.Window):
def _back(self) -> None: def _back(self) -> None:
from bauiv1lib.settings import controls from bauiv1lib.settings import controls
# no-op if our underlying widget is dead or on its way out.
if not self._root_widget or self._root_widget.transitioning_out:
return
bui.containerwidget(edit=self._root_widget, transition='out_right') bui.containerwidget(edit=self._root_widget, transition='out_right')
assert bui.app.classic is not None assert bui.app.classic is not None
bui.app.ui_v1.set_main_menu_window( bui.app.ui_v1.set_main_menu_window(
controls.ControlsSettingsWindow( controls.ControlsSettingsWindow(
transition='in_left' transition='in_left'
).get_root_widget() ).get_root_widget(),
from_window=self._root_widget,
) )

View File

@ -217,6 +217,10 @@ class TestingWindow(bui.Window):
# pylint: disable=cyclic-import # pylint: disable=cyclic-import
from bauiv1lib.settings.advanced import AdvancedSettingsWindow from bauiv1lib.settings.advanced import AdvancedSettingsWindow
# no-op if our underlying widget is dead or on its way out.
if not self._root_widget or self._root_widget.transitioning_out:
return
bui.containerwidget(edit=self._root_widget, transition='out_right') bui.containerwidget(edit=self._root_widget, transition='out_right')
backwin = ( backwin = (
self._back_call() self._back_call()
@ -224,4 +228,6 @@ class TestingWindow(bui.Window):
else AdvancedSettingsWindow(transition='in_left') else AdvancedSettingsWindow(transition='in_left')
) )
assert bui.app.classic is not None assert bui.app.classic is not None
bui.app.ui_v1.set_main_menu_window(backwin.get_root_widget()) bui.app.ui_v1.set_main_menu_window(
backwin.get_root_widget(), from_window=self._root_widget
)

View File

@ -276,11 +276,16 @@ class TouchscreenSettingsWindow(bui.Window):
def _back(self) -> None: def _back(self) -> None:
from bauiv1lib.settings import controls from bauiv1lib.settings import controls
# no-op if our underlying widget is dead or on its way out.
if not self._root_widget or self._root_widget.transitioning_out:
return
bui.containerwidget(edit=self._root_widget, transition='out_right') bui.containerwidget(edit=self._root_widget, transition='out_right')
assert bui.app.classic is not None assert bui.app.classic is not None
bui.app.ui_v1.set_main_menu_window( bui.app.ui_v1.set_main_menu_window(
controls.ControlsSettingsWindow( controls.ControlsSettingsWindow(
transition='in_left' transition='in_left'
).get_root_widget() ).get_root_widget(),
from_window=self._root_widget,
) )
bs.set_touchscreen_editing(False) bs.set_touchscreen_editing(False)

View File

@ -394,13 +394,18 @@ class SoundtrackBrowserWindow(bui.Window):
# pylint: disable=cyclic-import # pylint: disable=cyclic-import
from bauiv1lib.settings import audio from bauiv1lib.settings import audio
# no-op if our underlying widget is dead or on its way out.
if not self._root_widget or self._root_widget.transitioning_out:
return
self._save_state() self._save_state()
bui.containerwidget( bui.containerwidget(
edit=self._root_widget, transition=self._transition_out edit=self._root_widget, transition=self._transition_out
) )
assert bui.app.classic is not None assert bui.app.classic is not None
bui.app.ui_v1.set_main_menu_window( bui.app.ui_v1.set_main_menu_window(
audio.AudioSettingsWindow(transition='in_left').get_root_widget() audio.AudioSettingsWindow(transition='in_left').get_root_widget(),
from_window=self._root_widget,
) )
def _edit_soundtrack_with_sound(self) -> None: def _edit_soundtrack_with_sound(self) -> None:
@ -421,6 +426,10 @@ class SoundtrackBrowserWindow(bui.Window):
from bauiv1lib.purchase import PurchaseWindow from bauiv1lib.purchase import PurchaseWindow
from bauiv1lib.soundtrack.edit import SoundtrackEditWindow from bauiv1lib.soundtrack.edit import SoundtrackEditWindow
# no-op if our underlying widget is dead or on its way out.
if not self._root_widget or self._root_widget.transitioning_out:
return
if ( if (
bui.app.classic is not None bui.app.classic is not None
and not bui.app.classic.accounts.have_pro_options() and not bui.app.classic.accounts.have_pro_options()
@ -443,7 +452,8 @@ class SoundtrackBrowserWindow(bui.Window):
bui.app.ui_v1.set_main_menu_window( bui.app.ui_v1.set_main_menu_window(
SoundtrackEditWindow( SoundtrackEditWindow(
existing_soundtrack=self._selected_soundtrack existing_soundtrack=self._selected_soundtrack
).get_root_widget() ).get_root_widget(),
from_window=self._root_widget,
) )
def _get_soundtrack_display_name(self, soundtrack: str) -> bui.Lstr: def _get_soundtrack_display_name(self, soundtrack: str) -> bui.Lstr:

View File

@ -351,7 +351,8 @@ class SoundtrackEditWindow(bui.Window):
soundtrack[musictype] = entry soundtrack[musictype] = entry
bui.app.ui_v1.set_main_menu_window( bui.app.ui_v1.set_main_menu_window(
cls(state, transition='in_left').get_root_widget() cls(state, transition='in_left').get_root_widget(),
from_window=False, # Disable check here.
) )
def _get_entry( def _get_entry(
@ -359,6 +360,11 @@ class SoundtrackEditWindow(bui.Window):
) -> None: ) -> None:
assert bui.app.classic is not None assert bui.app.classic is not None
music = bui.app.classic.music music = bui.app.classic.music
# no-op if our underlying widget is dead or on its way out.
if not self._root_widget or self._root_widget.transitioning_out:
return
if selection_target_name != '': if selection_target_name != '':
selection_target_name = "'" + selection_target_name + "'" selection_target_name = "'" + selection_target_name + "'"
state = { state = {
@ -375,7 +381,8 @@ class SoundtrackEditWindow(bui.Window):
entry, entry,
selection_target_name, selection_target_name,
) )
.get_root_widget() .get_root_widget(),
from_window=self._root_widget,
) )
def _test(self, song_type: bs.MusicType) -> None: def _test(self, song_type: bs.MusicType) -> None:
@ -422,6 +429,10 @@ class SoundtrackEditWindow(bui.Window):
def _cancel(self) -> None: def _cancel(self) -> None:
from bauiv1lib.soundtrack import browser as stb from bauiv1lib.soundtrack import browser as stb
# no-op if our underlying widget is dead or on its way out.
if not self._root_widget or self._root_widget.transitioning_out:
return
assert bui.app.classic is not None assert bui.app.classic is not None
music = bui.app.classic.music music = bui.app.classic.music
@ -429,12 +440,17 @@ class SoundtrackEditWindow(bui.Window):
music.set_music_play_mode(bui.app.classic.MusicPlayMode.REGULAR) music.set_music_play_mode(bui.app.classic.MusicPlayMode.REGULAR)
bui.containerwidget(edit=self._root_widget, transition='out_right') bui.containerwidget(edit=self._root_widget, transition='out_right')
bui.app.ui_v1.set_main_menu_window( bui.app.ui_v1.set_main_menu_window(
stb.SoundtrackBrowserWindow(transition='in_left').get_root_widget() stb.SoundtrackBrowserWindow(transition='in_left').get_root_widget(),
from_window=self._root_widget,
) )
def _do_it(self) -> None: def _do_it(self) -> None:
from bauiv1lib.soundtrack import browser as stb from bauiv1lib.soundtrack import browser as stb
# no-op if our underlying widget is dead or on its way out.
if not self._root_widget or self._root_widget.transitioning_out:
return
assert bui.app.classic is not None assert bui.app.classic is not None
music = bui.app.classic.music music = bui.app.classic.music
cfg = bui.app.config cfg = bui.app.config
@ -483,7 +499,8 @@ class SoundtrackEditWindow(bui.Window):
) )
bui.app.ui_v1.set_main_menu_window( bui.app.ui_v1.set_main_menu_window(
stb.SoundtrackBrowserWindow(transition='in_left').get_root_widget() stb.SoundtrackBrowserWindow(transition='in_left').get_root_widget(),
from_window=self._root_widget,
) )
def _do_it_with_sound(self) -> None: def _do_it_with_sound(self) -> None:

View File

@ -166,6 +166,10 @@ class SoundtrackEntryTypeSelectWindow(bui.Window):
MacMusicAppPlaylistSelectWindow, MacMusicAppPlaylistSelectWindow,
) )
# no-op if our underlying widget is dead or on its way out.
if not self._root_widget or self._root_widget.transitioning_out:
return
bui.containerwidget(edit=self._root_widget, transition='out_left') bui.containerwidget(edit=self._root_widget, transition='out_left')
current_playlist_entry: str | None current_playlist_entry: str | None
@ -181,7 +185,8 @@ class SoundtrackEntryTypeSelectWindow(bui.Window):
bui.app.ui_v1.set_main_menu_window( bui.app.ui_v1.set_main_menu_window(
MacMusicAppPlaylistSelectWindow( MacMusicAppPlaylistSelectWindow(
self._callback, current_playlist_entry, self._current_entry self._callback, current_playlist_entry, self._current_entry
).get_root_widget() ).get_root_widget(),
from_window=self._root_widget,
) )
def _on_music_file_press(self) -> None: def _on_music_file_press(self) -> None:
@ -189,6 +194,10 @@ class SoundtrackEntryTypeSelectWindow(bui.Window):
from baclassic.osmusic import OSMusicPlayer from baclassic.osmusic import OSMusicPlayer
from bauiv1lib.fileselector import FileSelectorWindow from bauiv1lib.fileselector import FileSelectorWindow
# no-op if our underlying widget is dead or on its way out.
if not self._root_widget or self._root_widget.transitioning_out:
return
bui.containerwidget(edit=self._root_widget, transition='out_left') bui.containerwidget(edit=self._root_widget, transition='out_left')
base_path = android_get_external_files_dir() base_path = android_get_external_files_dir()
assert bui.app.classic is not None assert bui.app.classic is not None
@ -201,13 +210,18 @@ class SoundtrackEntryTypeSelectWindow(bui.Window):
OSMusicPlayer.get_valid_music_file_extensions() OSMusicPlayer.get_valid_music_file_extensions()
), ),
allow_folders=False, allow_folders=False,
).get_root_widget() ).get_root_widget(),
from_window=self._root_widget,
) )
def _on_music_folder_press(self) -> None: def _on_music_folder_press(self) -> None:
from bauiv1lib.fileselector import FileSelectorWindow from bauiv1lib.fileselector import FileSelectorWindow
from babase import android_get_external_files_dir from babase import android_get_external_files_dir
# no-op if our underlying widget is dead or on its way out.
if not self._root_widget or self._root_widget.transitioning_out:
return
bui.containerwidget(edit=self._root_widget, transition='out_left') bui.containerwidget(edit=self._root_widget, transition='out_left')
base_path = android_get_external_files_dir() base_path = android_get_external_files_dir()
assert bui.app.classic is not None assert bui.app.classic is not None
@ -218,7 +232,8 @@ class SoundtrackEntryTypeSelectWindow(bui.Window):
show_base_path=False, show_base_path=False,
valid_file_extensions=[], valid_file_extensions=[],
allow_folders=True, allow_folders=True,
).get_root_widget() ).get_root_widget(),
from_window=self._root_widget,
) )
def _music_file_selector_cb(self, result: str | None) -> None: def _music_file_selector_cb(self, result: str | None) -> None:

View File

@ -551,9 +551,11 @@ def show_offer() -> bool:
if bui.native_review_request_supported(): if bui.native_review_request_supported():
bui.native_review_request() bui.native_review_request()
else: else:
feedback.ask_for_rating() if app.ui_v1.available:
feedback.ask_for_rating()
else: else:
SpecialOfferWindow(app.classic.special_offer) if app.ui_v1.available:
SpecialOfferWindow(app.classic.special_offer)
app.classic.special_offer = None app.classic.special_offer = None
return True return True

View File

@ -1329,6 +1329,10 @@ class StoreBrowserWindow(bui.Window):
from bauiv1lib.account import show_sign_in_prompt from bauiv1lib.account import show_sign_in_prompt
from bauiv1lib.getcurrency import GetCurrencyWindow from bauiv1lib.getcurrency import GetCurrencyWindow
# no-op if our underlying widget is dead or on its way out.
if not self._root_widget or self._root_widget.transitioning_out:
return
plus = bui.app.plus plus = bui.app.plus
assert plus is not None assert plus is not None
@ -1343,13 +1347,19 @@ class StoreBrowserWindow(bui.Window):
).get_root_widget() ).get_root_widget()
if not self._modal: if not self._modal:
assert bui.app.classic is not None assert bui.app.classic is not None
bui.app.ui_v1.set_main_menu_window(window) bui.app.ui_v1.set_main_menu_window(
window, from_window=self._root_widget
)
def _back(self) -> None: def _back(self) -> None:
# pylint: disable=cyclic-import # pylint: disable=cyclic-import
from bauiv1lib.coop.browser import CoopBrowserWindow from bauiv1lib.coop.browser import CoopBrowserWindow
from bauiv1lib.mainmenu import MainMenuWindow from bauiv1lib.mainmenu import MainMenuWindow
# no-op if our underlying widget is dead or on its way out.
if not self._root_widget or self._root_widget.transitioning_out:
return
self._save_state() self._save_state()
bui.containerwidget( bui.containerwidget(
edit=self._root_widget, transition=self._transition_out edit=self._root_widget, transition=self._transition_out
@ -1358,11 +1368,13 @@ class StoreBrowserWindow(bui.Window):
assert bui.app.classic is not None assert bui.app.classic is not None
if self._back_location == 'CoopBrowserWindow': if self._back_location == 'CoopBrowserWindow':
bui.app.ui_v1.set_main_menu_window( bui.app.ui_v1.set_main_menu_window(
CoopBrowserWindow(transition='in_left').get_root_widget() CoopBrowserWindow(transition='in_left').get_root_widget(),
from_window=self._root_widget,
) )
else: else:
bui.app.ui_v1.set_main_menu_window( bui.app.ui_v1.set_main_menu_window(
MainMenuWindow(transition='in_left').get_root_widget() MainMenuWindow(transition='in_left').get_root_widget(),
from_window=self._root_widget,
) )
if self._on_close_call is not None: if self._on_close_call is not None:
self._on_close_call() self._on_close_call()

View File

@ -34,6 +34,7 @@ class TournamentEntryWindow(PopupWindow):
# pylint: disable=too-many-statements # pylint: disable=too-many-statements
assert bui.app.classic is not None assert bui.app.classic is not None
assert bui.app.plus
bui.set_analytics_screen('Tournament Entry Window') bui.set_analytics_screen('Tournament Entry Window')
self._tournament_id = tournament_id self._tournament_id = tournament_id
@ -100,7 +101,7 @@ class TournamentEntryWindow(PopupWindow):
self._launched = False self._launched = False
# Show the ad button only if we support ads *and* it has a level 1 fee. # Show the ad button only if we support ads *and* it has a level 1 fee.
self._do_ad_btn = bui.has_video_ads() and self._allow_ads self._do_ad_btn = bui.app.plus.has_video_ads() and self._allow_ads
x_offs = 0 if self._do_ad_btn else 85 x_offs = 0 if self._do_ad_btn else 85
@ -477,7 +478,7 @@ class TournamentEntryWindow(PopupWindow):
) )
if self._do_ad_btn: if self._do_ad_btn:
enabled = bui.have_incentivized_ad() enabled = plus.have_incentivized_ad()
have_ad_tries_remaining = ( have_ad_tries_remaining = (
self._tournament_info['adTriesRemaining'] is not None self._tournament_info['adTriesRemaining'] is not None
and self._tournament_info['adTriesRemaining'] > 0 and self._tournament_info['adTriesRemaining'] > 0

View File

@ -663,11 +663,16 @@ class WatchWindow(bui.Window):
def _back(self) -> None: def _back(self) -> None:
from bauiv1lib.mainmenu import MainMenuWindow from bauiv1lib.mainmenu import MainMenuWindow
# no-op if our underlying widget is dead or on its way out.
if not self._root_widget or self._root_widget.transitioning_out:
return
self._save_state() self._save_state()
bui.containerwidget( bui.containerwidget(
edit=self._root_widget, transition=self._transition_out edit=self._root_widget, transition=self._transition_out
) )
assert bui.app.classic is not None assert bui.app.classic is not None
bui.app.ui_v1.set_main_menu_window( bui.app.ui_v1.set_main_menu_window(
MainMenuWindow(transition='in_left').get_root_widget() MainMenuWindow(transition='in_left').get_root_widget(),
from_window=self._root_widget,
) )

View File

@ -25,138 +25,13 @@ void AppAdapter::OnMainThreadStartApp() {
} }
void AppAdapter::OnAppStart() { assert(g_base->InLogicThread()); } void AppAdapter::OnAppStart() { assert(g_base->InLogicThread()); }
void AppAdapter::OnAppPause() { assert(g_base->InLogicThread()); } void AppAdapter::OnAppSuspend() { assert(g_base->InLogicThread()); }
void AppAdapter::OnAppResume() { assert(g_base->InLogicThread()); } void AppAdapter::OnAppUnsuspend() { assert(g_base->InLogicThread()); }
void AppAdapter::OnAppShutdown() { assert(g_base->InLogicThread()); } void AppAdapter::OnAppShutdown() { assert(g_base->InLogicThread()); }
void AppAdapter::OnAppShutdownComplete() { assert(g_base->InLogicThread()); } void AppAdapter::OnAppShutdownComplete() { assert(g_base->InLogicThread()); }
void AppAdapter::OnScreenSizeChange() { assert(g_base->InLogicThread()); } void AppAdapter::OnScreenSizeChange() { assert(g_base->InLogicThread()); }
void AppAdapter::DoApplyAppConfig() { assert(g_base->InLogicThread()); } void AppAdapter::DoApplyAppConfig() { assert(g_base->InLogicThread()); }
void AppAdapter::OnAppSuspend_() {
assert(g_core->InMainThread());
// IMPORTANT: Any pause related stuff that event-loop-threads need to do
// should be done from their registered pause-callbacks. If we instead
// push runnables to them from here they may or may not be called before
// their event-loop is actually paused.
// Pause all event loops.
EventLoop::SetEventLoopsSuspended(true);
if (g_base->network_reader) {
g_base->network_reader->OnAppPause();
}
g_base->networking->OnAppPause();
}
void AppAdapter::OnAppUnsuspend_() {
assert(g_core->InMainThread());
// Spin all event-loops back up.
EventLoop::SetEventLoopsSuspended(false);
// Run resumes that expect to happen in the main thread.
g_base->network_reader->OnAppResume();
g_base->networking->OnAppResume();
// When resuming from a suspended state, we may want to pause whatever
// game was running when we last were active.
//
// TODO(efro): we should make this smarter so it doesn't happen if we're
// in a network game or something that we can't pause; bringing up the
// menu doesn't really accomplish anything there.
//
// In general this probably should be handled at a higher level.
// if (g_core->should_pause_active_game) {
// g_core->should_pause_active_game = false;
// // If we've been completely backgrounded, send a menu-press command to
// // the game; this will bring up a pause menu if we're in the game/etc.
// if (!g_base->ui->MainMenuVisible()) {
// g_base->ui->PushMainMenuPressCall(nullptr);
// }
// }
}
void AppAdapter::SuspendApp() {
assert(g_core);
assert(g_core->InMainThread());
if (app_suspended_) {
Log(LogLevel::kWarning,
"AppAdapter::SuspendApp() called with app already suspended.");
return;
}
millisecs_t start_time{core::CorePlatform::GetCurrentMillisecs()};
// Apple mentioned 5 seconds to run stuff once backgrounded or they bring
// down the hammer. Let's aim to stay under 2.
millisecs_t max_duration{2000};
g_core->platform->DebugLog(
"SuspendApp@"
+ std::to_string(core::CorePlatform::GetCurrentMillisecs()));
app_suspended_ = true;
OnAppSuspend_();
// We assume that the OS will completely suspend our process the moment we
// return from this call (though this is not technically true on all
// platforms). So we want to spin and wait for threads to actually process
// the pause message.
size_t running_thread_count{};
while (std::abs(core::CorePlatform::GetCurrentMillisecs() - start_time)
< max_duration) {
// If/when we get to a point with no threads waiting to be paused, we're
// good to go.
auto threads{EventLoop::GetStillSuspendingEventLoops()};
running_thread_count = threads.size();
if (running_thread_count == 0) {
if (g_buildconfig.debug_build()) {
Log(LogLevel::kDebug,
"SuspendApp() completed in "
+ std::to_string(core::CorePlatform::GetCurrentMillisecs()
- start_time)
+ "ms.");
}
return;
}
}
// If we made it here, we timed out. Complain.
Log(LogLevel::kError,
std::string("SuspendApp() took too long; ")
+ std::to_string(running_thread_count)
+ " threads not yet paused after "
+ std::to_string(core::CorePlatform::GetCurrentMillisecs()
- start_time)
+ " ms.");
}
void AppAdapter::UnsuspendApp() {
assert(g_core);
assert(g_core->InMainThread());
if (!app_suspended_) {
Log(LogLevel::kWarning,
"AppAdapter::UnsuspendApp() called with app not in suspendedstate.");
return;
}
millisecs_t start_time{core::CorePlatform::GetCurrentMillisecs()};
g_core->platform->DebugLog(
"UnsuspendApp@"
+ std::to_string(core::CorePlatform::GetCurrentMillisecs()));
app_suspended_ = false;
OnAppUnsuspend_();
if (g_buildconfig.debug_build()) {
Log(LogLevel::kDebug,
"UnsuspendApp() completed in "
+ std::to_string(core::CorePlatform::GetCurrentMillisecs()
- start_time)
+ "ms.");
}
}
void AppAdapter::RunMainThreadEventLoopToCompletion() { void AppAdapter::RunMainThreadEventLoopToCompletion() {
FatalError("RunMainThreadEventLoopToCompletion is not implemented here."); FatalError("RunMainThreadEventLoopToCompletion is not implemented here.");
} }
@ -242,41 +117,6 @@ auto AppAdapter::GetGraphicsClientContext() -> GraphicsClientContext* {
auto AppAdapter::GetKeyRepeatDelay() -> float { return 0.3f; } auto AppAdapter::GetKeyRepeatDelay() -> float { return 0.3f; }
auto AppAdapter::GetKeyRepeatInterval() -> float { return 0.08f; } auto AppAdapter::GetKeyRepeatInterval() -> float { return 0.08f; }
auto AppAdapter::ClipboardIsSupported() -> bool {
// We only call our actual virtual function once.
if (!have_clipboard_is_supported_) {
clipboard_is_supported_ = DoClipboardIsSupported();
have_clipboard_is_supported_ = true;
}
return clipboard_is_supported_;
}
auto AppAdapter::ClipboardHasText() -> bool {
// If subplatform says they don't support clipboards, don't even ask.
if (!ClipboardIsSupported()) {
return false;
}
return DoClipboardHasText();
}
void AppAdapter::ClipboardSetText(const std::string& text) {
// If subplatform says they don't support clipboards, this is an error.
if (!ClipboardIsSupported()) {
throw Exception("ClipboardSetText called with no clipboard support.",
PyExcType::kRuntime);
}
DoClipboardSetText(text);
}
auto AppAdapter::ClipboardGetText() -> std::string {
// If subplatform says they don't support clipboards, this is an error.
if (!ClipboardIsSupported()) {
throw Exception("ClipboardGetText called with no clipboard support.",
PyExcType::kRuntime);
}
return DoClipboardGetText();
}
auto AppAdapter::DoClipboardIsSupported() -> bool { return false; } auto AppAdapter::DoClipboardIsSupported() -> bool { return false; }
auto AppAdapter::DoClipboardHasText() -> bool { auto AppAdapter::DoClipboardHasText() -> bool {
@ -311,4 +151,6 @@ void AppAdapter::NativeReviewRequest() {
void AppAdapter::DoNativeReviewRequest() { FatalError("Fixme unimplemented."); } void AppAdapter::DoNativeReviewRequest() { FatalError("Fixme unimplemented."); }
auto AppAdapter::ShouldSilenceAudioForInactive() -> bool const { return false; }
} // namespace ballistica::base } // namespace ballistica::base

View File

@ -22,8 +22,8 @@ class AppAdapter {
// Logic thread callbacks. // Logic thread callbacks.
virtual void OnAppStart(); virtual void OnAppStart();
virtual void OnAppPause(); virtual void OnAppSuspend();
virtual void OnAppResume(); virtual void OnAppUnsuspend();
virtual void OnAppShutdown(); virtual void OnAppShutdown();
virtual void OnAppShutdownComplete(); virtual void OnAppShutdownComplete();
virtual void OnScreenSizeChange(); virtual void OnScreenSizeChange();
@ -88,9 +88,9 @@ class AppAdapter {
/// plugged in or unplugged/etc. Default implementation returns true. /// plugged in or unplugged/etc. Default implementation returns true.
virtual auto ShouldUseCursor() -> bool; virtual auto ShouldUseCursor() -> bool;
/// Return whether the app-adapter is having the OS show a cursor. /// Return whether the app-adapter is having the OS show a cursor. If this
/// If this returns false, the engine will take care of drawing a cursor /// returns false, the engine will take care of drawing a cursor when
/// when necessary. If true, SetHardwareCursorVisible will be called /// necessary. If true, SetHardwareCursorVisible will be called
/// periodically to inform the adapter what the cursor state should be. /// periodically to inform the adapter what the cursor state should be.
/// The default implementation returns false; /// The default implementation returns false;
virtual auto HasHardwareCursor() -> bool; virtual auto HasHardwareCursor() -> bool;
@ -106,21 +106,6 @@ class AppAdapter {
/// values. /// values.
virtual void CursorPositionForDraw(float* x, float* y); virtual void CursorPositionForDraw(float* x, float* y);
/// Put the app into a suspended state. Should be called from the main
/// thread. Pauses work, closes network sockets, etc. May correspond to
/// being backgrounded on mobile, being minimized on desktop, etc. It is
/// assumed that, as soon as this call returns, all work is finished and
/// all threads can be suspended by the OS without any negative side
/// effects.
void SuspendApp();
/// Return the app to a running state from a suspended one. Can correspond
/// to foregrounding on mobile, unminimizing on desktop, etc. Spins
/// threads back up, re-opens network sockets, etc.
void UnsuspendApp();
auto app_suspended() const { return app_suspended_; }
/// Return whether this AppAdapter supports a 'fullscreen' toggle for its /// Return whether this AppAdapter supports a 'fullscreen' toggle for its
/// display. This will affect whether that option is available in display /// display. This will affect whether that option is available in display
/// settings or via a hotkey. Must be called from the logic thread. /// settings or via a hotkey. Must be called from the logic thread.
@ -150,6 +135,15 @@ class AppAdapter {
/// Return whether this AppAdapter supports max-fps controls for its display. /// Return whether this AppAdapter supports max-fps controls for its display.
virtual auto SupportsMaxFPS() -> bool const; virtual auto SupportsMaxFPS() -> bool const;
/// Return whether audio should be silenced when the app goes inactive. On
/// Desktop systems it is generally normal to continue to hear things even
/// if their windows are hidden, but on mobile we probably want to silence
/// our audio when phone calls, ads, etc. pop up over it. Note that this
/// is called each time the app goes inactive, so the adapter may choose
/// to selectively silence audio depending on what caused the inactive
/// switch.
virtual auto ShouldSilenceAudioForInactive() -> bool const;
/// Return whether this platform supports soft-quit. A soft quit is /// Return whether this platform supports soft-quit. A soft quit is
/// when the app is reset/backgrounded/etc. but remains running in case /// when the app is reset/backgrounded/etc. but remains running in case
/// needed again. Generally this is the behavior on mobile apps. /// needed again. Generally this is the behavior on mobile apps.
@ -206,22 +200,6 @@ class AppAdapter {
virtual auto GetKeyRepeatDelay() -> float; virtual auto GetKeyRepeatDelay() -> float;
virtual auto GetKeyRepeatInterval() -> float; virtual auto GetKeyRepeatInterval() -> float;
/// Return whether clipboard operations are supported at all. This gets
/// called when determining whether to display clipboard related UI
/// elements/etc.
auto ClipboardIsSupported() -> bool;
/// Return whether there is currently text on the clipboard.
auto ClipboardHasText() -> bool;
/// Set current clipboard text. Raises an Exception if clipboard is
/// unsupported.
void ClipboardSetText(const std::string& text);
/// Return current text from the clipboard. Raises an Exception if
/// clipboard is unsupported or if there's no text on the clipboard.
auto ClipboardGetText() -> std::string;
/// Push a raw pointer Runnable to the platform's 'main' thread. The main /// Push a raw pointer Runnable to the platform's 'main' thread. The main
/// thread should call its RunAndLogErrors() method and then delete it. /// thread should call its RunAndLogErrors() method and then delete it.
virtual void DoPushMainThreadRunnable(Runnable* runnable) = 0; virtual void DoPushMainThreadRunnable(Runnable* runnable) = 0;
@ -239,25 +217,19 @@ class AppAdapter {
/// Asynchronously kick off a native review request. /// Asynchronously kick off a native review request.
void NativeReviewRequest(); void NativeReviewRequest();
protected:
virtual ~AppAdapter();
virtual auto DoClipboardIsSupported() -> bool; virtual auto DoClipboardIsSupported() -> bool;
virtual auto DoClipboardHasText() -> bool; virtual auto DoClipboardHasText() -> bool;
virtual void DoClipboardSetText(const std::string& text); virtual void DoClipboardSetText(const std::string& text);
virtual auto DoClipboardGetText() -> std::string; virtual auto DoClipboardGetText() -> std::string;
protected:
virtual ~AppAdapter();
/// Override to implement native review requests. Will be called in the /// Override to implement native review requests. Will be called in the
/// main thread. /// main thread.
virtual void DoNativeReviewRequest(); virtual void DoNativeReviewRequest();
private: private:
void OnAppSuspend_();
void OnAppUnsuspend_();
bool app_suspended_{};
bool have_clipboard_is_supported_{};
bool clipboard_is_supported_{};
}; };
} // namespace ballistica::base } // namespace ballistica::base

View File

@ -66,7 +66,6 @@ class AppAdapterApple : public AppAdapter {
private: private:
class ScopedAllowGraphics_; class ScopedAllowGraphics_;
// void UpdateScreenSizes_();
void ReloadRenderer_(const GraphicsSettings* settings); void ReloadRenderer_(const GraphicsSettings* settings);
std::thread::id graphics_thread_{}; std::thread::id graphics_thread_{};

View File

@ -471,6 +471,9 @@ void AppAdapterSDL::HandleSDLEvent_(const SDL_Event& event) {
// it to the config so that UIs can poll for it and pick up the // it to the config so that UIs can poll for it and pick up the
// change. We don't do this on other platforms where a maximized // change. We don't do this on other platforms where a maximized
// window is more distinctly different than a fullscreen one. // window is more distinctly different than a fullscreen one.
// Though I guess some Linux window managers have a fullscreen
// function so theoretically we should there. Le sigh. Maybe SDL
// 3 will tidy up this situation.
fullscreen_ = true; fullscreen_ = true;
g_base->logic->event_loop()->PushCall([] { g_base->logic->event_loop()->PushCall([] {
g_base->python->objs() g_base->python->objs()
@ -497,18 +500,22 @@ void AppAdapterSDL::HandleSDLEvent_(const SDL_Event& event) {
break; break;
case SDL_WINDOWEVENT_HIDDEN: { case SDL_WINDOWEVENT_HIDDEN: {
// Let's keep track of when we're hidden so we can stop drawing // We plug this into the app's overall 'Active' state so it can
// and sleep more. Theoretically we could put the app into a full // pause stuff or throttle down processing or whatever else.
// suspended state like we do on mobile (pausing event loops/etc.) if (!hidden_) {
// but that would be more involved; we'd need to ignore most SDL g_base->SetAppActive(false);
// events while sleeping (except for SDL_WINDOWEVENT_SHOWN) and }
// would need to rebuild our controller lists/etc when we resume. // Also note that we are *completely* hidden, so we can totally
// For now just gonna keep things simple and keep running. // stop drawing ('Inactive' app state does not imply this in and
// of itself).
hidden_ = true; hidden_ = true;
break; break;
} }
case SDL_WINDOWEVENT_SHOWN: { case SDL_WINDOWEVENT_SHOWN: {
if (hidden_) {
g_base->SetAppActive(true);
}
hidden_ = false; hidden_ = false;
break; break;
} }

View File

@ -14,8 +14,8 @@ void AppMode::OnActivate() {}
void AppMode::OnDeactivate() {} void AppMode::OnDeactivate() {}
void AppMode::OnAppStart() {} void AppMode::OnAppStart() {}
void AppMode::OnAppPause() {} void AppMode::OnAppSuspend() {}
void AppMode::OnAppResume() {} void AppMode::OnAppUnsuspend() {}
void AppMode::OnAppShutdown() {} void AppMode::OnAppShutdown() {}
void AppMode::OnAppShutdownComplete() {} void AppMode::OnAppShutdownComplete() {}

View File

@ -26,8 +26,8 @@ class AppMode {
/// Logic thread callbacks that run while the app-mode is active. /// Logic thread callbacks that run while the app-mode is active.
virtual void OnAppStart(); virtual void OnAppStart();
virtual void OnAppPause(); virtual void OnAppSuspend();
virtual void OnAppResume(); virtual void OnAppUnsuspend();
virtual void OnAppShutdown(); virtual void OnAppShutdown();
virtual void OnAppShutdownComplete(); virtual void OnAppShutdownComplete();
virtual void DoApplyAppConfig(); virtual void DoApplyAppConfig();

View File

@ -17,7 +17,8 @@
#include <alc.h> #include <alc.h>
#endif #endif
#if BA_OSTYPE_ANDROID #if BA_OPENAL_IS_SOFT
#define AL_ALEXT_PROTOTYPES
#include <alext.h> #include <alext.h>
#endif #endif

View File

@ -33,9 +33,9 @@ void Audio::Reset() {
void Audio::OnAppStart() { assert(g_base->InLogicThread()); } void Audio::OnAppStart() { assert(g_base->InLogicThread()); }
void Audio::OnAppPause() { assert(g_base->InLogicThread()); } void Audio::OnAppSuspend() { assert(g_base->InLogicThread()); }
void Audio::OnAppResume() { assert(g_base->InLogicThread()); } void Audio::OnAppUnsuspend() { assert(g_base->InLogicThread()); }
void Audio::OnAppShutdown() { assert(g_base->InLogicThread()); } void Audio::OnAppShutdown() { assert(g_base->InLogicThread()); }

View File

@ -21,8 +21,8 @@ class Audio {
void Reset(); void Reset();
virtual void OnAppStart(); virtual void OnAppStart();
virtual void OnAppPause(); virtual void OnAppSuspend();
virtual void OnAppResume(); virtual void OnAppUnsuspend();
virtual void OnAppShutdown(); virtual void OnAppShutdown();
virtual void OnAppShutdownComplete(); virtual void OnAppShutdownComplete();
virtual void DoApplyAppConfig(); virtual void DoApplyAppConfig();

View File

@ -4,6 +4,7 @@
#include <algorithm> #include <algorithm>
#include "ballistica/base/app_adapter/app_adapter.h"
#include "ballistica/base/assets/assets.h" #include "ballistica/base/assets/assets.h"
#include "ballistica/base/assets/sound_asset.h" #include "ballistica/base/assets/sound_asset.h"
#include "ballistica/base/audio/al_sys.h" #include "ballistica/base/audio/al_sys.h"
@ -27,9 +28,12 @@ namespace ballistica::base {
extern std::string g_rift_audio_device_name; extern std::string g_rift_audio_device_name;
#endif #endif
#if BA_OSTYPE_ANDROID #if BA_OPENAL_IS_SOFT
LPALCDEVICEPAUSESOFT alcDevicePauseSOFT{}; LPALCDEVICEPAUSESOFT alcDevicePauseSOFT{};
LPALCDEVICERESUMESOFT alcDeviceResumeSOFT{}; LPALCDEVICERESUMESOFT alcDeviceResumeSOFT{};
LPALCRESETDEVICESOFT alcResetDeviceSOFT{};
LPALEVENTCALLBACKSOFT alEventCallbackSOFT{};
LPALEVENTCONTROLSOFT alEventControlSOFT{};
#endif #endif
const int kAudioProcessIntervalNormal{500 * 1000}; const int kAudioProcessIntervalNormal{500 * 1000};
@ -107,29 +111,24 @@ class AudioServer::ThreadSource_ : public Object {
} }
private: private:
bool looping_{};
std::unique_ptr<AudioSource> client_source_;
float fade_{1.0f};
float gain_{1.0f};
AudioServer* audio_thread_{};
bool valid_{};
const Object::Ref<SoundAsset>* source_sound_{};
int id_{}; int id_{};
uint32_t play_count_{}; bool looping_{};
bool valid_{};
bool is_actually_playing_{}; bool is_actually_playing_{};
bool want_to_play_{}; bool want_to_play_{};
#if BA_ENABLE_AUDIO
ALuint source_{};
#endif
bool is_streamed_{}; bool is_streamed_{};
/// Whether we should be designated as "music" next time we play. /// Whether we should be designated as "music" next time we play.
bool is_music_{}; bool is_music_{};
/// Whether currently playing as music. /// Whether currently playing as music.
bool current_is_music_{}; bool current_is_music_{};
uint32_t play_count_{};
float fade_{1.0f};
float gain_{1.0f};
std::unique_ptr<AudioSource> client_source_;
AudioServer* audio_server_{};
const Object::Ref<SoundAsset>* source_sound_{};
#if BA_ENABLE_AUDIO #if BA_ENABLE_AUDIO
ALuint source_{};
Object::Ref<AudioStreamer> streamer_; Object::Ref<AudioStreamer> streamer_;
#endif #endif
}; // ThreadSource }; // ThreadSource
@ -155,6 +154,22 @@ void AudioServer::OnMainThreadStartApp() {
event_loop_->PushCallSynchronous([this] { OnAppStartInThread_(); }); event_loop_->PushCallSynchronous([this] { OnAppStartInThread_(); });
} }
#if BA_OPENAL_IS_SOFT
static void ALEventCallback_(ALenum eventType, ALuint object, ALuint param,
ALsizei length, const ALchar* message,
ALvoid* userParam) noexcept {
if (eventType == AL_EVENT_TYPE_DISCONNECTED_SOFT) {
if (g_base->audio_server) {
g_base->audio_server->event_loop()->PushCall(
[] { g_base->audio_server->OnDeviceDisconnected(); });
}
} else {
Log(LogLevel::kWarning, "Got unexpected OpenAL callback event "
+ std::to_string(static_cast<int>(eventType)));
}
}
#endif // BA_OPENAL_IS_SOFT
void AudioServer::OnAppStartInThread_() { void AudioServer::OnAppStartInThread_() {
assert(g_base->InAudioThread()); assert(g_base->InAudioThread());
@ -167,7 +182,7 @@ void AudioServer::OnAppStartInThread_() {
// Bring up OpenAL stuff. // Bring up OpenAL stuff.
{ {
const char* al_device_name = nullptr; const char* al_device_name{};
// On the rift build in vr mode we need to make sure we open the rift audio // On the rift build in vr mode we need to make sure we open the rift audio
// device. // device.
@ -211,21 +226,42 @@ void AudioServer::OnAppStartInThread_() {
"connected?"); "connected?");
} }
impl_->alc_context = alcCreateContext(device, nullptr); impl_->alc_context = alcCreateContext(device, nullptr);
BA_PRECONDITION(impl_->alc_context); if (!impl_->alc_context) {
BA_PRECONDITION(alcMakeContextCurrent(impl_->alc_context)); FatalError(
"Unable to init audio. Do you have speakers/headphones/etc. "
"connected?");
}
BA_PRECONDITION_FATAL(impl_->alc_context);
BA_PRECONDITION_FATAL(alcMakeContextCurrent(impl_->alc_context));
CHECK_AL_ERROR; CHECK_AL_ERROR;
#if BA_OSTYPE_ANDROID #if BA_OPENAL_IS_SOFT
if (alcIsExtensionPresent(device, "ALC_SOFT_pause_device")) { // Currently assuming the pause/resume and reset extensions are present.
alcDevicePauseSOFT = reinterpret_cast<LPALCDEVICEPAUSESOFT>( // if (alcIsExtensionPresent(device, "ALC_SOFT_pause_device")) {
alcGetProcAddress(device, "alcDevicePauseSOFT")); alcDevicePauseSOFT = reinterpret_cast<LPALCDEVICEPAUSESOFT>(
BA_PRECONDITION_FATAL(alcDevicePauseSOFT != nullptr); alcGetProcAddress(device, "alcDevicePauseSOFT"));
alcDeviceResumeSOFT = reinterpret_cast<LPALCDEVICERESUMESOFT>( BA_PRECONDITION_FATAL(alcDevicePauseSOFT != nullptr);
alcGetProcAddress(device, "alcDeviceResumeSOFT")); alcDeviceResumeSOFT = reinterpret_cast<LPALCDEVICERESUMESOFT>(
BA_PRECONDITION_FATAL(alcDeviceResumeSOFT != nullptr); alcGetProcAddress(device, "alcDeviceResumeSOFT"));
} else { BA_PRECONDITION_FATAL(alcDeviceResumeSOFT != nullptr);
FatalError("ALC_SOFT pause/resume functionality not found."); alcResetDeviceSOFT = reinterpret_cast<LPALCRESETDEVICESOFT>(
} alcGetProcAddress(device, "alcResetDeviceSOFT"));
BA_PRECONDITION_FATAL(alcResetDeviceSOFT != nullptr);
alEventCallbackSOFT = reinterpret_cast<LPALEVENTCALLBACKSOFT>(
alcGetProcAddress(device, "alEventCallbackSOFT"));
BA_PRECONDITION_FATAL(alEventCallbackSOFT != nullptr);
alEventControlSOFT = reinterpret_cast<LPALEVENTCONTROLSOFT>(
alcGetProcAddress(device, "alEventControlSOFT"));
BA_PRECONDITION_FATAL(alEventControlSOFT != nullptr);
// Ask to be notified when a device is disconnected.
alEventCallbackSOFT(ALEventCallback_, nullptr);
CHECK_AL_ERROR;
ALenum types[] = {AL_EVENT_TYPE_DISCONNECTED_SOFT};
alEventControlSOFT(1, types, AL_TRUE);
// } else {
// FatalError("ALC_SOFT pause/resume functionality not found.");
// }
#endif #endif
} }
@ -259,6 +295,7 @@ void AudioServer::OnAppStartInThread_() {
// Now make available any stopped sources (should be all of them). // Now make available any stopped sources (should be all of them).
UpdateAvailableSources_(); UpdateAvailableSources_();
last_started_playing_time_ = g_core->GetAppTimeSeconds();
#endif // BA_ENABLE_AUDIO #endif // BA_ENABLE_AUDIO
} }
@ -334,23 +371,27 @@ void AudioServer::SetSuspended_(bool suspend) {
#endif #endif
// Pause OpenALSoft. // Pause OpenALSoft.
#if BA_OSTYPE_ANDROID #if BA_OPENAL_IS_SOFT
BA_PRECONDITION_FATAL(alcDevicePauseSOFT != nullptr); BA_PRECONDITION_FATAL(alcDevicePauseSOFT != nullptr);
BA_PRECONDITION_FATAL(impl_ != nullptr && impl_->alc_context != nullptr); BA_PRECONDITION_FATAL(impl_ != nullptr && impl_->alc_context != nullptr);
auto* device = alcGetContextsDevice(impl_->alc_context); auto* device = alcGetContextsDevice(impl_->alc_context);
BA_PRECONDITION_FATAL(device != nullptr); BA_PRECONDITION_FATAL(device != nullptr);
try { try {
g_core->platform->LowLevelDebugLog(
"Calling alcDevicePauseSOFT at "
+ std::to_string(g_core->GetAppTimeSeconds()));
alcDevicePauseSOFT(device); alcDevicePauseSOFT(device);
} catch (const std::exception& e) { } catch (const std::exception& e) {
g_core->platform->DebugLog( Log(LogLevel::kError,
std::string("EXC pausing alcDevice: ") "Error in alcDevicePauseSOFT at time "
+ g_core->platform->DemangleCXXSymbol(typeid(e).name()) + " " + std::to_string(g_core->GetAppTimeSeconds())
+ e.what()); + "( playing since "
throw; + std::to_string(last_started_playing_time_)
+ "): " + g_core->platform->DemangleCXXSymbol(typeid(e).name())
+ " " + e.what());
} catch (...) { } catch (...) {
g_core->platform->DebugLog("UNKNOWN EXC pausing alcDevice"); Log(LogLevel::kError, "Unknown error in alcDevicePauseSOFT");
throw;
} }
#endif #endif
@ -372,25 +413,28 @@ void AudioServer::SetSuspended_(bool suspend) {
#endif #endif
#endif #endif
// On android lets tell openal-soft to stop processing. // With OpenALSoft lets tell openal-soft to resume processing.
#if BA_OSTYPE_ANDROID #if BA_OPENAL_IS_SOFT
BA_PRECONDITION_FATAL(alcDeviceResumeSOFT != nullptr); BA_PRECONDITION_FATAL(alcDeviceResumeSOFT != nullptr);
BA_PRECONDITION_FATAL(impl_ != nullptr && impl_->alc_context != nullptr); BA_PRECONDITION_FATAL(impl_ != nullptr && impl_->alc_context != nullptr);
auto* device = alcGetContextsDevice(impl_->alc_context); auto* device = alcGetContextsDevice(impl_->alc_context);
BA_PRECONDITION_FATAL(device != nullptr); BA_PRECONDITION_FATAL(device != nullptr);
try { try {
g_core->platform->LowLevelDebugLog(
"Calling alcDeviceResumeSOFT at "
+ std::to_string(g_core->GetAppTimeSeconds()));
alcDeviceResumeSOFT(device); alcDeviceResumeSOFT(device);
} catch (const std::exception& e) { } catch (const std::exception& e) {
g_core->platform->DebugLog( Log(LogLevel::kError,
std::string("EXC resuming alcDevice: ") "Error in alcDeviceResumeSOFT at time "
+ g_core->platform->DemangleCXXSymbol(typeid(e).name()) + " " + std::to_string(g_core->GetAppTimeSeconds()) + ": "
+ e.what()); + g_core->platform->DemangleCXXSymbol(typeid(e).name()) + " "
throw; + e.what());
} catch (...) { } catch (...) {
g_core->platform->DebugLog("UNKNOWN EXC resuming alcDevice"); Log(LogLevel::kError, "Unknown error in alcDeviceResumeSOFT");
throw;
} }
#endif #endif
last_started_playing_time_ = g_core->GetAppTimeSeconds();
suspended_ = false; suspended_ = false;
#if BA_ENABLE_AUDIO #if BA_ENABLE_AUDIO
CHECK_AL_ERROR; CHECK_AL_ERROR;
@ -477,8 +521,8 @@ void AudioServer::PushSourcePlayCall(uint32_t play_id,
// Let's take this opportunity to pass on newly available sources. // Let's take this opportunity to pass on newly available sources.
// This way the more things clients are playing, the more // This way the more things clients are playing, the more
// tight our source availability checking gets (instead of solely relying on // tight our source availability checking gets (instead of solely relying
// our periodic process() calls). // on our periodic process() calls).
UpdateAvailableSources_(); UpdateAvailableSources_();
}); });
} }
@ -685,12 +729,58 @@ void AudioServer::UpdateMusicPlayState_() {
} }
} }
void AudioServer::ProcessDeviceDisconnects_(seconds_t real_time_seconds) {
#if BA_OPENAL_IS_SOFT
// If our device has been disconnected, try to reconnect it
// periodically.
auto* device = alcGetContextsDevice(impl_->alc_context);
BA_PRECONDITION_FATAL(device != nullptr);
ALCint connected{-1};
alcGetIntegerv(device, ALC_CONNECTED, sizeof(connected), &connected);
CHECK_AL_ERROR;
if (connected == 0 && real_time_seconds - last_reset_attempt_time_ > 10.0) {
Log(LogLevel::kInfo, "OpenAL device disconnected; resetting...");
last_reset_attempt_time_ = real_time_seconds;
BA_PRECONDITION_FATAL(alcResetDeviceSOFT != nullptr);
alcResetDeviceSOFT(device, nullptr);
CHECK_AL_ERROR;
// Make noise if this ever fails to bring the device back.
ALCint connected{-1};
alcGetIntegerv(device, ALC_CONNECTED, sizeof(connected), &connected);
CHECK_AL_ERROR;
// If we were successful, don't require a wait for the next reset.
// (otherwise plugging in headphones and then unplugging will stay quiet
// for 10 seconds).
if (connected == 1) {
last_reset_attempt_time_ = -999.0;
}
if (connected == 0 && !reported_reset_fail_) {
reported_reset_fail_ = true;
Log(LogLevel::kError, "alcResetDeviceSOFT failed to reconnect device.");
}
}
#endif // BA_OPENAL_IS_SOFT
}
void AudioServer::OnDeviceDisconnected() {
assert(g_base->InAudioThread());
// All we do here is run an explicit Process_. This only saves us a half
// second or so over letting the timer do it, but hey we'll take it.
Process_();
}
void AudioServer::Process_() { void AudioServer::Process_() {
assert(g_base->InAudioThread()); assert(g_base->InAudioThread());
millisecs_t real_time = g_core->GetAppTimeMillisecs(); seconds_t real_time_seconds = g_core->GetAppTimeSeconds();
millisecs_t real_time_millisecs = real_time_seconds * 1000;
// If we're suspended we don't do nothin'. // Only do real work if we're in normal running mode.
if (!suspended_ && !shutting_down_) { if (!suspended_ && !shutting_down_) {
ProcessDeviceDisconnects_(real_time_seconds);
// Do some loading... // Do some loading...
have_pending_loads_ = g_base->assets->RunPendingAudioLoads(); have_pending_loads_ = g_base->assets->RunPendingAudioLoads();
@ -698,19 +788,33 @@ void AudioServer::Process_() {
UpdateAvailableSources_(); UpdateAvailableSources_();
// Update our fading sound volumes. // Update our fading sound volumes.
if (real_time - last_sound_fade_process_time_ > 50) { if (real_time_millisecs - last_sound_fade_process_time_ > 50) {
ProcessSoundFades_(); ProcessSoundFades_();
last_sound_fade_process_time_ = real_time; last_sound_fade_process_time_ = real_time_millisecs;
} }
// Update streaming sources. // Update streaming sources.
if (real_time - last_stream_process_time_ > 100) { if (real_time_millisecs - last_stream_process_time_ > 100) {
last_stream_process_time_ = real_time; last_stream_process_time_ = real_time_millisecs;
for (auto&& i : streaming_sources_) { for (auto&& i : streaming_sources_) {
i->Update(); i->Update();
} }
} }
// If the app has switched active/inactive state, update our volumes (we
// may silence our audio in these cases).
auto app_active = g_base->app_active();
if (app_active != app_active_) {
app_active_ = app_active;
app_active_volume_ =
(!app_active && g_base->app_adapter->ShouldSilenceAudioForInactive())
? 0.0f
: 1.0f;
for (auto&& i : sources_) {
i->UpdateVolume();
}
}
#if BA_ENABLE_AUDIO #if BA_ENABLE_AUDIO
CHECK_AL_ERROR; CHECK_AL_ERROR;
#endif #endif
@ -781,7 +885,8 @@ void AudioServer::ProcessSoundFades_() {
} }
void AudioServer::FadeSoundOut(uint32_t play_id, uint32_t time) { void AudioServer::FadeSoundOut(uint32_t play_id, uint32_t time) {
// Pop a new node on the list (this won't overwrite the old if there is one). // Pop a new node on the list (this won't overwrite the old if there is
// one).
sound_fade_nodes_.insert( sound_fade_nodes_.insert(
std::make_pair(play_id, SoundFadeNode_(play_id, time, true))); std::make_pair(play_id, SoundFadeNode_(play_id, time, true)));
} }
@ -792,9 +897,9 @@ void AudioServer::FadeSoundOut(uint32_t play_id, uint32_t time) {
// delete c; // delete c;
// } // }
AudioServer::ThreadSource_::ThreadSource_(AudioServer* audio_thread_in, AudioServer::ThreadSource_::ThreadSource_(AudioServer* audio_server_in,
int id_in, bool* valid_out) int id_in, bool* valid_out)
: id_(id_in), audio_thread_(audio_thread_in) { : id_(id_in), audio_server_(audio_server_in) {
#if BA_ENABLE_AUDIO #if BA_ENABLE_AUDIO
assert(g_core); assert(g_core);
assert(valid_out != nullptr); assert(valid_out != nullptr);
@ -839,10 +944,10 @@ AudioServer::ThreadSource_::~ThreadSource_() {
Stop(); Stop();
// Remove us from sources list. // Remove us from sources list.
for (auto i = audio_thread_->sources_.begin(); for (auto i = audio_server_->sources_.begin();
i != audio_thread_->sources_.end(); ++i) { i != audio_server_->sources_.end(); ++i) {
if (*i == this) { if (*i == this) {
audio_thread_->sources_.erase(i); audio_server_->sources_.erase(i);
break; break;
} }
} }
@ -866,8 +971,8 @@ void AudioServer::ThreadSource_::UpdateAvailability() {
assert(g_base->InAudioThread()); assert(g_base->InAudioThread());
// If it's waiting to be picked up by a client or has pending client commands, // If it's waiting to be picked up by a client or has pending client
// skip. // commands, skip.
if (!client_source_->TryLock(6)) { if (!client_source_->TryLock(6)) {
return; return;
} }
@ -879,10 +984,9 @@ void AudioServer::ThreadSource_::UpdateAvailability() {
} }
// We consider ourselves busy if there's an active looping play command // We consider ourselves busy if there's an active looping play command
// (regardless of its actual physical play state - music could be turned off, // (regardless of its actual physical play state - music could be turned
// stuttering, etc.). // off, stuttering, etc.). If it's non-looping, we check its play state and
// If it's non-looping, we check its play state and snatch it if it's not // snatch it if it's not playing.
// playing.
bool busy; bool busy;
if (looping_ || (is_streamed_ && streamer_.Exists() && streamer_->loops())) { if (looping_ || (is_streamed_ && streamer_.Exists() && streamer_->loops())) {
busy = want_to_play_; busy = want_to_play_;
@ -1079,12 +1183,12 @@ void AudioServer::ThreadSource_::ExecPlay() {
looping_ = false; looping_ = false;
// Push us on the list of streaming sources if we're not on it. // Push us on the list of streaming sources if we're not on it.
for (auto&& i : audio_thread_->streaming_sources_) { for (auto&& i : audio_server_->streaming_sources_) {
if (i == this) { if (i == this) {
throw Exception(); throw Exception();
} }
} }
audio_thread_->streaming_sources_.push_back(this); audio_server_->streaming_sources_.push_back(this);
// Make sure stereo sounds aren't positional. // Make sure stereo sounds aren't positional.
// This is default behavior on Mac/Win, but we enforce it for linux. // This is default behavior on Mac/Win, but we enforce it for linux.
@ -1162,10 +1266,10 @@ void AudioServer::ThreadSource_::ExecStop() {
if (streamer_.Exists()) { if (streamer_.Exists()) {
assert(is_streamed_); assert(is_streamed_);
streamer_->Stop(); streamer_->Stop();
for (auto i = audio_thread_->streaming_sources_.begin(); for (auto i = audio_server_->streaming_sources_.begin();
i != audio_thread_->streaming_sources_.end(); ++i) { i != audio_server_->streaming_sources_.end(); ++i) {
if (*i == this) { if (*i == this) {
audio_thread_->streaming_sources_.erase(i); audio_server_->streaming_sources_.erase(i);
break; break;
} }
} }
@ -1182,15 +1286,16 @@ void AudioServer::ThreadSource_::ExecStop() {
void AudioServer::ThreadSource_::UpdateVolume() { void AudioServer::ThreadSource_::UpdateVolume() {
#if BA_ENABLE_AUDIO #if BA_ENABLE_AUDIO
assert(g_base->InAudioThread()); assert(g_base->InAudioThread());
if (g_base->audio_server->suspended_ if (audio_server_->suspended_ || audio_server_->shutting_down_) {
|| g_base->audio_server->shutting_down_) {
return; return;
} }
float val = gain_ * fade_; float val = gain_ * fade_;
val *= audio_server_->app_active_volume_;
if (current_is_music()) { if (current_is_music()) {
val *= audio_thread_->music_volume_ / 7.0f; val *= audio_server_->music_volume_ / 7.0f;
} else { } else {
val *= audio_thread_->sound_volume_; val *= audio_server_->sound_volume_;
} }
alSourcef(source_, AL_GAIN, std::max(0.0f, val)); alSourcef(source_, AL_GAIN, std::max(0.0f, val));
CHECK_AL_ERROR; CHECK_AL_ERROR;
@ -1208,7 +1313,7 @@ void AudioServer::ThreadSource_::UpdatePitch() {
float val = 1.0f; float val = 1.0f;
if (current_is_music()) { if (current_is_music()) {
} else { } else {
val *= audio_thread_->sound_pitch_; val *= audio_server_->sound_pitch_;
} }
alSourcef(source_, AL_PITCH, val); alSourcef(source_, AL_PITCH, val);
CHECK_AL_ERROR; CHECK_AL_ERROR;
@ -1227,16 +1332,6 @@ void AudioServer::PushSetSoundPitchCall(float val) {
event_loop()->PushCall([this, val] { SetSoundPitch_(val); }); event_loop()->PushCall([this, val] { SetSoundPitch_(val); });
} }
// void AudioServer::PushSetSuspendedCall(bool suspend) {
// event_loop()->PushCall([this, suspend] {
// if (g_buildconfig.ostype_android()) {
// Log(LogLevel::kError, "Shouldn't be getting SetSuspendedCall on
// android.");
// }
// SetSuspended_(suspend);
// });
// }
void AudioServer::PushComponentUnloadCall( void AudioServer::PushComponentUnloadCall(
const std::vector<Object::Ref<Asset>*>& components) { const std::vector<Object::Ref<Asset>*>& components) {
event_loop()->PushCall([components] { event_loop()->PushCall([components] {

View File

@ -67,6 +67,8 @@ class AudioServer {
auto event_loop() const -> EventLoop* { return event_loop_; } auto event_loop() const -> EventLoop* { return event_loop_; }
void OnDeviceDisconnected();
private: private:
class ThreadSource_; class ThreadSource_;
struct Impl_; struct Impl_;
@ -90,6 +92,7 @@ class AudioServer {
void Reset_(); void Reset_();
void Process_(); void Process_();
void ProcessDeviceDisconnects_(seconds_t real_time_seconds);
/// Send a component to the audio thread to delete. /// Send a component to the audio thread to delete.
// void DeleteAssetComponent_(Asset* c); // void DeleteAssetComponent_(Asset* c);
@ -115,12 +118,18 @@ class AudioServer {
float sound_volume_{1.0f}; float sound_volume_{1.0f};
float sound_pitch_{1.0f}; float sound_pitch_{1.0f};
float music_volume_{1.0f}; float music_volume_{1.0f};
float app_active_volume_{1.0f};
bool have_pending_loads_{}; bool have_pending_loads_{};
bool app_active_{true};
bool suspended_{}; bool suspended_{};
bool shutdown_completed_{}; bool shutdown_completed_{};
bool shutting_down_{}; bool shutting_down_{};
bool reported_reset_fail_{};
int al_source_count_{};
seconds_t last_reset_attempt_time_{-999.0};
seconds_t shutdown_start_time_{}; seconds_t shutdown_start_time_{};
seconds_t last_started_playing_time_{};
millisecs_t last_sound_fade_process_time_{}; millisecs_t last_sound_fade_process_time_{};
/// Indexed list of sources. /// Indexed list of sources.
@ -144,8 +153,6 @@ class AudioServer {
// Our list of sound media components to delete via the main thread. // Our list of sound media components to delete via the main thread.
std::vector<const Object::Ref<SoundAsset>*> sound_ref_delete_list_; std::vector<const Object::Ref<SoundAsset>*> sound_ref_delete_list_;
int al_source_count_{};
}; };
} // namespace ballistica::base } // namespace ballistica::base

View File

@ -195,9 +195,17 @@ void BaseFeatureSet::OnAssetsAvailable() {
} }
void BaseFeatureSet::StartApp() { void BaseFeatureSet::StartApp() {
// {
// // TEST - recreate the ID python dumps in its thread tracebacks.
// auto val = PyThread_get_thread_ident();
// printf("MAIN THREAD IS %#018lx\n", val);
// }
BA_PRECONDITION(g_core->InMainThread()); BA_PRECONDITION(g_core->InMainThread());
BA_PRECONDITION(g_base); BA_PRECONDITION(g_base);
auto start_time = g_core->GetAppTimeSeconds();
// Currently limiting this to once per process. // Currently limiting this to once per process.
BA_PRECONDITION(!called_start_app_); BA_PRECONDITION(!called_start_app_);
called_start_app_ = true; called_start_app_ = true;
@ -248,6 +256,177 @@ void BaseFeatureSet::StartApp() {
} }
g_core->LifecycleLog("start-app end (main thread)"); g_core->LifecycleLog("start-app end (main thread)");
// Make some noise if this takes more than a few seconds. If we pass 5
// seconds or so we start to trigger App-Not-Responding reports which
// isn't good.
auto duration = g_core->GetAppTimeSeconds() - start_time;
if (duration > 3.0) {
char buffer[128];
snprintf(buffer, sizeof(buffer),
"StartApp() took too long (%.2lf seconds).", duration);
Log(LogLevel::kWarning, buffer);
}
}
void BaseFeatureSet::SuspendApp() {
assert(g_core);
assert(g_core->InMainThread());
if (app_suspended_) {
Log(LogLevel::kWarning,
"AppAdapter::SuspendApp() called with app already suspended.");
return;
}
millisecs_t start_time{core::CorePlatform::GetCurrentMillisecs()};
// Apple mentioned 5 seconds to run stuff once backgrounded or they bring
// down the hammer. Let's aim to stay under 2.
millisecs_t max_duration{2000};
g_core->platform->LowLevelDebugLog(
"SuspendApp@"
+ std::to_string(core::CorePlatform::GetCurrentMillisecs()));
app_suspended_ = true;
// IMPORTANT: Any pause related stuff that event-loop-threads need to do
// should be done from their registered pause-callbacks. If we instead
// push runnables to them from here they may or may not be called before
// their event-loop is actually paused.
// Pause all event loops.
EventLoop::SetEventLoopsSuspended(true);
if (g_base->network_reader) {
g_base->network_reader->OnAppSuspend();
}
g_base->networking->OnAppSuspend();
// We assume that the OS will completely suspend our process the moment we
// return from this call (though this is not technically true on all
// platforms). So we want to spin here and give our various event loop
// threads time to park themselves.
std::vector<EventLoop*> running_loops;
do {
// If/when we get to a point with no threads waiting to be paused, we're
// good to go.
// auto loops{EventLoop::GetStillSuspendingEventLoops()};
running_loops = EventLoop::GetStillSuspendingEventLoops();
// running_loop_count = loops.size();
if (running_loops.empty()) {
if (g_buildconfig.debug_build()) {
Log(LogLevel::kDebug,
"SuspendApp() completed in "
+ std::to_string(core::CorePlatform::GetCurrentMillisecs()
- start_time)
+ "ms.");
}
return;
}
} while (std::abs(core::CorePlatform::GetCurrentMillisecs() - start_time)
< max_duration);
// If we made it here, we timed out. Complain.
std::string msg =
std::string("SuspendApp() took too long; ")
+ std::to_string(running_loops.size())
+ " event-loops not yet suspended after "
+ std::to_string(core::CorePlatform::GetCurrentMillisecs() - start_time)
+ " ms: (";
bool first = true;
for (auto* loop : running_loops) {
if (!first) {
msg += ", ";
}
// Note: not adding a default here so compiler complains if we
// add/change something.
switch (loop->identifier()) {
case EventLoopID::kInvalid:
msg += "invalid";
break;
case EventLoopID::kLogic:
msg += "logic";
break;
case EventLoopID::kAssets:
msg += "assets";
break;
case EventLoopID::kFileOut:
msg += "fileout";
break;
case EventLoopID::kMain:
msg += "main";
break;
case EventLoopID::kAudio:
msg += "audio";
break;
case EventLoopID::kNetworkWrite:
msg += "networkwrite";
break;
case EventLoopID::kSuicide:
msg += "suicide";
break;
case EventLoopID::kStdin:
msg += "stdin";
break;
case EventLoopID::kBGDynamics:
msg += "bgdynamics";
break;
}
first = false;
}
msg += ").";
Log(LogLevel::kError, msg);
}
void BaseFeatureSet::UnsuspendApp() {
assert(g_core);
assert(g_core->InMainThread());
if (!app_suspended_) {
Log(LogLevel::kWarning,
"AppAdapter::UnsuspendApp() called with app not in suspendedstate.");
return;
}
millisecs_t start_time{core::CorePlatform::GetCurrentMillisecs()};
g_core->platform->LowLevelDebugLog(
"UnsuspendApp@"
+ std::to_string(core::CorePlatform::GetCurrentMillisecs()));
app_suspended_ = false;
// Spin all event-loops back up.
EventLoop::SetEventLoopsSuspended(false);
// Run resumes that expect to happen in the main thread.
g_base->network_reader->OnAppUnsuspend();
g_base->networking->OnAppUnsuspend();
// When resuming from a suspended state, we may want to pause whatever
// game was running when we last were active.
//
// TODO(efro): we should make this smarter so it doesn't happen if we're
// in a network game or something that we can't pause; bringing up the
// menu doesn't really accomplish anything there.
//
// In general this probably should be handled at a higher level.
// if (g_core->should_pause_active_game) {
// g_core->should_pause_active_game = false;
// // If we've been completely backgrounded, send a menu-press command to
// // the game; this will bring up a pause menu if we're in the game/etc.
// if (!g_base->ui->MainMenuVisible()) {
// g_base->ui->PushMainMenuPressCall(nullptr);
// }
// }
if (g_buildconfig.debug_build()) {
Log(LogLevel::kDebug,
"UnsuspendApp() completed in "
+ std::to_string(core::CorePlatform::GetCurrentMillisecs()
- start_time)
+ "ms.");
}
} }
void BaseFeatureSet::OnAppShutdownComplete() { void BaseFeatureSet::OnAppShutdownComplete() {
@ -730,8 +909,6 @@ void BaseFeatureSet::ShutdownSuppressDisallow() {
shutdown_suppress_disallowed_ = true; shutdown_suppress_disallowed_ = true;
} }
// auto BaseFeatureSet::GetReturnValue() const -> int { return return_value(); }
void BaseFeatureSet::QuitApp(bool confirm, QuitType quit_type) { void BaseFeatureSet::QuitApp(bool confirm, QuitType quit_type) {
// If they want a confirm dialog and we're able to present one, do that. // If they want a confirm dialog and we're able to present one, do that.
if (confirm && !g_core->HeadlessMode() && !g_base->input->IsInputLocked() if (confirm && !g_core->HeadlessMode() && !g_base->input->IsInputLocked()
@ -760,4 +937,57 @@ void BaseFeatureSet::PushMainThreadRunnable(Runnable* runnable) {
app_adapter->DoPushMainThreadRunnable(runnable); app_adapter->DoPushMainThreadRunnable(runnable);
} }
auto BaseFeatureSet::ClipboardIsSupported() -> bool {
// We only call our actual virtual function once.
if (!have_clipboard_is_supported_) {
clipboard_is_supported_ = app_adapter->DoClipboardIsSupported();
have_clipboard_is_supported_ = true;
}
return clipboard_is_supported_;
}
auto BaseFeatureSet::ClipboardHasText() -> bool {
// If subplatform says they don't support clipboards, don't even ask.
if (!ClipboardIsSupported()) {
return false;
}
return app_adapter->DoClipboardHasText();
}
void BaseFeatureSet::ClipboardSetText(const std::string& text) {
// If subplatform says they don't support clipboards, this is an error.
if (!ClipboardIsSupported()) {
throw Exception("ClipboardSetText called with no clipboard support.",
PyExcType::kRuntime);
}
app_adapter->DoClipboardSetText(text);
}
auto BaseFeatureSet::ClipboardGetText() -> std::string {
// If subplatform says they don't support clipboards, this is an error.
if (!ClipboardIsSupported()) {
throw Exception("ClipboardGetText called with no clipboard support.",
PyExcType::kRuntime);
}
return app_adapter->DoClipboardGetText();
}
void BaseFeatureSet::SetAppActive(bool active) {
assert(InMainThread());
g_core->platform->LowLevelDebugLog(
"SetAppActive(" + std::to_string(active) + ")@"
+ std::to_string(core::CorePlatform::GetCurrentMillisecs()));
printf("APP ACTIVE %d\n", static_cast<int>(active));
// Issue a gentle warning if they are feeding us the same state twice in a
// row; might imply faulty logic.
if (app_active_set_ && app_active_ == active) {
Log(LogLevel::kWarning, "SetAppActive called with state "
+ std::to_string(active) + " twice in a row.");
}
app_active_set_ = true;
app_active_ = active;
}
} // namespace ballistica::base } // namespace ballistica::base

View File

@ -609,6 +609,31 @@ class BaseFeatureSet : public FeatureSetNativeComponent,
/// Start app systems in motion. /// Start app systems in motion.
void StartApp() override; void StartApp() override;
/// Set the app's active state. Should be called from the main thread.
/// Generally called by the AppAdapter. Being inactive means the app
/// experience is not front and center and thus it may want to throttle
/// down its rendering rate, pause single play gameplay, etc. This does
/// not, however, cause any extreme action such as halting event loops;
/// use Suspend/Resume for that. And note that the app may still be
/// visible while inactive, so it should not *completely* stop
/// drawing/etc.
void SetAppActive(bool active);
/// Put the app into a suspended state. Should be called from the main
/// thread. Generally called by the AppAdapter. Suspends event loops,
/// closes network sockets, etc. Generally corresponds to being
/// backgrounded on mobile platforms. It is assumed that, as soon as this
/// call returns, all engine work is finished and all threads can be
/// immediately suspended by the OS without any problems.
void SuspendApp();
/// Return the app to a running state from a suspended one. Can correspond
/// to foregrounding on mobile, unminimizing on desktop, etc. Spins
/// threads back up, re-opens network sockets, etc.
void UnsuspendApp();
auto app_suspended() const { return app_suspended_; }
/// Issue a high level app quit request. Can be called from any thread and /// Issue a high level app quit request. Can be called from any thread and
/// can be safely called repeatedly. If 'confirm' is true, a confirmation /// can be safely called repeatedly. If 'confirm' is true, a confirmation
/// dialog will be presented if the environment and situation allows; /// dialog will be presented if the environment and situation allows;
@ -738,6 +763,22 @@ class BaseFeatureSet : public FeatureSetNativeComponent,
/// reported by the Python layer. /// reported by the Python layer.
auto GetV2AccountID() -> std::optional<std::string>; auto GetV2AccountID() -> std::optional<std::string>;
/// Return whether clipboard operations are supported at all. This gets
/// called when determining whether to display clipboard related UI
/// elements/etc.
auto ClipboardIsSupported() -> bool;
/// Return whether there is currently text on the clipboard.
auto ClipboardHasText() -> bool;
/// Set current clipboard text. Raises an Exception if clipboard is
/// unsupported.
void ClipboardSetText(const std::string& text);
/// Return current text from the clipboard. Raises an Exception if
/// clipboard is unsupported or if there's no text on the clipboard.
auto ClipboardGetText() -> std::string;
// Const subsystems. // Const subsystems.
AppAdapter* const app_adapter; AppAdapter* const app_adapter;
AppConfig* const app_config; AppConfig* const app_config;
@ -774,10 +815,7 @@ class BaseFeatureSet : public FeatureSetNativeComponent,
// Non-const bits (fixme: clean up access to these). // Non-const bits (fixme: clean up access to these).
TouchInput* touch_input{}; TouchInput* touch_input{};
// auto return_value() const { return return_value_; } auto app_active() const { return app_active_; }
// void set_return_value(int val) { return_value_ = val; }
// auto GetReturnValue() const -> int override;
private: private:
BaseFeatureSet(); BaseFeatureSet();
@ -789,8 +827,12 @@ class BaseFeatureSet : public FeatureSetNativeComponent,
AppMode* app_mode_; AppMode* app_mode_;
PlusSoftInterface* plus_soft_{}; PlusSoftInterface* plus_soft_{};
ClassicSoftInterface* classic_soft_{}; ClassicSoftInterface* classic_soft_{};
std::mutex shutdown_suppress_lock_; std::mutex shutdown_suppress_lock_;
bool have_clipboard_is_supported_{};
bool clipboard_is_supported_{};
bool app_active_set_{};
bool app_active_{true};
bool app_suspended_{};
bool shutdown_suppress_disallowed_{}; bool shutdown_suppress_disallowed_{};
bool tried_importing_plus_{}; bool tried_importing_plus_{};
bool tried_importing_classic_{}; bool tried_importing_classic_{};
@ -803,7 +845,6 @@ class BaseFeatureSet : public FeatureSetNativeComponent,
bool basn_log_behavior_{}; bool basn_log_behavior_{};
bool server_wrapper_managed_{}; bool server_wrapper_managed_{};
int shutdown_suppress_count_{}; int shutdown_suppress_count_{};
// int return_value_{};
}; };
} // namespace ballistica::base } // namespace ballistica::base

View File

@ -83,12 +83,12 @@ Graphics::~Graphics() = default;
void Graphics::OnAppStart() { assert(g_base->InLogicThread()); } void Graphics::OnAppStart() { assert(g_base->InLogicThread()); }
void Graphics::OnAppPause() { void Graphics::OnAppSuspend() {
assert(g_base->InLogicThread()); assert(g_base->InLogicThread());
SetGyroEnabled(false); SetGyroEnabled(false);
} }
void Graphics::OnAppResume() { void Graphics::OnAppUnsuspend() {
assert(g_base->InLogicThread()); assert(g_base->InLogicThread());
g_base->graphics->SetGyroEnabled(true); g_base->graphics->SetGyroEnabled(true);
} }
@ -615,7 +615,7 @@ void Graphics::FadeScreen(bool to, millisecs_t time, PyObject* endcall) {
Log(LogLevel::kWarning, Log(LogLevel::kWarning,
"2 fades overlapping; running first fade-end-call early."); "2 fades overlapping; running first fade-end-call early.");
} }
fade_end_call_->ScheduleOnce(); fade_end_call_->Schedule();
fade_end_call_.Clear(); fade_end_call_.Clear();
} }
set_fade_start_on_next_draw_ = true; set_fade_start_on_next_draw_ = true;
@ -993,7 +993,10 @@ void Graphics::DrawFades(FrameDef* frame_def) {
// Guard against accidental fades that never fade back in. // Guard against accidental fades that never fade back in.
if (fade_ <= 0.0f && fade_out_) { if (fade_ <= 0.0f && fade_out_) {
millisecs_t faded_time = real_time - (fade_start_ + fade_time_); millisecs_t faded_time = real_time - (fade_start_ + fade_time_);
if (faded_time > 15000) {
// TEMP HACK - don't trigger this while inactive.
// Need to make overall fade logic smarter.
if (faded_time > 15000 && g_base->app_active()) {
Log(LogLevel::kError, "FORCE-ENDING STUCK FADE"); Log(LogLevel::kError, "FORCE-ENDING STUCK FADE");
fade_out_ = false; fade_out_ = false;
fade_ = 1.0f; fade_ = 1.0f;
@ -1021,7 +1024,7 @@ void Graphics::DrawFades(FrameDef* frame_def) {
} else { } else {
fade_ = 0; fade_ = 0;
if (!was_done && fade_end_call_.Exists()) { if (!was_done && fade_end_call_.Exists()) {
fade_end_call_->ScheduleOnce(); fade_end_call_->Schedule();
fade_end_call_.Clear(); fade_end_call_.Clear();
} }
} }

View File

@ -56,8 +56,8 @@ class Graphics {
Graphics(); Graphics();
void OnAppStart(); void OnAppStart();
void OnAppPause(); void OnAppSuspend();
void OnAppResume(); void OnAppUnsuspend();
void OnAppShutdown(); void OnAppShutdown();
void OnAppShutdownComplete(); void OnAppShutdownComplete();
void OnScreenSizeChange(); void OnScreenSizeChange();

View File

@ -143,7 +143,7 @@ auto GraphicsServer::WaitForRenderFrameDef_() -> FrameDef* {
// Spin and wait for a short bit for a frame_def to appear. // Spin and wait for a short bit for a frame_def to appear.
while (true) { while (true) {
// Stop waiting if we can't/shouldn't render anyway. // Stop waiting if we can't/shouldn't render anyway.
if (!renderer_ || shutting_down_ || g_base->app_adapter->app_suspended()) { if (!renderer_ || shutting_down_ || g_base->app_suspended()) {
return nullptr; return nullptr;
} }

View File

@ -87,7 +87,7 @@ class TextGroup : public Object {
Object::Ref<TextureAsset> os_texture_; Object::Ref<TextureAsset> os_texture_;
std::vector<std::unique_ptr<TextMeshEntry>> entries_; std::vector<std::unique_ptr<TextMeshEntry>> entries_;
std::string text_; std::string text_;
bool big_; bool big_{};
}; };
} // namespace ballistica::base } // namespace ballistica::base

View File

@ -155,27 +155,24 @@ void Input::AnnounceConnects_() {
if (first_print && g_core->GetAppTimeSeconds() < 3.0) { if (first_print && g_core->GetAppTimeSeconds() < 3.0) {
first_print = false; first_print = false;
// Disabling this completely on Android for now; we often get large
// numbers of devices there that aren't actually devices.
bool do_print_initial_counts{!g_buildconfig.ostype_android()};
// If there's been several connected, just give a number. // If there's been several connected, just give a number.
if (explicit_bool(do_print_initial_counts)) { if (newly_connected_controllers_.size() > 1) {
if (newly_connected_controllers_.size() > 1) { std::string s =
std::string s = g_base->assets->GetResourceString("controllersDetectedText");
g_base->assets->GetResourceString("controllersDetectedText"); Utils::StringReplaceOne(
Utils::StringReplaceOne( &s, "${COUNT}", std::to_string(newly_connected_controllers_.size()));
&s, "${COUNT}", ScreenMessage(s);
std::to_string(newly_connected_controllers_.size())); } else {
ScreenMessage(s); ScreenMessage(
} else { g_base->assets->GetResourceString("controllerDetectedText"));
ScreenMessage(
g_base->assets->GetResourceString("controllerDetectedText"));
}
} }
} else { } else {
// If there's been several connected, just give a number. // If there's been several connected, just give a number.
if (newly_connected_controllers_.size() > 1) { if (newly_connected_controllers_.size() > 1) {
for (auto&& s : newly_connected_controllers_) {
Log(LogLevel::kInfo, "GOT CONTROLLER " + s);
}
std::string s = std::string s =
g_base->assets->GetResourceString("controllersConnectedText"); g_base->assets->GetResourceString("controllersConnectedText");
Utils::StringReplaceOne( Utils::StringReplaceOne(
@ -193,7 +190,6 @@ void Input::AnnounceConnects_() {
g_base->audio->PlaySound(g_base->assets->SysSound(SysSoundID::kGunCock)); g_base->audio->PlaySound(g_base->assets->SysSound(SysSoundID::kGunCock));
} }
} }
newly_connected_controllers_.clear(); newly_connected_controllers_.clear();
} }
@ -222,6 +218,14 @@ void Input::AnnounceDisconnects_() {
void Input::ShowStandardInputDeviceConnectedMessage_(InputDevice* j) { void Input::ShowStandardInputDeviceConnectedMessage_(InputDevice* j) {
assert(g_base->InLogicThread()); assert(g_base->InLogicThread());
// On Android we never show messages for initial input-devices; we often
// get large numbers of strange virtual devices that aren't actually
// controllers so this is more confusing than helpful.
if (g_buildconfig.ostype_android() && g_core->GetAppTimeSeconds() < 3.0) {
return;
}
std::string suffix; std::string suffix;
suffix += j->GetPersistentIdentifier(); suffix += j->GetPersistentIdentifier();
suffix += j->GetDeviceExtraDescription(); suffix += j->GetDeviceExtraDescription();
@ -516,9 +520,9 @@ void Input::OnAppStart() {
} }
} }
void Input::OnAppPause() { assert(g_base->InLogicThread()); } void Input::OnAppSuspend() { assert(g_base->InLogicThread()); }
void Input::OnAppResume() { assert(g_base->InLogicThread()); } void Input::OnAppUnsuspend() { assert(g_base->InLogicThread()); }
void Input::OnAppShutdown() { assert(g_base->InLogicThread()); } void Input::OnAppShutdown() { assert(g_base->InLogicThread()); }
@ -1239,7 +1243,14 @@ void Input::HandleSmoothMouseScroll_(const Vector2f& velocity, bool momentum) {
} }
void Input::PushMouseMotionEvent(const Vector2f& position) { void Input::PushMouseMotionEvent(const Vector2f& position) {
assert(g_base->logic->event_loop()); auto* loop = g_base->logic->event_loop();
assert(loop);
// Don't overload it with events if it's stuck.
if (!loop->CheckPushSafety()) {
return;
}
g_base->logic->event_loop()->PushCall( g_base->logic->event_loop()->PushCall(
[this, position] { HandleMouseMotion_(position); }); [this, position] { HandleMouseMotion_(position); });
} }

View File

@ -21,8 +21,8 @@ class Input {
Input(); Input();
void OnAppStart(); void OnAppStart();
void OnAppPause(); void OnAppSuspend();
void OnAppResume(); void OnAppUnsuspend();
void OnAppShutdown(); void OnAppShutdown();
void OnAppShutdownComplete(); void OnAppShutdownComplete();
void StepDisplayTime(); void StepDisplayTime();

View File

@ -376,8 +376,10 @@ auto RemoteAppServer::GetClient(int request_id, struct sockaddr* addr,
Vector3f(1, 1, 1)); Vector3f(1, 1, 1));
}); });
g_base->logic->event_loop()->PushCall([] { g_base->logic->event_loop()->PushCall([] {
g_base->audio->PlaySound( if (g_base->assets->asset_loads_allowed()) {
g_base->assets->SysSound(SysSoundID::kGunCock)); g_base->audio->PlaySound(
g_base->assets->SysSound(SysSoundID::kGunCock));
}
}); });
} }
clients_[i].in_use = true; clients_[i].in_use = true;
@ -426,9 +428,12 @@ auto RemoteAppServer::GetClient(int request_id, struct sockaddr* addr,
}); });
g_base->logic->event_loop()->PushCall([] { g_base->logic->event_loop()->PushCall([] {
g_base->audio->PlaySound( if (g_base->assets->asset_loads_allowed()) {
g_base->assets->SysSound(SysSoundID::kGunCock)); g_base->audio->PlaySound(
g_base->assets->SysSound(SysSoundID::kGunCock));
}
}); });
std::string utf8 = Utils::GetValidUTF8(clients_[i].display_name, "rsgc1"); std::string utf8 = Utils::GetValidUTF8(clients_[i].display_name, "rsgc1");
clients_[i].joystick_ = Object::NewDeferred<JoystickInput>( clients_[i].joystick_ = Object::NewDeferred<JoystickInput>(
-1, // not an sdl joystick -1, // not an sdl joystick

View File

@ -48,9 +48,9 @@ void Logic::OnAppStart() {
// Stay informed when our event loop is pausing/unpausing. // Stay informed when our event loop is pausing/unpausing.
event_loop_->AddSuspendCallback( event_loop_->AddSuspendCallback(
NewLambdaRunnableUnmanaged([this] { OnAppPause(); })); NewLambdaRunnableUnmanaged([this] { OnAppSuspend(); }));
event_loop_->AddUnsuspendCallback( event_loop_->AddUnsuspendCallback(
NewLambdaRunnableUnmanaged([this] { OnAppResume(); })); NewLambdaRunnableUnmanaged([this] { OnAppUnsuspend(); }));
// Running in a specific order here and should try to stick to it in // Running in a specific order here and should try to stick to it in
// other OnAppXXX callbacks so any subsystem interdependencies behave // other OnAppXXX callbacks so any subsystem interdependencies behave
@ -179,40 +179,40 @@ void Logic::OnInitialAppModeSet() {
} }
} }
void Logic::OnAppPause() { void Logic::OnAppSuspend() {
assert(g_base->InLogicThread()); assert(g_base->InLogicThread());
assert(g_base->CurrentContext().IsEmpty()); assert(g_base->CurrentContext().IsEmpty());
// Note: keep these in opposite order of OnAppStart. // Note: keep these in opposite order of OnAppStart.
g_base->python->OnAppPause(); g_base->python->OnAppSuspend();
if (g_base->HavePlus()) { if (g_base->HavePlus()) {
g_base->plus()->OnAppPause(); g_base->plus()->OnAppSuspend();
} }
g_base->app_mode()->OnAppPause(); g_base->app_mode()->OnAppSuspend();
g_base->ui->OnAppPause(); g_base->ui->OnAppSuspend();
g_base->input->OnAppPause(); g_base->input->OnAppSuspend();
g_base->audio->OnAppPause(); g_base->audio->OnAppSuspend();
g_base->graphics->OnAppPause(); g_base->graphics->OnAppSuspend();
g_base->platform->OnAppPause(); g_base->platform->OnAppSuspend();
g_base->app_adapter->OnAppPause(); g_base->app_adapter->OnAppSuspend();
} }
void Logic::OnAppResume() { void Logic::OnAppUnsuspend() {
assert(g_base->InLogicThread()); assert(g_base->InLogicThread());
assert(g_base->CurrentContext().IsEmpty()); assert(g_base->CurrentContext().IsEmpty());
// Note: keep these in the same order as OnAppStart. // Note: keep these in the same order as OnAppStart.
g_base->app_adapter->OnAppResume(); g_base->app_adapter->OnAppUnsuspend();
g_base->platform->OnAppResume(); g_base->platform->OnAppUnsuspend();
g_base->graphics->OnAppResume(); g_base->graphics->OnAppUnsuspend();
g_base->audio->OnAppResume(); g_base->audio->OnAppUnsuspend();
g_base->input->OnAppResume(); g_base->input->OnAppUnsuspend();
g_base->ui->OnAppResume(); g_base->ui->OnAppUnsuspend();
g_base->app_mode()->OnAppResume(); g_base->app_mode()->OnAppUnsuspend();
if (g_base->HavePlus()) { if (g_base->HavePlus()) {
g_base->plus()->OnAppResume(); g_base->plus()->OnAppUnsuspend();
} }
g_base->python->OnAppResume(); g_base->python->OnAppUnsuspend();
} }
void Logic::Shutdown() { void Logic::Shutdown() {

View File

@ -52,11 +52,11 @@ class Logic {
/// Called when our event-loop pauses. Informs Python and other /// Called when our event-loop pauses. Informs Python and other
/// subsystems. /// subsystems.
void OnAppPause(); void OnAppSuspend();
/// Called when our event-loop resumes. Informs Python and other /// Called when our event-loop resumes. Informs Python and other
/// subsystems. /// subsystems.
void OnAppResume(); void OnAppUnsuspend();
void OnAppShutdown(); void OnAppShutdown();
void OnAppShutdownComplete(); void OnAppShutdownComplete();

View File

@ -25,7 +25,7 @@ void NetworkReader::SetPort(int port) {
thread_ = new std::thread(RunThreadStatic_, this); thread_ = new std::thread(RunThreadStatic_, this);
} }
void NetworkReader::OnAppPause() { void NetworkReader::OnAppSuspend() {
assert(g_core->InMainThread()); assert(g_core->InMainThread());
assert(!paused_); assert(!paused_);
{ {
@ -33,16 +33,14 @@ void NetworkReader::OnAppPause() {
paused_ = true; paused_ = true;
} }
// Ok now attempt to send a quick ping to ourself to wake us up so we can kill // It's possible that we get suspended before port is set, so this could
// our socket. // still be -1.
if (port4_ != -1) { if (port4_ != -1) {
PokeSelf_(); PokeSelf_();
} else {
Log(LogLevel::kError, "NetworkReader port is -1 on pause");
} }
} }
void NetworkReader::OnAppResume() { void NetworkReader::OnAppUnsuspend() {
assert(g_core->InMainThread()); assert(g_core->InMainThread());
assert(paused_); assert(paused_);

Some files were not shown because too many files have changed in this diff Show More