diff --git a/.efrocachemap b/.efrocachemap
index 48f8ee57..87cd4596 100644
--- a/.efrocachemap
+++ b/.efrocachemap
@@ -421,43 +421,43 @@
"build/assets/ba_data/audio/zoeOw.ogg": "74befe45a8417e95b6a2233c51992a26",
"build/assets/ba_data/audio/zoePickup01.ogg": "48ab8cddfcde36a750856f3f81dd20c8",
"build/assets/ba_data/audio/zoeScream01.ogg": "2b468aedfa8741090247f04eb9e6df55",
- "build/assets/ba_data/data/langdata.json": "99ad02cbe90ab3e46168ddf277633c68",
- "build/assets/ba_data/data/languages/arabic.json": "dcb913b3d89fe665adbf35e697d2e5d0",
+ "build/assets/ba_data/data/langdata.json": "65b989c0d893d8981992f66ff81a8a97",
+ "build/assets/ba_data/data/languages/arabic.json": "3be73283cb8009cc2c95e53df36e8b86",
"build/assets/ba_data/data/languages/belarussian.json": "3d5523d0004293aa2df02f3f6f3b84f8",
- "build/assets/ba_data/data/languages/chinese.json": "d4a89bf007a0624c6852c512d1d38cdc",
- "build/assets/ba_data/data/languages/chinesetraditional.json": "319565f8a15667488f48dbce59278e39",
+ "build/assets/ba_data/data/languages/chinese.json": "fc69790c41e6750d20a7719afc5a7527",
+ "build/assets/ba_data/data/languages/chinesetraditional.json": "86671be47e2b5d0badeb3b90a3db6402",
"build/assets/ba_data/data/languages/croatian.json": "b23619cb396ac16640c47458f884b16a",
"build/assets/ba_data/data/languages/czech.json": "61bcfce75c0d53d2f2af709cee42187a",
"build/assets/ba_data/data/languages/danish.json": "8e57db30c5250df2abff14a822f83ea7",
"build/assets/ba_data/data/languages/dutch.json": "b0900d572c9141897d53d6574c471343",
- "build/assets/ba_data/data/languages/english.json": "eae3a17af2efc2388f96c1e05c024455",
+ "build/assets/ba_data/data/languages/english.json": "5a73dea22df1117d58a79459def62ff5",
"build/assets/ba_data/data/languages/esperanto.json": "0e397cfa5f3fb8cef5f4a64f21cda880",
- "build/assets/ba_data/data/languages/filipino.json": "fabaedeefc28cf02ec365ef37046ec0b",
- "build/assets/ba_data/data/languages/french.json": "b7d11199756f0eb4f1a745ceee652b2a",
+ "build/assets/ba_data/data/languages/filipino.json": "a291d4d3619adc82c5c4096bbfefe28a",
+ "build/assets/ba_data/data/languages/french.json": "73a01df9b44b3fb030750a1b5f56f07b",
"build/assets/ba_data/data/languages/german.json": "198b9860c5b9df7b8e3e30b03d8755cb",
- "build/assets/ba_data/data/languages/gibberish.json": "085f438ccdb7553bbb7cf5d68e9cd300",
- "build/assets/ba_data/data/languages/greek.json": "ad3c0d38f34d809824892d6f22808dbf",
+ "build/assets/ba_data/data/languages/gibberish.json": "d6810f99fc9055b5203c382a83bc5128",
+ "build/assets/ba_data/data/languages/greek.json": "d28d1092fbb00ed857cbd53124c0dc78",
"build/assets/ba_data/data/languages/hindi.json": "54cd56bade6922b40989a8ac5e0c17f6",
- "build/assets/ba_data/data/languages/hungarian.json": "0cf994bf9fedf004c9a1011bee179a07",
+ "build/assets/ba_data/data/languages/hungarian.json": "3a974ea6e6ffccca41aed308ad5a7a26",
"build/assets/ba_data/data/languages/indonesian.json": "ed9038bf4b9216f93eb73e753e162706",
- "build/assets/ba_data/data/languages/italian.json": "017f2b20584838d9f5ad88822a4caf62",
+ "build/assets/ba_data/data/languages/italian.json": "ffc58952260b63fdf88805a2d9a68257",
"build/assets/ba_data/data/languages/korean.json": "4e3524327a0174250aff5e1ef4c0c597",
"build/assets/ba_data/data/languages/malay.json": "f6ce0426d03a62612e3e436ed5d1be1f",
- "build/assets/ba_data/data/languages/persian.json": "87f9dfc085f0ff715b17a8082fc79483",
- "build/assets/ba_data/data/languages/polish.json": "3ce5635118b34ea89d6e0f83c8c7e9df",
- "build/assets/ba_data/data/languages/portuguese.json": "0accd9462f0acbc5f869f33a2a7a24cd",
- "build/assets/ba_data/data/languages/romanian.json": "ef68520f749cf3641d4e4225a6166349",
- "build/assets/ba_data/data/languages/russian.json": "e7b90ce1eee8247e164ed7d1470f76bd",
+ "build/assets/ba_data/data/languages/persian.json": "ededb9c015afb58b1324a096ea740f72",
+ "build/assets/ba_data/data/languages/polish.json": "62b56ace320191985689bfbcfacd56ea",
+ "build/assets/ba_data/data/languages/portuguese.json": "2be5c25e55946197bd0e0f646d444b2c",
+ "build/assets/ba_data/data/languages/romanian.json": "55a8744e466801013ea131266a856924",
+ "build/assets/ba_data/data/languages/russian.json": "c7c5bfc6f82d74e49ac746d187314ba7",
"build/assets/ba_data/data/languages/serbian.json": "d7452dd72ac0e51680cb39b5ebaa1c69",
"build/assets/ba_data/data/languages/slovak.json": "3c08c748c96c71bd9e1d7291fb8817b6",
- "build/assets/ba_data/data/languages/spanish.json": "63f80b2c37a1d8092304db1a10ecf789",
+ "build/assets/ba_data/data/languages/spanish.json": "4b262447f703eb4c6683b54af6b7b592",
"build/assets/ba_data/data/languages/swedish.json": "5142a96597d17d8344be96a603da64ac",
- "build/assets/ba_data/data/languages/tamil.json": "b9fcc523639f55e05c7f4e7914f3321a",
+ "build/assets/ba_data/data/languages/tamil.json": "5ececa2dde2bbe33ad61e580fa5b79ad",
"build/assets/ba_data/data/languages/thai.json": "1d665629361f302693dead39de8fa945",
- "build/assets/ba_data/data/languages/turkish.json": "60691e93b58e1612901e8558b967697b",
- "build/assets/ba_data/data/languages/ukrainian.json": "6207f0e4048f9391526321d879a5d61e",
- "build/assets/ba_data/data/languages/venetian.json": "9bb01eea11d1627c0ceb876d26d86d0c",
- "build/assets/ba_data/data/languages/vietnamese.json": "4bc1ba96ef291fed6d6409ad8cf7357f",
+ "build/assets/ba_data/data/languages/turkish.json": "1c0a5c0c0c115107fb0752c92907f584",
+ "build/assets/ba_data/data/languages/ukrainian.json": "23a98e5722d3e71e809a8a0063daa603",
+ "build/assets/ba_data/data/languages/venetian.json": "a1315f5233ebbee1464683ac55d5d9d5",
+ "build/assets/ba_data/data/languages/vietnamese.json": "5ae84265600b6cfda45c9bed18724e1d",
"build/assets/ba_data/data/maps/big_g.json": "1dd301d490643088a435ce75df971054",
"build/assets/ba_data/data/maps/bridgit.json": "6aea74805f4880cc11237c5734a24422",
"build/assets/ba_data/data/maps/courtyard.json": "4b836554c8949bcd2ae382f5e3c1a9cc",
@@ -1388,10 +1388,10 @@
"build/assets/ba_data/textures/clayStroke.ktx": "8c648180e290a4acfff1196fa358f042",
"build/assets/ba_data/textures/clayStroke.pvr": "84c31a1a8ab4ea051a5b6a804dd952df",
"build/assets/ba_data/textures/clayStroke_preview.png": "ec248139546f51c007148ef0250a5e0c",
- "build/assets/ba_data/textures/coin.dds": "e69e7ba8ff87d5ed9ca7c7ab96d13546",
- "build/assets/ba_data/textures/coin.ktx": "d1de4b2fd4fe028b889ed53a829f9a72",
- "build/assets/ba_data/textures/coin.pvr": "b8477ed7afdc5e460843e4d8413a2c08",
- "build/assets/ba_data/textures/coin_preview.png": "2bdae85f94cfa39eafdfc4330db90605",
+ "build/assets/ba_data/textures/coin.dds": "ae86318038e6badeb5e9a7943e466c99",
+ "build/assets/ba_data/textures/coin.ktx": "6202185bbf5158191b267c2c85b40dd9",
+ "build/assets/ba_data/textures/coin.pvr": "d4b729057e06c2db64dd3fcae37588fb",
+ "build/assets/ba_data/textures/coin_preview.png": "ee84407488f8812e08875224316f3fad",
"build/assets/ba_data/textures/controllerIcon.dds": "9c805344b3d663d9ecf5a41d70289fc9",
"build/assets/ba_data/textures/controllerIcon.ktx": "b5cf73a08e788f4e2a609d516715bd2b",
"build/assets/ba_data/textures/controllerIcon.pvr": "2f9193b6eb0b7c20113d4c21b7e1ef8d",
@@ -2495,10 +2495,10 @@
"build/assets/ba_data/textures/uiAtlas.dds": "76a861c711ea4700ad2f484adb53a837",
"build/assets/ba_data/textures/uiAtlas.ktx": "4bd0550d4297b6b290ceceb0d3dae490",
"build/assets/ba_data/textures/uiAtlas.pvr": "e7601e1d8cb5a03324b7e240a46eb62f",
- "build/assets/ba_data/textures/uiAtlas2.dds": "ac969ae516faf8e57800dd37801550ef",
- "build/assets/ba_data/textures/uiAtlas2.ktx": "744c37bbee308e1580e2ad569539b314",
- "build/assets/ba_data/textures/uiAtlas2.pvr": "10ee6675a5c7892efa6924bdb4ce5331",
- "build/assets/ba_data/textures/uiAtlas2_preview.png": "22077f30b690f0c21cfa03549cd55d2b",
+ "build/assets/ba_data/textures/uiAtlas2.dds": "6d4b31087da911fa4fe4ccbd3935cc63",
+ "build/assets/ba_data/textures/uiAtlas2.ktx": "7679ce6fab577500b956973ea10a2697",
+ "build/assets/ba_data/textures/uiAtlas2.pvr": "70383679a25fa1739beb2b2b97f4fbee",
+ "build/assets/ba_data/textures/uiAtlas2_preview.png": "6a48c238db2453ee0333cf554bd68054",
"build/assets/ba_data/textures/uiAtlas_preview.png": "314c4fe9ba75ff03bb5d75d5003c2462",
"build/assets/ba_data/textures/upButton.dds": "96f0df23bb2055a30d0073d146138a8e",
"build/assets/ba_data/textures/upButton.ktx": "a5cc08ddb7e67a1b9e5e067d06fbbfcd",
@@ -4096,33 +4096,33 @@
"build/assets/windows/Win32/ucrtbased.dll": "2def5335207d41b21b9823f6805997f1",
"build/assets/windows/Win32/vc_redist.x86.exe": "b08a55e2e77623fe657bea24f223a3ae",
"build/assets/windows/Win32/vcruntime140d.dll": "865b2af4d1e26a1a8073c89acb06e599",
- "build/prefab/full/linux_arm64_gui/debug/ballisticakit": "bf270e4b3ae3fdecff554052ab6b5b00",
- "build/prefab/full/linux_arm64_gui/release/ballisticakit": "78116aab2b62c8ca18dfc534a3db829e",
- "build/prefab/full/linux_arm64_server/debug/dist/ballisticakit_headless": "ef3f99b303627e36866d119950745815",
- "build/prefab/full/linux_arm64_server/release/dist/ballisticakit_headless": "d93df4793c2798d489a12d848af2daa0",
- "build/prefab/full/linux_x86_64_gui/debug/ballisticakit": "e4ac642421fc80630950dc39b0d5b488",
- "build/prefab/full/linux_x86_64_gui/release/ballisticakit": "51ea783b32a84a10e97275c9163dc767",
- "build/prefab/full/linux_x86_64_server/debug/dist/ballisticakit_headless": "607c744559cf66f7cc565ad6c0f21d90",
- "build/prefab/full/linux_x86_64_server/release/dist/ballisticakit_headless": "5d869b3cd51a85de3613f39d4180196e",
- "build/prefab/full/mac_arm64_gui/debug/ballisticakit": "d1c78f94885520e6ae8a7f3afe4d6162",
- "build/prefab/full/mac_arm64_gui/release/ballisticakit": "58b78d9fbbd6c4b9515b35f9914ccd7e",
- "build/prefab/full/mac_arm64_server/debug/dist/ballisticakit_headless": "1ee3373fb1f9a0fedc7f0f4bd574bac9",
- "build/prefab/full/mac_arm64_server/release/dist/ballisticakit_headless": "d3da9a35e807bc10f45f3aa38eea2a95",
- "build/prefab/full/mac_x86_64_gui/debug/ballisticakit": "c26823caeca6398af29accbbebbb0159",
- "build/prefab/full/mac_x86_64_gui/release/ballisticakit": "cf58fa232755c64c71035dce8074707f",
- "build/prefab/full/mac_x86_64_server/debug/dist/ballisticakit_headless": "f077a737910c340fd42ce91e5249447e",
- "build/prefab/full/mac_x86_64_server/release/dist/ballisticakit_headless": "37d37d17cb9878f630da859903fc8e47",
- "build/prefab/full/windows_x86_gui/debug/BallisticaKit.exe": "dde17d5f07a4837ef331fe9012ed4976",
- "build/prefab/full/windows_x86_gui/release/BallisticaKit.exe": "53d938dd0541c62f5e31a871002c9291",
- "build/prefab/full/windows_x86_server/debug/dist/BallisticaKitHeadless.exe": "2791119fd7b475228aa8cb02d6db7a96",
- "build/prefab/full/windows_x86_server/release/dist/BallisticaKitHeadless.exe": "bbcad92bb94abfe2dde3b568f6bb3595",
- "build/prefab/lib/linux_arm64_gui/debug/libballisticaplus.a": "273ffd46a618fe347a570e6bd01d54a4",
+ "build/prefab/full/linux_arm64_gui/debug/ballisticakit": "0a166c970637de5e318e4f07e8c0aeae",
+ "build/prefab/full/linux_arm64_gui/release/ballisticakit": "71fee3398234820d038235f02ea7ed6a",
+ "build/prefab/full/linux_arm64_server/debug/dist/ballisticakit_headless": "682d622e82fbdf7d41f170146ae8cf6f",
+ "build/prefab/full/linux_arm64_server/release/dist/ballisticakit_headless": "ed9a7e820ab3a59d6326b898973d5cff",
+ "build/prefab/full/linux_x86_64_gui/debug/ballisticakit": "4efa411d0d43debb7c8db1db1d9a4658",
+ "build/prefab/full/linux_x86_64_gui/release/ballisticakit": "8892f774f2d079604d77241adaf3e529",
+ "build/prefab/full/linux_x86_64_server/debug/dist/ballisticakit_headless": "c479f3ab633f0b19f3e733943b3c1c3b",
+ "build/prefab/full/linux_x86_64_server/release/dist/ballisticakit_headless": "2797e7b22388c68b2e41a1eb7c820a62",
+ "build/prefab/full/mac_arm64_gui/debug/ballisticakit": "f58b978d2978fdf6e2f7f0cc13fbb2c7",
+ "build/prefab/full/mac_arm64_gui/release/ballisticakit": "1ad711461c2a65ecb1fbab1aac2aacae",
+ "build/prefab/full/mac_arm64_server/debug/dist/ballisticakit_headless": "ec90ed3ec92a6db3f8b9651f2464d8d4",
+ "build/prefab/full/mac_arm64_server/release/dist/ballisticakit_headless": "12cc0539e86a408b01ac3b3a74cff98a",
+ "build/prefab/full/mac_x86_64_gui/debug/ballisticakit": "1f59835f71e01827f056f503302c45c4",
+ "build/prefab/full/mac_x86_64_gui/release/ballisticakit": "c747103d9fe3206f94b54ed9ae5d4aa4",
+ "build/prefab/full/mac_x86_64_server/debug/dist/ballisticakit_headless": "603f77208cb0df9161b84387631b8778",
+ "build/prefab/full/mac_x86_64_server/release/dist/ballisticakit_headless": "00a72972b6b0531b176406ad0e8d243f",
+ "build/prefab/full/windows_x86_gui/debug/BallisticaKit.exe": "af0063f60bb84a65ca71df83d00b9507",
+ "build/prefab/full/windows_x86_gui/release/BallisticaKit.exe": "817b90e953f655e83cd8e69d1feb5526",
+ "build/prefab/full/windows_x86_server/debug/dist/BallisticaKitHeadless.exe": "c132d48c3b560ac0dcf090a3bce8ba26",
+ "build/prefab/full/windows_x86_server/release/dist/BallisticaKitHeadless.exe": "b3c44cd6282d4100b730e997baa3f28e",
+ "build/prefab/lib/linux_arm64_gui/debug/libballisticaplus.a": "73ad3303fe1a82005918fbc5dae3446c",
"build/prefab/lib/linux_arm64_gui/release/libballisticaplus.a": "fa659b5d6119acba6570c92ce4d35ae2",
- "build/prefab/lib/linux_arm64_server/debug/libballisticaplus.a": "273ffd46a618fe347a570e6bd01d54a4",
+ "build/prefab/lib/linux_arm64_server/debug/libballisticaplus.a": "73ad3303fe1a82005918fbc5dae3446c",
"build/prefab/lib/linux_arm64_server/release/libballisticaplus.a": "fa659b5d6119acba6570c92ce4d35ae2",
- "build/prefab/lib/linux_x86_64_gui/debug/libballisticaplus.a": "712db7e1fcd065a3a22faddc0def8670",
+ "build/prefab/lib/linux_x86_64_gui/debug/libballisticaplus.a": "ab8467107ed371eb542e0317a7e7395d",
"build/prefab/lib/linux_x86_64_gui/release/libballisticaplus.a": "4a4c19120f810ff5b4c7afbf11c23cf6",
- "build/prefab/lib/linux_x86_64_server/debug/libballisticaplus.a": "712db7e1fcd065a3a22faddc0def8670",
+ "build/prefab/lib/linux_x86_64_server/debug/libballisticaplus.a": "ab8467107ed371eb542e0317a7e7395d",
"build/prefab/lib/linux_x86_64_server/release/libballisticaplus.a": "4a4c19120f810ff5b4c7afbf11c23cf6",
"build/prefab/lib/mac_arm64_gui/debug/libballisticaplus.a": "bbee8acd115ca24fc14146a9fc47c676",
"build/prefab/lib/mac_arm64_gui/release/libballisticaplus.a": "8b68b56dda9a9f421823e653b752445a",
@@ -4132,14 +4132,14 @@
"build/prefab/lib/mac_x86_64_gui/release/libballisticaplus.a": "3e5c5fd0a09f55ba7b05ce1e2ec7171e",
"build/prefab/lib/mac_x86_64_server/debug/libballisticaplus.a": "1659535e95e3047fda529543e265ac97",
"build/prefab/lib/mac_x86_64_server/release/libballisticaplus.a": "3e5c5fd0a09f55ba7b05ce1e2ec7171e",
- "build/prefab/lib/windows/Debug_Win32/BallisticaKitGenericPlus.lib": "953223ec1a24ef6ba5cb7f883727af75",
- "build/prefab/lib/windows/Debug_Win32/BallisticaKitGenericPlus.pdb": "b390389ff9bb586326603b8b74f686d7",
- "build/prefab/lib/windows/Debug_Win32/BallisticaKitHeadlessPlus.lib": "c8b6d94ac084539a7370c6528582cc2b",
- "build/prefab/lib/windows/Debug_Win32/BallisticaKitHeadlessPlus.pdb": "8e857d5edefbd2bd10837cd8224eb690",
- "build/prefab/lib/windows/Release_Win32/BallisticaKitGenericPlus.lib": "23d724d38988f6d41dc2c8fc818b74b1",
- "build/prefab/lib/windows/Release_Win32/BallisticaKitGenericPlus.pdb": "b5011a047d8c23d20d1bc4405012ff1d",
- "build/prefab/lib/windows/Release_Win32/BallisticaKitHeadlessPlus.lib": "b524a2838f1d3c540afa2d63335bb036",
- "build/prefab/lib/windows/Release_Win32/BallisticaKitHeadlessPlus.pdb": "e51f7a0643a02a6962c70bf884e0b8ae",
+ "build/prefab/lib/windows/Debug_Win32/BallisticaKitGenericPlus.lib": "d4aca724c3184c6c2c82e7b11c1a352a",
+ "build/prefab/lib/windows/Debug_Win32/BallisticaKitGenericPlus.pdb": "f4fbab89e11f52329d65a9afcabc2e5e",
+ "build/prefab/lib/windows/Debug_Win32/BallisticaKitHeadlessPlus.lib": "09733e1291806ece13f108ad6fb72b68",
+ "build/prefab/lib/windows/Debug_Win32/BallisticaKitHeadlessPlus.pdb": "099e20f57cfa2c36bf7b44e389d01d9a",
+ "build/prefab/lib/windows/Release_Win32/BallisticaKitGenericPlus.lib": "aae5ce60761c700d23a480cac83a2df5",
+ "build/prefab/lib/windows/Release_Win32/BallisticaKitGenericPlus.pdb": "1fdc6ee85373af9df3a0b5eb601990af",
+ "build/prefab/lib/windows/Release_Win32/BallisticaKitHeadlessPlus.lib": "809e62f1d2ac54c239a3a13c2d2c8ee7",
+ "build/prefab/lib/windows/Release_Win32/BallisticaKitHeadlessPlus.pdb": "68b49b66a76b9ab0d1b17de2bab19c04",
"src/assets/ba_data/python/babase/_mgen/__init__.py": "f885fed7f2ed98ff2ba271f9dbe3391c",
"src/assets/ba_data/python/babase/_mgen/enums.py": "cb299985623bbcc86015cb103a424ae6",
"src/ballistica/base/mgen/pyembed/binding_base.inc": "efa61468cf098f77cc6a234461d8b86d",
@@ -4150,5 +4150,5 @@
"src/ballistica/core/mgen/python_modules_monolithic.h": "fb967ed1c7db0c77d8deb4f00a7103c5",
"src/ballistica/scene_v1/mgen/pyembed/binding_scene_v1.inc": "c25b263f2a31fb5ebe057db07d144879",
"src/ballistica/template_fs/mgen/pyembed/binding_template_fs.inc": "44a45492db057bf7f7158c3b0fa11f0f",
- "src/ballistica/ui_v1/mgen/pyembed/binding_ui_v1.inc": "f5f054050d2b2fcd3763a4833fb32269"
+ "src/ballistica/ui_v1/mgen/pyembed/binding_ui_v1.inc": "1081c8d6a7ed45cbe211b26b6a3b8af0"
}
\ No newline at end of file
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 0a42ac1b..98301646 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,4 +1,41 @@
-### 1.7.37 (build 21949, api 8, 2024-07-31)
+### 1.7.37 (build 21968, api 8, 2024-08-29)
+- Playlist customization no longer requires pro.
+- Soundtrack customization no longer requires pro.
+- Switching over to the new 'toolbar mode' UI that has been in the works for
+ several years. This includes a number of handy things such as consistent
+ buttons and widgets for league status, currencies, inventory, and the store.
+ It also adds a fixed back button on phones that should be easier to hit and a
+ dock for earned treasure chests at the bottom of the screen (will finally use
+ those treasure chest textures!). This is a substantial change so please holler
+ if you run into anything that looks broken or doesn't behave as you think it
+ should.
+- When running in 'small' UI mode (phones) the engine now uses 1300x600 as its
+ virtual resolution. This gives a wider 19.5:9 aspect ratio which lines up with
+ most modern smartphones, so people with such phones should no longer see
+ wasted space on the sides of their screen. The virtual resolution on 'medium'
+ and 'large' is now 1280x720. This gives the same 16:9 aspect ratio as the old
+ resolution (1207x680) but is a cleaner number. The 16:9 aspect ratio still
+ works well for tablets monitors, and TVs. When writing a UI, always be sure to
+ test it on 'small', 'medium', and 'large' modes to make sure it fits on screen
+ and feels similar in scale to the rest of the UI. Ideally when 'ui_v2' rolls
+ around we can make it possible to build UIs that adapt better to screen sizes
+ so things like fixed aspect ratios will no longer be necessary.
+- Split the main menu UI into two classes: `bauiv1.mainmenu.MainMenuWindow` and
+ `bauiv1.ingamemenu.InGameMenuWindow`.
+- Removed some bits of `bauiv1` which were never fully implemented and which I
+ feel were a flawed/outdated design. This includes `UILocation`,
+ `UILocationWindow`, `UIEntry`, and `UIController`. The whole purpose of these
+ was to add a higher level layer to the UI to make things like saving/restoring
+ UI states easier, but I now plan to use `WindowState` classes to accomplish
+ much of that in a more backward-compatible way. More on that below.
+- Added a new `bauiv1.Window` subclass called `bauiv1.MainWindow` which handles
+ what was previously called the 'main-menu-window' system which was a bit
+ ad-hoc and messy. MainMenuWindows have a built-in stack system so things like
+ back-button handling are more automatic and windows don't have to hard-code
+ where they came from. There are also other benefits such as better state
+ saving/restoring. When writing a MainWindow, pretty much all navigation should
+ only need too use methods: `can_change_main_window()`, `main_window_back()`,
+ and `main_window_replace()`.
- Finally got things updated so language testing works again, and made it a bit
spiffier while at it. You now simply point the game at your test language and
it will update dynamically as you make edits; no need to download any files.
@@ -11,9 +48,30 @@
doing more heavy downloading with Asset Packages coming online so its time to
upgrade to a more modern web client library than Python's basic built in
urllib stuff.
-- Pasting a single line of text followed by newlines now works. Previously it
- would complain that multiple lines of text aren't supported, but now it
- just ignores the trailing newlines.
+- Pasting a single line of text followed by newlines to the dev console now
+ works. Previously it would complain that multiple lines of text aren't
+ supported, but now it just ignores the trailing newlines.
+- Added an 'AppModes' tab to the dev console, allowing switching between any
+ AppModes defined in the current build for testing. Currently this is just
+ SceneV1AppMode and EmptyAppMode. This will become more useful in the future
+ when things like SquadsAppMode (Squads mode) or RemoteAppMode (the revamped
+ BSRemote app) happen.
+- Added a 'UI' tab to the dev console allowing debugging virtual screen bounds
+ and testing different UI scales dynamically.
+- Renamed `SceneV1AppMode` to `ClassicAppMode` and relocated it from the
+ `scene_v1` featureset to the `classic` one. This makes more logical sense
+ since `classic` is more about app operation and `scene_v1` is more about
+ gameplay, though realistically it doesn't matter since those two featuresets
+ are hopelessly entangled. Future parallels such as `squads` and `scene_v2`
+ featuresets should be more independent of eachother.
+- Removed the warning when calling `ba*.screenmessage` in a game context.
+ Hopefully most code has been ported at this point and it has done its job. As
+ a final reminder, `ba*.screenmessage()` will only show messages locally now;
+ you need to use something like `bascenev1.broadcastmessage()` to show things
+ to everyone in a game.
+- Removed `efro.util.enum_by_value()` which was a workaround for a Python bug
+ that has been fixed for a few versions now. Instaed of
+ `enum_by_value(MyEnumType, foo)` you can simply do `MyEnumType(foo)`.
### 1.7.36 (build 21944, api 8, 2024-07-26)
- Wired up Tokens, BombSquad's new purchasable currency. The first thing these
diff --git a/ballisticakit-cmake/CMakeLists.txt b/ballisticakit-cmake/CMakeLists.txt
index ce5f81f4..ef00fe33 100644
--- a/ballisticakit-cmake/CMakeLists.txt
+++ b/ballisticakit-cmake/CMakeLists.txt
@@ -209,8 +209,8 @@ set(BALLISTICA_SOURCES
${BA_SRC_ROOT}/ballistica/base/app_adapter/app_adapter_vr.h
${BA_SRC_ROOT}/ballistica/base/app_mode/app_mode.cc
${BA_SRC_ROOT}/ballistica/base/app_mode/app_mode.h
- ${BA_SRC_ROOT}/ballistica/base/app_mode/app_mode_empty.cc
- ${BA_SRC_ROOT}/ballistica/base/app_mode/app_mode_empty.h
+ ${BA_SRC_ROOT}/ballistica/base/app_mode/empty_app_mode.cc
+ ${BA_SRC_ROOT}/ballistica/base/app_mode/empty_app_mode.h
${BA_SRC_ROOT}/ballistica/base/assets/asset.cc
${BA_SRC_ROOT}/ballistica/base/assets/asset.h
${BA_SRC_ROOT}/ballistica/base/assets/assets.cc
@@ -463,6 +463,8 @@ set(BALLISTICA_SOURCES
${BA_SRC_ROOT}/ballistica/classic/python/classic_python.h
${BA_SRC_ROOT}/ballistica/classic/python/methods/python_methods_classic.cc
${BA_SRC_ROOT}/ballistica/classic/python/methods/python_methods_classic.h
+ ${BA_SRC_ROOT}/ballistica/classic/support/classic_app_mode.cc
+ ${BA_SRC_ROOT}/ballistica/classic/support/classic_app_mode.h
${BA_SRC_ROOT}/ballistica/classic/support/stress_test.cc
${BA_SRC_ROOT}/ballistica/classic/support/stress_test.h
${BA_SRC_ROOT}/ballistica/classic/support/v1_account.cc
@@ -657,8 +659,6 @@ set(BALLISTICA_SOURCES
${BA_SRC_ROOT}/ballistica/scene_v1/support/player_spec.h
${BA_SRC_ROOT}/ballistica/scene_v1/support/scene.cc
${BA_SRC_ROOT}/ballistica/scene_v1/support/scene.h
- ${BA_SRC_ROOT}/ballistica/scene_v1/support/scene_v1_app_mode.cc
- ${BA_SRC_ROOT}/ballistica/scene_v1/support/scene_v1_app_mode.h
${BA_SRC_ROOT}/ballistica/scene_v1/support/scene_v1_context.cc
${BA_SRC_ROOT}/ballistica/scene_v1/support/scene_v1_context.h
${BA_SRC_ROOT}/ballistica/scene_v1/support/scene_v1_input_device_delegate.cc
@@ -752,8 +752,6 @@ set(BALLISTICA_SOURCES
${BA_SRC_ROOT}/ballistica/ui_v1/python/methods/python_methods_ui_v1.h
${BA_SRC_ROOT}/ballistica/ui_v1/python/ui_v1_python.cc
${BA_SRC_ROOT}/ballistica/ui_v1/python/ui_v1_python.h
- ${BA_SRC_ROOT}/ballistica/ui_v1/support/root_ui.cc
- ${BA_SRC_ROOT}/ballistica/ui_v1/support/root_ui.h
${BA_SRC_ROOT}/ballistica/ui_v1/ui_v1.cc
${BA_SRC_ROOT}/ballistica/ui_v1/ui_v1.h
${BA_SRC_ROOT}/ballistica/ui_v1/widget/button_widget.cc
diff --git a/ballisticakit-windows/Generic/BallisticaKitGeneric.vcxproj b/ballisticakit-windows/Generic/BallisticaKitGeneric.vcxproj
index e04617c4..4041abab 100644
--- a/ballisticakit-windows/Generic/BallisticaKitGeneric.vcxproj
+++ b/ballisticakit-windows/Generic/BallisticaKitGeneric.vcxproj
@@ -201,8 +201,8 @@
-
-
+
+
@@ -455,6 +455,8 @@
+
+
@@ -649,8 +651,6 @@
-
-
@@ -744,8 +744,6 @@
-
-
diff --git a/ballisticakit-windows/Generic/BallisticaKitGeneric.vcxproj.filters b/ballisticakit-windows/Generic/BallisticaKitGeneric.vcxproj.filters
index 25638b88..578def22 100644
--- a/ballisticakit-windows/Generic/BallisticaKitGeneric.vcxproj.filters
+++ b/ballisticakit-windows/Generic/BallisticaKitGeneric.vcxproj.filters
@@ -37,10 +37,10 @@
ballistica\base\app_mode
-
+
ballistica\base\app_mode
-
+
ballistica\base\app_mode
@@ -799,6 +799,12 @@
ballistica\classic\python\methods
+
+ ballistica\classic\support
+
+
+ ballistica\classic\support
+
ballistica\classic\support
@@ -1381,12 +1387,6 @@
ballistica\scene_v1\support
-
- ballistica\scene_v1\support
-
-
- ballistica\scene_v1\support
-
ballistica\scene_v1\support
@@ -1666,12 +1666,6 @@
ballistica\ui_v1\python
-
- ballistica\ui_v1\support
-
-
- ballistica\ui_v1\support
-
ballistica\ui_v1
@@ -2054,7 +2048,6 @@
-
diff --git a/ballisticakit-windows/Headless/BallisticaKitHeadless.vcxproj b/ballisticakit-windows/Headless/BallisticaKitHeadless.vcxproj
index 29dbe54a..978f56bb 100644
--- a/ballisticakit-windows/Headless/BallisticaKitHeadless.vcxproj
+++ b/ballisticakit-windows/Headless/BallisticaKitHeadless.vcxproj
@@ -196,8 +196,8 @@
-
-
+
+
@@ -450,6 +450,8 @@
+
+
@@ -644,8 +646,6 @@
-
-
@@ -739,8 +739,6 @@
-
-
diff --git a/ballisticakit-windows/Headless/BallisticaKitHeadless.vcxproj.filters b/ballisticakit-windows/Headless/BallisticaKitHeadless.vcxproj.filters
index 25638b88..578def22 100644
--- a/ballisticakit-windows/Headless/BallisticaKitHeadless.vcxproj.filters
+++ b/ballisticakit-windows/Headless/BallisticaKitHeadless.vcxproj.filters
@@ -37,10 +37,10 @@
ballistica\base\app_mode
-
+
ballistica\base\app_mode
-
+
ballistica\base\app_mode
@@ -799,6 +799,12 @@
ballistica\classic\python\methods
+
+ ballistica\classic\support
+
+
+ ballistica\classic\support
+
ballistica\classic\support
@@ -1381,12 +1387,6 @@
ballistica\scene_v1\support
-
- ballistica\scene_v1\support
-
-
- ballistica\scene_v1\support
-
ballistica\scene_v1\support
@@ -1666,12 +1666,6 @@
ballistica\ui_v1\python
-
- ballistica\ui_v1\support
-
-
- ballistica\ui_v1\support
-
ballistica\ui_v1
@@ -2054,7 +2048,6 @@
-
diff --git a/config/projectconfig.json b/config/projectconfig.json
index 85dc6c6e..c0ece20e 100644
--- a/config/projectconfig.json
+++ b/config/projectconfig.json
@@ -19,7 +19,7 @@
"src/ballistica/core/platform/android/android_gl3.c"
],
"default_app_modes": [
- "bascenev1.SceneV1AppMode",
+ "baclassic.ClassicAppMode",
"babase.EmptyAppMode"
],
"efrocache_repository_url": "https://files.ballistica.net/cache/ba1",
diff --git a/config/requirements.txt b/config/requirements.txt
index 229ecbd0..95c86a2f 100644
--- a/config/requirements.txt
+++ b/config/requirements.txt
@@ -1,20 +1,20 @@
cpplint==1.6.1
-dmgbuild==1.6.1
+dmgbuild==1.6.2
filelock==3.15.4
-furo==2024.7.18
-mypy==1.11.1
-pbxproj==4.2.0
-pdoc==14.6.0
+furo==2024.8.6
+mypy==1.11.2
+pbxproj==4.2.1
+pdoc==14.6.1
pur==7.3.2
pylint==3.2.6
pylsp-mypy==0.6.8
pytest==8.3.2
python-daemon==3.0.1
python-lsp-black==2.0.0
-python-lsp-server==1.11.0
+python-lsp-server==1.12.0
requests==2.32.3
-Sphinx==7.4.7
-tomlkit==0.13.0
+Sphinx==8.0.2
+tomlkit==0.13.2
types-certifi==2021.10.8.3
types-filelock==3.2.7
types-requests==2.32.0.20240712
diff --git a/config/toolconfigsrc/dir-locals.el b/config/toolconfigsrc/dir-locals.el
index 389432c7..3e3a0693 100644
--- a/config/toolconfigsrc/dir-locals.el
+++ b/config/toolconfigsrc/dir-locals.el
@@ -12,6 +12,7 @@
(project-vc-ignores . ("docs"
"submodules"
"src/external"
+ "ballisticakit-android/BallisticaKit/src/main/cpp/src"
"src/assets/ba_data/python-site-packages"
"src/assets/pylib-android"
"src/assets/pylib-apple"
diff --git a/src/assets/.asset_manifest_public.json b/src/assets/.asset_manifest_public.json
index 34a6bcb0..a65c4972 100644
--- a/src/assets/.asset_manifest_public.json
+++ b/src/assets/.asset_manifest_public.json
@@ -68,14 +68,14 @@
"ba_data/python/baclassic/__pycache__/_achievement.cpython-312.opt-1.pyc",
"ba_data/python/baclassic/__pycache__/_ads.cpython-312.opt-1.pyc",
"ba_data/python/baclassic/__pycache__/_analytics.cpython-312.opt-1.pyc",
- "ba_data/python/baclassic/__pycache__/_appdelegate.cpython-312.opt-1.pyc",
+ "ba_data/python/baclassic/__pycache__/_appmode.cpython-312.opt-1.pyc",
+ "ba_data/python/baclassic/__pycache__/_appsubsystem.cpython-312.opt-1.pyc",
"ba_data/python/baclassic/__pycache__/_benchmark.cpython-312.opt-1.pyc",
"ba_data/python/baclassic/__pycache__/_input.cpython-312.opt-1.pyc",
"ba_data/python/baclassic/__pycache__/_music.cpython-312.opt-1.pyc",
"ba_data/python/baclassic/__pycache__/_net.cpython-312.opt-1.pyc",
"ba_data/python/baclassic/__pycache__/_servermode.cpython-312.opt-1.pyc",
"ba_data/python/baclassic/__pycache__/_store.cpython-312.opt-1.pyc",
- "ba_data/python/baclassic/__pycache__/_subsystem.cpython-312.opt-1.pyc",
"ba_data/python/baclassic/__pycache__/_tips.cpython-312.opt-1.pyc",
"ba_data/python/baclassic/__pycache__/_tournament.cpython-312.opt-1.pyc",
"ba_data/python/baclassic/__pycache__/macmusicapp.cpython-312.opt-1.pyc",
@@ -84,14 +84,14 @@
"ba_data/python/baclassic/_achievement.py",
"ba_data/python/baclassic/_ads.py",
"ba_data/python/baclassic/_analytics.py",
- "ba_data/python/baclassic/_appdelegate.py",
+ "ba_data/python/baclassic/_appmode.py",
+ "ba_data/python/baclassic/_appsubsystem.py",
"ba_data/python/baclassic/_benchmark.py",
"ba_data/python/baclassic/_input.py",
"ba_data/python/baclassic/_music.py",
"ba_data/python/baclassic/_net.py",
"ba_data/python/baclassic/_servermode.py",
"ba_data/python/baclassic/_store.py",
- "ba_data/python/baclassic/_subsystem.py",
"ba_data/python/baclassic/_tips.py",
"ba_data/python/baclassic/_tournament.py",
"ba_data/python/baclassic/macmusicapp.py",
@@ -123,18 +123,17 @@
"ba_data/python/baenv.py",
"ba_data/python/baplus/__init__.py",
"ba_data/python/baplus/__pycache__/__init__.cpython-312.opt-1.pyc",
+ "ba_data/python/baplus/__pycache__/_appsubsystem.cpython-312.opt-1.pyc",
"ba_data/python/baplus/__pycache__/_cloud.cpython-312.opt-1.pyc",
"ba_data/python/baplus/__pycache__/_hooks.cpython-312.opt-1.pyc",
- "ba_data/python/baplus/__pycache__/_subsystem.cpython-312.opt-1.pyc",
+ "ba_data/python/baplus/_appsubsystem.py",
"ba_data/python/baplus/_cloud.py",
"ba_data/python/baplus/_hooks.py",
- "ba_data/python/baplus/_subsystem.py",
"ba_data/python/bascenev1/__init__.py",
"ba_data/python/bascenev1/__pycache__/__init__.cpython-312.opt-1.pyc",
"ba_data/python/bascenev1/__pycache__/_activity.cpython-312.opt-1.pyc",
"ba_data/python/bascenev1/__pycache__/_activitytypes.cpython-312.opt-1.pyc",
"ba_data/python/bascenev1/__pycache__/_actor.cpython-312.opt-1.pyc",
- "ba_data/python/bascenev1/__pycache__/_appmode.cpython-312.opt-1.pyc",
"ba_data/python/bascenev1/__pycache__/_campaign.cpython-312.opt-1.pyc",
"ba_data/python/bascenev1/__pycache__/_collision.cpython-312.opt-1.pyc",
"ba_data/python/bascenev1/__pycache__/_coopgame.cpython-312.opt-1.pyc",
@@ -169,7 +168,6 @@
"ba_data/python/bascenev1/_activity.py",
"ba_data/python/bascenev1/_activitytypes.py",
"ba_data/python/bascenev1/_actor.py",
- "ba_data/python/bascenev1/_appmode.py",
"ba_data/python/bascenev1/_campaign.py",
"ba_data/python/bascenev1/_collision.py",
"ba_data/python/bascenev1/_coopgame.py",
@@ -349,42 +347,44 @@
"ba_data/python/bascenev1lib/tutorial.py",
"ba_data/python/batemplatefs/__init__.py",
"ba_data/python/batemplatefs/__pycache__/__init__.cpython-312.opt-1.pyc",
+ "ba_data/python/batemplatefs/__pycache__/_appsubsystem.cpython-312.opt-1.pyc",
"ba_data/python/batemplatefs/__pycache__/_hooks.cpython-312.opt-1.pyc",
- "ba_data/python/batemplatefs/__pycache__/_subsystem.cpython-312.opt-1.pyc",
+ "ba_data/python/batemplatefs/_appsubsystem.py",
"ba_data/python/batemplatefs/_hooks.py",
- "ba_data/python/batemplatefs/_subsystem.py",
"ba_data/python/bauiv1/__init__.py",
"ba_data/python/bauiv1/__pycache__/__init__.cpython-312.opt-1.pyc",
+ "ba_data/python/bauiv1/__pycache__/_appsubsystem.cpython-312.opt-1.pyc",
"ba_data/python/bauiv1/__pycache__/_hooks.cpython-312.opt-1.pyc",
"ba_data/python/bauiv1/__pycache__/_keyboard.cpython-312.opt-1.pyc",
- "ba_data/python/bauiv1/__pycache__/_subsystem.cpython-312.opt-1.pyc",
"ba_data/python/bauiv1/__pycache__/_uitypes.cpython-312.opt-1.pyc",
"ba_data/python/bauiv1/__pycache__/onscreenkeyboard.cpython-312.opt-1.pyc",
+ "ba_data/python/bauiv1/_appsubsystem.py",
"ba_data/python/bauiv1/_hooks.py",
"ba_data/python/bauiv1/_keyboard.py",
- "ba_data/python/bauiv1/_subsystem.py",
"ba_data/python/bauiv1/_uitypes.py",
"ba_data/python/bauiv1/onscreenkeyboard.py",
"ba_data/python/bauiv1lib/__init__.py",
"ba_data/python/bauiv1lib/__pycache__/__init__.cpython-312.opt-1.pyc",
"ba_data/python/bauiv1lib/__pycache__/achievements.cpython-312.opt-1.pyc",
"ba_data/python/bauiv1lib/__pycache__/appinvite.cpython-312.opt-1.pyc",
+ "ba_data/python/bauiv1lib/__pycache__/benchmarks.cpython-312.opt-1.pyc",
"ba_data/python/bauiv1lib/__pycache__/characterpicker.cpython-312.opt-1.pyc",
"ba_data/python/bauiv1lib/__pycache__/colorpicker.cpython-312.opt-1.pyc",
"ba_data/python/bauiv1lib/__pycache__/config.cpython-312.opt-1.pyc",
"ba_data/python/bauiv1lib/__pycache__/confirm.cpython-312.opt-1.pyc",
"ba_data/python/bauiv1lib/__pycache__/connectivity.cpython-312.opt-1.pyc",
"ba_data/python/bauiv1lib/__pycache__/continues.cpython-312.opt-1.pyc",
- "ba_data/python/bauiv1lib/__pycache__/creditslist.cpython-312.opt-1.pyc",
- "ba_data/python/bauiv1lib/__pycache__/debug.cpython-312.opt-1.pyc",
+ "ba_data/python/bauiv1lib/__pycache__/credits.cpython-312.opt-1.pyc",
"ba_data/python/bauiv1lib/__pycache__/discord.cpython-312.opt-1.pyc",
"ba_data/python/bauiv1lib/__pycache__/feedback.cpython-312.opt-1.pyc",
"ba_data/python/bauiv1lib/__pycache__/fileselector.cpython-312.opt-1.pyc",
"ba_data/python/bauiv1lib/__pycache__/getremote.cpython-312.opt-1.pyc",
- "ba_data/python/bauiv1lib/__pycache__/gettickets.cpython-312.opt-1.pyc",
"ba_data/python/bauiv1lib/__pycache__/gettokens.cpython-312.opt-1.pyc",
"ba_data/python/bauiv1lib/__pycache__/helpui.cpython-312.opt-1.pyc",
"ba_data/python/bauiv1lib/__pycache__/iconpicker.cpython-312.opt-1.pyc",
+ "ba_data/python/bauiv1lib/__pycache__/inbox.cpython-312.opt-1.pyc",
+ "ba_data/python/bauiv1lib/__pycache__/ingamemenu.cpython-312.opt-1.pyc",
+ "ba_data/python/bauiv1lib/__pycache__/inventory.cpython-312.opt-1.pyc",
"ba_data/python/bauiv1lib/__pycache__/kiosk.cpython-312.opt-1.pyc",
"ba_data/python/bauiv1lib/__pycache__/mainmenu.cpython-312.opt-1.pyc",
"ba_data/python/bauiv1lib/__pycache__/party.cpython-312.opt-1.pyc",
@@ -422,6 +422,7 @@
"ba_data/python/bauiv1lib/account/viewer.py",
"ba_data/python/bauiv1lib/achievements.py",
"ba_data/python/bauiv1lib/appinvite.py",
+ "ba_data/python/bauiv1lib/benchmarks.py",
"ba_data/python/bauiv1lib/characterpicker.py",
"ba_data/python/bauiv1lib/colorpicker.py",
"ba_data/python/bauiv1lib/config.py",
@@ -438,8 +439,7 @@
"ba_data/python/bauiv1lib/coop/gamebutton.py",
"ba_data/python/bauiv1lib/coop/level.py",
"ba_data/python/bauiv1lib/coop/tournamentbutton.py",
- "ba_data/python/bauiv1lib/creditslist.py",
- "ba_data/python/bauiv1lib/debug.py",
+ "ba_data/python/bauiv1lib/credits.py",
"ba_data/python/bauiv1lib/discord.py",
"ba_data/python/bauiv1lib/feedback.py",
"ba_data/python/bauiv1lib/fileselector.py",
@@ -456,10 +456,12 @@
"ba_data/python/bauiv1lib/gather/privatetab.py",
"ba_data/python/bauiv1lib/gather/publictab.py",
"ba_data/python/bauiv1lib/getremote.py",
- "ba_data/python/bauiv1lib/gettickets.py",
"ba_data/python/bauiv1lib/gettokens.py",
"ba_data/python/bauiv1lib/helpui.py",
"ba_data/python/bauiv1lib/iconpicker.py",
+ "ba_data/python/bauiv1lib/inbox.py",
+ "ba_data/python/bauiv1lib/ingamemenu.py",
+ "ba_data/python/bauiv1lib/inventory.py",
"ba_data/python/bauiv1lib/keyboard/__init__.py",
"ba_data/python/bauiv1lib/keyboard/__pycache__/__init__.cpython-312.opt-1.pyc",
"ba_data/python/bauiv1lib/keyboard/__pycache__/englishkeyboard.cpython-312.opt-1.pyc",
diff --git a/src/assets/Makefile b/src/assets/Makefile
index ec7bbaf9..1e474a92 100644
--- a/src/assets/Makefile
+++ b/src/assets/Makefile
@@ -198,28 +198,27 @@ SCRIPT_TARGETS_PY_PUBLIC = \
$(BUILD_DIR)/ba_data/python/baclassic/_achievement.py \
$(BUILD_DIR)/ba_data/python/baclassic/_ads.py \
$(BUILD_DIR)/ba_data/python/baclassic/_analytics.py \
- $(BUILD_DIR)/ba_data/python/baclassic/_appdelegate.py \
+ $(BUILD_DIR)/ba_data/python/baclassic/_appmode.py \
+ $(BUILD_DIR)/ba_data/python/baclassic/_appsubsystem.py \
$(BUILD_DIR)/ba_data/python/baclassic/_benchmark.py \
$(BUILD_DIR)/ba_data/python/baclassic/_input.py \
$(BUILD_DIR)/ba_data/python/baclassic/_music.py \
$(BUILD_DIR)/ba_data/python/baclassic/_net.py \
$(BUILD_DIR)/ba_data/python/baclassic/_servermode.py \
$(BUILD_DIR)/ba_data/python/baclassic/_store.py \
- $(BUILD_DIR)/ba_data/python/baclassic/_subsystem.py \
$(BUILD_DIR)/ba_data/python/baclassic/_tips.py \
$(BUILD_DIR)/ba_data/python/baclassic/_tournament.py \
$(BUILD_DIR)/ba_data/python/baclassic/macmusicapp.py \
$(BUILD_DIR)/ba_data/python/baclassic/osmusic.py \
$(BUILD_DIR)/ba_data/python/baenv.py \
$(BUILD_DIR)/ba_data/python/baplus/__init__.py \
+ $(BUILD_DIR)/ba_data/python/baplus/_appsubsystem.py \
$(BUILD_DIR)/ba_data/python/baplus/_cloud.py \
$(BUILD_DIR)/ba_data/python/baplus/_hooks.py \
- $(BUILD_DIR)/ba_data/python/baplus/_subsystem.py \
$(BUILD_DIR)/ba_data/python/bascenev1/__init__.py \
$(BUILD_DIR)/ba_data/python/bascenev1/_activity.py \
$(BUILD_DIR)/ba_data/python/bascenev1/_activitytypes.py \
$(BUILD_DIR)/ba_data/python/bascenev1/_actor.py \
- $(BUILD_DIR)/ba_data/python/bascenev1/_appmode.py \
$(BUILD_DIR)/ba_data/python/bascenev1/_campaign.py \
$(BUILD_DIR)/ba_data/python/bascenev1/_collision.py \
$(BUILD_DIR)/ba_data/python/bascenev1/_coopgame.py \
@@ -325,12 +324,12 @@ SCRIPT_TARGETS_PY_PUBLIC = \
$(BUILD_DIR)/ba_data/python/bascenev1lib/session/__init__.py \
$(BUILD_DIR)/ba_data/python/bascenev1lib/tutorial.py \
$(BUILD_DIR)/ba_data/python/batemplatefs/__init__.py \
+ $(BUILD_DIR)/ba_data/python/batemplatefs/_appsubsystem.py \
$(BUILD_DIR)/ba_data/python/batemplatefs/_hooks.py \
- $(BUILD_DIR)/ba_data/python/batemplatefs/_subsystem.py \
$(BUILD_DIR)/ba_data/python/bauiv1/__init__.py \
+ $(BUILD_DIR)/ba_data/python/bauiv1/_appsubsystem.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/_uitypes.py \
$(BUILD_DIR)/ba_data/python/bauiv1/onscreenkeyboard.py \
$(BUILD_DIR)/ba_data/python/bauiv1lib/__init__.py \
@@ -342,6 +341,7 @@ SCRIPT_TARGETS_PY_PUBLIC = \
$(BUILD_DIR)/ba_data/python/bauiv1lib/account/viewer.py \
$(BUILD_DIR)/ba_data/python/bauiv1lib/achievements.py \
$(BUILD_DIR)/ba_data/python/bauiv1lib/appinvite.py \
+ $(BUILD_DIR)/ba_data/python/bauiv1lib/benchmarks.py \
$(BUILD_DIR)/ba_data/python/bauiv1lib/characterpicker.py \
$(BUILD_DIR)/ba_data/python/bauiv1lib/colorpicker.py \
$(BUILD_DIR)/ba_data/python/bauiv1lib/config.py \
@@ -353,8 +353,7 @@ SCRIPT_TARGETS_PY_PUBLIC = \
$(BUILD_DIR)/ba_data/python/bauiv1lib/coop/gamebutton.py \
$(BUILD_DIR)/ba_data/python/bauiv1lib/coop/level.py \
$(BUILD_DIR)/ba_data/python/bauiv1lib/coop/tournamentbutton.py \
- $(BUILD_DIR)/ba_data/python/bauiv1lib/creditslist.py \
- $(BUILD_DIR)/ba_data/python/bauiv1lib/debug.py \
+ $(BUILD_DIR)/ba_data/python/bauiv1lib/credits.py \
$(BUILD_DIR)/ba_data/python/bauiv1lib/discord.py \
$(BUILD_DIR)/ba_data/python/bauiv1lib/feedback.py \
$(BUILD_DIR)/ba_data/python/bauiv1lib/fileselector.py \
@@ -365,10 +364,12 @@ SCRIPT_TARGETS_PY_PUBLIC = \
$(BUILD_DIR)/ba_data/python/bauiv1lib/gather/privatetab.py \
$(BUILD_DIR)/ba_data/python/bauiv1lib/gather/publictab.py \
$(BUILD_DIR)/ba_data/python/bauiv1lib/getremote.py \
- $(BUILD_DIR)/ba_data/python/bauiv1lib/gettickets.py \
$(BUILD_DIR)/ba_data/python/bauiv1lib/gettokens.py \
$(BUILD_DIR)/ba_data/python/bauiv1lib/helpui.py \
$(BUILD_DIR)/ba_data/python/bauiv1lib/iconpicker.py \
+ $(BUILD_DIR)/ba_data/python/bauiv1lib/inbox.py \
+ $(BUILD_DIR)/ba_data/python/bauiv1lib/ingamemenu.py \
+ $(BUILD_DIR)/ba_data/python/bauiv1lib/inventory.py \
$(BUILD_DIR)/ba_data/python/bauiv1lib/keyboard/__init__.py \
$(BUILD_DIR)/ba_data/python/bauiv1lib/keyboard/englishkeyboard.py \
$(BUILD_DIR)/ba_data/python/bauiv1lib/kiosk.py \
@@ -476,28 +477,27 @@ SCRIPT_TARGETS_PYC_PUBLIC = \
$(BUILD_DIR)/ba_data/python/baclassic/__pycache__/_achievement.cpython-312.opt-1.pyc \
$(BUILD_DIR)/ba_data/python/baclassic/__pycache__/_ads.cpython-312.opt-1.pyc \
$(BUILD_DIR)/ba_data/python/baclassic/__pycache__/_analytics.cpython-312.opt-1.pyc \
- $(BUILD_DIR)/ba_data/python/baclassic/__pycache__/_appdelegate.cpython-312.opt-1.pyc \
+ $(BUILD_DIR)/ba_data/python/baclassic/__pycache__/_appmode.cpython-312.opt-1.pyc \
+ $(BUILD_DIR)/ba_data/python/baclassic/__pycache__/_appsubsystem.cpython-312.opt-1.pyc \
$(BUILD_DIR)/ba_data/python/baclassic/__pycache__/_benchmark.cpython-312.opt-1.pyc \
$(BUILD_DIR)/ba_data/python/baclassic/__pycache__/_input.cpython-312.opt-1.pyc \
$(BUILD_DIR)/ba_data/python/baclassic/__pycache__/_music.cpython-312.opt-1.pyc \
$(BUILD_DIR)/ba_data/python/baclassic/__pycache__/_net.cpython-312.opt-1.pyc \
$(BUILD_DIR)/ba_data/python/baclassic/__pycache__/_servermode.cpython-312.opt-1.pyc \
$(BUILD_DIR)/ba_data/python/baclassic/__pycache__/_store.cpython-312.opt-1.pyc \
- $(BUILD_DIR)/ba_data/python/baclassic/__pycache__/_subsystem.cpython-312.opt-1.pyc \
$(BUILD_DIR)/ba_data/python/baclassic/__pycache__/_tips.cpython-312.opt-1.pyc \
$(BUILD_DIR)/ba_data/python/baclassic/__pycache__/_tournament.cpython-312.opt-1.pyc \
$(BUILD_DIR)/ba_data/python/baclassic/__pycache__/macmusicapp.cpython-312.opt-1.pyc \
$(BUILD_DIR)/ba_data/python/baclassic/__pycache__/osmusic.cpython-312.opt-1.pyc \
$(BUILD_DIR)/ba_data/python/__pycache__/baenv.cpython-312.opt-1.pyc \
$(BUILD_DIR)/ba_data/python/baplus/__pycache__/__init__.cpython-312.opt-1.pyc \
+ $(BUILD_DIR)/ba_data/python/baplus/__pycache__/_appsubsystem.cpython-312.opt-1.pyc \
$(BUILD_DIR)/ba_data/python/baplus/__pycache__/_cloud.cpython-312.opt-1.pyc \
$(BUILD_DIR)/ba_data/python/baplus/__pycache__/_hooks.cpython-312.opt-1.pyc \
- $(BUILD_DIR)/ba_data/python/baplus/__pycache__/_subsystem.cpython-312.opt-1.pyc \
$(BUILD_DIR)/ba_data/python/bascenev1/__pycache__/__init__.cpython-312.opt-1.pyc \
$(BUILD_DIR)/ba_data/python/bascenev1/__pycache__/_activity.cpython-312.opt-1.pyc \
$(BUILD_DIR)/ba_data/python/bascenev1/__pycache__/_activitytypes.cpython-312.opt-1.pyc \
$(BUILD_DIR)/ba_data/python/bascenev1/__pycache__/_actor.cpython-312.opt-1.pyc \
- $(BUILD_DIR)/ba_data/python/bascenev1/__pycache__/_appmode.cpython-312.opt-1.pyc \
$(BUILD_DIR)/ba_data/python/bascenev1/__pycache__/_campaign.cpython-312.opt-1.pyc \
$(BUILD_DIR)/ba_data/python/bascenev1/__pycache__/_collision.cpython-312.opt-1.pyc \
$(BUILD_DIR)/ba_data/python/bascenev1/__pycache__/_coopgame.cpython-312.opt-1.pyc \
@@ -603,12 +603,12 @@ SCRIPT_TARGETS_PYC_PUBLIC = \
$(BUILD_DIR)/ba_data/python/bascenev1lib/session/__pycache__/__init__.cpython-312.opt-1.pyc \
$(BUILD_DIR)/ba_data/python/bascenev1lib/__pycache__/tutorial.cpython-312.opt-1.pyc \
$(BUILD_DIR)/ba_data/python/batemplatefs/__pycache__/__init__.cpython-312.opt-1.pyc \
+ $(BUILD_DIR)/ba_data/python/batemplatefs/__pycache__/_appsubsystem.cpython-312.opt-1.pyc \
$(BUILD_DIR)/ba_data/python/batemplatefs/__pycache__/_hooks.cpython-312.opt-1.pyc \
- $(BUILD_DIR)/ba_data/python/batemplatefs/__pycache__/_subsystem.cpython-312.opt-1.pyc \
$(BUILD_DIR)/ba_data/python/bauiv1/__pycache__/__init__.cpython-312.opt-1.pyc \
+ $(BUILD_DIR)/ba_data/python/bauiv1/__pycache__/_appsubsystem.cpython-312.opt-1.pyc \
$(BUILD_DIR)/ba_data/python/bauiv1/__pycache__/_hooks.cpython-312.opt-1.pyc \
$(BUILD_DIR)/ba_data/python/bauiv1/__pycache__/_keyboard.cpython-312.opt-1.pyc \
- $(BUILD_DIR)/ba_data/python/bauiv1/__pycache__/_subsystem.cpython-312.opt-1.pyc \
$(BUILD_DIR)/ba_data/python/bauiv1/__pycache__/_uitypes.cpython-312.opt-1.pyc \
$(BUILD_DIR)/ba_data/python/bauiv1/__pycache__/onscreenkeyboard.cpython-312.opt-1.pyc \
$(BUILD_DIR)/ba_data/python/bauiv1lib/__pycache__/__init__.cpython-312.opt-1.pyc \
@@ -620,6 +620,7 @@ SCRIPT_TARGETS_PYC_PUBLIC = \
$(BUILD_DIR)/ba_data/python/bauiv1lib/account/__pycache__/viewer.cpython-312.opt-1.pyc \
$(BUILD_DIR)/ba_data/python/bauiv1lib/__pycache__/achievements.cpython-312.opt-1.pyc \
$(BUILD_DIR)/ba_data/python/bauiv1lib/__pycache__/appinvite.cpython-312.opt-1.pyc \
+ $(BUILD_DIR)/ba_data/python/bauiv1lib/__pycache__/benchmarks.cpython-312.opt-1.pyc \
$(BUILD_DIR)/ba_data/python/bauiv1lib/__pycache__/characterpicker.cpython-312.opt-1.pyc \
$(BUILD_DIR)/ba_data/python/bauiv1lib/__pycache__/colorpicker.cpython-312.opt-1.pyc \
$(BUILD_DIR)/ba_data/python/bauiv1lib/__pycache__/config.cpython-312.opt-1.pyc \
@@ -631,8 +632,7 @@ SCRIPT_TARGETS_PYC_PUBLIC = \
$(BUILD_DIR)/ba_data/python/bauiv1lib/coop/__pycache__/gamebutton.cpython-312.opt-1.pyc \
$(BUILD_DIR)/ba_data/python/bauiv1lib/coop/__pycache__/level.cpython-312.opt-1.pyc \
$(BUILD_DIR)/ba_data/python/bauiv1lib/coop/__pycache__/tournamentbutton.cpython-312.opt-1.pyc \
- $(BUILD_DIR)/ba_data/python/bauiv1lib/__pycache__/creditslist.cpython-312.opt-1.pyc \
- $(BUILD_DIR)/ba_data/python/bauiv1lib/__pycache__/debug.cpython-312.opt-1.pyc \
+ $(BUILD_DIR)/ba_data/python/bauiv1lib/__pycache__/credits.cpython-312.opt-1.pyc \
$(BUILD_DIR)/ba_data/python/bauiv1lib/__pycache__/discord.cpython-312.opt-1.pyc \
$(BUILD_DIR)/ba_data/python/bauiv1lib/__pycache__/feedback.cpython-312.opt-1.pyc \
$(BUILD_DIR)/ba_data/python/bauiv1lib/__pycache__/fileselector.cpython-312.opt-1.pyc \
@@ -643,10 +643,12 @@ SCRIPT_TARGETS_PYC_PUBLIC = \
$(BUILD_DIR)/ba_data/python/bauiv1lib/gather/__pycache__/privatetab.cpython-312.opt-1.pyc \
$(BUILD_DIR)/ba_data/python/bauiv1lib/gather/__pycache__/publictab.cpython-312.opt-1.pyc \
$(BUILD_DIR)/ba_data/python/bauiv1lib/__pycache__/getremote.cpython-312.opt-1.pyc \
- $(BUILD_DIR)/ba_data/python/bauiv1lib/__pycache__/gettickets.cpython-312.opt-1.pyc \
$(BUILD_DIR)/ba_data/python/bauiv1lib/__pycache__/gettokens.cpython-312.opt-1.pyc \
$(BUILD_DIR)/ba_data/python/bauiv1lib/__pycache__/helpui.cpython-312.opt-1.pyc \
$(BUILD_DIR)/ba_data/python/bauiv1lib/__pycache__/iconpicker.cpython-312.opt-1.pyc \
+ $(BUILD_DIR)/ba_data/python/bauiv1lib/__pycache__/inbox.cpython-312.opt-1.pyc \
+ $(BUILD_DIR)/ba_data/python/bauiv1lib/__pycache__/ingamemenu.cpython-312.opt-1.pyc \
+ $(BUILD_DIR)/ba_data/python/bauiv1lib/__pycache__/inventory.cpython-312.opt-1.pyc \
$(BUILD_DIR)/ba_data/python/bauiv1lib/keyboard/__pycache__/__init__.cpython-312.opt-1.pyc \
$(BUILD_DIR)/ba_data/python/bauiv1lib/keyboard/__pycache__/englishkeyboard.cpython-312.opt-1.pyc \
$(BUILD_DIR)/ba_data/python/bauiv1lib/__pycache__/kiosk.cpython-312.opt-1.pyc \
diff --git a/src/assets/ba_data/python/babase/__init__.py b/src/assets/ba_data/python/babase/__init__.py
index 61b75aed..50a08100 100644
--- a/src/assets/ba_data/python/babase/__init__.py
+++ b/src/assets/ba_data/python/babase/__init__.py
@@ -21,6 +21,7 @@ from efro.util import set_canonical_module_names
import _babase
from _babase import (
add_clean_frame_callback,
+ allows_ticket_sales,
android_get_external_files_dir,
appname,
appnameupper,
@@ -86,6 +87,7 @@ from _babase import (
overlay_web_browser_is_supported,
overlay_web_browser_open_url,
print_load_info,
+ push_back_press,
pushcall,
quit,
reload_media,
@@ -188,6 +190,7 @@ __all__ = [
'AccountV2Subsystem',
'ActivityNotFoundError',
'ActorNotFoundError',
+ 'allows_ticket_sales',
'add_clean_frame_callback',
'android_get_external_files_dir',
'app',
@@ -305,6 +308,7 @@ __all__ = [
'print_error',
'print_exception',
'print_load_info',
+ 'push_back_press',
'pushcall',
'quit',
'QuitType',
diff --git a/src/assets/ba_data/python/babase/_app.py b/src/assets/ba_data/python/babase/_app.py
index 58ba2fce..f852fbd6 100644
--- a/src/assets/ba_data/python/babase/_app.py
+++ b/src/assets/ba_data/python/babase/_app.py
@@ -36,9 +36,9 @@ if TYPE_CHECKING:
# __FEATURESET_APP_SUBSYSTEM_IMPORTS_BEGIN__
# This section generated by batools.appmodule; do not edit.
- from baclassic import ClassicSubsystem
- from baplus import PlusSubsystem
- from bauiv1 import UIV1Subsystem
+ from baclassic import ClassicAppSubsystem
+ from baplus import PlusAppSubsystem
+ from bauiv1 import UIV1AppSubsystem
# __FEATURESET_APP_SUBSYSTEM_IMPORTS_END__
@@ -137,11 +137,11 @@ class App:
# Ask our default app modes to handle it.
# (generated from 'default_app_modes' in projectconfig).
- import bascenev1
+ import baclassic
import babase
for appmode in [
- bascenev1.SceneV1AppMode,
+ baclassic.ClassicAppMode,
babase.EmptyAppMode,
]:
if appmode.can_handle_intent(intent):
@@ -151,6 +151,24 @@ class App:
# __DEFAULT_APP_MODE_SELECTION_END__
+ @override
+ def testable_app_modes(self) -> list[type[AppMode]]:
+ # pylint: disable=cyclic-import
+
+ # __DEFAULT_TESTABLE_APP_MODES_BEGIN__
+ # This section generated by batools.appmodule; do not edit.
+
+ # Return all our default_app_modes as testable.
+ # (generated from 'default_app_modes' in projectconfig).
+ import baclassic
+ import babase
+
+ return [
+ baclassic.ClassicAppMode,
+ babase.EmptyAppMode,
+ ]
+ # __DEFAULT_TESTABLE_APP_MODES_END__
+
def __init__(self) -> None:
"""(internal)
@@ -208,8 +226,9 @@ class App:
self._config: babase.AppConfig | None = None
self._pending_intent: AppIntent | None = None
self._intent: AppIntent | None = None
- self._mode: AppMode | None = None
self._mode_selector: babase.AppModeSelector | None = None
+ self._mode_instances: dict[type[AppMode], AppMode] = {}
+ self._mode: AppMode | None = None
self._shutdown_task: asyncio.Task[None] | None = None
self._shutdown_tasks: list[Coroutine[None, None, None]] = [
self._wait_for_shutdown_suppressions(),
@@ -289,7 +308,7 @@ class App:
"""
assert _babase.in_logic_thread()
- # Hold a strong reference to the task until it is done.
+ # We hold a strong reference to the task until it is done.
# Otherwise it is possible for it to be garbage collected and
# disappear midway if the caller does not hold on to the
# returned task, which seems like a great way to introduce
@@ -387,19 +406,19 @@ class App:
# This section generated by batools.appmodule; do not edit.
@property
- def classic(self) -> ClassicSubsystem | None:
+ def classic(self) -> ClassicAppSubsystem | None:
"""Our classic subsystem (if available)."""
return self._get_subsystem_property(
'classic', self._create_classic_subsystem
) # type: ignore
@staticmethod
- def _create_classic_subsystem() -> ClassicSubsystem | None:
+ def _create_classic_subsystem() -> ClassicAppSubsystem | None:
# pylint: disable=cyclic-import
try:
- from baclassic import ClassicSubsystem
+ from baclassic import ClassicAppSubsystem
- return ClassicSubsystem()
+ return ClassicAppSubsystem()
except ImportError:
return None
except Exception:
@@ -407,19 +426,19 @@ class App:
return None
@property
- def plus(self) -> PlusSubsystem | None:
+ def plus(self) -> PlusAppSubsystem | None:
"""Our plus subsystem (if available)."""
return self._get_subsystem_property(
'plus', self._create_plus_subsystem
) # type: ignore
@staticmethod
- def _create_plus_subsystem() -> PlusSubsystem | None:
+ def _create_plus_subsystem() -> PlusAppSubsystem | None:
# pylint: disable=cyclic-import
try:
- from baplus import PlusSubsystem
+ from baplus import PlusAppSubsystem
- return PlusSubsystem()
+ return PlusAppSubsystem()
except ImportError:
return None
except Exception:
@@ -427,19 +446,19 @@ class App:
return None
@property
- def ui_v1(self) -> UIV1Subsystem:
+ def ui_v1(self) -> UIV1AppSubsystem:
"""Our ui_v1 subsystem (always available)."""
return self._get_subsystem_property(
'ui_v1', self._create_ui_v1_subsystem
) # type: ignore
@staticmethod
- def _create_ui_v1_subsystem() -> UIV1Subsystem:
+ def _create_ui_v1_subsystem() -> UIV1AppSubsystem:
# pylint: disable=cyclic-import
- from bauiv1 import UIV1Subsystem
+ from bauiv1 import UIV1AppSubsystem
- return UIV1Subsystem()
+ return UIV1AppSubsystem()
# __FEATURESET_APP_SUBSYSTEM_PROPERTIES_END__
@@ -611,17 +630,32 @@ class App:
self._update_state()
def _set_intent(self, intent: AppIntent) -> None:
+ from babase._appmode import AppMode
+
# This should be happening in a bg thread.
assert not _babase.in_logic_thread()
try:
# Ask the selector what app-mode to use for this intent.
if self.mode_selector is None:
raise RuntimeError('No AppModeSelector set.')
- modetype = self.mode_selector.app_mode_for_intent(intent)
- # NOTE: Since intents are somewhat high level things, should
- # we do some universal thing like a screenmessage saying
- # 'The app cannot handle that request' on failure?
+ modetype: type[AppMode] | None
+
+ # Special case - for testing we may force a specific
+ # app-mode to handle this intent instead of going through our
+ # usual selector.
+ forced_mode_type = getattr(intent, '_force_app_mode_handler', None)
+ if isinstance(forced_mode_type, type) and issubclass(
+ forced_mode_type, AppMode
+ ):
+ modetype = forced_mode_type
+ else:
+ modetype = self.mode_selector.app_mode_for_intent(intent)
+
+ # NOTE: Since intents are somewhat high level things,
+ # perhaps we should do some universal thing like a
+ # screenmessage saying 'The app cannot handle the request'
+ # on failure.
if modetype is None:
raise RuntimeError(
@@ -640,7 +674,9 @@ class App:
# Ok; seems legit. Now instantiate the mode if necessary and
# kick back to the logic thread to apply.
- mode = modetype()
+ mode = self._mode_instances.get(modetype)
+ if mode is None:
+ self._mode_instances[modetype] = mode = modetype()
_babase.pushcall(
partial(self._apply_intent, intent, mode),
from_other_thread=True,
@@ -661,7 +697,7 @@ class App:
return
# If the app-mode for this intent is different than the active
- # one, switch.
+ # one, switch modes.
if type(mode) is not type(self._mode):
if self._mode is None:
is_initial_mode = True
@@ -673,6 +709,18 @@ class App:
logging.exception(
'Error deactivating app-mode %s.', self._mode
)
+
+ # Reset all subsystems. We assume subsystems won't be added
+ # at this point so we can use the list directly.
+ assert self._subsystem_registration_ended
+ for subsystem in self._subsystems:
+ try:
+ subsystem.reset()
+ except Exception:
+ logging.exception(
+ 'Error in reset for subsystem %s.', subsystem
+ )
+
self._mode = mode
try:
mode.on_activate()
@@ -750,8 +798,8 @@ class App:
self.meta.start_scan(scan_complete_cb=self._on_meta_scan_complete)
# Inform all app subsystems in the same order they were inited.
- # Operate on a copy here because subsystems can still be added
- # at this point.
+ # Operate on a copy of the list here because subsystems can
+ # still be added at this point.
for subsystem in self._subsystems.copy():
try:
subsystem.on_app_loading()
diff --git a/src/assets/ba_data/python/babase/_appmode.py b/src/assets/ba_data/python/babase/_appmode.py
index cfe01f7b..26a8b6f8 100644
--- a/src/assets/ba_data/python/babase/_appmode.py
+++ b/src/assets/ba_data/python/babase/_appmode.py
@@ -31,7 +31,7 @@ class AppMode:
AppExperience associated with the AppMode must be supported by
the current app and runtime environment.
"""
- # FIXME: check AppExperience.
+ # TODO: check AppExperience.
return cls._supports_intent(intent)
@classmethod
diff --git a/src/assets/ba_data/python/babase/_appmodeselector.py b/src/assets/ba_data/python/babase/_appmodeselector.py
index 9dff2cbc..dcd60602 100644
--- a/src/assets/ba_data/python/babase/_appmodeselector.py
+++ b/src/assets/ba_data/python/babase/_appmodeselector.py
@@ -11,7 +11,7 @@ if TYPE_CHECKING:
class AppModeSelector:
- """Defines which AppModes to use to handle given AppIntents.
+ """Defines which AppModes are available or used to handle given AppIntents.
Category: **App Classes**
@@ -29,4 +29,16 @@ class AppModeSelector:
This may be called in a background thread, so avoid any calls
limited to logic thread use/etc.
"""
- raise NotImplementedError('app_mode_for_intent() should be overridden.')
+ raise NotImplementedError()
+
+ def testable_app_modes(self) -> list[type[AppMode]]:
+ """Return a list of modes to appear in the dev-console app-mode ui.
+
+ The user can switch between these app modes for testing. App-modes
+ will be passed an AppIntentDefault when selected by the user.
+
+ Note that in normal circumstances AppModes should never be
+ selected explicitly by the user but rather determined implicitly
+ based on AppIntents.
+ """
+ raise NotImplementedError()
diff --git a/src/assets/ba_data/python/babase/_appsubsystem.py b/src/assets/ba_data/python/babase/_appsubsystem.py
index 78ba01d5..b1636af2 100644
--- a/src/assets/ba_data/python/babase/_appsubsystem.py
+++ b/src/assets/ba_data/python/babase/_appsubsystem.py
@@ -53,3 +53,10 @@ class AppSubsystem:
def do_apply_app_config(self) -> None:
"""Called when the app config should be applied."""
+
+ def reset(self) -> None:
+ """Reset the subsystem to a default state.
+
+ This is called when switching app modes, but may be called
+ at other times too.
+ """
diff --git a/src/assets/ba_data/python/babase/_devconsole.py b/src/assets/ba_data/python/babase/_devconsole.py
index d4e70996..e4380f4e 100644
--- a/src/assets/ba_data/python/babase/_devconsole.py
+++ b/src/assets/ba_data/python/babase/_devconsole.py
@@ -13,6 +13,8 @@ import _babase
if TYPE_CHECKING:
from typing import Callable, Any, Literal
+ from babase import AppMode
+
class DevConsoleTab:
"""Defines behavior for a tab in the dev-console."""
@@ -101,6 +103,102 @@ class DevConsoleTabPython(DevConsoleTab):
self.python_terminal()
+class DevConsoleTabAppModes(DevConsoleTab):
+ """Tab to switch app modes."""
+
+ @override
+ def refresh(self) -> None:
+ from functools import partial
+
+ modes = _babase.app.mode_selector.testable_app_modes()
+ self.text(
+ 'Available AppModes:',
+ scale=0.8,
+ pos=(15, 55),
+ h_anchor='left',
+ h_align='left',
+ v_align='none',
+ )
+ for i, mode in enumerate(modes):
+ self.button(
+ f'{mode.__module__}.{mode.__qualname__}',
+ pos=(10 + i * 260, 10),
+ size=(250, 40),
+ h_anchor='left',
+ label_scale=0.6,
+ call=partial(self._set_app_mode, mode),
+ )
+
+ @staticmethod
+ def _set_app_mode(mode: type[AppMode]) -> None:
+ from babase._appintent import AppIntentDefault
+
+ intent = AppIntentDefault()
+
+ # Use private functionality to force a specific app-mode to
+ # handle this intent. Note that this should never be done
+ # outside of this explicit testing case. It is the app's job to
+ # determine which app-mode should be used to handle a given
+ # intent.
+ setattr(intent, '_force_app_mode_handler', mode)
+
+ _babase.app.set_intent(intent)
+
+
+class DevConsoleTabUI(DevConsoleTab):
+ """Tab to debug/test UI stuff."""
+
+ @override
+ def refresh(self) -> None:
+
+ self.text(
+ 'UI Testing: Make sure all static UI fits in the'
+ ' virtual screen at all UI scales (not counting things'
+ ' that follow screen edges).',
+ scale=0.8,
+ pos=(15, 55),
+ h_anchor='left',
+ h_align='left',
+ v_align='none',
+ )
+
+ ui_overlay = _babase.get_draw_ui_bounds()
+ self.button(
+ 'Hide Virtual Screen' if ui_overlay else 'Show Virtual Screen',
+ pos=(10, 10),
+ size=(200, 30),
+ h_anchor='left',
+ label_scale=0.6,
+ call=self.toggle_ui_overlay,
+ )
+ x = 320
+ self.text(
+ 'UI Scale:',
+ pos=(x - 10, 15),
+ h_anchor='left',
+ h_align='right',
+ v_align='none',
+ scale=0.6,
+ )
+
+ bwidth = 100
+ for sz in ('small', 'medium', 'large'):
+ self.button(
+ sz,
+ pos=(x, 10),
+ size=(bwidth, 30),
+ h_anchor='left',
+ label_scale=0.6,
+ call=lambda: _babase.screenmessage('UNDER CONSTRUCTION.'),
+ )
+ x += bwidth + 10
+
+ def toggle_ui_overlay(self) -> None:
+ """Toggle UI overlay drawing."""
+ _babase.set_draw_ui_bounds(not _babase.get_draw_ui_bounds())
+ self.request_refresh()
+
+
class DevConsoleTabTest(DevConsoleTab):
"""Test dev-console tab."""
@@ -157,7 +255,9 @@ class DevConsoleSubsystem:
# All tabs in the dev-console. Add your own stuff here via
# plugins or whatnot.
self.tabs: list[DevConsoleTabEntry] = [
- DevConsoleTabEntry('Python', DevConsoleTabPython)
+ DevConsoleTabEntry('Python', DevConsoleTabPython),
+ DevConsoleTabEntry('AppModes', DevConsoleTabAppModes),
+ DevConsoleTabEntry('UI', DevConsoleTabUI),
]
if os.environ.get('BA_DEV_CONSOLE_TEST_TAB', '0') == '1':
self.tabs.append(DevConsoleTabEntry('Test', DevConsoleTabTest))
diff --git a/src/assets/ba_data/python/babase/_emptyappmode.py b/src/assets/ba_data/python/babase/_emptyappmode.py
index 6745d21c..801aa513 100644
--- a/src/assets/ba_data/python/babase/_emptyappmode.py
+++ b/src/assets/ba_data/python/babase/_emptyappmode.py
@@ -16,7 +16,7 @@ if TYPE_CHECKING:
class EmptyAppMode(AppMode):
- """An empty app mode that can be used as a fallback/etc."""
+ """An AppMode that does not do much at all."""
@override
@classmethod
@@ -32,17 +32,17 @@ class EmptyAppMode(AppMode):
@override
def handle_intent(self, intent: AppIntent) -> None:
if isinstance(intent, AppIntentExec):
- _babase.empty_app_mode_handle_intent_exec(intent.code)
+ _babase.empty_app_mode_handle_app_intent_exec(intent.code)
return
assert isinstance(intent, AppIntentDefault)
- _babase.empty_app_mode_handle_intent_default()
+ _babase.empty_app_mode_handle_app_intent_default()
@override
def on_activate(self) -> None:
# Let the native layer do its thing.
- _babase.on_empty_app_mode_activate()
+ _babase.empty_app_mode_activate()
@override
def on_deactivate(self) -> None:
# Let the native layer do its thing.
- _babase.on_empty_app_mode_deactivate()
+ _babase.empty_app_mode_deactivate()
diff --git a/src/assets/ba_data/python/baclassic/__init__.py b/src/assets/ba_data/python/baclassic/__init__.py
index 51f50da5..8d9bbb81 100644
--- a/src/assets/ba_data/python/baclassic/__init__.py
+++ b/src/assets/ba_data/python/baclassic/__init__.py
@@ -1,15 +1,15 @@
# Released under the MIT License. See LICENSE for details.
#
-"""Classic ballistica components.
+"""Components for the classic BombSquad experience.
-This package is used as a 'dumping ground' for functionality that is
-necessary to keep legacy parts of the app working, but which may no
-longer be the best way to do things going forward.
+This package is used as a dumping ground for functionality that is
+necessary to keep classic BombSquad working, but which may no longer be
+the best way to do things going forward.
New code should try to avoid using code from here when possible.
Functionality in this package should be exposed through the
-ClassicSubsystem. This allows type-checked code to go through the
+ClassicAppSubsystem. This allows type-checked code to go through the
babase.app.classic singleton which forces it to explicitly handle the
possibility of babase.app.classic being None. When code instead imports
classic submodules directly, it is much harder to make it cleanly handle
@@ -19,20 +19,28 @@ classic not being present.
# ba_meta require api 8
# Note: Code relying on classic should import things from here *only*
-# for type-checking and use the versions in app.classic at runtime; that
-# way type-checking will cleanly cover the classic-not-present case
-# (app.classic being None).
+# for type-checking and use the versions in ba*.app.classic at runtime;
+# that way type-checking will cleanly cover the classic-not-present case
+# (ba*.app.classic being None).
import logging
-from baclassic._subsystem import ClassicSubsystem
+from efro.util import set_canonical_module_names
+
+from baclassic._appmode import ClassicAppMode
+from baclassic._appsubsystem import ClassicAppSubsystem
from baclassic._achievement import Achievement, AchievementSubsystem
__all__ = [
- 'ClassicSubsystem',
+ 'ClassicAppMode',
+ 'ClassicAppSubsystem',
'Achievement',
'AchievementSubsystem',
]
+# We want stuff here to show up as packagename.Foo instead of
+# packagename._submodule.Foo.
+set_canonical_module_names(globals())
+
# Sanity check: we want to keep ballistica's dependencies and
# bootstrapping order clearly defined; let's check a few particular
# modules to make sure they never directly or indirectly import us
diff --git a/src/assets/ba_data/python/baclassic/_appdelegate.py b/src/assets/ba_data/python/baclassic/_appdelegate.py
deleted file mode 100644
index 1dd1a6b0..00000000
--- a/src/assets/ba_data/python/baclassic/_appdelegate.py
+++ /dev/null
@@ -1,46 +0,0 @@
-# Released under the MIT License. See LICENSE for details.
-#
-"""Defines AppDelegate class for handling high level app functionality."""
-from __future__ import annotations
-
-from typing import TYPE_CHECKING
-
-import babase
-
-if TYPE_CHECKING:
- from typing import Callable
- import bascenev1
-
-
-class AppDelegate:
- """Defines handlers for high level app functionality.
-
- Category: App Classes
- """
-
- def create_default_game_settings_ui(
- self,
- gameclass: type[bascenev1.GameActivity],
- sessiontype: type[bascenev1.Session],
- settings: dict | None,
- completion_call: Callable[[dict | None], None],
- ) -> None:
- """Launch a UI to configure the given game config.
-
- It should manipulate the contents of config and call completion_call
- when done.
- """
- # Replace the main window once we come up successfully.
- from bauiv1lib.playlist.editgame import PlaylistEditGameWindow
-
- assert babase.app.classic is not None
- babase.app.ui_v1.clear_main_menu_window(transition='out_left')
- babase.app.ui_v1.set_main_menu_window(
- PlaylistEditGameWindow(
- gameclass,
- sessiontype,
- settings,
- completion_call=completion_call,
- ).get_root_widget(),
- from_window=False, # Disable check since we don't know.
- )
diff --git a/src/assets/ba_data/python/baclassic/_appmode.py b/src/assets/ba_data/python/baclassic/_appmode.py
new file mode 100644
index 00000000..bcb3b3c0
--- /dev/null
+++ b/src/assets/ba_data/python/baclassic/_appmode.py
@@ -0,0 +1,310 @@
+# Released under the MIT License. See LICENSE for details.
+#
+"""Contains ClassicAppMode."""
+from __future__ import annotations
+
+import logging
+from functools import partial
+from typing import TYPE_CHECKING, override
+
+from bacommon.app import AppExperience
+from babase import (
+ app,
+ AppMode,
+ AppIntentExec,
+ AppIntentDefault,
+ invoke_main_menu,
+ screenmessage,
+)
+
+import _baclassic
+
+if TYPE_CHECKING:
+ from babase import AppIntent
+ from bauiv1 import UIV1AppSubsystem, MainWindow
+
+
+class ClassicAppMode(AppMode):
+ """AppMode for the classic BombSquad experience."""
+
+ @override
+ @classmethod
+ def get_app_experience(cls) -> AppExperience:
+ return AppExperience.MELEE
+
+ @override
+ @classmethod
+ def _supports_intent(cls, intent: AppIntent) -> bool:
+ # We support default and exec intents currently.
+ return isinstance(intent, AppIntentExec | AppIntentDefault)
+
+ @override
+ def handle_intent(self, intent: AppIntent) -> None:
+ if isinstance(intent, AppIntentExec):
+ _baclassic.classic_app_mode_handle_app_intent_exec(intent.code)
+ return
+ assert isinstance(intent, AppIntentDefault)
+ _baclassic.classic_app_mode_handle_app_intent_default()
+
+ @override
+ def on_activate(self) -> None:
+ # Let the native layer do its thing.
+ _baclassic.classic_app_mode_activate()
+
+ # Wire up the root ui to do what we want.
+ ui = app.ui_v1
+ ui.root_ui_calls[ui.RootUIElement.ACCOUNT_BUTTON] = (
+ self._root_ui_account_press
+ )
+ ui.root_ui_calls[ui.RootUIElement.MENU_BUTTON] = (
+ self._root_ui_menu_press
+ )
+ ui.root_ui_calls[ui.RootUIElement.SQUAD_BUTTON] = (
+ self._root_ui_squad_press
+ )
+ ui.root_ui_calls[ui.RootUIElement.SETTINGS_BUTTON] = (
+ self._root_ui_settings_press
+ )
+ ui.root_ui_calls[ui.RootUIElement.STORE_BUTTON] = (
+ self._root_ui_store_press
+ )
+ ui.root_ui_calls[ui.RootUIElement.INVENTORY_BUTTON] = (
+ self._root_ui_inventory_press
+ )
+ ui.root_ui_calls[ui.RootUIElement.GET_TOKENS_BUTTON] = (
+ self._root_ui_get_tokens_press
+ )
+ ui.root_ui_calls[ui.RootUIElement.INBOX_BUTTON] = (
+ self._root_ui_inbox_press
+ )
+ ui.root_ui_calls[ui.RootUIElement.TICKETS_METER] = (
+ self._root_ui_tickets_meter_press
+ )
+ ui.root_ui_calls[ui.RootUIElement.TOKENS_METER] = (
+ self._root_ui_tokens_meter_press
+ )
+ ui.root_ui_calls[ui.RootUIElement.TROPHY_METER] = (
+ self._root_ui_trophy_meter_press
+ )
+ ui.root_ui_calls[ui.RootUIElement.LEVEL_METER] = (
+ self._root_ui_level_meter_press
+ )
+ ui.root_ui_calls[ui.RootUIElement.ACHIEVEMENTS_BUTTON] = (
+ self._root_ui_achievements_press
+ )
+ ui.root_ui_calls[ui.RootUIElement.CHEST_SLOT_1] = partial(
+ self._root_ui_chest_slot_pressed, 1
+ )
+ ui.root_ui_calls[ui.RootUIElement.CHEST_SLOT_2] = partial(
+ self._root_ui_chest_slot_pressed, 2
+ )
+ ui.root_ui_calls[ui.RootUIElement.CHEST_SLOT_3] = partial(
+ self._root_ui_chest_slot_pressed, 3
+ )
+ ui.root_ui_calls[ui.RootUIElement.CHEST_SLOT_4] = partial(
+ self._root_ui_chest_slot_pressed, 4
+ )
+
+ @override
+ def on_deactivate(self) -> None:
+ # Let the native layer do its thing.
+ _baclassic.classic_app_mode_deactivate()
+
+ @override
+ def on_app_active_changed(self) -> None:
+ # If we've gone inactive, bring up the main menu, which has the
+ # side effect of pausing the action (when possible).
+ if not app.active:
+ invoke_main_menu()
+
+ def _jump_to_main_window(self, window: MainWindow) -> None:
+ """Jump to a window with the main menu as its parent."""
+ from bauiv1lib.mainmenu import MainMenuWindow
+
+ ui = app.ui_v1
+
+ old_window = ui.get_main_window()
+ if isinstance(old_window, MainMenuWindow):
+ old_window.main_window_replace(window)
+ else:
+ # Blow away the window stack.
+ ui.clear_main_window()
+
+ ui.set_main_window(
+ window,
+ from_window=False, # Disable from-check.
+ back_state=MainMenuWindow.do_get_main_window_state(),
+ )
+
+ def _root_ui_menu_press(self) -> None:
+ from babase import push_back_press
+
+ ui = app.ui_v1
+
+ # If *any* main-window is up, kill it.
+ old_window = ui.get_main_window()
+ if old_window is not None:
+ ui.clear_main_window()
+ return
+
+ push_back_press()
+
+ def _root_ui_account_press(self) -> None:
+ import bauiv1
+ from bauiv1lib.account.settings import AccountSettingsWindow
+
+ ui = app.ui_v1
+
+ # If the window is already showing, back out of it.
+ current_main_window = ui.get_main_window()
+ if isinstance(current_main_window, AccountSettingsWindow):
+ current_main_window.main_window_back()
+ return
+
+ self._jump_to_main_window(
+ AccountSettingsWindow(
+ origin_widget=bauiv1.get_special_widget('account_button')
+ )
+ )
+
+ def _root_ui_squad_press(self) -> None:
+ import bauiv1
+
+ btn = bauiv1.get_special_widget('squad_button')
+ center = btn.get_screen_space_center()
+ if bauiv1.app.classic is not None:
+ bauiv1.app.classic.party_icon_activate(center)
+ else:
+ logging.warning('party_icon_activate: no classic.')
+
+ def _root_ui_settings_press(self) -> None:
+ import bauiv1
+ from bauiv1lib.settings.allsettings import AllSettingsWindow
+
+ ui = app.ui_v1
+
+ # If the window is already showing, back out of it.
+ current_main_window = ui.get_main_window()
+ if isinstance(current_main_window, AllSettingsWindow):
+ current_main_window.main_window_back()
+ return
+
+ self._jump_to_main_window(
+ AllSettingsWindow(
+ origin_widget=bauiv1.get_special_widget('settings_button')
+ )
+ )
+
+ def _root_ui_achievements_press(self) -> None:
+ import bauiv1
+ from bauiv1lib.achievements import AchievementsWindow
+
+ btn = bauiv1.get_special_widget('achievements_button')
+
+ AchievementsWindow(position=btn.get_screen_space_center())
+
+ def _root_ui_inbox_press(self) -> None:
+ import bauiv1
+ from bauiv1lib.inbox import InboxWindow
+
+ btn = bauiv1.get_special_widget('inbox_button')
+
+ InboxWindow(position=btn.get_screen_space_center())
+
+ def _root_ui_store_press(self) -> None:
+ import bauiv1
+ from bauiv1lib.store.browser import StoreBrowserWindow
+
+ ui = app.ui_v1
+
+ # If the window is already showing, back out of it.
+ current_main_window = ui.get_main_window()
+ if isinstance(current_main_window, StoreBrowserWindow):
+ current_main_window.main_window_back()
+ return
+
+ self._jump_to_main_window(
+ StoreBrowserWindow(
+ origin_widget=bauiv1.get_special_widget('store_button')
+ )
+ )
+
+ def _root_ui_tickets_meter_press(self) -> None:
+ import bauiv1
+ from bauiv1lib.resourcetypeinfo import ResourceTypeInfoWindow
+
+ ResourceTypeInfoWindow(
+ 'tickets', origin_widget=bauiv1.get_special_widget('tickets_meter')
+ )
+
+ def _root_ui_tokens_meter_press(self) -> None:
+ import bauiv1
+ from bauiv1lib.resourcetypeinfo import ResourceTypeInfoWindow
+
+ ResourceTypeInfoWindow(
+ 'tokens', origin_widget=bauiv1.get_special_widget('tokens_meter')
+ )
+
+ def _root_ui_trophy_meter_press(self) -> None:
+ import bauiv1
+ from bauiv1lib.account import show_sign_in_prompt
+ from bauiv1lib.league.rankwindow import LeagueRankWindow
+
+ ui = app.ui_v1
+
+ # If the window is already showing, back out of it.
+ current_main_window = ui.get_main_window()
+ if isinstance(current_main_window, LeagueRankWindow):
+ current_main_window.main_window_back()
+ return
+
+ plus = bauiv1.app.plus
+ assert plus is not None
+
+ if plus.get_v1_account_state() != 'signed_in':
+ show_sign_in_prompt()
+ return
+
+ self._jump_to_main_window(
+ LeagueRankWindow(
+ origin_widget=bauiv1.get_special_widget('trophy_meter')
+ )
+ )
+
+ def _root_ui_level_meter_press(self) -> None:
+ import bauiv1
+ from bauiv1lib.resourcetypeinfo import ResourceTypeInfoWindow
+
+ ResourceTypeInfoWindow(
+ 'xp', origin_widget=bauiv1.get_special_widget('level_meter')
+ )
+
+ def _root_ui_inventory_press(self) -> None:
+ import bauiv1
+ from bauiv1lib.inventory import InventoryWindow
+
+ ui = app.ui_v1
+
+ # If the window is already showing, back out of it.
+ current_main_window = ui.get_main_window()
+ if isinstance(current_main_window, InventoryWindow):
+ current_main_window.main_window_back()
+ return
+
+ self._jump_to_main_window(
+ InventoryWindow(
+ origin_widget=bauiv1.get_special_widget('inventory_button')
+ )
+ )
+
+ def _root_ui_get_tokens_press(self) -> None:
+ import bauiv1
+ from bauiv1lib.gettokens import GetTokensWindow
+
+ GetTokensWindow(
+ origin_widget=bauiv1.get_special_widget('get_tokens_button')
+ )
+
+ def _root_ui_chest_slot_pressed(self, index: int) -> None:
+ print(f'CHEST {index} PRESSED')
+ screenmessage('UNDER CONSTRUCTION.')
diff --git a/src/assets/ba_data/python/baclassic/_subsystem.py b/src/assets/ba_data/python/baclassic/_appsubsystem.py
similarity index 80%
rename from src/assets/ba_data/python/baclassic/_subsystem.py
rename to src/assets/ba_data/python/baclassic/_appsubsystem.py
index 16611ec9..a0fcb72c 100644
--- a/src/assets/ba_data/python/baclassic/_subsystem.py
+++ b/src/assets/ba_data/python/baclassic/_appsubsystem.py
@@ -29,12 +29,11 @@ if TYPE_CHECKING:
from bascenev1lib.actor import spazappearance
from bauiv1lib.party import PartyWindow
- from baclassic._appdelegate import AppDelegate
from baclassic._servermode import ServerController
from baclassic._net import MasterServerCallback
-class ClassicSubsystem(babase.AppSubsystem):
+class ClassicAppSubsystem(babase.AppSubsystem):
"""Subsystem for classic functionality in the app.
The single shared instance of this app can be accessed at
@@ -111,8 +110,12 @@ class ClassicSubsystem(babase.AppSubsystem):
self.did_menu_intro = False # FIXME: Move to mainmenu class.
self.main_menu_window_refresh_check_count = 0 # FIXME: Mv to mainmenu.
self.invite_confirm_windows: list[Any] = [] # FIXME: Don't use Any.
- self.delegate: AppDelegate | None = None
self.party_window: weakref.ref[PartyWindow] | None = None
+ self.main_menu_resume_callbacks: list = []
+ # Switch our overall game selection UI flow between Play and
+ # Private-party playlist selection modes; should do this in
+ # a more elegant way once we revamp high level UI stuff a bit.
+ self.selecting_private_party_playlist: bool = False
# Store.
self.store_layout: dict[str, list[dict[str, Any]]] | None = None
@@ -120,6 +123,16 @@ class ClassicSubsystem(babase.AppSubsystem):
self.pro_sale_start_time: int | None = None
self.pro_sale_start_val: int | None = None
+ def add_main_menu_close_callback(self, call: Callable[[], Any]) -> None:
+ """(internal)"""
+
+ # If there's no main window up, just call immediately.
+ if not babase.app.ui_v1.has_main_window():
+ with babase.ContextRef.empty():
+ call()
+ else:
+ self.main_menu_resume_callbacks.append(call)
+
@property
def platform(self) -> str:
"""Name of the current platform.
@@ -154,8 +167,6 @@ class ClassicSubsystem(babase.AppSubsystem):
from bascenev1lib.actor import spazappearance
from bascenev1lib import maps as stdmaps
- from baclassic._appdelegate import AppDelegate
-
plus = babase.app.plus
assert plus is not None
@@ -164,8 +175,6 @@ class ClassicSubsystem(babase.AppSubsystem):
self.music.on_app_loading()
- self.delegate = AppDelegate()
-
# Non-test, non-debug builds should generally be blessed; warn if not.
# (so I don't accidentally release a build that can't play tourneys)
if not env.debug and not env.test and not plus.is_blessed():
@@ -374,7 +383,7 @@ class ClassicSubsystem(babase.AppSubsystem):
assert plus is not None
if reset_ui:
- babase.app.ui_v1.clear_main_menu_window()
+ babase.app.ui_v1.clear_main_window()
if isinstance(bascenev1.get_foreground_host_session(), MainMenuSession):
# It may be possible we're on the main menu but the screen is faded
@@ -684,13 +693,13 @@ class ClassicSubsystem(babase.AppSubsystem):
babase.Call(ServerDialogWindow, sddata),
)
- def ticket_icon_press(self) -> None:
- """(internal)"""
- from bauiv1lib.resourcetypeinfo import ResourceTypeInfoWindow
+ # def root_ui_ticket_icon_press(self) -> None:
+ # """(internal)"""
+ # from bauiv1lib.resourcetypeinfo import ResourceTypeInfoWindow
- ResourceTypeInfoWindow(
- origin_widget=bauiv1.get_special_widget('tickets_info_button')
- )
+ # ResourceTypeInfoWindow(
+ # origin_widget=bauiv1.get_special_widget('tickets_meter')
+ # )
def show_url_window(self, address: str) -> None:
"""(internal)"""
@@ -781,6 +790,9 @@ class ClassicSubsystem(babase.AppSubsystem):
assert app.env.gui
+ # Play explicit swish sound so it occurs due to keypresses/etc.
+ # This means we have to disable it for any button or else we get
+ # double.
bauiv1.getsound('swish').play()
# If it exists, dismiss it; otherwise make a new one.
@@ -794,18 +806,132 @@ class ClassicSubsystem(babase.AppSubsystem):
def device_menu_press(self, device_id: int | None) -> None:
"""(internal)"""
- from bauiv1lib.mainmenu import MainMenuWindow
+ from bauiv1lib.ingamemenu import InGameMenuWindow
from bauiv1 import set_ui_input_device
assert babase.app is not None
- in_main_menu = babase.app.ui_v1.has_main_menu_window()
+ in_main_menu = babase.app.ui_v1.has_main_window()
if not in_main_menu:
set_ui_input_device(device_id)
+ # Hack(ish). We play swish sound here so it happens for
+ # device presses, but this means we need to disable default
+ # swish sounds for any menu buttons or we'll get double.
if babase.app.env.gui:
bauiv1.getsound('swish').play()
- babase.app.ui_v1.set_main_menu_window(
- MainMenuWindow().get_root_widget(),
+ babase.app.ui_v1.set_main_window(
+ InGameMenuWindow(),
from_window=False, # Disable check here.
+ is_top_level=True,
)
+
+ def invoke_main_menu_ui(self) -> None:
+ """Bring up main menu ui."""
+ # Bring up the last place we were, or start at the main menu otherwise.
+ app = bauiv1.app
+ env = app.env
+ with bascenev1.ContextRef.empty():
+ from bauiv1lib import specialoffer
+
+ assert app.classic is not None
+ if app.env.headless:
+ # UI stuff fails now in headless builds; avoid it.
+ pass
+ else:
+ # main_menu_location = (
+ # bascenev1.app.ui_v1.get_main_menu_location()
+ # )
+
+ # When coming back from a kiosk-mode game, jump to
+ # the kiosk start screen.
+ if env.demo or env.arcade:
+ # pylint: disable=cyclic-import
+ from bauiv1lib.kiosk import KioskWindow
+
+ app.ui_v1.set_main_window(
+ KioskWindow(), from_window=False # Disable check here.
+ )
+ # ..or in normal cases go back to the main menu
+ else:
+ # if main_menu_location == 'Gather':
+ # # pylint: disable=cyclic-import
+ # from bauiv1lib.gather import GatherWindow
+
+ # app.ui_v1.set_main_window(
+ # GatherWindow(transition=None),
+ # from_window=False, # Disable check here.
+ # )
+ # elif main_menu_location == 'Watch':
+ # # pylint: disable=cyclic-import
+ # from bauiv1lib.watch import WatchWindow
+
+ # app.ui_v1.set_main_window(
+ # WatchWindow(transition=None),
+ # from_window=False, # Disable check here.
+ # )
+ # elif main_menu_location == 'Team Game Select':
+ # # pylint: disable=cyclic-import
+ # from bauiv1lib.playlist.browser import (
+ # PlaylistBrowserWindow,
+ # )
+
+ # app.ui_v1.set_main_window(
+ # PlaylistBrowserWindow(
+ # sessiontype=bascenev1.DualTeamSession,
+ # transition=None,
+ # ),
+ # from_window=False, # Disable check here.
+ # )
+ # elif main_menu_location == 'Free-for-All Game Select':
+ # # pylint: disable=cyclic-import
+ # from bauiv1lib.playlist.browser import (
+ # PlaylistBrowserWindow,
+ # )
+
+ # app.ui_v1.set_main_window(
+ # PlaylistBrowserWindow(
+ # sessiontype=bascenev1.FreeForAllSession,
+ # transition=None,
+ # ),
+ # from_window=False, # Disable check here.
+ # )
+ # elif main_menu_location == 'Coop Select':
+ # # pylint: disable=cyclic-import
+ # from bauiv1lib.coop.browser import CoopBrowserWindow
+
+ # app.ui_v1.set_main_window(
+ # CoopBrowserWindow(transition=None),
+ # from_window=False, # Disable check here.
+ # )
+ # elif main_menu_location == 'Benchmarks & Stress Tests':
+ # # pylint: disable=cyclic-import
+ # from bauiv1lib.debug import DebugWindow
+
+ # app.ui_v1.set_main_window(
+ # DebugWindow(transition=None),
+ # from_window=False, # Disable check here.
+ # )
+ # else:
+ # pylint: disable=cyclic-import
+ from bauiv1lib.mainmenu import MainMenuWindow
+
+ app.ui_v1.set_main_window(
+ MainMenuWindow(transition=None),
+ from_window=False, # Disable check.
+ is_top_level=True,
+ )
+
+ # attempt to show any pending offers immediately.
+ # If that doesn't work, try again in a few seconds
+ # (we may not have heard back from the server)
+ # ..if that doesn't work they'll just have to wait
+ # until the next opportunity.
+ if not specialoffer.show_offer():
+
+ def try_again() -> None:
+ if not specialoffer.show_offer():
+ # Try one last time..
+ bauiv1.apptimer(2.0, specialoffer.show_offer)
+
+ bauiv1.apptimer(2.0, try_again)
diff --git a/src/assets/ba_data/python/baclassic/_music.py b/src/assets/ba_data/python/baclassic/_music.py
index 8f439d79..58246379 100644
--- a/src/assets/ba_data/python/baclassic/_music.py
+++ b/src/assets/ba_data/python/baclassic/_music.py
@@ -16,6 +16,8 @@ from bascenev1 import MusicType
if TYPE_CHECKING:
from typing import Callable, Any
+ import bauiv1
+
class MusicPlayMode(Enum):
"""Influences behavior when playing music.
@@ -389,7 +391,7 @@ class MusicPlayer:
callback: Callable[[Any], None],
current_entry: Any,
selection_target_name: str,
- ) -> Any:
+ ) -> bauiv1.MainWindow:
"""Summons a UI to select a new soundtrack entry."""
return self.on_select_entry(
callback, current_entry, selection_target_name
@@ -432,11 +434,12 @@ class MusicPlayer:
callback: Callable[[Any], None],
current_entry: Any,
selection_target_name: str,
- ) -> Any:
+ ) -> bauiv1.MainWindow:
"""Present a GUI to select an entry.
The callback should be called with a valid entry or None to
signify that the default soundtrack should be used.."""
+ raise NotImplementedError()
# Subclasses should override the following:
diff --git a/src/assets/ba_data/python/baclassic/macmusicapp.py b/src/assets/ba_data/python/baclassic/macmusicapp.py
index 6b4b9b49..48a41e5a 100644
--- a/src/assets/ba_data/python/baclassic/macmusicapp.py
+++ b/src/assets/ba_data/python/baclassic/macmusicapp.py
@@ -15,6 +15,8 @@ from baclassic._music import MusicPlayer
if TYPE_CHECKING:
from typing import Callable, Any
+ import bauiv1
+
class MacMusicAppMusicPlayer(MusicPlayer):
"""A music-player that utilizes the macOS Music.app for playback.
@@ -33,7 +35,7 @@ class MacMusicAppMusicPlayer(MusicPlayer):
callback: Callable[[Any], None],
current_entry: Any,
selection_target_name: str,
- ) -> Any:
+ ) -> bauiv1.MainWindow:
# pylint: disable=cyclic-import
from bauiv1lib.soundtrack import entrytypeselect as etsel
diff --git a/src/assets/ba_data/python/baclassic/osmusic.py b/src/assets/ba_data/python/baclassic/osmusic.py
index 919df491..8a79bf73 100644
--- a/src/assets/ba_data/python/baclassic/osmusic.py
+++ b/src/assets/ba_data/python/baclassic/osmusic.py
@@ -16,6 +16,8 @@ from baclassic._music import MusicPlayer
if TYPE_CHECKING:
from typing import Callable, Any
+ import bauiv1
+
class OSMusicPlayer(MusicPlayer):
"""Music player that talks to internal C++ layer for functionality.
@@ -39,7 +41,7 @@ class OSMusicPlayer(MusicPlayer):
callback: Callable[[Any], None],
current_entry: Any,
selection_target_name: str,
- ) -> Any:
+ ) -> bauiv1.MainWindow:
# pylint: disable=cyclic-import
from bauiv1lib.soundtrack.entrytypeselect import (
SoundtrackEntryTypeSelectWindow,
diff --git a/src/assets/ba_data/python/baenv.py b/src/assets/ba_data/python/baenv.py
index a0875246..c8940edb 100644
--- a/src/assets/ba_data/python/baenv.py
+++ b/src/assets/ba_data/python/baenv.py
@@ -52,7 +52,7 @@ if TYPE_CHECKING:
# Build number and version of the ballistica binary we expect to be
# using.
-TARGET_BALLISTICA_BUILD = 21949
+TARGET_BALLISTICA_BUILD = 21968
TARGET_BALLISTICA_VERSION = '1.7.37'
diff --git a/src/assets/ba_data/python/baplus/__init__.py b/src/assets/ba_data/python/baplus/__init__.py
index 4040f7b4..9b2dba24 100644
--- a/src/assets/ba_data/python/baplus/__init__.py
+++ b/src/assets/ba_data/python/baplus/__init__.py
@@ -5,23 +5,23 @@
This code concerns sensitive things like accounts and master-server
communication so the native C++ parts of it remain closed. Native
precompiled static libraries of this portion are provided for those who
-want to compile the rest of the engine, and a fully open-source engine
-can also be built by removing this 'plus' feature-set.
+want to compile the rest of the engine, or a fully open-source app
+can also be built by removing this feature-set.
"""
from __future__ import annotations
-# Note: there's not much here.
-# All comms with this feature-set should go through app.plus.
+# Note: there's not much here. Most interaction with this feature-set
+# should go through ba*.app.plus.
import logging
from baplus._cloud import CloudSubsystem
-from baplus._subsystem import PlusSubsystem
+from baplus._appsubsystem import PlusAppSubsystem
__all__ = [
'CloudSubsystem',
- 'PlusSubsystem',
+ 'PlusAppSubsystem',
]
# Sanity check: we want to keep ballistica's dependencies and
diff --git a/src/assets/ba_data/python/baplus/_subsystem.py b/src/assets/ba_data/python/baplus/_appsubsystem.py
similarity index 99%
rename from src/assets/ba_data/python/baplus/_subsystem.py
rename to src/assets/ba_data/python/baplus/_appsubsystem.py
index c5ef5ff6..6733699c 100644
--- a/src/assets/ba_data/python/baplus/_subsystem.py
+++ b/src/assets/ba_data/python/baplus/_appsubsystem.py
@@ -17,7 +17,7 @@ if TYPE_CHECKING:
from baplus._cloud import CloudSubsystem
-class PlusSubsystem(AppSubsystem):
+class PlusAppSubsystem(AppSubsystem):
"""Subsystem for plus functionality in the app.
The single shared instance of this app can be accessed at
diff --git a/src/assets/ba_data/python/bascenev1/__init__.py b/src/assets/ba_data/python/bascenev1/__init__.py
index b3513480..43c6550a 100644
--- a/src/assets/ba_data/python/bascenev1/__init__.py
+++ b/src/assets/ba_data/python/bascenev1/__init__.py
@@ -1,6 +1,6 @@
# Released under the MIT License. See LICENSE for details.
#
-"""Ballistica scene api version 1. Basically all gameplay related code."""
+"""Gameplay-centric api for classic BombSquad."""
# ba_meta require api 8
@@ -18,6 +18,7 @@ import logging
from efro.util import set_canonical_module_names
from babase import (
+ add_clean_frame_callback,
app,
AppIntent,
AppIntentDefault,
@@ -149,7 +150,6 @@ from _bascenev1 import (
from bascenev1._activity import Activity
from bascenev1._activitytypes import JoinActivity, ScoreScreenActivity
from bascenev1._actor import Actor
-from bascenev1._appmode import SceneV1AppMode
from bascenev1._campaign import init_campaigns, Campaign
from bascenev1._collision import Collision, getcollision
from bascenev1._coopgame import CoopGameActivity
@@ -249,6 +249,7 @@ __all__ = [
'Actor',
'animate',
'animate_array',
+ 'add_clean_frame_callback',
'app',
'AppIntent',
'AppIntentDefault',
@@ -410,7 +411,6 @@ __all__ = [
'seek_replay',
'safecolor',
'screenmessage',
- 'SceneV1AppMode',
'ScoreConfig',
'ScoreScreenActivity',
'ScoreType',
diff --git a/src/assets/ba_data/python/bascenev1/_appmode.py b/src/assets/ba_data/python/bascenev1/_appmode.py
deleted file mode 100644
index 72a77aee..00000000
--- a/src/assets/ba_data/python/bascenev1/_appmode.py
+++ /dev/null
@@ -1,60 +0,0 @@
-# Released under the MIT License. See LICENSE for details.
-#
-"""Provides AppMode functionality."""
-from __future__ import annotations
-
-from typing import TYPE_CHECKING, override
-
-from bacommon.app import AppExperience
-from babase import (
- app,
- AppMode,
- AppIntentExec,
- AppIntentDefault,
- invoke_main_menu,
-)
-
-import _bascenev1
-
-if TYPE_CHECKING:
- from babase import AppIntent
-
-
-class SceneV1AppMode(AppMode):
- """Our app-mode."""
-
- @override
- @classmethod
- def get_app_experience(cls) -> AppExperience:
- return AppExperience.MELEE
-
- @override
- @classmethod
- def _supports_intent(cls, intent: AppIntent) -> bool:
- # We support default and exec intents currently.
- return isinstance(intent, AppIntentExec | AppIntentDefault)
-
- @override
- def handle_intent(self, intent: AppIntent) -> None:
- if isinstance(intent, AppIntentExec):
- _bascenev1.handle_app_intent_exec(intent.code)
- return
- assert isinstance(intent, AppIntentDefault)
- _bascenev1.handle_app_intent_default()
-
- @override
- def on_activate(self) -> None:
- # Let the native layer do its thing.
- _bascenev1.on_app_mode_activate()
-
- @override
- def on_deactivate(self) -> None:
- # Let the native layer do its thing.
- _bascenev1.on_app_mode_deactivate()
-
- @override
- def on_app_active_changed(self) -> None:
- # If we've gone inactive, bring up the main menu, which has the
- # side effect of pausing the action (when possible).
- if not app.active:
- invoke_main_menu()
diff --git a/src/assets/ba_data/python/bascenev1/_gameactivity.py b/src/assets/ba_data/python/bascenev1/_gameactivity.py
index 15177334..de6ecc05 100644
--- a/src/assets/ba_data/python/bascenev1/_gameactivity.py
+++ b/src/assets/ba_data/python/bascenev1/_gameactivity.py
@@ -87,11 +87,19 @@ class GameActivity(Activity[PlayerT, TeamT]):
bascenev1.GameActivity.get_supported_maps() they can just rely on
the default implementation here which calls those methods.
"""
+ # pylint: disable=cyclic-import
+ from bauiv1lib.playlist.editgame import PlaylistEditGameWindow
+
assert babase.app.classic is not None
- delegate = babase.app.classic.delegate
- assert delegate is not None
- delegate.create_default_game_settings_ui(
- cls, sessiontype, settings, completion_call
+ babase.app.ui_v1.clear_main_window()
+ babase.app.ui_v1.set_main_window(
+ PlaylistEditGameWindow(
+ cls,
+ sessiontype,
+ settings,
+ completion_call=completion_call,
+ ),
+ from_window=False, # Disable check since we don't know.
)
@classmethod
@@ -465,7 +473,7 @@ class GameActivity(Activity[PlayerT, TeamT]):
# Only attempt this if we're not currently paused
# and there appears to be no UI.
assert babase.app.classic is not None
- hmmw = babase.app.ui_v1.has_main_menu_window()
+ hmmw = babase.app.ui_v1.has_main_window()
if not gnode.paused and not hmmw:
self._is_waiting_for_continue = True
with babase.ContextRef.empty():
diff --git a/src/assets/ba_data/python/bascenev1lib/activity/coopscore.py b/src/assets/ba_data/python/bascenev1lib/activity/coopscore.py
index 4b92b69c..646b60a1 100644
--- a/src/assets/ba_data/python/bascenev1lib/activity/coopscore.py
+++ b/src/assets/ba_data/python/bascenev1lib/activity/coopscore.py
@@ -336,12 +336,13 @@ class CoopScoreScreen(bs.Activity[bs.Player, bs.Team]):
def request_ui(self) -> None:
"""Set up a callback to show our UI at the next opportune time."""
- assert bui.app.classic is not None
+ classic = bui.app.classic
+ assert classic is not None
# We don't want to just show our UI in case the user already has the
# main menu up, so instead we add a callback for when the menu
# closes; if we're still alive, we'll come up then.
# If there's no main menu this gets called immediately.
- bui.app.ui_v1.add_main_menu_close_callback(bui.WeakCall(self.show_ui))
+ classic.add_main_menu_close_callback(bui.WeakCall(self.show_ui))
def show_ui(self) -> None:
"""Show the UI for restarting, playing the next Level, etc."""
diff --git a/src/assets/ba_data/python/bascenev1lib/mainmenu.py b/src/assets/ba_data/python/bascenev1lib/mainmenu.py
index 84343e35..15d5e24f 100644
--- a/src/assets/ba_data/python/bascenev1lib/mainmenu.py
+++ b/src/assets/ba_data/python/bascenev1lib/mainmenu.py
@@ -1,7 +1,6 @@
# Released under the MIT License. See LICENSE for details.
#
"""Session and Activity for displaying the main menu bg."""
-# pylint: disable=too-many-lines
from __future__ import annotations
@@ -48,48 +47,15 @@ class MainMenuActivity(bs.Activity[bs.Player, bs.Team]):
def on_transition_in(self) -> None:
# pylint: disable=too-many-locals
# pylint: disable=too-many-statements
- # pylint: disable=too-many-branches
super().on_transition_in()
random.seed(123)
app = bs.app
env = app.env
assert app.classic is not None
- plus = bui.app.plus
+ plus = bs.app.plus
assert plus is not None
- # FIXME: We shouldn't be doing things conditionally based on whether
- # the host is VR mode or not (clients may differ in that regard).
- # Any differences need to happen at the engine level so everyone
- # sees things in their own optimal way.
- vr_mode = bs.app.env.vr
-
- if not bs.app.ui_v1.use_toolbars:
- color = (1.0, 1.0, 1.0, 1.0) if vr_mode else (0.5, 0.6, 0.5, 0.6)
-
- # FIXME: Need a node attr for vr-specific-scale.
- scale = (
- 0.9
- if (app.ui_v1.uiscale is bs.UIScale.SMALL or vr_mode)
- else 0.7
- )
- self.my_name = bs.NodeActor(
- bs.newnode(
- 'text',
- attrs={
- 'v_attach': 'bottom',
- 'h_align': 'center',
- 'color': color,
- 'flatness': 1.0,
- 'shadow': 1.0 if vr_mode else 0.5,
- 'scale': scale,
- 'position': (0, 10),
- 'vr_depth': -10,
- 'text': '\xa9 2011-2024 Eric Froemling',
- },
- )
- )
-
# Throw up some text that only clients can see so they know that the
# host is navigating menus while they're just staring at an
# empty-ish screen.
@@ -109,74 +75,17 @@ class MainMenuActivity(bs.Activity[bs.Player, bs.Team]):
},
)
)
- if not app.classic.main_menu_did_initial_transition and hasattr(
- self, 'my_name'
+ if (
+ not app.classic.main_menu_did_initial_transition
+ and self.my_name is not None
):
- assert self.my_name is not None
assert self.my_name.node
bs.animate(self.my_name.node, 'opacity', {2.3: 0, 3.0: 1.0})
- # FIXME: We shouldn't be doing things conditionally based on whether
- # the host is vr mode or not (clients may not be or vice versa).
- # Any differences need to happen at the engine level so everyone sees
- # things in their own optimal way.
- vr_mode = app.env.vr
- uiscale = app.ui_v1.uiscale
-
- # In cases where we're doing lots of dev work lets always show the
- # build number.
- force_show_build_number = False
-
- if not bs.app.ui_v1.use_toolbars:
- if env.debug or env.test or force_show_build_number:
- if env.debug:
- text = bs.Lstr(
- value='${V} (${B}) (${D})',
- subs=[
- ('${V}', app.env.engine_version),
- ('${B}', str(app.env.engine_build_number)),
- ('${D}', bs.Lstr(resource='debugText')),
- ],
- )
- else:
- text = bs.Lstr(
- value='${V} (${B})',
- subs=[
- ('${V}', app.env.engine_version),
- ('${B}', str(app.env.engine_build_number)),
- ],
- )
- else:
- text = bs.Lstr(
- value='${V}', subs=[('${V}', app.env.engine_version)]
- )
- scale = 0.9 if (uiscale is bs.UIScale.SMALL or vr_mode) else 0.7
- color = (1, 1, 1, 1) if vr_mode else (0.5, 0.6, 0.5, 0.7)
- self.version = bs.NodeActor(
- bs.newnode(
- 'text',
- attrs={
- 'v_attach': 'bottom',
- 'h_attach': 'right',
- 'h_align': 'right',
- 'flatness': 1.0,
- 'vr_depth': -10,
- 'shadow': 1.0 if vr_mode else 0.5,
- 'color': color,
- 'scale': scale,
- 'position': (-260, 10) if vr_mode else (-10, 10),
- 'text': text,
- },
- )
- )
- if not app.classic.main_menu_did_initial_transition:
- assert self.version.node
- bs.animate(self.version.node, 'opacity', {2.3: 0, 3.0: 1.0})
-
# Throw in test build info.
self.beta_info = self.beta_info_2 = None
- if env.test and not (env.demo or env.arcade):
- pos = (230, 35)
+ if env.test:
+ pos = (230, -5)
self.beta_info = bs.NodeActor(
bs.newnode(
'text',
@@ -292,125 +201,20 @@ class MainMenuActivity(bs.Activity[bs.Player, bs.Team]):
self._update()
# Hopefully this won't hitch but lets space these out anyway.
- bui.add_clean_frame_callback(bs.WeakCall(self._start_preloads))
+ bs.add_clean_frame_callback(bs.WeakCall(self._start_preloads))
random.seed()
- if not (env.demo or env.arcade) and not app.ui_v1.use_toolbars:
- self._news = NewsDisplay(self)
+ # Need to update this for toolbar mode; currenly doesn't fit.
+ if bool(False):
+ if not (env.demo or env.arcade):
+ self._news = NewsDisplay(self)
self._attract_mode_timer = bs.Timer(
3.12, self._update_attract_mode, repeat=True
)
- # Bring up the last place we were, or start at the main menu otherwise.
- with bs.ContextRef.empty():
- from bauiv1lib import specialoffer
-
- assert bs.app.classic is not None
- if bui.app.env.headless:
- # UI stuff fails now in headless builds; avoid it.
- pass
- elif bool(False):
- uicontroller = bs.app.ui_v1.controller
- assert uicontroller is not None
- uicontroller.show_main_menu()
- else:
- main_menu_location = bs.app.ui_v1.get_main_menu_location()
-
- # When coming back from a kiosk-mode game, jump to
- # the kiosk start screen.
- if env.demo or env.arcade:
- # pylint: disable=cyclic-import
- from bauiv1lib.kiosk import KioskWindow
-
- bs.app.ui_v1.set_main_menu_window(
- KioskWindow().get_root_widget(),
- from_window=False, # Disable check here.
- )
- # ..or in normal cases go back to the main menu
- else:
- if main_menu_location == 'Gather':
- # pylint: disable=cyclic-import
- from bauiv1lib.gather import GatherWindow
-
- bs.app.ui_v1.set_main_menu_window(
- GatherWindow(transition=None).get_root_widget(),
- from_window=False, # Disable check here.
- )
- elif main_menu_location == 'Watch':
- # pylint: disable=cyclic-import
- from bauiv1lib.watch import WatchWindow
-
- bs.app.ui_v1.set_main_menu_window(
- WatchWindow(transition=None).get_root_widget(),
- from_window=False, # Disable check here.
- )
- elif main_menu_location == 'Team Game Select':
- # pylint: disable=cyclic-import
- from bauiv1lib.playlist.browser import (
- PlaylistBrowserWindow,
- )
-
- bs.app.ui_v1.set_main_menu_window(
- PlaylistBrowserWindow(
- sessiontype=bs.DualTeamSession, transition=None
- ).get_root_widget(),
- from_window=False, # Disable check here.
- )
- elif main_menu_location == 'Free-for-All Game Select':
- # pylint: disable=cyclic-import
- from bauiv1lib.playlist.browser import (
- PlaylistBrowserWindow,
- )
-
- bs.app.ui_v1.set_main_menu_window(
- PlaylistBrowserWindow(
- sessiontype=bs.FreeForAllSession,
- transition=None,
- ).get_root_widget(),
- from_window=False, # Disable check here.
- )
- elif main_menu_location == 'Coop Select':
- # pylint: disable=cyclic-import
- from bauiv1lib.coop.browser import CoopBrowserWindow
-
- bs.app.ui_v1.set_main_menu_window(
- CoopBrowserWindow(
- transition=None
- ).get_root_widget(),
- from_window=False, # Disable check here.
- )
- elif main_menu_location == 'Benchmarks & Stress Tests':
- # pylint: disable=cyclic-import
- from bauiv1lib.debug import DebugWindow
-
- bs.app.ui_v1.set_main_menu_window(
- DebugWindow(transition=None).get_root_widget(),
- from_window=False, # Disable check here.
- )
- else:
- # pylint: disable=cyclic-import
- from bauiv1lib.mainmenu import MainMenuWindow
-
- bs.app.ui_v1.set_main_menu_window(
- MainMenuWindow(transition=None).get_root_widget(),
- from_window=False, # Disable check here.
- )
-
- # attempt to show any pending offers immediately.
- # If that doesn't work, try again in a few seconds
- # (we may not have heard back from the server)
- # ..if that doesn't work they'll just have to wait
- # until the next opportunity.
- if not specialoffer.show_offer():
-
- def try_again() -> None:
- if not specialoffer.show_offer():
- # Try one last time..
- bui.apptimer(2.0, specialoffer.show_offer)
-
- bui.apptimer(2.0, try_again)
+ app.classic.invoke_main_menu_ui()
app.classic.main_menu_did_initial_transition = True
@@ -442,7 +246,7 @@ class MainMenuActivity(bs.Activity[bs.Player, bs.Team]):
lang = app.lang.language
if lang != self._language:
self._language = lang
- y = 20
+ y = -15
base_scale = 1.1
self._word_actors = []
base_delay = 1.0
@@ -536,7 +340,7 @@ class MainMenuActivity(bs.Activity[bs.Player, bs.Team]):
self._make_word(
'B',
x - 50,
- y - 23 + 0.8 * y_extra,
+ y - 14 + 0.8 * y_extra,
scale=1.3 * base_scale,
delay=delay,
vr_depth_offset=3,
@@ -568,7 +372,7 @@ class MainMenuActivity(bs.Activity[bs.Player, bs.Team]):
self._make_word(
'S',
x,
- y - 25 + 0.8 * y_extra,
+ y - 15 + 0.8 * y_extra,
scale=1.35 * base_scale,
delay=delay,
vr_depth_offset=14,
@@ -676,9 +480,8 @@ class MainMenuActivity(bs.Activity[bs.Player, bs.Team]):
)
self._word_actors.append(word_obj)
- # Add a bit of stop-motion-y jitter to the logo
- # (unless we're in VR mode in which case its best to
- # leave things still).
+ # Add a bit of stop-motion-y jitter to the logo (unless we're in
+ # VR mode in which case its best to leave things still).
if not bs.app.env.vr:
cmb: bs.Node | None
cmb2: bs.Node | None
@@ -763,6 +566,7 @@ class MainMenuActivity(bs.Activity[bs.Player, bs.Team]):
vr_depth_offset: float = 0.0,
) -> None:
# pylint: disable=too-many-locals
+
# Temp easter goodness.
if custom_texture is None:
custom_texture = self._get_custom_logo_tex_name()
@@ -794,9 +598,8 @@ class MainMenuActivity(bs.Activity[bs.Player, bs.Team]):
self._logo_node = logo.node
self._word_actors.append(logo)
- # Add a bit of stop-motion-y jitter to the logo
- # (unless we're in VR mode in which case its best to
- # leave things still).
+ # Add a bit of stop-motion-y jitter to the logo (unless we're in
+ # VR mode in which case its best to leave things still).
assert logo.node
if not bs.app.env.vr:
cmb = bs.newnode('combine', owner=logo.node, attrs={'size': 2})
@@ -879,8 +682,8 @@ class NewsDisplay:
self._used_phrases: list[str] = []
self._phrase_change_timer: bs.Timer | None = None
- # If we're signed in, fetch news immediately.
- # Otherwise wait until we are signed in.
+ # If we're signed in, fetch news immediately. Otherwise wait
+ # until we are signed in.
self._fetch_timer: bs.Timer | None = bs.Timer(
1.0, bs.WeakCall(self._try_fetching_news), repeat=True
)
@@ -913,8 +716,8 @@ class NewsDisplay:
app = bs.app
assert app.classic is not None
- # If our news is way out of date, lets re-request it;
- # otherwise, rotate our phrase.
+ # If our news is way out of date, lets re-request it; otherwise,
+ # rotate our phrase.
assert app.classic.main_menu_last_news_fetch_time is not None
if time.time() - app.classic.main_menu_last_news_fetch_time > 600.0:
self._fetch_news()
@@ -981,17 +784,16 @@ class NewsDisplay:
self._text.node.text = val
def _got_news(self, news: str) -> None:
- # Run this stuff in the context of our activity since we
- # need to make nodes and stuff.. should fix the serverget
- # call so it.
+ # Run this stuff in the context of our activity since we need to
+ # make nodes and stuff.. should fix the serverget call so it.
activity = self._activity()
if activity is None or activity.expired:
return
with activity.context:
self._phrases.clear()
- # Show upcoming achievements in non-vr versions
- # (currently too hard to read in vr).
+ # Show upcoming achievements in non-vr versions (currently
+ # too hard to read in vr).
self._used_phrases = (['__ACH__'] if not bs.app.env.vr else []) + [
s for s in news.split('
\n') if s != ''
]
diff --git a/src/assets/ba_data/python/batemplatefs/__init__.py b/src/assets/ba_data/python/batemplatefs/__init__.py
index 58ad2cd3..379f75e3 100644
--- a/src/assets/ba_data/python/batemplatefs/__init__.py
+++ b/src/assets/ba_data/python/batemplatefs/__init__.py
@@ -7,9 +7,9 @@
# Package up various private bits (including stuff from our native
# module) into a nice clean public API.
from _batemplatefs import hello_again_world
-from batemplatefs._subsystem import TemplateFsSubsystem
+from batemplatefs._appsubsystem import TemplateFsAppSubsystem
__all__ = [
- 'TemplateFsSubsystem',
+ 'TemplateFsAppSubsystem',
'hello_again_world',
]
diff --git a/src/assets/ba_data/python/batemplatefs/_subsystem.py b/src/assets/ba_data/python/batemplatefs/_appsubsystem.py
similarity index 61%
rename from src/assets/ba_data/python/batemplatefs/_subsystem.py
rename to src/assets/ba_data/python/batemplatefs/_appsubsystem.py
index 2645fbf8..2bc9fe3d 100644
--- a/src/assets/ba_data/python/batemplatefs/_subsystem.py
+++ b/src/assets/ba_data/python/batemplatefs/_appsubsystem.py
@@ -1,6 +1,6 @@
# Released under the MIT License. See LICENSE for details.
#
-"""Provides the TemplateFs subsystem."""
+"""Provides the TemplateFs App-Subsystem."""
from __future__ import annotations
from typing import TYPE_CHECKING
@@ -9,11 +9,11 @@ if TYPE_CHECKING:
pass
-class TemplateFsSubsystem:
+class TemplateFsAppSubsystem:
"""Subsystem for TemplateFs functionality in the app.
- The single shared instance of this app can be accessed at
- babase.app.templatefs. Note that it is possible for babase.app.templatefs
+ The single shared instance of this class can be accessed at
+ ba*.app.templatefs. Note that it is possible for ba*.app.templatefs
to be None if the TemplateFs feature-set is not enabled, and code
should handle that case gracefully.
"""
diff --git a/src/assets/ba_data/python/bauiv1/__init__.py b/src/assets/ba_data/python/bauiv1/__init__.py
index 2e3144ba..d3a454d2 100644
--- a/src/assets/ba_data/python/bauiv1/__init__.py
+++ b/src/assets/ba_data/python/bauiv1/__init__.py
@@ -19,6 +19,7 @@ import logging
from efro.util import set_canonical_module_names
from babase import (
add_clean_frame_callback,
+ allows_ticket_sales,
app,
AppIntent,
AppIntentDefault,
@@ -113,7 +114,6 @@ from _bauiv1 import (
Mesh,
rowwidget,
scrollwidget,
- set_party_icon_always_visible,
set_party_window_open,
Sound,
Texture,
@@ -123,11 +123,18 @@ from _bauiv1 import (
widget,
)
from bauiv1._keyboard import Keyboard
-from bauiv1._uitypes import Window, uicleanupcheck
-from bauiv1._subsystem import UIV1Subsystem
+from bauiv1._uitypes import (
+ Window,
+ MainWindowState,
+ BasicMainWindowState,
+ uicleanupcheck,
+ MainWindow,
+)
+from bauiv1._appsubsystem import UIV1AppSubsystem
__all__ = [
'add_clean_frame_callback',
+ 'allows_ticket_sales',
'app',
'AppIntent',
'AppIntentDefault',
@@ -140,6 +147,7 @@ __all__ = [
'AppTime',
'apptimer',
'AppTimer',
+ 'BasicMainWindowState',
'buttonwidget',
'Call',
'fullscreen_control_available',
@@ -189,6 +197,8 @@ __all__ = [
'LoginAdapter',
'LoginInfo',
'Lstr',
+ 'MainWindow',
+ 'MainWindowState',
'Mesh',
'native_review_request',
'native_review_request_supported',
@@ -212,7 +222,6 @@ __all__ = [
'scrollwidget',
'set_analytics_screen',
'set_low_level_config_value',
- 'set_party_icon_always_visible',
'set_party_window_open',
'set_ui_input_device',
'Sound',
@@ -225,7 +234,7 @@ __all__ = [
'uibounds',
'uicleanupcheck',
'UIScale',
- 'UIV1Subsystem',
+ 'UIV1AppSubsystem',
'unlock_all_input',
'WeakCall',
'widget',
diff --git a/src/assets/ba_data/python/bauiv1/_appsubsystem.py b/src/assets/ba_data/python/bauiv1/_appsubsystem.py
new file mode 100644
index 00000000..c58b44ba
--- /dev/null
+++ b/src/assets/ba_data/python/bauiv1/_appsubsystem.py
@@ -0,0 +1,400 @@
+# Released under the MIT License. See LICENSE for details.
+#
+"""User interface related functionality."""
+
+from __future__ import annotations
+
+import logging
+import inspect
+import weakref
+from enum import Enum
+from typing import TYPE_CHECKING, override
+
+from efro.util import empty_weakref
+import babase
+
+import _bauiv1
+
+if TYPE_CHECKING:
+ from typing import Any, Callable
+
+ from bauiv1._uitypes import (
+ UICleanupCheck,
+ Window,
+ MainWindow,
+ MainWindowState,
+ )
+ import bauiv1
+
+
+class UIV1AppSubsystem(babase.AppSubsystem):
+ """Consolidated UI functionality for the app.
+
+ Category: **App Classes**
+
+ To use this class, access the single instance of it at 'ba.app.ui'.
+ """
+
+ class RootUIElement(Enum):
+ """Stuff provided by the root ui."""
+
+ MENU_BUTTON = 'menu_button'
+ SQUAD_BUTTON = 'squad_button'
+ ACCOUNT_BUTTON = 'account_button'
+ SETTINGS_BUTTON = 'settings_button'
+ INBOX_BUTTON = 'inbox_button'
+ STORE_BUTTON = 'store_button'
+ INVENTORY_BUTTON = 'inventory_button'
+ ACHIEVEMENTS_BUTTON = 'achievements_button'
+ GET_TOKENS_BUTTON = 'get_tokens_button'
+ TICKETS_METER = 'tickets_meter'
+ TOKENS_METER = 'tokens_meter'
+ TROPHY_METER = 'trophy_meter'
+ LEVEL_METER = 'level_meter'
+ CHEST_SLOT_1 = 'chest_slot_1'
+ CHEST_SLOT_2 = 'chest_slot_2'
+ CHEST_SLOT_3 = 'chest_slot_3'
+ CHEST_SLOT_4 = 'chest_slot_4'
+
+ def __init__(self) -> None:
+ from bauiv1._uitypes import MainWindow
+
+ super().__init__()
+ env = babase.env()
+
+ # We hold only a weak ref to the current main Window; we want it
+ # to be able to disappear on its own. That being said, we do
+ # expect MainWindows to keep themselves alive until replaced by
+ # another MainWindow and we complain if they don't.
+ self._main_window = empty_weakref(MainWindow)
+ self._main_window_widget: bauiv1.Widget | None = None
+ self.main_window_group_id: str | None = None
+
+ self.quit_window: bauiv1.Widget | None = None
+
+ # The following should probably go away or move to classic.
+ # self._main_menu_location: str | None = None
+
+ # For storing arbitrary class-level state data for Windows or
+ # other UI related classes.
+ self.window_states: dict[type, Any] = {}
+
+ uiscalestr = babase.app.config.get('UI Scale', env['ui_scale'])
+ if uiscalestr == 'auto':
+ uiscalestr = env['ui_scale']
+
+ self._uiscale: babase.UIScale
+ if uiscalestr == 'large':
+ self._uiscale = babase.UIScale.LARGE
+ elif uiscalestr == 'medium':
+ self._uiscale = babase.UIScale.MEDIUM
+ elif uiscalestr == 'small':
+ self._uiscale = babase.UIScale.SMALL
+ else:
+ logging.error("Invalid UIScale '%s'.", uiscalestr)
+ self._uiscale = babase.UIScale.MEDIUM
+
+ self.cleanupchecks: list[UICleanupCheck] = []
+ self.upkeeptimer: babase.AppTimer | None = None
+
+ self.title_color = (0.72, 0.7, 0.75)
+ self.heading_color = (0.72, 0.7, 0.75)
+ self.infotextcolor = (0.7, 0.9, 0.7)
+
+ # Elements in our root UI will call anything here when activated.
+ self.root_ui_calls: dict[
+ UIV1AppSubsystem.RootUIElement, Callable[[], None]
+ ] = {}
+
+ @property
+ def available(self) -> bool:
+ """Can uiv1 currently be used?
+
+ Code that may run in headless mode, before the UI has been spun up,
+ while other ui systems are active, etc. can check this to avoid
+ likely erroring.
+ """
+ return _bauiv1.is_available()
+
+ @override
+ def reset(self) -> None:
+ from bauiv1._uitypes import MainWindow
+
+ self.root_ui_calls.clear()
+ self._main_window = empty_weakref(MainWindow)
+ self._main_window_widget = None
+ self.main_window_group_id = None
+
+ @property
+ def uiscale(self) -> babase.UIScale:
+ """Current ui scale for the app."""
+ return self._uiscale
+
+ @override
+ def on_app_loading(self) -> None:
+ from bauiv1._uitypes import ui_upkeep
+
+ # IMPORTANT: If tweaking UI stuff, make sure it behaves for small,
+ # medium, and large UI modes. (doesn't run off screen, etc).
+ # The overrides below can be used to test with different sizes.
+ # Generally small is used on phones, medium is used on tablets/tvs,
+ # and large is on desktop computers or perhaps large tablets. When
+ # possible, run in windowed mode and resize the window to assure
+ # this holds true at all aspect ratios.
+
+ # UPDATE: A better way to test this is now by setting the environment
+ # variable BA_UI_SCALE to "small", "medium", or "large".
+ # This will affect system UIs not covered by the values below such
+ # as screen-messages. The below values remain functional, however,
+ # for cases such as Android where environment variables can't be set
+ # easily.
+
+ if bool(False): # force-test ui scale
+ self._uiscale = babase.UIScale.SMALL
+ with babase.ContextRef.empty():
+ babase.pushcall(
+ lambda: babase.screenmessage(
+ f'FORCING UISCALE {self._uiscale.name} FOR TESTING',
+ color=(1, 0, 1),
+ log=True,
+ )
+ )
+
+ # Kick off our periodic UI upkeep.
+ # FIXME: Can probably kill this if we do immediate UI death checks.
+ self.upkeeptimer = babase.AppTimer(2.6543, ui_upkeep, repeat=True)
+
+ def do_main_window_back(self, window: MainWindow) -> None:
+ """Sets the main menu window automatically from a parent WindowState."""
+
+ main_window = self._main_window()
+ back_state = (
+ None if main_window is None else main_window.main_window_back_state
+ )
+ if back_state is None:
+ raise RuntimeError(
+ f'Main window {main_window} provides no back-state;'
+ f' cannot use auto-back.'
+ )
+ backwin = back_state.create_window(transition='in_left')
+ backwin.main_window_back_state = back_state.parent
+ self.set_main_window(backwin, from_window=window, is_back=True)
+
+ def get_main_window(self) -> bauiv1.MainWindow | None:
+ """Return main window, if any."""
+ return self._main_window()
+
+ def set_main_window(
+ self,
+ window: bauiv1.MainWindow,
+ from_window: bauiv1.MainWindow | None | bool = True,
+ is_back: bool = False,
+ group_id: str | None = None,
+ is_top_level: bool = False,
+ back_state: MainWindowState | None = None,
+ ) -> None:
+ """Set the current 'main' window, replacing any existing.
+
+ If 'from_window' is passed as a bauiv1.Widget or bauiv1.Window
+ or None, a warning will be issued if it that value does not
+ match the current main window. This can help identify flawed
+ code that can lead to bad UI states. A value of False will
+ disable the check, which is necessary in some cases when the
+ current main window is not known.
+
+ When navigating somewhere from a cancel or back-button, pass
+ is_back=True; this will prevent the new main window from itself
+ being registered as a new location on the stack that can be
+ returned to.
+
+ If a 'group_id' string is provided and the window being replaced
+ has the same group-id, the WindowState stack is left unchanged,
+ effectively replacing the previous window with the new one in
+ the stack. This can be useful in cases where tab-bar-like UIs
+ allow flipping between sibling windows with the back button
+ always leading to a shared parent.
+ """
+ # pylint: disable=too-many-locals
+ # pylint: disable=too-many-branches
+ # pylint: disable=too-many-statements
+ from bauiv1._uitypes import MainWindow
+
+ from_window_widget: bauiv1.Widget | None
+
+ # We used to accept Widgets but now want MainWindows.
+ assert isinstance(window, MainWindow)
+ window_weakref = weakref.ref(window)
+ window_widget = window.get_root_widget()
+
+ if isinstance(from_window, MainWindow):
+ from_window_widget = from_window.get_root_widget()
+ else:
+ from_window_widget = None
+
+ existing = self._main_window_widget
+
+ 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_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_widget is not existing:
+ caller_frame = inspect.stack()[1]
+ caller_filename = caller_frame.filename
+ caller_line_number = caller_frame.lineno
+ logging.warning(
+ "set_main_window() was passed 'from_window' %s"
+ ' but existing main-menu-window is %s. (%s line %i).',
+ from_window_widget,
+ 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 if
+ # we have to force-kill the existing window (which normally
+ # should not happen).
+ frameline = None
+ try:
+ frame = inspect.currentframe()
+ if frame is not None:
+ frame = frame.f_back
+ if frame is not None:
+ frameinfo = inspect.getframeinfo(frame)
+ frameline = f'{frameinfo.filename} {frameinfo.lineno}'
+ except Exception:
+ logging.exception('Error calcing line for set_main_window')
+
+ # NOTE: disabling this for now since hopefully our new system
+ # will be bulletproof enough to avoid this. Can turn it back on
+ # if that's not the case.
+
+ # With our legacy main-menu system, the caller is responsible
+ # for clearing out the old main menu window when assigning the
+ # new. However there are corner cases where that doesn't happen
+ # and we get old windows stuck under the new main one. So let's
+ # guard against that. However, we can't simply delete the
+ # existing main window when a new one is assigned because the
+ # user may transition the old out *after* the assignment. Sigh.
+ # So, as a happy medium, let's check in on the old after a short
+ # bit of time and kill it if its still alive. That will be a bit
+ # ugly on screen but at least should un-break things.
+ def _delay_kill() -> None:
+ import time
+
+ if existing:
+ print(
+ f'Killing old main_menu_window'
+ f' when called at: {frameline} t={time.time():.3f}'
+ )
+ existing.delete()
+
+ if bool(False):
+ babase.apptimer(1.0, _delay_kill)
+
+ if is_back:
+ pass
+ else:
+ # When navigating forward, generate a back-window-state from
+ # the outgoing window.
+
+ # Exception is when we were passed a group and it matches
+ # the existing group; in that case we just keep the existing
+ # back-state.
+ if group_id is not None and group_id == self.main_window_group_id:
+ assert not is_top_level
+ print(f'GOT GROUP ID MATCH {group_id}; KEEPING BACK STATE.')
+ oldwin = self._main_window()
+ if oldwin is None:
+ # We currenty only hold weak refs to windows so
+ # that they are free to die on their own, but we
+ # expect the main menu window to keep itself
+ # alive as long as its the main one. Holler if
+ # that seems to not be happening.
+ logging.warning(
+ 'set_main_window: no existing MainWindow found'
+ ' (and is_top_level is False); should not happen.'
+ ' a MainWindow should keep itself alive as long'
+ ' as it is main.'
+ )
+ window.main_window_back_state = None
+ else:
+ window.main_window_back_state = (
+ oldwin.main_window_back_state
+ )
+ else:
+ if is_top_level:
+ # Top level windows don't have or expect anywhere to go
+ # back to.
+ # self._main_window_back_state = None
+ window.main_window_back_state = None
+ elif back_state is not None:
+ window.main_window_back_state = back_state
+ else:
+ oldwin = self._main_window()
+ if oldwin is None:
+ # We currenty only hold weak refs to windows so
+ # that they are free to die on their own, but we
+ # expect the main menu window to keep itself
+ # alive as long as its the main one. Holler if
+ # that seems to not be happening.
+ logging.warning(
+ 'set_main_window: No old MainWindow found'
+ ' and is_top_level is False;'
+ ' this should not happen.'
+ )
+ window.main_window_back_state = None
+ else:
+ oldwinstate = oldwin.get_main_window_state()
+
+ # Store our previous back state on this new one.
+ oldwinstate.parent = oldwin.main_window_back_state
+ window.main_window_back_state = oldwinstate
+
+ self._main_window = window_weakref
+ self._main_window_widget = window_widget
+ self.main_window_group_id = group_id
+
+ def has_main_window(self) -> bool:
+ """Return whether a main menu window is present."""
+ return bool(self._main_window_widget)
+
+ def clear_main_window(self) -> None:
+ """Clear any existing main window."""
+ from bauiv1._uitypes import MainWindow
+
+ main_window = self._main_window()
+ if main_window:
+ main_window.main_window_close()
+ else:
+ # Fallback; if we have a widget but no window, nuke the widget.
+ if self._main_window_widget:
+ logging.error(
+ 'Have _main_window_widget but no main_window'
+ ' on clear_main_window; unexpected.'
+ )
+ self._main_window_widget.delete()
+
+ self._main_window = empty_weakref(MainWindow)
+ self._main_window_widget = None
+ self.main_window_group_id = None
diff --git a/src/assets/ba_data/python/bauiv1/_hooks.py b/src/assets/ba_data/python/bauiv1/_hooks.py
index b26b9fd3..979f43fe 100644
--- a/src/assets/ba_data/python/bauiv1/_hooks.py
+++ b/src/assets/ba_data/python/bauiv1/_hooks.py
@@ -15,49 +15,130 @@ if TYPE_CHECKING:
from typing import Sequence
import babase
-
-
-def ticket_icon_press() -> None:
- from babase import app
-
- if app.classic is None:
- logging.exception('Classic not present.')
- return
-
- app.classic.ticket_icon_press()
-
-
-def trophy_icon_press() -> None:
- print('TROPHY ICON PRESSED')
-
-
-def level_icon_press() -> None:
- print('LEVEL ICON PRESSED')
-
-
-def coin_icon_press() -> None:
- print('COIN ICON PRESSED')
+ import bauiv1
def empty_call() -> None:
pass
-def back_button_press() -> None:
- _bauiv1.back_press()
+def _root_ui_button_press(
+ rootuitype: bauiv1.UIV1AppSubsystem.RootUIElement,
+) -> None:
+ import babase
+
+ ui = babase.app.ui_v1
+ call = ui.root_ui_calls.get(rootuitype)
+ if call is not None:
+ call()
-def friends_button_press() -> None:
- print('FRIEND BUTTON PRESSED!')
+def root_ui_account_button_press() -> None:
+ from bauiv1._appsubsystem import UIV1AppSubsystem
+
+ _root_ui_button_press(UIV1AppSubsystem.RootUIElement.ACCOUNT_BUTTON)
-def party_icon_activate(origin: Sequence[float]) -> None:
- from babase import app
+def root_ui_inbox_button_press() -> None:
+ from bauiv1._appsubsystem import UIV1AppSubsystem
- if app.classic is not None:
- app.classic.party_icon_activate(origin)
- else:
- logging.warning('party_icon_activate: no classic.')
+ _root_ui_button_press(UIV1AppSubsystem.RootUIElement.INBOX_BUTTON)
+
+
+def root_ui_settings_button_press() -> None:
+ from bauiv1._appsubsystem import UIV1AppSubsystem
+
+ _root_ui_button_press(UIV1AppSubsystem.RootUIElement.SETTINGS_BUTTON)
+
+
+def root_ui_achievements_button_press() -> None:
+ from bauiv1._appsubsystem import UIV1AppSubsystem
+
+ _root_ui_button_press(UIV1AppSubsystem.RootUIElement.ACHIEVEMENTS_BUTTON)
+
+
+def root_ui_store_button_press() -> None:
+ from bauiv1._appsubsystem import UIV1AppSubsystem
+
+ _root_ui_button_press(UIV1AppSubsystem.RootUIElement.STORE_BUTTON)
+
+
+def root_ui_chest_slot_1_press() -> None:
+ from bauiv1._appsubsystem import UIV1AppSubsystem
+
+ _root_ui_button_press(UIV1AppSubsystem.RootUIElement.CHEST_SLOT_1)
+
+
+def root_ui_chest_slot_2_press() -> None:
+ from bauiv1._appsubsystem import UIV1AppSubsystem
+
+ _root_ui_button_press(UIV1AppSubsystem.RootUIElement.CHEST_SLOT_2)
+
+
+def root_ui_chest_slot_3_press() -> None:
+ from bauiv1._appsubsystem import UIV1AppSubsystem
+
+ _root_ui_button_press(UIV1AppSubsystem.RootUIElement.CHEST_SLOT_3)
+
+
+def root_ui_chest_slot_4_press() -> None:
+ from bauiv1._appsubsystem import UIV1AppSubsystem
+
+ _root_ui_button_press(UIV1AppSubsystem.RootUIElement.CHEST_SLOT_4)
+
+
+def root_ui_inventory_button_press() -> None:
+ from bauiv1._appsubsystem import UIV1AppSubsystem
+
+ _root_ui_button_press(UIV1AppSubsystem.RootUIElement.INVENTORY_BUTTON)
+
+
+def root_ui_ticket_icon_press() -> None:
+ from bauiv1._appsubsystem import UIV1AppSubsystem
+
+ _root_ui_button_press(UIV1AppSubsystem.RootUIElement.TICKETS_METER)
+
+
+def root_ui_get_tokens_button_press() -> None:
+ from bauiv1._appsubsystem import UIV1AppSubsystem
+
+ _root_ui_button_press(UIV1AppSubsystem.RootUIElement.GET_TOKENS_BUTTON)
+
+
+def root_ui_tokens_meter_press() -> None:
+ from bauiv1._appsubsystem import UIV1AppSubsystem
+
+ _root_ui_button_press(UIV1AppSubsystem.RootUIElement.TOKENS_METER)
+
+
+def root_ui_trophy_meter_press() -> None:
+ from bauiv1._appsubsystem import UIV1AppSubsystem
+
+ _root_ui_button_press(UIV1AppSubsystem.RootUIElement.TROPHY_METER)
+
+
+def root_ui_level_icon_press() -> None:
+ from bauiv1._appsubsystem import UIV1AppSubsystem
+
+ _root_ui_button_press(UIV1AppSubsystem.RootUIElement.LEVEL_METER)
+
+
+def root_ui_menu_button_press() -> None:
+ from bauiv1._appsubsystem import UIV1AppSubsystem
+
+ _root_ui_button_press(UIV1AppSubsystem.RootUIElement.MENU_BUTTON)
+
+
+def root_ui_back_button_press() -> None:
+ # Native layer handles this directly. (technically we could wire
+ # this up to not even come through Python).
+ _bauiv1.root_ui_back_press()
+
+
+def root_ui_squad_button_press() -> None:
+ from bauiv1._appsubsystem import UIV1AppSubsystem
+
+ _root_ui_button_press(UIV1AppSubsystem.RootUIElement.SQUAD_BUTTON)
def quit_window(quit_type: babase.QuitType) -> None:
diff --git a/src/assets/ba_data/python/bauiv1/_subsystem.py b/src/assets/ba_data/python/bauiv1/_subsystem.py
deleted file mode 100644
index c9bd7f50..00000000
--- a/src/assets/ba_data/python/bauiv1/_subsystem.py
+++ /dev/null
@@ -1,250 +0,0 @@
-# Released under the MIT License. See LICENSE for details.
-#
-"""User interface related functionality."""
-
-from __future__ import annotations
-
-import logging
-import inspect
-from typing import TYPE_CHECKING, override
-
-import babase
-
-import _bauiv1
-
-if TYPE_CHECKING:
- from typing import Any, Callable
-
- from bauiv1._uitypes import UICleanupCheck, UIController
- import bauiv1
-
-
-class UIV1Subsystem(babase.AppSubsystem):
- """Consolidated UI functionality for the app.
-
- Category: **App Classes**
-
- To use this class, access the single instance of it at 'ba.app.ui'.
- """
-
- def __init__(self) -> None:
- super().__init__()
- env = babase.env()
-
- self.controller: UIController | None = None
-
- self._main_menu_window: bauiv1.Widget | None = None
- self._main_menu_location: str | None = None
- self.quit_window: bauiv1.Widget | None = None
-
- # From classic.
- self.main_menu_resume_callbacks: list = [] # Can probably go away.
-
- self._uiscale: babase.UIScale
-
- interfacetype = babase.app.config.get('UI Scale', env['ui_scale'])
- if interfacetype == 'auto':
- interfacetype = env['ui_scale']
-
- if interfacetype == 'large':
- self._uiscale = babase.UIScale.LARGE
- elif interfacetype == 'medium':
- self._uiscale = babase.UIScale.MEDIUM
- elif interfacetype == 'small':
- self._uiscale = babase.UIScale.SMALL
- else:
- raise RuntimeError(f'Invalid UIScale value: {interfacetype}')
-
- self.window_states: dict[type, Any] = {} # FIXME: Kill this.
- self.main_menu_selection: str | None = None # FIXME: Kill this.
- self.have_party_queue_window = False
- self.cleanupchecks: list[UICleanupCheck] = []
- self.upkeeptimer: babase.AppTimer | None = None
- self.use_toolbars = _bauiv1.toolbar_test()
-
- self.title_color = (0.72, 0.7, 0.75)
- self.heading_color = (0.72, 0.7, 0.75)
- self.infotextcolor = (0.7, 0.9, 0.7)
-
- # Switch our overall game selection UI flow between Play and
- # Private-party playlist selection modes; should do this in
- # a more elegant way once we revamp high level UI stuff a bit.
- self.selecting_private_party_playlist: bool = False
-
- @property
- def available(self) -> bool:
- """Can uiv1 currently be used?
-
- Code that may run in headless mode, before the UI has been spun up,
- while other ui systems are active, etc. can check this to avoid
- likely erroring.
- """
- return _bauiv1.is_available()
-
- @property
- def uiscale(self) -> babase.UIScale:
- """Current ui scale for the app."""
- return self._uiscale
-
- @override
- def on_app_loading(self) -> None:
- from bauiv1._uitypes import UIController, ui_upkeep
-
- # IMPORTANT: If tweaking UI stuff, make sure it behaves for small,
- # medium, and large UI modes. (doesn't run off screen, etc).
- # The overrides below can be used to test with different sizes.
- # Generally small is used on phones, medium is used on tablets/tvs,
- # and large is on desktop computers or perhaps large tablets. When
- # possible, run in windowed mode and resize the window to assure
- # this holds true at all aspect ratios.
-
- # UPDATE: A better way to test this is now by setting the environment
- # variable BA_UI_SCALE to "small", "medium", or "large".
- # This will affect system UIs not covered by the values below such
- # as screen-messages. The below values remain functional, however,
- # for cases such as Android where environment variables can't be set
- # easily.
-
- if bool(False): # force-test ui scale
- self._uiscale = babase.UIScale.SMALL
- with babase.ContextRef.empty():
- babase.pushcall(
- lambda: babase.screenmessage(
- f'FORCING UISCALE {self._uiscale.name} FOR TESTING',
- color=(1, 0, 1),
- log=True,
- )
- )
-
- self.controller = UIController()
-
- # Kick off our periodic UI upkeep.
- # FIXME: Can probably kill this if we do immediate UI death checks.
- self.upkeeptimer = babase.AppTimer(2.6543, ui_upkeep, repeat=True)
-
- def set_main_menu_window(
- 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
-
- 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
- # if we have to force-kill the existing window (which normally
- # should not happen).
- frameline = None
- try:
- frame = inspect.currentframe()
- if frame is not None:
- frame = frame.f_back
- if frame is not None:
- frameinfo = inspect.getframeinfo(frame)
- frameline = f'{frameinfo.filename} {frameinfo.lineno}'
- except Exception:
- logging.exception('Error calcing line for set_main_menu_window')
-
- # With our legacy main-menu system, the caller is responsible for
- # clearing out the old main menu window when assigning the new.
- # However there are corner cases where that doesn't happen and we get
- # old windows stuck under the new main one. So let's guard against
- # that. However, we can't simply delete the existing main window when
- # a new one is assigned because the user may transition the old out
- # *after* the assignment. Sigh. So, as a happy medium, let's check in
- # on the old after a short bit of time and kill it if its still alive.
- # That will be a bit ugly on screen but at least should un-break
- # things.
- def _delay_kill() -> None:
- import time
-
- if existing:
- print(
- f'Killing old main_menu_window'
- f' when called at: {frameline} t={time.time():.3f}'
- )
- existing.delete()
-
- babase.apptimer(1.0, _delay_kill)
- self._main_menu_window = window
-
- def clear_main_menu_window(self, transition: str | None = None) -> None:
- """Clear any existing 'main' window with the provided transition."""
- assert transition is None or not transition.endswith('_in')
- if self._main_menu_window:
- if (
- transition is not None
- and not self._main_menu_window.transitioning_out
- ):
- _bauiv1.containerwidget(
- edit=self._main_menu_window, transition=transition
- )
- else:
- self._main_menu_window.delete()
- self._main_menu_window = None
-
- def add_main_menu_close_callback(self, call: Callable[[], Any]) -> None:
- """(internal)"""
-
- # If there's no main menu up, just call immediately.
- if not self.has_main_menu_window():
- with babase.ContextRef.empty():
- call()
- else:
- self.main_menu_resume_callbacks.append(call)
-
- def has_main_menu_window(self) -> bool:
- """Return whether a main menu window is present."""
- return bool(self._main_menu_window)
-
- def set_main_menu_location(self, location: str) -> None:
- """Set the location represented by the current main menu window."""
- self._main_menu_location = location
-
- def get_main_menu_location(self) -> str | None:
- """Return the current named main menu location, if any."""
- return self._main_menu_location
diff --git a/src/assets/ba_data/python/bauiv1/_uitypes.py b/src/assets/ba_data/python/bauiv1/_uitypes.py
index 6431a554..0106213c 100644
--- a/src/assets/ba_data/python/bauiv1/_uitypes.py
+++ b/src/assets/ba_data/python/bauiv1/_uitypes.py
@@ -6,6 +6,7 @@ from __future__ import annotations
import os
import weakref
+import logging
from dataclasses import dataclass
from typing import TYPE_CHECKING, override
@@ -14,7 +15,7 @@ import babase
import _bauiv1
if TYPE_CHECKING:
- from typing import Any, Type
+ from typing import Any, Type, Literal, Callable
import bauiv1
@@ -27,6 +28,9 @@ class Window:
"""A basic window.
Category: User Interface Classes
+
+ Essentially wraps a ContainerWidget with some higher level
+ functionality.
"""
def __init__(self, root_widget: bauiv1.Widget, cleanupcheck: bool = True):
@@ -41,6 +45,192 @@ class Window:
return self._root_widget
+class MainWindow(Window):
+ """A special window that can be used as a main window."""
+
+ def __init__(
+ self,
+ root_widget: bauiv1.Widget,
+ transition: str | None,
+ origin_widget: bauiv1.Widget | None,
+ cleanupcheck: bool = True,
+ ):
+ """Create a MainWindow given a root widget and transition info.
+
+ Automatically handles in and out transitions on the provided widget,
+ so there is no need to set transitions when creating it.
+ """
+ # TODO - move to MainWindow
+ # A back-state supplied by the ui system.
+ self.main_window_back_state: MainWindowState | None = None
+
+ self._main_window_transition = transition
+ self._main_window_origin_widget = origin_widget
+ super().__init__(root_widget, cleanupcheck)
+
+ scale_origin: tuple[float, float] | None
+ if origin_widget is not None:
+ self._main_window_transition_out = 'out_scale'
+ scale_origin = origin_widget.get_screen_space_center()
+ transition = 'in_scale'
+ else:
+ self._main_window_transition_out = 'out_right'
+ scale_origin = None
+ _bauiv1.containerwidget(
+ edit=root_widget,
+ transition=transition,
+ scale_origin_stack_offset=scale_origin,
+ )
+
+ def main_window_close(self) -> None:
+ """Get window transitioning out if still alive."""
+
+ # 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
+
+ # Transition ourself out.
+ try:
+ self.on_main_window_close()
+ except Exception:
+ logging.exception('Error in on_main_window_close() for %s.', self)
+
+ _bauiv1.containerwidget(
+ edit=self._root_widget, transition=self._main_window_transition_out
+ )
+
+ def can_change_main_window(self) -> bool:
+ """Is this MainWindow allowed to change the global main window?
+
+ It is a good idea to make sure this is True before calling
+ either main_window_back() or main_window_replace(). This
+ prevents fluke UI breakage such as multiple simultaneous events
+ causing a MainWindow to spawn multiple replacements for itself.
+ """
+ # We are allowed to change main windows if we are the current one
+ # AND our underlying widget is still alive and not transitioning out.
+ return (
+ babase.app.ui_v1.get_main_window() is self
+ and bool(self._root_widget)
+ and not self._root_widget.transitioning_out
+ )
+
+ def main_window_back(self) -> None:
+ """Move back in the main window stack."""
+
+ # Users should always check can_change_main_window() before
+ # calling us. Error if it seems they did not.
+ if not self.can_change_main_window():
+ raise RuntimeError(
+ 'main_window_back() should only be called'
+ ' if can_change_main_window() returns True'
+ ' (it currently is False).'
+ )
+
+ # Get the 'back' window coming in.
+ babase.app.ui_v1.do_main_window_back(self)
+
+ self.main_window_close()
+
+ def main_window_replace(
+ self, new_window: MainWindow, group_id: str | None = None
+ ) -> None:
+ """Replace ourself with a new MainWindow."""
+
+ # Users should always check can_change_main_window() before
+ # creating new MainWindows and passing them in here. Error if it
+ # seems they did not.
+ if not self.can_change_main_window():
+ raise RuntimeError(
+ 'main_window_replace() should only be called'
+ ' if can_change_main_window() returns True'
+ ' (it currently is False).'
+ )
+
+ # If we're navigating within a group, we want it to look like we're
+ # backing out of the old one and going into the new one.
+ if (
+ group_id is not None
+ and babase.app.ui_v1.main_window_group_id == group_id
+ ):
+ transition = self._main_window_transition_out
+ else:
+ # Otherwise just shove the old out the left to give the feel
+ # that we're adding to the nav stack.
+ transition = 'out_left'
+
+ # Transition ourself out.
+ try:
+ self.on_main_window_close()
+ except Exception:
+ logging.exception('Error in on_main_window_close() for %s.', self)
+
+ _bauiv1.containerwidget(edit=self._root_widget, transition=transition)
+ babase.app.ui_v1.set_main_window(
+ new_window, from_window=self, group_id=group_id
+ )
+
+ def on_main_window_close(self) -> None:
+ """Called before transitioning out a main window.
+
+ A good opportunity to save window state/etc.
+ """
+
+ def get_main_window_state(self) -> MainWindowState:
+ """Return a WindowState to recreate this window, if supported."""
+ # TODO - change to NotImplementedError when moved to MainWindow.
+ raise RuntimeError('FIXME NOT IMPLEMENTED')
+
+
+class MainWindowState:
+ """Persistent state for a specific main-window and its ancestors.
+
+ This allows MainWindows to be automatically recreated for back-button
+ purposes, when switching app-modes, etc.
+ """
+
+ def __init__(self, parent: MainWindowState | None = None) -> None:
+ # The window that back/cancel navigation should take us to.
+ self.parent = parent
+
+ def create_window(
+ self,
+ transition: Literal['in_right', 'in_left', 'in_scale'] | None = None,
+ origin_widget: bauiv1.Widget | None = None,
+ ) -> MainWindow:
+ """Create a window based on this state.
+
+ WindowState child classes should override this to recreate their
+ particular type of window.
+ """
+ raise NotImplementedError()
+
+
+class BasicMainWindowState(MainWindowState):
+ """A basic MainWindowState holding a lambda to recreate a MainWindow."""
+
+ def __init__(
+ self,
+ create_call: Callable[
+ [
+ Literal['in_right', 'in_left', 'in_scale'] | None,
+ bauiv1.Widget | None,
+ ],
+ bauiv1.MainWindow,
+ ],
+ ) -> None:
+ super().__init__()
+ self.create_call = create_call
+
+ @override
+ def create_window(
+ self,
+ transition: Literal['in_right', 'in_left', 'in_scale'] | None = None,
+ origin_widget: bauiv1.Widget | None = None,
+ ) -> bauiv1.MainWindow:
+ return self.create_call(transition, origin_widget)
+
+
@dataclass
class UICleanupCheck:
"""Holds info about a uicleanupcheck target."""
@@ -50,127 +240,8 @@ class UICleanupCheck:
widget_death_time: float | None
-class UILocation:
- """Defines a specific 'place' in the UI the user can navigate to.
-
- Category: User Interface Classes
- """
-
- def __init__(self) -> None:
- pass
-
- def save_state(self) -> None:
- """Serialize this instance's state to a dict."""
-
- def restore_state(self) -> None:
- """Restore this instance's state from a dict."""
-
- def push_location(self, location: str) -> None:
- """Push a new location to the stack and transition to it."""
-
-
-class UILocationWindow(UILocation):
- """A UILocation consisting of a single root window widget.
-
- Category: User Interface Classes
- """
-
- def __init__(self) -> None:
- super().__init__()
- self._root_widget: bauiv1.Widget | None = None
-
- def get_root_widget(self) -> bauiv1.Widget:
- """Return the root widget for this window."""
- assert self._root_widget is not None
- return self._root_widget
-
-
-class UIEntry:
- """State for a UILocation on the stack."""
-
- def __init__(self, name: str, controller: UIController):
- self._name = name
- self._state = None
- self._args = None
- self._instance: UILocation | None = None
- self._controller = weakref.ref(controller)
-
- def create(self) -> None:
- """Create an instance of our UI."""
- cls = self._get_class()
- self._instance = cls()
-
- def destroy(self) -> None:
- """Transition out our UI if it exists."""
- if self._instance is None:
- return
- print('WOULD TRANSITION OUT', self._name)
-
- def _get_class(self) -> Type[UILocation]:
- """Returns the UI class our name points to."""
- # pylint: disable=cyclic-import
-
- # TEMP HARD CODED - WILL REPLACE THIS WITH BA_META LOOKUPS.
- if self._name == 'mainmenu':
- # Shut pylint up.
- if bool(False):
- return UILocation
- raise RuntimeError('FIXME UNIMPLEMENTED')
- # from bauiv1lib import mainmenu
- # return cast(Type[UILocation], mainmenu.MainMenuWindow)
-
- raise ValueError('unknown ui class ' + str(self._name))
-
-
-class UIController:
- """Wrangles bauiv1.UILocations.
-
- Category: User Interface Classes
- """
-
- def __init__(self) -> None:
- # FIXME: document why we have separate stacks for game and menu...
- self._main_stack_game: list[UIEntry] = []
- self._main_stack_menu: list[UIEntry] = []
-
- # This points at either the game or menu stack.
- self._main_stack: list[UIEntry] | None = None
-
- # There's only one of these since we don't need to preserve its state
- # between sessions.
- self._dialog_stack: list[UIEntry] = []
-
- def show_main_menu(self, in_game: bool = True) -> None:
- """Show the main menu, clearing other UIs from location stacks."""
- self._main_stack = []
- self._dialog_stack = []
- self._main_stack = (
- self._main_stack_game if in_game else self._main_stack_menu
- )
- self._main_stack.append(UIEntry('mainmenu', self))
- self._update_ui()
-
- def _update_ui(self) -> None:
- """Instantiate the topmost ui in our stacks."""
-
- # First tell any existing UIs to get outta here.
- for stack in (self._dialog_stack, self._main_stack):
- assert stack is not None
- for entry in stack:
- entry.destroy()
-
- # Now create the topmost one if there is one.
- entrynew = (
- self._dialog_stack[-1]
- if self._dialog_stack
- else self._main_stack[-1] if self._main_stack else None
- )
- if entrynew is not None:
- entrynew.create()
-
-
def uicleanupcheck(obj: Any, widget: bauiv1.Widget) -> None:
- """Add a check to ensure a widget-owning object gets cleaned up properly.
+ """Checks to ensure a widget-owning object gets cleaned up properly.
Category: User Interface Functions
@@ -233,8 +304,10 @@ def ui_upkeep() -> None:
'WARNING:',
obj,
'is still alive 5 second after its widget died;'
- ' you might have a memory leak. See efro.debug module'
- ' for tools to help debug this.',
+ ' you might have a memory leak. Look for circular'
+ ' references or outside things referencing your window'
+ ' instance. See efro.debug module'
+ ' for tools that can help debug this sort of thing.',
)
else:
remainingchecks.append(check)
diff --git a/src/assets/ba_data/python/bauiv1lib/account/__init__.py b/src/assets/ba_data/python/bauiv1lib/account/__init__.py
index 5ce26500..d34e9ef5 100644
--- a/src/assets/ba_data/python/bauiv1lib/account/__init__.py
+++ b/src/assets/ba_data/python/bauiv1lib/account/__init__.py
@@ -7,32 +7,15 @@ from __future__ import annotations
import bauiv1 as bui
-def show_sign_in_prompt(account_type: str | None = None) -> None:
+def show_sign_in_prompt() -> None:
"""Bring up a prompt telling the user they must sign in."""
from bauiv1lib.confirm import ConfirmWindow
- from bauiv1lib.account import settings
+ from bauiv1lib.account.settings import AccountSettingsWindow
- if account_type == 'Google Play':
-
- def _do_sign_in() -> None:
- plus = bui.app.plus
- assert plus is not None
- plus.sign_in_v1('Google Play')
-
- ConfirmWindow(
- bui.Lstr(resource='notSignedInGooglePlayErrorText'),
- _do_sign_in,
- ok_text=bui.Lstr(resource='accountSettingsWindow.signInText'),
- width=460,
- height=130,
- )
- else:
- ConfirmWindow(
- bui.Lstr(resource='notSignedInErrorText'),
- lambda: settings.AccountSettingsWindow(
- modal=True, close_once_signed_in=True
- ),
- ok_text=bui.Lstr(resource='accountSettingsWindow.signInText'),
- width=460,
- height=130,
- )
+ ConfirmWindow(
+ bui.Lstr(resource='notSignedInErrorText'),
+ lambda: AccountSettingsWindow(modal=True, close_once_signed_in=True),
+ ok_text=bui.Lstr(resource='accountSettingsWindow.signInText'),
+ width=460,
+ height=130,
+ )
diff --git a/src/assets/ba_data/python/bauiv1lib/account/settings.py b/src/assets/ba_data/python/bauiv1lib/account/settings.py
index 9476f326..9ba34ed1 100644
--- a/src/assets/ba_data/python/bauiv1lib/account/settings.py
+++ b/src/assets/ba_data/python/bauiv1lib/account/settings.py
@@ -7,6 +7,7 @@ from __future__ import annotations
import time
import logging
+from typing import override
from bacommon.cloud import WebLocation
from bacommon.login import LoginType
@@ -20,12 +21,12 @@ import bauiv1 as bui
FORCE_ENABLE_V1_LINKING = False
-class AccountSettingsWindow(bui.Window):
+class AccountSettingsWindow(bui.MainWindow):
"""Window for account related functionality."""
def __init__(
self,
- transition: str = 'in_right',
+ transition: str | None = 'in_right',
modal: bool = False,
origin_widget: bui.Widget | None = None,
close_once_signed_in: bool = False,
@@ -46,16 +47,6 @@ class AccountSettingsWindow(bui.Window):
self._explicitly_signed_out_of_gpgs = False
- # If they provided an origin-widget, scale up from that.
- scale_origin: tuple[float, float] | None
- if origin_widget is not None:
- self._transition_out = 'out_scale'
- scale_origin = origin_widget.get_screen_space_center()
- transition = 'in_scale'
- else:
- self._transition_out = 'out_right'
- scale_origin = None
-
self._r = 'accountSettingsWindow'
self._modal = modal
self._needs_refresh = False
@@ -71,10 +62,10 @@ class AccountSettingsWindow(bui.Window):
assert app.classic is not None
uiscale = app.ui_v1.uiscale
- self._width = 860 if uiscale is bui.UIScale.SMALL else 660
- x_offs = 100 if uiscale is bui.UIScale.SMALL else 0
+ self._width = 850 if uiscale is bui.UIScale.SMALL else 660
+ x_offs = 70 if uiscale is bui.UIScale.SMALL else 0
self._height = (
- 390
+ 380
if uiscale is bui.UIScale.SMALL
else 430 if uiscale is bui.UIScale.MEDIUM else 490
)
@@ -107,23 +98,28 @@ class AccountSettingsWindow(bui.Window):
super().__init__(
root_widget=bui.containerwidget(
size=(self._width, self._height + top_extra),
- transition=transition,
- toolbar_visibility='menu_minimal',
- scale_origin_stack_offset=scale_origin,
+ # transition=transition,
+ toolbar_visibility=(
+ 'menu_minimal'
+ if uiscale is bui.UIScale.SMALL
+ else 'menu_full'
+ ),
scale=(
- 2.09
+ 2.07
if uiscale is bui.UIScale.SMALL
else 1.4 if uiscale is bui.UIScale.MEDIUM else 1.0
),
stack_offset=(
- (0, -19) if uiscale is bui.UIScale.SMALL else (0, 0)
+ (0, 8) if uiscale is bui.UIScale.SMALL else (0, 0)
),
- )
+ ),
+ transition=transition,
+ origin_widget=origin_widget,
)
- if uiscale is bui.UIScale.SMALL and app.ui_v1.use_toolbars:
+ if uiscale is bui.UIScale.SMALL:
self._back_button = None
bui.containerwidget(
- edit=self._root_widget, on_cancel_call=self._back
+ edit=self._root_widget, on_cancel_call=self.main_window_back
)
else:
self._back_button = btn = bui.buttonwidget(
@@ -137,7 +133,7 @@ class AccountSettingsWindow(bui.Window):
resource='doneText' if self._modal else 'backText'
),
button_type='regular' if self._modal else 'back',
- on_activate_call=self._back,
+ on_activate_call=self.main_window_back,
)
bui.containerwidget(edit=self._root_widget, cancel_button=btn)
if not self._modal:
@@ -148,12 +144,15 @@ class AccountSettingsWindow(bui.Window):
label=bui.charstr(bui.SpecialChar.BACK),
)
+ titleyoffs = -12 if uiscale is bui.UIScale.SMALL else 0
+ titlescale = 0.6 if uiscale is bui.UIScale.SMALL else 1.0
bui.textwidget(
parent=self._root_widget,
- position=(self._width * 0.5, self._height - 41),
+ position=(self._width * 0.5, self._height - 41 + titleyoffs),
size=(0, 0),
text=bui.Lstr(resource=f'{self._r}.titleText'),
color=app.ui_v1.title_color,
+ scale=titlescale,
maxwidth=self._width - 340,
h_align='center',
v_align='center',
@@ -175,13 +174,27 @@ class AccountSettingsWindow(bui.Window):
self._refresh()
self._restore_state()
+ @override
+ def get_main_window_state(self) -> bui.MainWindowState:
+ # Support recreating our window for back/refresh purposes.
+ cls = type(self)
+ return bui.BasicMainWindowState(
+ create_call=lambda transition, origin_widget: cls(
+ transition=transition, origin_widget=origin_widget
+ )
+ )
+
+ @override
+ def on_main_window_close(self) -> None:
+ self._save_state()
+
def _update(self) -> None:
plus = bui.app.plus
assert plus is not None
# If they want us to close once we're signed in, do so.
if self._close_once_signed_in and self._v1_signed_in:
- self._back()
+ self.main_window_back()
return
# Hmm should update this to use get_account_state_num.
@@ -215,7 +228,6 @@ class AccountSettingsWindow(bui.Window):
# pylint: disable=too-many-branches
# pylint: disable=too-many-locals
# pylint: disable=cyclic-import
- from bauiv1lib import confirm
plus = bui.app.plus
assert plus is not None
@@ -316,10 +328,6 @@ class AccountSettingsWindow(bui.Window):
show_game_service_button = game_center_active
game_service_button_space = 60.0
- # Phasing this out.
- show_what_is_v2 = False
- # show_what_is_v2 = self._v1_signed_in and v1_account_type == 'V2'
-
# Phasing this out (for V2 accounts at least).
show_linked_accounts_text = (
self._v1_signed_in and v1_account_type != 'V2'
@@ -328,36 +336,41 @@ class AccountSettingsWindow(bui.Window):
# Always show achievements except in the game-center case where
# its unified UI covers them.
- show_achievements_button = self._v1_signed_in and not game_center_active
+ # show_achievements_button =
+ # self._v1_signed_in and not game_center_active
+
+ # Update: No longer showing this since its visible on main
+ # toolbar.
+ show_achievements_button = False
achievements_button_space = 60.0
- show_achievements_text = (
- self._v1_signed_in and not show_achievements_button
- )
+ # show_achievements_text = (
+ # self._v1_signed_in and not show_achievements_button
+ # )
+
+ # Update: No longer showing this since its visible on main
+ # toolbar.
+ show_achievements_text = False
achievements_text_space = 27.0
show_leaderboards_button = self._v1_signed_in and gpgs_active
leaderboards_button_space = 60.0
- show_campaign_progress = self._v1_signed_in
+ # Update: No longer showing this; trying to get progress type
+ # stuff out of the account panel.
+ # show_campaign_progress = self._v1_signed_in
+ show_campaign_progress = False
campaign_progress_space = 27.0
- show_tickets = self._v1_signed_in
+ # show_tickets = self._v1_signed_in
+ show_tickets = False
tickets_space = 27.0
- show_reset_progress_button = False
- reset_progress_button_space = 70.0
-
- show_manage_v2_account_button = primary_v2_account is not None
- manage_v2_account_button_space = 100.0
+ show_manage_account_button = primary_v2_account is not None
+ manage_account_button_space = 70.0
show_delete_account_button = primary_v2_account is not None
- delete_account_button_space = 80.0
-
- show_player_profiles_button = self._v1_signed_in
- player_profiles_button_space = (
- 70.0 if show_manage_v2_account_button else 100.0
- )
+ delete_account_button_space = 70.0
show_link_accounts_button = self._v1_signed_in and (
primary_v2_account is None or FORCE_ENABLE_V1_LINKING
@@ -376,7 +389,7 @@ class AccountSettingsWindow(bui.Window):
show_sign_out_button = primary_v2_account is not None or (
self._v1_signed_in and v1_account_type == 'Local'
)
- sign_out_button_space = 80.0
+ sign_out_button_space = 70.0
# We can show cancel if we're either waiting on an adapter to
# provide us with v2 credentials or waiting for those
@@ -419,12 +432,8 @@ class AccountSettingsWindow(bui.Window):
self._sub_height += tickets_space
if show_sign_in_benefits:
self._sub_height += sign_in_benefits_space
- if show_reset_progress_button:
- self._sub_height += reset_progress_button_space
- if show_manage_v2_account_button:
- self._sub_height += manage_v2_account_button_space
- if show_player_profiles_button:
- self._sub_height += player_profiles_button_space
+ if show_manage_account_button:
+ self._sub_height += manage_account_button_space
if show_link_accounts_button:
self._sub_height += link_accounts_button_space
if show_unlink_accounts_button:
@@ -452,8 +461,6 @@ class AccountSettingsWindow(bui.Window):
v = self._sub_height - 10.0
assert bui.app.classic is not None
- self._account_name_what_is_text: bui.Widget | None
- self._account_name_what_is_y = 0.0
self._account_name_text: bui.Widget | None
if show_signed_in_as:
v -= signed_in_as_space * 0.2
@@ -485,32 +492,6 @@ class AccountSettingsWindow(bui.Window):
v_align='center',
)
- if show_what_is_v2:
- self._account_name_what_is_y = v - 23.0
- self._account_name_what_is_text = bui.textwidget(
- parent=self._subcontainer,
- position=(0.0, self._account_name_what_is_y),
- size=(220.0, 60),
- text=bui.Lstr(
- value='${WHAT} -->',
- subs=[('${WHAT}', bui.Lstr(resource='whatIsThisText'))],
- ),
- scale=0.6,
- color=(0.3, 0.7, 0.05),
- maxwidth=130.0,
- h_align='right',
- v_align='center',
- autoselect=True,
- selectable=True,
- on_activate_call=show_what_is_v2_page,
- click_activate=True,
- glow_type='uniform',
- )
- if first_selectable is None:
- first_selectable = self._account_name_what_is_text
- else:
- self._account_name_what_is_text = None
-
self._refresh_account_name_text()
v -= signed_in_as_space * 0.4
@@ -565,7 +546,6 @@ class AccountSettingsWindow(bui.Window):
else:
self._account_name_text = None
- self._account_name_what_is_text = None
if self._back_button is None:
bbtn = bui.get_special_widget('back_button')
@@ -641,11 +621,9 @@ class AccountSettingsWindow(bui.Window):
)
if first_selectable is None:
first_selectable = btn
- if bui.app.ui_v1.use_toolbars:
- bui.widget(
- edit=btn,
- right_widget=bui.get_special_widget('party_button'),
- )
+ bui.widget(
+ edit=btn, right_widget=bui.get_special_widget('squad_button')
+ )
bui.widget(edit=btn, left_widget=bbtn)
bui.widget(edit=btn, show_buffer_bottom=40, show_buffer_top=100)
self._sign_in_text = None
@@ -683,11 +661,9 @@ class AccountSettingsWindow(bui.Window):
)
if first_selectable is None:
first_selectable = btn
- if bui.app.ui_v1.use_toolbars:
- bui.widget(
- edit=btn,
- right_widget=bui.get_special_widget('party_button'),
- )
+ bui.widget(
+ edit=btn, right_widget=bui.get_special_widget('squad_button')
+ )
bui.widget(edit=btn, left_widget=bbtn)
bui.widget(edit=btn, show_buffer_bottom=40, show_buffer_top=100)
self._sign_in_text = None
@@ -752,11 +728,9 @@ class AccountSettingsWindow(bui.Window):
)
if first_selectable is None:
first_selectable = btn
- if bui.app.ui_v1.use_toolbars:
- bui.widget(
- edit=btn,
- right_widget=bui.get_special_widget('party_button'),
- )
+ bui.widget(
+ edit=btn, right_widget=bui.get_special_widget('squad_button')
+ )
bui.widget(edit=btn, left_widget=bbtn)
bui.widget(edit=btn, show_buffer_bottom=40, show_buffer_top=100)
self._sign_in_text = None
@@ -821,21 +795,19 @@ class AccountSettingsWindow(bui.Window):
)
if first_selectable is None:
first_selectable = btn
- if bui.app.ui_v1.use_toolbars:
- bui.widget(
- edit=btn,
- right_widget=bui.get_special_widget('party_button'),
- )
+ bui.widget(
+ edit=btn, right_widget=bui.get_special_widget('squad_button')
+ )
bui.widget(edit=btn, left_widget=bbtn)
bui.widget(edit=btn, show_buffer_bottom=40, show_buffer_top=100)
self._sign_in_text = None
- if show_manage_v2_account_button:
+ if show_manage_account_button:
button_width = 300
- v -= manage_v2_account_button_space
- self._manage_v2_button = btn = bui.buttonwidget(
+ v -= manage_account_button_space
+ self._manage_button = btn = bui.buttonwidget(
parent=self._subcontainer,
- position=((self._sub_width - button_width) * 0.5, v + 30),
+ position=((self._sub_width - button_width) * 0.5, v),
autoselect=True,
size=(button_width, 60),
label=bui.Lstr(resource=f'{self._r}.manageAccountText'),
@@ -846,35 +818,10 @@ class AccountSettingsWindow(bui.Window):
)
if first_selectable is None:
first_selectable = btn
- if bui.app.ui_v1.use_toolbars:
- bui.widget(
- edit=btn,
- right_widget=bui.get_special_widget('party_button'),
- )
- bui.widget(edit=btn, left_widget=bbtn)
-
- if show_player_profiles_button:
- button_width = 300
- v -= player_profiles_button_space
- self._player_profiles_button = btn = bui.buttonwidget(
- parent=self._subcontainer,
- position=((self._sub_width - button_width) * 0.5, v + 30),
- autoselect=True,
- size=(button_width, 60),
- label=bui.Lstr(resource='playerProfilesWindow.titleText'),
- color=(0.55, 0.5, 0.6),
- icon=bui.gettexture('cuteSpaz'),
- textcolor=(0.75, 0.7, 0.8),
- on_activate_call=self._player_profiles_press,
+ bui.widget(
+ edit=btn, right_widget=bui.get_special_widget('squad_button')
)
- if first_selectable is None:
- first_selectable = btn
- if bui.app.ui_v1.use_toolbars:
- bui.widget(
- edit=btn,
- right_widget=bui.get_special_widget('party_button'),
- )
- bui.widget(edit=btn, left_widget=bbtn, show_buffer_bottom=0)
+ bui.widget(edit=btn, left_widget=bbtn)
# the button to go to OS-Specific leaderboards/high-score-lists/etc.
if show_game_service_button:
@@ -904,11 +851,9 @@ class AccountSettingsWindow(bui.Window):
)
if first_selectable is None:
first_selectable = btn
- if bui.app.ui_v1.use_toolbars:
- bui.widget(
- edit=btn,
- right_widget=bui.get_special_widget('party_button'),
- )
+ bui.widget(
+ edit=btn, right_widget=bui.get_special_widget('squad_button')
+ )
bui.widget(edit=btn, left_widget=bbtn)
v -= game_service_button_space * 0.4
else:
@@ -959,11 +904,9 @@ class AccountSettingsWindow(bui.Window):
)
if first_selectable is None:
first_selectable = btn
- if bui.app.ui_v1.use_toolbars:
- bui.widget(
- edit=btn,
- right_widget=bui.get_special_widget('party_button'),
- )
+ bui.widget(
+ edit=btn, right_widget=bui.get_special_widget('squad_button')
+ )
bui.widget(edit=btn, left_widget=bbtn)
v -= achievements_button_space * 0.15
else:
@@ -990,11 +933,9 @@ class AccountSettingsWindow(bui.Window):
)
if first_selectable is None:
first_selectable = btn
- if bui.app.ui_v1.use_toolbars:
- bui.widget(
- edit=btn,
- right_widget=bui.get_special_widget('party_button'),
- )
+ bui.widget(
+ edit=btn, right_widget=bui.get_special_widget('squad_button')
+ )
bui.widget(edit=btn, left_widget=bbtn)
v -= leaderboards_button_space * 0.15
else:
@@ -1039,41 +980,9 @@ class AccountSettingsWindow(bui.Window):
self._tickets_text = None
# bit of spacing before the reset/sign-out section
- v -= 5
+ # v -= 5
button_width = 250
- if show_reset_progress_button:
- confirm_text = (
- bui.Lstr(resource=f'{self._r}.resetProgressConfirmText')
- if self._can_reset_achievements
- else bui.Lstr(
- resource=f'{self._r}.resetProgressConfirmNoAchievementsText'
- )
- )
- v -= reset_progress_button_space
- self._reset_progress_button = btn = bui.buttonwidget(
- parent=self._subcontainer,
- position=((self._sub_width - button_width) * 0.5, v),
- color=(0.55, 0.5, 0.6),
- textcolor=(0.75, 0.7, 0.8),
- autoselect=True,
- size=(button_width, 60),
- label=bui.Lstr(resource=f'{self._r}.resetProgressText'),
- on_activate_call=lambda: confirm.ConfirmWindow(
- text=confirm_text,
- width=500,
- height=200,
- action=self._reset_progress,
- ),
- )
- if first_selectable is None:
- first_selectable = btn
- if bui.app.ui_v1.use_toolbars:
- bui.widget(
- edit=btn,
- right_widget=bui.get_special_widget('party_button'),
- )
- bui.widget(edit=btn, left_widget=bbtn)
self._linked_accounts_text: bui.Widget | None
if show_linked_accounts_text:
@@ -1133,11 +1042,9 @@ class AccountSettingsWindow(bui.Window):
)
if first_selectable is None:
first_selectable = btn
- if bui.app.ui_v1.use_toolbars:
- bui.widget(
- edit=btn,
- right_widget=bui.get_special_widget('party_button'),
- )
+ bui.widget(
+ edit=btn, right_widget=bui.get_special_widget('squad_button')
+ )
bui.widget(edit=btn, left_widget=bbtn, show_buffer_bottom=50)
self._unlink_accounts_button: bui.Widget | None
@@ -1165,11 +1072,9 @@ class AccountSettingsWindow(bui.Window):
)
if first_selectable is None:
first_selectable = btn
- if bui.app.ui_v1.use_toolbars:
- bui.widget(
- edit=btn,
- right_widget=bui.get_special_widget('party_button'),
- )
+ bui.widget(
+ edit=btn, right_widget=bui.get_special_widget('squad_button')
+ )
bui.widget(edit=btn, left_widget=bbtn, show_buffer_bottom=50)
self._update_unlink_accounts_button()
else:
@@ -1235,11 +1140,9 @@ class AccountSettingsWindow(bui.Window):
)
if first_selectable is None:
first_selectable = btn
- if bui.app.ui_v1.use_toolbars:
- bui.widget(
- edit=btn,
- right_widget=bui.get_special_widget('party_button'),
- )
+ bui.widget(
+ edit=btn, right_widget=bui.get_special_widget('squad_button')
+ )
bui.widget(edit=btn, left_widget=bbtn, show_buffer_bottom=15)
if show_cancel_sign_in_button:
@@ -1256,11 +1159,9 @@ class AccountSettingsWindow(bui.Window):
)
if first_selectable is None:
first_selectable = btn
- if bui.app.ui_v1.use_toolbars:
- bui.widget(
- edit=btn,
- right_widget=bui.get_special_widget('party_button'),
- )
+ bui.widget(
+ edit=btn, right_widget=bui.get_special_widget('squad_button')
+ )
bui.widget(edit=btn, left_widget=bbtn, show_buffer_bottom=15)
if show_delete_account_button:
@@ -1277,11 +1178,9 @@ class AccountSettingsWindow(bui.Window):
)
if first_selectable is None:
first_selectable = btn
- if bui.app.ui_v1.use_toolbars:
- bui.widget(
- edit=btn,
- right_widget=bui.get_special_widget('party_button'),
- )
+ bui.widget(
+ edit=btn, right_widget=bui.get_special_widget('squad_button')
+ )
bui.widget(edit=btn, left_widget=bbtn, show_buffer_bottom=15)
# Whatever the topmost selectable thing is, we want it to scroll all
@@ -1322,9 +1221,6 @@ class AccountSettingsWindow(bui.Window):
position=self._achievements_button.get_screen_space_center()
)
- def _on_what_is_v2_press(self) -> None:
- show_what_is_v2_page()
-
def _on_manage_account_press(self) -> None:
self._do_manage_account_press(WebLocation.ACCOUNT_EDITOR)
@@ -1502,16 +1398,6 @@ class AccountSettingsWindow(bui.Window):
name_str = '??'
bui.textwidget(edit=self._account_name_text, text=name_str)
- if self._account_name_what_is_text is not None:
- swidth = bui.get_string_width(name_str, suppress_warning=True)
- # Eww; number-fudging. Need to recalibrate this if
- # account name scaling changes.
- x = self._sub_width * 0.5 - swidth * 0.75 - 190
-
- bui.textwidget(
- edit=self._account_name_what_is_text,
- position=(x, self._account_name_what_is_y),
- )
def _refresh_achievements(self) -> None:
assert bui.app.classic is not None
@@ -1550,23 +1436,6 @@ class AccountSettingsWindow(bui.Window):
AccountUnlinkWindow(origin_widget=self._unlink_accounts_button)
- def _player_profiles_press(self) -> None:
- # pylint: disable=cyclic-import
- 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()
- bui.containerwidget(edit=self._root_widget, transition='out_left')
- 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:
# If we're waiting on an adapter to give us credentials, abort.
self._signing_in_adapter = None
@@ -1674,14 +1543,14 @@ class AccountSettingsWindow(bui.Window):
bui.getsound('error').play()
else:
# Success! Plug in these credentials which will begin
- # verifying them and set our primary account-handle
- # when finished.
+ # verifying them and set our primary account-handle when
+ # finished.
plus = bui.app.plus
assert plus is not None
plus.accounts.set_primary_credentials(result.credentials)
- # Special case - if the user has explicitly logged out and
- # logged in again with GPGS via this button, warn them that
+ # Special case - if the user has explicitly signed out and
+ # signed in again with GPGS via this button, warn them that
# they need to use the app if they want to switch to a
# different GPGS account.
if (
@@ -1709,9 +1578,9 @@ class AccountSettingsWindow(bui.Window):
# pylint: disable=cyclic-import
from bauiv1lib.connectivity import wait_for_connectivity
- # If we're still waiting for our master-server connection,
- # keep the user informed of this instead of rushing in and
- # failing immediately.
+ # If we're still waiting for our master-server connection, keep
+ # the user informed of this instead of rushing in and failing
+ # immediately.
wait_for_connectivity(on_connected=self._v2_proxy_sign_in)
def _v2_proxy_sign_in(self) -> None:
@@ -1721,44 +1590,6 @@ class AccountSettingsWindow(bui.Window):
assert self._sign_in_v2_proxy_button is not None
V2ProxySignInWindow(origin_widget=self._sign_in_v2_proxy_button)
- def _reset_progress(self) -> None:
- try:
- assert bui.app.classic is not None
- # FIXME: This would need to happen server-side these days.
- if self._can_reset_achievements:
- logging.warning('ach resets not wired up.')
- # bui.app.config['Achievements'] = {}
- # bui.reset_achievements()
- campaign = bui.app.classic.getcampaign('Default')
- campaign.reset() # also writes the config..
- campaign = bui.app.classic.getcampaign('Challenges')
- campaign.reset() # also writes the config..
- except Exception:
- logging.exception('Error resetting co-op campaign progress.')
-
- bui.getsound('shieldDown').play()
- self._refresh()
-
- def _back(self) -> None:
- # pylint: disable=cyclic-import
- 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()
- bui.containerwidget(
- edit=self._root_widget, transition=self._transition_out
- )
-
- if not self._modal:
- assert bui.app.classic is not None
- bui.app.ui_v1.set_main_menu_window(
- MainMenuWindow(transition='in_left').get_root_widget(),
- from_window=self._root_widget,
- )
-
def _save_state(self) -> None:
try:
sel = self._root_widget.get_selected_child()
@@ -1788,15 +1619,6 @@ class AccountSettingsWindow(bui.Window):
logging.exception('Error restoring state for %s.', self)
-def show_what_is_v2_page() -> None:
- """Show the webpage describing V2 accounts."""
- plus = bui.app.plus
- assert plus is not None
-
- bamasteraddr = plus.get_master_server_address(version=2)
- bui.open_url(f'{bamasteraddr}/whatisv2')
-
-
def show_what_is_legacy_unlinking_page() -> None:
"""Show the webpage describing legacy unlinking."""
plus = bui.app.plus
diff --git a/src/assets/ba_data/python/bauiv1lib/achievements.py b/src/assets/ba_data/python/bauiv1lib/achievements.py
index 228e7731..00744b3b 100644
--- a/src/assets/ba_data/python/bauiv1lib/achievements.py
+++ b/src/assets/ba_data/python/bauiv1lib/achievements.py
@@ -40,6 +40,7 @@ class AchievementsWindow(PopupWindow):
size=(self._width, self._height),
scale=scale,
bg_color=bg_color,
+ edge_buffer_scale=4.0, # Try to keep button unobscured.
)
self._cancel_button = bui.buttonwidget(
@@ -74,7 +75,7 @@ class AchievementsWindow(PopupWindow):
scale=0.6,
text=txt_final,
maxwidth=200,
- color=(1, 1, 1, 0.4),
+ color=bui.app.ui_v1.title_color,
)
self._scrollwidget = bui.scrollwidget(
diff --git a/src/assets/ba_data/python/bauiv1lib/appinvite.py b/src/assets/ba_data/python/bauiv1lib/appinvite.py
index 4bcbbcb7..48857c33 100644
--- a/src/assets/ba_data/python/bauiv1lib/appinvite.py
+++ b/src/assets/ba_data/python/bauiv1lib/appinvite.py
@@ -29,7 +29,7 @@ class ShowFriendCodeWindow(bui.Window):
color=(0.45, 0.63, 0.15),
transition='in_scale',
scale=(
- 1.7
+ 1.5
if uiscale is bui.UIScale.SMALL
else 1.35 if uiscale is bui.UIScale.MEDIUM else 1.0
),
diff --git a/src/assets/ba_data/python/bauiv1lib/debug.py b/src/assets/ba_data/python/bauiv1lib/benchmarks.py
similarity index 87%
rename from src/assets/ba_data/python/bauiv1lib/debug.py
rename to src/assets/ba_data/python/bauiv1lib/benchmarks.py
index e57ae074..357be9ee 100644
--- a/src/assets/ba_data/python/bauiv1lib/debug.py
+++ b/src/assets/ba_data/python/bauiv1lib/benchmarks.py
@@ -5,25 +5,27 @@
from __future__ import annotations
import logging
-from typing import cast
+from typing import cast, override
import bauiv1 as bui
-class DebugWindow(bui.Window):
- """Window for debugging internal values."""
+class BenchmarksAndStressTestsWindow(bui.MainWindow):
+ """Window for launching benchmarks or stress tests."""
- def __init__(self, transition: str | None = 'in_right'):
+ def __init__(
+ self,
+ transition: str | None = 'in_right',
+ origin_widget: bui.Widget | None = None,
+ ):
# pylint: disable=too-many-statements
# pylint: disable=cyclic-import
from bauiv1lib import popup
- assert bui.app.classic is not None
- bui.app.ui_v1.set_main_menu_location('Benchmarks & Stress Tests')
uiscale = bui.app.ui_v1.uiscale
- self._width = width = 580
+ self._width = width = 650 if uiscale is bui.UIScale.SMALL else 580
self._height = height = (
- 350
+ 330
if uiscale is bui.UIScale.SMALL
else 420 if uiscale is bui.UIScale.MEDIUM else 520
)
@@ -44,28 +46,41 @@ class DebugWindow(bui.Window):
super().__init__(
root_widget=bui.containerwidget(
size=(width, height),
- transition=transition,
scale=(
- 2.35
+ 2.32
if uiscale is bui.UIScale.SMALL
else 1.55 if uiscale is bui.UIScale.MEDIUM else 1.0
),
stack_offset=(
(0, -30) if uiscale is bui.UIScale.SMALL else (0, 0)
),
- )
+ toolbar_visibility=(
+ 'menu_minimal'
+ if uiscale is bui.UIScale.SMALL
+ else 'menu_full'
+ ),
+ ),
+ transition=transition,
+ origin_widget=origin_widget,
)
- self._done_button = btn = bui.buttonwidget(
- parent=self._root_widget,
- position=(40, height - 67),
- size=(120, 60),
- scale=0.8,
- autoselect=True,
- label=bui.Lstr(resource='doneText'),
- on_activate_call=self._done,
- )
- bui.containerwidget(edit=self._root_widget, cancel_button=btn)
+ if bui.app.ui_v1.uiscale is bui.UIScale.SMALL:
+ bui.containerwidget(
+ edit=self._root_widget, on_cancel_call=self.main_window_back
+ )
+ self._done_button = bui.get_special_widget('back_button')
+ else:
+ self._done_button = btn = bui.buttonwidget(
+ parent=self._root_widget,
+ position=(40, height - 67),
+ size=(120, 60),
+ scale=0.8,
+ autoselect=True,
+ label=bui.Lstr(resource='doneText'),
+ on_activate_call=self.main_window_back,
+ )
+ bui.containerwidget(edit=self._root_widget, cancel_button=btn)
+
bui.textwidget(
parent=self._root_widget,
position=(0, height - 60),
@@ -303,6 +318,16 @@ class DebugWindow(bui.Window):
)
bui.widget(btn, show_buffer_bottom=50)
+ @override
+ def get_main_window_state(self) -> bui.MainWindowState:
+ # Support recreating our window for back/refresh purposes.
+ cls = type(self)
+ return bui.BasicMainWindowState(
+ create_call=lambda transition, origin_widget: cls(
+ transition=transition, origin_widget=origin_widget
+ )
+ )
+
def _stress_test_player_count_decrement(self) -> None:
self._stress_test_player_count = max(
1, self._stress_test_player_count - 1
@@ -370,18 +395,3 @@ class DebugWindow(bui.Window):
round_duration=self._stress_test_round_duration,
)
bui.containerwidget(edit=self._root_widget, transition='out_right')
-
- def _done(self) -> None:
- # pylint: disable=cyclic-import
- 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')
- assert bui.app.classic is not None
- bui.app.ui_v1.set_main_menu_window(
- AdvancedSettingsWindow(transition='in_left').get_root_widget(),
- from_window=self._root_widget,
- )
diff --git a/src/assets/ba_data/python/bauiv1lib/confirm.py b/src/assets/ba_data/python/bauiv1lib/confirm.py
index 394f2fe9..d0e848ad 100644
--- a/src/assets/ba_data/python/bauiv1lib/confirm.py
+++ b/src/assets/ba_data/python/bauiv1lib/confirm.py
@@ -61,7 +61,7 @@ class ConfirmWindow:
toolbar_visibility='menu_minimal_no_back',
parent=bui.get_special_widget('overlay_stack'),
scale=(
- 2.1
+ 1.9
if uiscale is bui.UIScale.SMALL
else 1.5 if uiscale is bui.UIScale.MEDIUM else 1.0
),
diff --git a/src/assets/ba_data/python/bauiv1lib/continues.py b/src/assets/ba_data/python/bauiv1lib/continues.py
index 366fcbb4..a0d12339 100644
--- a/src/assets/ba_data/python/bauiv1lib/continues.py
+++ b/src/assets/ba_data/python/bauiv1lib/continues.py
@@ -38,7 +38,7 @@ class ContinuesWindow(bui.Window):
bui.containerwidget(
size=(self._width, self._height),
background=False,
- toolbar_visibility='menu_currency',
+ toolbar_visibility='menu_store',
transition='in_scale',
scale=1.5,
)
@@ -100,29 +100,8 @@ class ContinuesWindow(bui.Window):
self._tickets_text_base: str | None
self._tickets_text: bui.Widget | None
- if not bui.app.ui_v1.use_toolbars:
- self._tickets_text_base = bui.Lstr(
- resource='getTicketsWindow.youHaveShortText',
- fallback_resource='getTicketsWindow.youHaveText',
- ).evaluate()
- self._tickets_text = bui.textwidget(
- parent=self._root_widget,
- text='',
- flatness=1.0,
- color=(0.2, 1.0, 0.2),
- shadow=1.0,
- position=(
- self._width * 0.5 + width_total_half,
- self._height - 50,
- ),
- size=(0, 0),
- scale=0.35,
- h_align='right',
- v_align='center',
- )
- else:
- self._tickets_text_base = None
- self._tickets_text = None
+ self._tickets_text_base = None
+ self._tickets_text = None
self._counter_text = bui.textwidget(
parent=self._root_widget,
@@ -214,7 +193,7 @@ class ContinuesWindow(bui.Window):
self._on_cancel()
def _on_continue_press(self) -> None:
- from bauiv1lib import gettickets
+ # from bauiv1lib import gettickets
plus = bui.app.plus
assert plus is not None
@@ -238,7 +217,7 @@ class ContinuesWindow(bui.Window):
self._counting_down = False
bui.textwidget(edit=self._counter_text, text='')
bui.getsound('error').play()
- gettickets.show_get_tickets_prompt()
+ # gettickets.show_get_tickets_prompt()
return
if not self._transitioning_out:
bui.getsound('swish').play()
diff --git a/src/assets/ba_data/python/bauiv1lib/coop/browser.py b/src/assets/ba_data/python/bauiv1lib/coop/browser.py
index 81a936ef..7e914fdd 100644
--- a/src/assets/ba_data/python/bauiv1lib/coop/browser.py
+++ b/src/assets/ba_data/python/bauiv1lib/coop/browser.py
@@ -7,12 +7,8 @@
from __future__ import annotations
import logging
-from threading import Thread
-from typing import TYPE_CHECKING
+from typing import TYPE_CHECKING, override
-from bauiv1lib.store.button import StoreButton
-from bauiv1lib.league.rankbutton import LeagueRankButton
-from bauiv1lib.store.browser import StoreBrowserWindow
import bauiv1 as bui
if TYPE_CHECKING:
@@ -21,7 +17,7 @@ if TYPE_CHECKING:
from bauiv1lib.coop.tournamentbutton import TournamentButton
-class CoopBrowserWindow(bui.Window):
+class CoopBrowserWindow(bui.MainWindow):
"""Window for browsing co-op levels/games/etc."""
def __init__(
@@ -37,16 +33,18 @@ class CoopBrowserWindow(bui.Window):
# Preload some modules we use in a background thread so we won't
# have a visual hitch when the user taps them.
- Thread(target=self._preload_modules).start()
+ bui.app.threadpool_submit_no_wait(self._preload_modules)
bui.set_analytics_screen('Coop Window')
app = bui.app
- assert app.classic is not None
+ classic = app.classic
+ assert classic is not None
cfg = app.config
# Quick note to players that tourneys won't work in ballistica
- # core builds. (need to split the word so it won't get subbed out)
+ # core builds. (need to split the word so it won't get subbed
+ # out)
if 'ballistica' + 'kit' == bui.appname():
bui.apptimer(
1.0,
@@ -56,16 +54,6 @@ class CoopBrowserWindow(bui.Window):
),
)
- # If they provided an origin-widget, scale up from that.
- scale_origin: tuple[float, float] | None
- if origin_widget is not None:
- self._transition_out = 'out_scale'
- scale_origin = origin_widget.get_screen_space_center()
- transition = 'in_scale'
- else:
- self._transition_out = 'out_right'
- scale_origin = None
-
# Try to recreate the same number of buttons we had last time so our
# re-selection code works.
self._tournament_button_count = app.config.get('Tournament Rows', 0)
@@ -83,16 +71,14 @@ class CoopBrowserWindow(bui.Window):
self._hard_button_lock_image: bui.Widget | None = None
self._campaign_percent_text: bui.Widget | None = None
- assert bui.app.classic is not None
- uiscale = bui.app.ui_v1.uiscale
+ uiscale = app.ui_v1.uiscale
self._width = 1520 if uiscale is bui.UIScale.SMALL else 1120
self._x_inset = x_inset = 200 if uiscale is bui.UIScale.SMALL else 0
self._height = (
- 657
+ 565
if uiscale is bui.UIScale.SMALL
else 730 if uiscale is bui.UIScale.MEDIUM else 800
)
- app.ui_v1.set_main_menu_location('Coop Select')
self._r = 'coopSelectWindow'
top_extra = 20 if uiscale is bui.UIScale.SMALL else 0
@@ -104,7 +90,7 @@ class CoopBrowserWindow(bui.Window):
if (
self._campaign_difficulty == 'hard'
- and not app.classic.accounts.have_pro_options()
+ and not classic.accounts.have_pro_options()
):
plus.add_v1_account_transaction(
{
@@ -119,99 +105,114 @@ class CoopBrowserWindow(bui.Window):
root_widget=bui.containerwidget(
size=(self._width, self._height + top_extra),
toolbar_visibility='menu_full',
- scale_origin_stack_offset=scale_origin,
stack_offset=(
- (0, -15)
+ (0, -17)
if uiscale is bui.UIScale.SMALL
else (0, 0) if uiscale is bui.UIScale.MEDIUM else (0, 0)
),
- transition=transition,
scale=(
- 1.2
+ 1.28
if uiscale is bui.UIScale.SMALL
else 0.8 if uiscale is bui.UIScale.MEDIUM else 0.75
),
- )
+ ),
+ transition=transition,
+ origin_widget=origin_widget,
)
- if app.ui_v1.use_toolbars and uiscale is bui.UIScale.SMALL:
- self._back_button = None
+ if uiscale is bui.UIScale.SMALL:
+ self._back_button = bui.get_special_widget('back_button')
+ bui.containerwidget(
+ edit=self._root_widget, on_cancel_call=self.main_window_back
+ )
else:
self._back_button = bui.buttonwidget(
parent=self._root_widget,
- position=(
- 75 + x_inset,
- self._height
- - 87
- - (4 if uiscale is bui.UIScale.SMALL else 0),
- ),
+ position=(75 + x_inset, self._height - 87),
size=(120, 60),
scale=1.2,
autoselect=True,
label=bui.Lstr(resource='backText'),
button_type='back',
+ on_activate_call=self.main_window_back,
)
-
- self._league_rank_button: LeagueRankButton | None
- self._store_button: StoreButton | None
- self._store_button_widget: bui.Widget | None
- self._league_rank_button_widget: bui.Widget | None
-
- if not app.ui_v1.use_toolbars:
- prb = self._league_rank_button = LeagueRankButton(
- parent=self._root_widget,
- position=(
- self._width - (282 + x_inset),
- self._height
- - 85
- - (4 if uiscale is bui.UIScale.SMALL else 0),
- ),
- size=(100, 60),
- color=(0.4, 0.4, 0.9),
- textcolor=(0.9, 0.9, 2.0),
- scale=1.05,
- on_activate_call=bui.WeakCall(self._switch_to_league_rankings),
- )
- self._league_rank_button_widget = prb.get_button()
-
- sbtn = self._store_button = StoreButton(
- parent=self._root_widget,
- position=(
- self._width - (170 + x_inset),
- self._height
- - 85
- - (4 if uiscale is bui.UIScale.SMALL else 0),
- ),
- size=(100, 60),
- color=(0.6, 0.4, 0.7),
- show_tickets=True,
- button_type='square',
- sale_scale=0.85,
- textcolor=(0.9, 0.7, 1.0),
- scale=1.05,
- on_activate_call=bui.WeakCall(self._switch_to_score, None),
- )
- self._store_button_widget = sbtn.get_button()
- bui.widget(
+ bui.buttonwidget(
edit=self._back_button,
- right_widget=self._league_rank_button_widget,
+ button_type='backSmall',
+ size=(60, 50),
+ position=(
+ 75 + x_inset,
+ self._height - 87 + 6,
+ ),
+ label=bui.charstr(bui.SpecialChar.BACK),
)
- bui.widget(
- edit=self._league_rank_button_widget,
- left_widget=self._back_button,
+ bui.containerwidget(
+ edit=self._root_widget, cancel_button=self._back_button
)
- else:
- self._league_rank_button = None
- self._store_button = None
- self._store_button_widget = None
- self._league_rank_button_widget = None
+
+ # self._league_rank_button: LeagueRankButton | None
+ # self._store_button: StoreButton | None
+ # self._store_button_widget: bui.Widget | None
+ # self._league_rank_button_widget: bui.Widget | None
+
+ # if not app.ui_v1.use_toolbars:
+ # prb = self._league_rank_button = LeagueRankButton(
+ # parent=self._root_widget,
+ # position=(
+ # self._width - (282 + x_inset),
+ # self._height
+ # - 85
+ # - (4 if uiscale is bui.UIScale.SMALL else 0),
+ # ),
+ # size=(100, 60),
+ # color=(0.4, 0.4, 0.9),
+ # textcolor=(0.9, 0.9, 2.0),
+ # scale=1.05,
+ # on_activate_call=bui.WeakCall(
+ # self._switch_to_league_rankings),
+ # )
+ # self._league_rank_button_widget = prb.get_button()
+
+ # sbtn = self._store_button = StoreButton(
+ # parent=self._root_widget,
+ # position=(
+ # self._width - (170 + x_inset),
+ # self._height
+ # - 85
+ # - (4 if uiscale is bui.UIScale.SMALL else 0),
+ # ),
+ # size=(100, 60),
+ # color=(0.6, 0.4, 0.7),
+ # show_tickets=True,
+ # button_type='square',
+ # sale_scale=0.85,
+ # textcolor=(0.9, 0.7, 1.0),
+ # scale=1.05,
+ # on_activate_call=bui.WeakCall(self._switch_to_score, None),
+ # )
+ # self._store_button_widget = sbtn.get_button()
+ # assert self._back_button is not None
+ # bui.widget(
+ # edit=self._back_button,
+ # right_widget=self._league_rank_button_widget,
+ # )
+ # bui.widget(
+ # edit=self._league_rank_button_widget,
+ # left_widget=self._back_button,
+ # )
+ # else:
+ # self._league_rank_button = None
+ # self._store_button = None
+ # self._store_button_widget = None
+ # self._league_rank_button_widget = None
# Move our corner buttons dynamically to keep them out of the way of
# the party icon :-(
- self._update_corner_button_positions()
- self._update_corner_button_positions_timer = bui.AppTimer(
- 1.0, bui.WeakCall(self._update_corner_button_positions), repeat=True
- )
+ # self._update_corner_button_positions()
+ # self._update_corner_button_positions_timer = bui.AppTimer(
+ # 1.0, bui.WeakCall(
+ # self._update_corner_button_positions), repeat=True
+ # )
self._last_tournament_query_time: float | None = None
self._last_tournament_query_response_time: float | None = None
@@ -245,31 +246,14 @@ class CoopBrowserWindow(bui.Window):
v_align='center',
)
- if app.ui_v1.use_toolbars and uiscale is bui.UIScale.SMALL:
+ if uiscale is bui.UIScale.SMALL:
bui.textwidget(edit=txt, text='')
- if self._back_button is not None:
- bui.buttonwidget(
- edit=self._back_button,
- button_type='backSmall',
- size=(60, 50),
- position=(
- 75 + x_inset,
- self._height
- - 87
- - (4 if uiscale is bui.UIScale.SMALL else 0)
- + 6,
- ),
- label=bui.charstr(bui.SpecialChar.BACK),
- )
-
self._selected_row = cfg.get('Selected Coop Row', None)
self._scroll_width = self._width - (130 + 2 * x_inset)
self._scroll_height = self._height - (
- 190
- if uiscale is bui.UIScale.SMALL and app.ui_v1.use_toolbars
- else 160
+ 185 if uiscale is bui.UIScale.SMALL else 160
)
self._subcontainerwidth = 800.0
@@ -280,7 +264,7 @@ class CoopBrowserWindow(bui.Window):
highlight=False,
position=(
(65 + x_inset, 120)
- if uiscale is bui.UIScale.SMALL and app.ui_v1.use_toolbars
+ if uiscale is bui.UIScale.SMALL
else (65 + x_inset, 70)
),
size=(self._scroll_width, self._scroll_height),
@@ -309,17 +293,17 @@ class CoopBrowserWindow(bui.Window):
# each one of those tournaments, go ahead and display it as a
# starting point.
if (
- app.classic.accounts.account_tournament_list is not None
- and app.classic.accounts.account_tournament_list[0]
+ classic.accounts.account_tournament_list is not None
+ and classic.accounts.account_tournament_list[0]
== plus.get_v1_account_state_num()
and all(
- t_id in app.classic.accounts.tournament_info
- for t_id in app.classic.accounts.account_tournament_list[1]
+ t_id in classic.accounts.tournament_info
+ for t_id in classic.accounts.account_tournament_list[1]
)
):
tourney_data = [
- app.classic.accounts.tournament_info[t_id]
- for t_id in app.classic.accounts.account_tournament_list[1]
+ classic.accounts.tournament_info[t_id]
+ for t_id in classic.accounts.account_tournament_list[1]
]
self._update_for_data(tourney_data)
@@ -329,37 +313,24 @@ class CoopBrowserWindow(bui.Window):
)
self._update()
- def _update_corner_button_positions(self) -> None:
- assert bui.app.classic is not None
- uiscale = bui.app.ui_v1.uiscale
- offs = (
- -55
- if uiscale is bui.UIScale.SMALL and bui.is_party_icon_visible()
- else 0
+ @override
+ def get_main_window_state(self) -> bui.MainWindowState:
+ # Support recreating our window for back/refresh purposes.
+ cls = type(self)
+ return bui.BasicMainWindowState(
+ create_call=lambda transition, origin_widget: cls(
+ transition=transition, origin_widget=origin_widget
+ )
)
- if self._league_rank_button is not None:
- self._league_rank_button.set_position(
- (
- self._width - 282 + offs - self._x_inset,
- self._height
- - 85
- - (4 if uiscale is bui.UIScale.SMALL else 0),
- )
- )
- if self._store_button is not None:
- self._store_button.set_position(
- (
- self._width - 170 + offs - self._x_inset,
- self._height
- - 85
- - (4 if uiscale is bui.UIScale.SMALL else 0),
- )
- )
- # noinspection PyUnresolvedReferences
+ @override
+ def on_main_window_close(self) -> None:
+ self._save_state()
+
@staticmethod
def _preload_modules() -> None:
"""Preload modules we use; avoids hitches (called in bg thread)."""
+ # pylint: disable=cyclic-import
import bauiv1lib.purchase as _unused1
import bauiv1lib.coop.gamebutton as _unused2
import bauiv1lib.confirm as _unused3
@@ -382,9 +353,9 @@ class CoopBrowserWindow(bui.Window):
cur_time = bui.apptime()
- # If its been a while since we got a tournament update, consider the
- # data invalid (prevents us from joining tournaments if our internet
- # connection goes down for a while).
+ # If its been a while since we got a tournament update, consider
+ # the data invalid (prevents us from joining tournaments if our
+ # internet connection goes down for a while).
if (
self._last_tournament_query_response_time is None
or bui.apptime() - self._last_tournament_query_response_time
@@ -466,8 +437,8 @@ class CoopBrowserWindow(bui.Window):
logging.exception('Error updating campaign lock.')
def _update_for_data(self, data: list[dict[str, Any]] | None) -> None:
- # If the number of tournaments or challenges in the data differs from
- # our current arrangement, refresh with the new number.
+ # If the number of tournaments or challenges in the data differs
+ # from our current arrangement, refresh with the new number.
if (data is None and self._tournament_button_count != 0) or (
data is not None and (len(data) != self._tournament_button_count)
):
@@ -541,6 +512,7 @@ class CoopBrowserWindow(bui.Window):
def _refresh_campaign_row(self) -> None:
# pylint: disable=too-many-locals
+ # pylint: disable=too-many-statements
# pylint: disable=cyclic-import
from bauiv1lib.coop.gamebutton import GameButton
@@ -660,14 +632,21 @@ class CoopBrowserWindow(bui.Window):
bui.widget(edit=campaign_buttons[0], left_widget=self._easy_button)
- if self._back_button is not None:
- bui.widget(edit=self._easy_button, up_widget=self._back_button)
- for btn in campaign_buttons:
- bui.widget(
- edit=btn,
- up_widget=self._back_button,
- down_widget=next_widget_down,
- )
+ # bui.widget(edit=self._easy_button)
+ # if self._back_button is not None:
+ bui.widget(
+ edit=self._easy_button,
+ left_widget=self._back_button,
+ up_widget=self._back_button,
+ )
+ bui.widget(edit=self._hard_button, left_widget=self._back_button)
+ for btn in campaign_buttons:
+ bui.widget(
+ edit=btn,
+ up_widget=self._back_button,
+ )
+ for btn in campaign_buttons:
+ bui.widget(edit=btn, down_widget=next_widget_down)
# Update our existing percent-complete text.
assert bui.app.classic is not None
@@ -721,7 +700,7 @@ class CoopBrowserWindow(bui.Window):
tourney_row_height = 200
self._subcontainerheight = (
- 620 + self._tournament_button_count * tourney_row_height
+ 700 + self._tournament_button_count * tourney_row_height
)
self._subcontainer = bui.containerwidget(
@@ -736,15 +715,11 @@ class CoopBrowserWindow(bui.Window):
bui.containerwidget(
edit=self._root_widget, selected_child=self._scrollwidget
)
- if self._back_button is not None:
- bui.containerwidget(
- edit=self._root_widget, cancel_button=self._back_button
- )
w_parent = self._subcontainer
h_base = 6
- v = self._subcontainerheight - 73
+ v = self._subcontainerheight - 90
self._campaign_percent_text = bui.textwidget(
parent=w_parent,
@@ -757,7 +732,7 @@ class CoopBrowserWindow(bui.Window):
scale=1.1,
)
- row_v_show_buffer = 100
+ row_v_show_buffer = 80
v -= 198
h_scroll = bui.hscrollwidget(
@@ -817,17 +792,17 @@ class CoopBrowserWindow(bui.Window):
textcolor=(0.7, 0.6, 0.75),
autoselect=True,
up_widget=self._campaign_h_scroll,
+ left_widget=self._back_button,
on_activate_call=self._on_tournament_info_press,
)
bui.widget(
edit=self._tournament_info_button,
- left_widget=self._tournament_info_button,
right_widget=self._tournament_info_button,
)
- # Say 'unavailable' if there are zero tournaments, and if we're not
- # signed in add that as well (that's probably why we see
- # no tournaments).
+ # Say 'unavailable' if there are zero tournaments, and if we're
+ # not signed in add that as well (that's probably why we see no
+ # tournaments).
if self._tournament_button_count == 0:
unavailable_text = bui.Lstr(resource='unavailableText')
if plus.get_v1_account_state() != 'signed_in':
@@ -989,6 +964,7 @@ class CoopBrowserWindow(bui.Window):
if i + 1 < len(self._tournament_buttons)
else custom_h_scroll
),
+ left_widget=self._back_button,
)
bui.widget(
edit=tbutton.more_scores_button,
@@ -1007,7 +983,7 @@ class CoopBrowserWindow(bui.Window):
),
)
- for btn in self._custom_buttons:
+ for i, btn in enumerate(self._custom_buttons):
try:
bui.widget(
edit=btn.get_button(),
@@ -1017,18 +993,14 @@ class CoopBrowserWindow(bui.Window):
else self._tournament_info_button
),
)
+ if i == 0:
+ bui.widget(
+ edit=btn.get_button(), left_widget=self._back_button
+ )
+
except Exception:
logging.exception('Error wiring up custom buttons.')
- if self._back_button is not None:
- bui.buttonwidget(
- edit=self._back_button, on_activate_call=self._back
- )
- else:
- bui.containerwidget(
- edit=self._root_widget, on_cancel_call=self._back
- )
-
# There's probably several 'onSelected' callbacks pushed onto the
# event queue.. we need to push ours too so we're enabled *after* them.
bui.pushcall(self._enable_selectable_callback)
@@ -1041,63 +1013,63 @@ class CoopBrowserWindow(bui.Window):
def _enable_selectable_callback(self) -> None:
self._do_selection_callbacks = True
- def _switch_to_league_rankings(self) -> None:
- # pylint: disable=cyclic-import
- from bauiv1lib.account import show_sign_in_prompt
- from bauiv1lib.league.rankwindow import LeagueRankWindow
+ # def _switch_to_league_rankings(self) -> None:
+ # # pylint: disable=cyclic-import
+ # from bauiv1lib.account import show_sign_in_prompt
+ # 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
+ # # 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
- assert plus is not None
+ # plus = bui.app.plus
+ # assert plus is not None
- if plus.get_v1_account_state() != 'signed_in':
- show_sign_in_prompt()
- return
- self._save_state()
- bui.containerwidget(edit=self._root_widget, transition='out_left')
- assert self._league_rank_button is not None
- assert bui.app.classic is not None
- bui.app.ui_v1.set_main_menu_window(
- LeagueRankWindow(
- origin_widget=self._league_rank_button.get_button()
- ).get_root_widget(),
- from_window=self._root_widget,
- )
+ # if plus.get_v1_account_state() != 'signed_in':
+ # show_sign_in_prompt()
+ # return
+ # self._save_state()
+ # bui.containerwidget(edit=self._root_widget, transition='out_left')
+ # assert self._league_rank_button is not None
+ # assert bui.app.classic is not None
+ # bui.app.ui_v1.set_main_window(
+ # LeagueRankWindow(
+ # origin_widget=self._league_rank_button.get_button()
+ # ),
+ # from_window=self,
+ # )
- def _switch_to_score(
- self,
- show_tab: (
- StoreBrowserWindow.TabID | None
- ) = StoreBrowserWindow.TabID.EXTRAS,
- ) -> None:
- # pylint: disable=cyclic-import
- from bauiv1lib.account import show_sign_in_prompt
+ # def _switch_to_score(
+ # self,
+ # show_tab: (
+ # StoreBrowserWindow.TabID | None
+ # ) = StoreBrowserWindow.TabID.EXTRAS,
+ # ) -> None:
+ # # pylint: disable=cyclic-import
+ # 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
+ # # 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
- assert plus is not None
+ # plus = bui.app.plus
+ # assert plus is not None
- if plus.get_v1_account_state() != 'signed_in':
- show_sign_in_prompt()
- return
- self._save_state()
- bui.containerwidget(edit=self._root_widget, transition='out_left')
- assert self._store_button is not None
- assert bui.app.classic is not None
- bui.app.ui_v1.set_main_menu_window(
- StoreBrowserWindow(
- origin_widget=self._store_button.get_button(),
- show_tab=show_tab,
- back_location='CoopBrowserWindow',
- ).get_root_widget(),
- from_window=self._root_widget,
- )
+ # if plus.get_v1_account_state() != 'signed_in':
+ # show_sign_in_prompt()
+ # return
+ # self._save_state()
+ # bui.containerwidget(edit=self._root_widget, transition='out_left')
+ # assert self._store_button is not None
+ # assert bui.app.classic is not None
+ # bui.app.ui_v1.set_main_window(
+ # StoreBrowserWindow(
+ # origin_widget=self._store_button.get_button(),
+ # show_tab=show_tab,
+ # back_location='CoopBrowserWindow',
+ # ),
+ # from_window=self,
+ # )
def is_tourney_data_up_to_date(self) -> bool:
"""Return whether our tourney data is up to date."""
@@ -1252,24 +1224,23 @@ class CoopBrowserWindow(bui.Window):
position=tournament_button.button.get_screen_space_center(),
)
- def _back(self) -> None:
- # pylint: disable=cyclic-import
- from bauiv1lib.play import PlayWindow
+ # def _back(self) -> None:
+ # # pylint: disable=cyclic-import
+ # 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
+ # # 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.
- self._save_state()
- bui.containerwidget(
- edit=self._root_widget, transition=self._transition_out
- )
- assert bui.app.classic is not None
- bui.app.ui_v1.set_main_menu_window(
- PlayWindow(transition='in_left').get_root_widget(),
- from_window=self._root_widget,
- )
+ # # If something is selected, store it.
+ # self._save_state()
+ # bui.containerwidget(
+ # edit=self._root_widget, transition=self._transition_out
+ # )
+ # assert bui.app.classic is not None
+ # bui.app.ui_v1.set_main_window(
+ # PlayWindow(transition='in_left'), from_window=self, is_back=True
+ # )
def _save_state(self) -> None:
cfg = bui.app.config
@@ -1277,10 +1248,10 @@ class CoopBrowserWindow(bui.Window):
sel = self._root_widget.get_selected_child()
if sel == self._back_button:
sel_name = 'Back'
- elif sel == self._store_button_widget:
- sel_name = 'Store'
- elif sel == self._league_rank_button_widget:
- sel_name = 'PowerRanking'
+ # elif sel == self._store_button_widget:
+ # sel_name = 'Store'
+ # elif sel == self._league_rank_button_widget:
+ # sel_name = 'PowerRanking'
elif sel == self._scrollwidget:
sel_name = 'Scroll'
else:
@@ -1305,10 +1276,10 @@ class CoopBrowserWindow(bui.Window):
sel = self._back_button
elif sel_name == 'Scroll':
sel = self._scrollwidget
- elif sel_name == 'PowerRanking':
- sel = self._league_rank_button_widget
- elif sel_name == 'Store':
- sel = self._store_button_widget
+ # elif sel_name == 'PowerRanking':
+ # sel = self._league_rank_button_widget
+ # elif sel_name == 'Store':
+ # sel = self._store_button_widget
else:
sel = self._scrollwidget
bui.containerwidget(edit=self._root_widget, selected_child=sel)
diff --git a/src/assets/ba_data/python/bauiv1lib/creditslist.py b/src/assets/ba_data/python/bauiv1lib/credits.py
similarity index 82%
rename from src/assets/ba_data/python/bauiv1lib/creditslist.py
rename to src/assets/ba_data/python/bauiv1lib/credits.py
index 711d3ffc..5c735e49 100644
--- a/src/assets/ba_data/python/bauiv1lib/creditslist.py
+++ b/src/assets/ba_data/python/bauiv1lib/credits.py
@@ -6,7 +6,7 @@ from __future__ import annotations
import os
import logging
-from typing import TYPE_CHECKING
+from typing import TYPE_CHECKING, override
import bauiv1 as bui
@@ -14,30 +14,23 @@ if TYPE_CHECKING:
from typing import Sequence
-class CreditsListWindow(bui.Window):
+class CreditsWindow(bui.MainWindow):
"""Window for displaying game credits."""
- def __init__(self, origin_widget: bui.Widget | None = None):
+ def __init__(
+ self,
+ transition: str | None = 'in_right',
+ origin_widget: bui.Widget | None = None,
+ ):
# pylint: disable=too-many-locals
# pylint: disable=too-many-statements
import json
bui.set_analytics_screen('Credits Window')
- # if they provided an origin-widget, scale up from that
- scale_origin: tuple[float, float] | None
- if origin_widget is not None:
- self._transition_out = 'out_scale'
- scale_origin = origin_widget.get_screen_space_center()
- transition = 'in_scale'
- else:
- self._transition_out = 'out_right'
- scale_origin = None
- transition = 'in_right'
-
assert bui.app.classic is not None
uiscale = bui.app.ui_v1.uiscale
- width = 870 if uiscale is bui.UIScale.SMALL else 670
+ width = 990 if uiscale is bui.UIScale.SMALL else 670
x_inset = 100 if uiscale is bui.UIScale.SMALL else 0
height = 398 if uiscale is bui.UIScale.SMALL else 500
@@ -45,36 +38,37 @@ class CreditsListWindow(bui.Window):
super().__init__(
root_widget=bui.containerwidget(
size=(width, height),
- transition=transition,
- toolbar_visibility='menu_minimal',
- scale_origin_stack_offset=scale_origin,
- scale=(
- 2.0
+ toolbar_visibility=(
+ 'menu_minimal'
if uiscale is bui.UIScale.SMALL
- else 1.3 if uiscale is bui.UIScale.MEDIUM else 1.0
+ else 'menu_full'
+ ),
+ scale=(
+ 1.8
+ if uiscale is bui.UIScale.SMALL
+ else 1.2 if uiscale is bui.UIScale.MEDIUM else 1.0
),
stack_offset=(
- (0, -8) if uiscale is bui.UIScale.SMALL else (0, 0)
+ (0, 0) if uiscale is bui.UIScale.SMALL else (0, 0)
),
- )
+ ),
+ transition=transition,
+ origin_widget=origin_widget,
)
- if bui.app.ui_v1.use_toolbars and uiscale is bui.UIScale.SMALL:
+ if uiscale is bui.UIScale.SMALL:
bui.containerwidget(
- edit=self._root_widget, on_cancel_call=self._back
+ edit=self._root_widget, on_cancel_call=self.main_window_back
)
else:
btn = bui.buttonwidget(
parent=self._root_widget,
- position=(
- 40 + x_inset,
- height - (68 if uiscale is bui.UIScale.SMALL else 62),
- ),
+ position=(40 + x_inset, height - 62),
size=(140, 60),
scale=0.8,
label=bui.Lstr(resource='backText'),
button_type='back',
- on_activate_call=self._back,
+ on_activate_call=self.main_window_back,
autoselect=True,
)
bui.containerwidget(edit=self._root_widget, cancel_button=btn)
@@ -84,7 +78,7 @@ class CreditsListWindow(bui.Window):
button_type='backSmall',
position=(
40 + x_inset,
- height - (68 if uiscale is bui.UIScale.SMALL else 62) + 5,
+ height - 62 + 5,
),
size=(60, 48),
label=bui.charstr(bui.SpecialChar.BACK),
@@ -92,8 +86,9 @@ class CreditsListWindow(bui.Window):
bui.textwidget(
parent=self._root_widget,
- position=(0, height - (59 if uiscale is bui.UIScale.SMALL else 54)),
+ position=(0, height - (65 if uiscale is bui.UIScale.SMALL else 54)),
size=(width, 30),
+ scale=0.8 if uiscale is bui.UIScale.SMALL else 1.0,
text=bui.Lstr(
resource=f'{self._r}.titleText',
subs=[('${APP_NAME}', bui.Lstr(resource='titleText'))],
@@ -111,16 +106,15 @@ class CreditsListWindow(bui.Window):
capture_arrows=True,
)
- if bui.app.ui_v1.use_toolbars:
+ bui.widget(
+ edit=scroll,
+ right_widget=bui.get_special_widget('squad_button'),
+ )
+ if uiscale is bui.UIScale.SMALL:
bui.widget(
edit=scroll,
- right_widget=bui.get_special_widget('party_button'),
+ left_widget=bui.get_special_widget('back_button'),
)
- if uiscale is bui.UIScale.SMALL:
- bui.widget(
- edit=scroll,
- left_widget=bui.get_special_widget('back_button'),
- )
def _format_names(names2: Sequence[str], inset: float) -> str:
sval = ''
@@ -354,18 +348,12 @@ class CreditsListWindow(bui.Window):
)
voffs -= line_height
- def _back(self) -> None:
- 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(
- edit=self._root_widget, transition=self._transition_out
- )
- assert bui.app.classic is not None
- bui.app.ui_v1.set_main_menu_window(
- MainMenuWindow(transition='in_left').get_root_widget(),
- from_window=self._root_widget,
+ @override
+ def get_main_window_state(self) -> bui.MainWindowState:
+ # Support recreating our window for back/refresh purposes.
+ cls = type(self)
+ return bui.BasicMainWindowState(
+ create_call=lambda transition, origin_widget: cls(
+ transition=transition, origin_widget=origin_widget
+ )
)
diff --git a/src/assets/ba_data/python/bauiv1lib/discord.py b/src/assets/ba_data/python/bauiv1lib/discord.py
index de891b29..40aa34f1 100644
--- a/src/assets/ba_data/python/bauiv1lib/discord.py
+++ b/src/assets/ba_data/python/bauiv1lib/discord.py
@@ -51,7 +51,7 @@ class DiscordWindow(bui.Window):
)
)
- if app.ui_v1.use_toolbars and uiscale is bui.UIScale.SMALL:
+ if uiscale is bui.UIScale.SMALL:
bui.containerwidget(
edit=self._root_widget, on_cancel_call=self._do_back
)
diff --git a/src/assets/ba_data/python/bauiv1lib/fileselector.py b/src/assets/ba_data/python/bauiv1lib/fileselector.py
index a2547b26..df17ad11 100644
--- a/src/assets/ba_data/python/bauiv1lib/fileselector.py
+++ b/src/assets/ba_data/python/bauiv1lib/fileselector.py
@@ -16,7 +16,7 @@ if TYPE_CHECKING:
from typing import Any, Callable, Sequence
-class FileSelectorWindow(bui.Window):
+class FileSelectorWindow(bui.MainWindow):
"""Window for selecting files."""
def __init__(
@@ -26,6 +26,8 @@ class FileSelectorWindow(bui.Window):
show_base_path: bool = True,
valid_file_extensions: Sequence[str] | None = None,
allow_folders: bool = False,
+ transition: str | None = 'in_right',
+ origin_widget: bui.Widget | None = None,
):
if valid_file_extensions is None:
valid_file_extensions = []
@@ -51,7 +53,6 @@ class FileSelectorWindow(bui.Window):
super().__init__(
root_widget=bui.containerwidget(
size=(self._width, self._height),
- transition='in_right',
scale=(
2.23
if uiscale is bui.UIScale.SMALL
@@ -60,7 +61,9 @@ class FileSelectorWindow(bui.Window):
stack_offset=(
(0, -35) if uiscale is bui.UIScale.SMALL else (0, 0)
),
- )
+ ),
+ transition=transition,
+ origin_widget=origin_widget,
)
bui.textwidget(
parent=self._root_widget,
@@ -135,6 +138,31 @@ class FileSelectorWindow(bui.Window):
)
self._set_path(path)
+ @override
+ def get_main_window_state(self) -> bui.MainWindowState:
+ # Support recreating our window for back/refresh purposes.
+ cls = type(self)
+
+ # Pull everything out of self here. If we do it below in the lambda,
+ # we'll keep self alive which is bad.
+ path = self._base_path
+ callback = self._callback
+ show_base_path = self._show_base_path
+ valid_file_extensions = self._valid_file_extensions
+ allow_folders = self._allow_folders
+
+ return bui.BasicMainWindowState(
+ create_call=lambda transition, origin_widget: cls(
+ transition=transition,
+ origin_widget=origin_widget,
+ path=path,
+ callback=callback,
+ show_base_path=show_base_path,
+ valid_file_extensions=valid_file_extensions,
+ allow_folders=allow_folders,
+ )
+ )
+
def _on_up_press(self) -> None:
self._on_entry_activated('..')
@@ -458,6 +486,7 @@ class FileSelectorWindow(bui.Window):
)
def _cancel(self) -> None:
- bui.containerwidget(edit=self._root_widget, transition='out_right')
+ # bui.containerwidget(edit=self._root_widget, transition='out_right')
+ self.main_window_back()
if self._callback is not None:
self._callback(None)
diff --git a/src/assets/ba_data/python/bauiv1lib/gather/__init__.py b/src/assets/ba_data/python/bauiv1lib/gather/__init__.py
index 6e35d6a8..e71d2cc3 100644
--- a/src/assets/ba_data/python/bauiv1lib/gather/__init__.py
+++ b/src/assets/ba_data/python/bauiv1lib/gather/__init__.py
@@ -7,6 +7,7 @@ from __future__ import annotations
import weakref
import logging
from enum import Enum
+from typing import override
from bauiv1lib.tabs import TabRow
import bauiv1 as bui
@@ -52,7 +53,7 @@ class GatherTab:
"""Called when the parent window is restoring state."""
-class GatherWindow(bui.Window):
+class GatherWindow(bui.MainWindow):
"""Window for joining/inviting friends."""
class TabID(Enum):
@@ -82,22 +83,11 @@ class GatherWindow(bui.Window):
assert plus is not None
bui.set_analytics_screen('Gather Window')
- scale_origin: tuple[float, float] | None
- if origin_widget is not None:
- self._transition_out = 'out_scale'
- scale_origin = origin_widget.get_screen_space_center()
- transition = 'in_scale'
- else:
- self._transition_out = 'out_right'
- scale_origin = None
- assert bui.app.classic is not None
- bui.app.ui_v1.set_main_menu_location('Gather')
- bui.set_party_icon_always_visible(True)
uiscale = bui.app.ui_v1.uiscale
- self._width = 1440 if uiscale is bui.UIScale.SMALL else 1040
+ self._width = 1640 if uiscale is bui.UIScale.SMALL else 1040
x_offs = 200 if uiscale is bui.UIScale.SMALL else 0
self._height = (
- 582
+ 550
if uiscale is bui.UIScale.SMALL
else 680 if uiscale is bui.UIScale.MEDIUM else 800
)
@@ -108,25 +98,29 @@ class GatherWindow(bui.Window):
super().__init__(
root_widget=bui.containerwidget(
size=(self._width, self._height + extra_top),
- transition=transition,
- toolbar_visibility='menu_minimal',
- scale_origin_stack_offset=scale_origin,
- scale=(
- 1.3
+ toolbar_visibility=(
+ 'menu_tokens'
if uiscale is bui.UIScale.SMALL
- else 0.97 if uiscale is bui.UIScale.MEDIUM else 0.8
+ else 'menu_full'
+ ),
+ scale=(
+ 1.15
+ if uiscale is bui.UIScale.SMALL
+ else 0.95 if uiscale is bui.UIScale.MEDIUM else 0.7
),
stack_offset=(
- (0, -11)
+ (0, -20)
if uiscale is bui.UIScale.SMALL
else (0, 0) if uiscale is bui.UIScale.MEDIUM else (0, 0)
),
- )
+ ),
+ transition=transition,
+ origin_widget=origin_widget,
)
- if uiscale is bui.UIScale.SMALL and bui.app.ui_v1.use_toolbars:
+ if uiscale is bui.UIScale.SMALL:
bui.containerwidget(
- edit=self._root_widget, on_cancel_call=self._back
+ edit=self._root_widget, on_cancel_call=self.main_window_back
)
self._back_button = None
else:
@@ -138,7 +132,7 @@ class GatherWindow(bui.Window):
autoselect=True,
label=bui.Lstr(resource='backText'),
button_type='back',
- on_activate_call=self._back,
+ on_activate_call=self.main_window_back,
)
bui.containerwidget(edit=self._root_widget, cancel_button=btn)
bui.buttonwidget(
@@ -151,7 +145,7 @@ class GatherWindow(bui.Window):
condensed = uiscale is not bui.UIScale.LARGE
t_offs_y = (
- 0 if not condensed else 25 if uiscale is bui.UIScale.MEDIUM else 17
+ 0 if not condensed else 25 if uiscale is bui.UIScale.MEDIUM else 33
)
bui.textwidget(
parent=self._root_widget,
@@ -161,12 +155,12 @@ class GatherWindow(bui.Window):
scale=(
1.5
if not condensed
- else 1.0 if uiscale is bui.UIScale.MEDIUM else 0.6
+ else 1.0 if uiscale is bui.UIScale.MEDIUM else 1.0
),
h_align='center',
v_align='center',
text=bui.Lstr(resource=f'{self._r}.titleText'),
- maxwidth=550,
+ maxwidth=320,
)
scroll_buffer_h = 130 + 2 * x_offs
@@ -218,16 +212,15 @@ class GatherWindow(bui.Window):
if tabtype is not None:
self._tabs[tab_id] = tabtype(self)
- if bui.app.ui_v1.use_toolbars:
+ bui.widget(
+ edit=self._tab_row.tabs[tabdefs[-1][0]].button,
+ right_widget=bui.get_special_widget('squad_button'),
+ )
+ if uiscale is bui.UIScale.SMALL:
bui.widget(
- edit=self._tab_row.tabs[tabdefs[-1][0]].button,
- right_widget=bui.get_special_widget('party_button'),
+ edit=self._tab_row.tabs[tabdefs[0][0]].button,
+ left_widget=bui.get_special_widget('back_button'),
)
- if uiscale is bui.UIScale.SMALL:
- bui.widget(
- edit=self._tab_row.tabs[tabdefs[0][0]].button,
- left_widget=bui.get_special_widget('back_button'),
- )
self._scroll_width = self._width - scroll_buffer_h
self._scroll_height = self._height - 180.0 + tabs_top_extra
@@ -257,24 +250,36 @@ class GatherWindow(bui.Window):
self._restore_state()
- def __del__(self) -> None:
- bui.set_party_icon_always_visible(False)
+ @override
+ def get_main_window_state(self) -> bui.MainWindowState:
+ # Support recreating our window for back/refresh purposes.
+ cls = type(self)
+ return bui.BasicMainWindowState(
+ create_call=lambda transition, origin_widget: cls(
+ transition=transition, origin_widget=origin_widget
+ )
+ )
+
+ @override
+ def on_main_window_close(self) -> None:
+ self._save_state()
def playlist_select(self, origin_widget: bui.Widget) -> None:
"""Called by the private-hosting tab to select a playlist."""
from bauiv1lib.play import PlayWindow
+ classic = bui.app.classic
+ 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
self._save_state()
bui.containerwidget(edit=self._root_widget, transition='out_left')
- assert bui.app.classic is not None
- bui.app.ui_v1.selecting_private_party_playlist = True
- bui.app.ui_v1.set_main_menu_window(
- PlayWindow(origin_widget=origin_widget).get_root_widget(),
- from_window=self._root_widget,
+ classic.selecting_private_party_playlist = True
+ bui.app.ui_v1.set_main_window(
+ PlayWindow(origin_widget=origin_widget), from_window=self
)
def _set_tab(self, tab_id: TabID) -> None:
@@ -340,8 +345,6 @@ class GatherWindow(bui.Window):
logging.exception('Error saving state for %s.', self)
def _restore_state(self) -> None:
- from efro.util import enum_by_value
-
try:
for tab in self._tabs.values():
tab.restore_state()
@@ -354,7 +357,7 @@ class GatherWindow(bui.Window):
current_tab = self.TabID.ABOUT
gather_tab_val = bui.app.config.get('Gather Tab')
try:
- stored_tab = enum_by_value(self.TabID, gather_tab_val)
+ stored_tab = self.TabID(gather_tab_val)
if stored_tab in self._tab_row.tabs:
current_tab = stored_tab
except ValueError:
@@ -366,9 +369,7 @@ class GatherWindow(bui.Window):
sel = self._tab_container
elif isinstance(sel_name, str) and sel_name.startswith('Tab:'):
try:
- sel_tab_id = enum_by_value(
- self.TabID, sel_name.split(':')[-1]
- )
+ sel_tab_id = self.TabID(sel_name.split(':')[-1])
except ValueError:
sel_tab_id = self.TabID.ABOUT
sel = self._tab_row.tabs[sel_tab_id].button
@@ -378,20 +379,3 @@ class GatherWindow(bui.Window):
except Exception:
logging.exception('Error restoring state for %s.', self)
-
- def _back(self) -> None:
- 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()
- bui.containerwidget(
- edit=self._root_widget, transition=self._transition_out
- )
- assert bui.app.classic is not None
- bui.app.ui_v1.set_main_menu_window(
- MainMenuWindow(transition='in_left').get_root_widget(),
- from_window=self._root_widget,
- )
diff --git a/src/assets/ba_data/python/bauiv1lib/gather/abouttab.py b/src/assets/ba_data/python/bauiv1lib/gather/abouttab.py
index 086471e0..dacf2c6c 100644
--- a/src/assets/ba_data/python/bauiv1lib/gather/abouttab.py
+++ b/src/assets/ba_data/python/bauiv1lib/gather/abouttab.py
@@ -157,6 +157,7 @@ class AboutGatherTab(GatherTab):
autoselect=True,
on_activate_call=bui.WeakCall(self._invite_to_try_press),
up_widget=tab_button,
+ show_buffer_top=500,
)
y -= invite_height
else:
diff --git a/src/assets/ba_data/python/bauiv1lib/gather/manualtab.py b/src/assets/ba_data/python/bauiv1lib/gather/manualtab.py
index 8c889efd..4dcc9c79 100644
--- a/src/assets/ba_data/python/bauiv1lib/gather/manualtab.py
+++ b/src/assets/ba_data/python/bauiv1lib/gather/manualtab.py
@@ -89,10 +89,10 @@ class ManualGatherTab(GatherTab):
self._container: bui.Widget | None = None
self._join_by_address_text: bui.Widget | None = None
self._favorites_text: bui.Widget | None = None
- self._width: int | None = None
- self._height: int | None = None
- self._scroll_width: int | None = None
- self._scroll_height: int | None = None
+ self._width: float | None = None
+ self._height: float | None = None
+ self._scroll_width: float | None = None
+ self._scroll_height: float | None = None
self._favorites_scroll_width: int | None = None
self._favorites_connect_button: bui.Widget | None = None
self._scrollwidget: bui.Widget | None = None
@@ -241,7 +241,7 @@ class ManualGatherTab(GatherTab):
self._build_join_by_address_tab(region_width, region_height)
if value is SubTabType.FAVORITES:
- self._build_favorites_tab(region_height)
+ self._build_favorites_tab(region_width, region_height)
# The old manual tab
def _build_join_by_address_tab(
@@ -276,7 +276,9 @@ class ManualGatherTab(GatherTab):
maxwidth=380,
size=(420, 60),
)
+ assert self._join_by_address_text is not None
bui.widget(edit=self._join_by_address_text, down_widget=txt)
+ assert self._favorites_text is not None
bui.widget(edit=self._favorites_text, down_widget=txt)
bui.textwidget(
parent=self._container,
@@ -349,13 +351,16 @@ class ManualGatherTab(GatherTab):
bui.widget(edit=self._check_button, up_widget=btn)
# Tab containing saved favorite addresses
- def _build_favorites_tab(self, region_height: float) -> None:
+ def _build_favorites_tab(
+ self, region_width: float, region_height: float
+ ) -> None:
c_height = region_height - 20
v = c_height - 35 - 25 - 30
assert bui.app.classic is not None
uiscale = bui.app.ui_v1.uiscale
- self._width = 1240 if uiscale is bui.UIScale.SMALL else 1040
+ # self._width = 1240 if uiscale is bui.UIScale.SMALL else 1040
+ self._width = region_width
x_inset = 100 if uiscale is bui.UIScale.SMALL else 0
self._height = (
578
@@ -400,7 +405,7 @@ class ManualGatherTab(GatherTab):
self._favorites_connect_button = btn1 = bui.buttonwidget(
parent=self._container,
size=(b_width, b_height),
- position=(40 if uiscale is bui.UIScale.SMALL else 40, btnv),
+ position=(140 if uiscale is bui.UIScale.SMALL else 40, btnv),
button_type='square',
color=(0.6, 0.53, 0.63),
textcolor=(0.75, 0.7, 0.8),
@@ -409,7 +414,7 @@ class ManualGatherTab(GatherTab):
label=bui.Lstr(resource='gatherWindow.manualConnectText'),
autoselect=True,
)
- if uiscale is bui.UIScale.SMALL and bui.app.ui_v1.use_toolbars:
+ if uiscale is bui.UIScale.SMALL:
bui.widget(
edit=btn1,
left_widget=bui.get_special_widget('back_button'),
@@ -418,7 +423,7 @@ class ManualGatherTab(GatherTab):
bui.buttonwidget(
parent=self._container,
size=(b_width, b_height),
- position=(40 if uiscale is bui.UIScale.SMALL else 40, btnv),
+ position=(140 if uiscale is bui.UIScale.SMALL else 40, btnv),
button_type='square',
color=(0.6, 0.53, 0.63),
textcolor=(0.75, 0.7, 0.8),
@@ -431,7 +436,7 @@ class ManualGatherTab(GatherTab):
bui.buttonwidget(
parent=self._container,
size=(b_width, b_height),
- position=(40 if uiscale is bui.UIScale.SMALL else 40, btnv),
+ position=(140 if uiscale is bui.UIScale.SMALL else 40, btnv),
button_type='square',
color=(0.6, 0.53, 0.63),
textcolor=(0.75, 0.7, 0.8),
@@ -444,7 +449,7 @@ class ManualGatherTab(GatherTab):
v -= sub_scroll_height + 23
self._scrollwidget = scrlw = bui.scrollwidget(
parent=self._container,
- position=(190 if uiscale is bui.UIScale.SMALL else 225, v),
+ position=(290 if uiscale is bui.UIScale.SMALL else 225, v),
size=(sub_scroll_width, sub_scroll_height),
claims_left_right=True,
)
@@ -469,7 +474,7 @@ class ManualGatherTab(GatherTab):
scale=1.2,
position=(
(
- (190 if uiscale is bui.UIScale.SMALL else 225)
+ (240 if uiscale is bui.UIScale.SMALL else 225)
+ sub_scroll_width * 0.5
),
v + sub_scroll_height * 0.5,
@@ -760,6 +765,7 @@ class ManualGatherTab(GatherTab):
claims_left_right=bool(servers),
claims_up_down=bool(servers),
)
+ assert self._scrollwidget is not None
bui.widget(
edit=self._scrollwidget,
up_widget=self._favorites_text,
diff --git a/src/assets/ba_data/python/bauiv1lib/gather/privatetab.py b/src/assets/ba_data/python/bauiv1lib/gather/privatetab.py
index 223d6d2c..6bdf4c6b 100644
--- a/src/assets/ba_data/python/bauiv1lib/gather/privatetab.py
+++ b/src/assets/ba_data/python/bauiv1lib/gather/privatetab.py
@@ -559,7 +559,8 @@ class PrivateGatherTab(GatherTab):
def _build_host_tab(self) -> None:
# pylint: disable=too-many-branches
# pylint: disable=too-many-statements
- assert bui.app.classic is not None
+ classic = bui.app.classic
+ assert classic is not None
plus = bui.app.plus
assert plus is not None
@@ -636,41 +637,7 @@ class PrivateGatherTab(GatherTab):
and hostingstate.tickets_to_host_now != 0
and not havegoldpass
):
- if not bui.app.ui_v1.use_toolbars:
-
- # Currently have no allow_token_purchases value like
- # we had with tickets; just assuming we always allow.
- if bool(True):
- # if bui.app.classic.allow_ticket_purchases:
- self._get_tokens_button = bui.buttonwidget(
- parent=self._container,
- position=(
- self._c_width - 210 + 125,
- self._c_height - 44,
- ),
- autoselect=True,
- scale=0.6,
- size=(120, 60),
- textcolor=(1.0, 0.6, 0.0),
- label=bui.charstr(bui.SpecialChar.TOKEN),
- color=(0.65, 0.5, 0.8),
- on_activate_call=self._on_get_tokens_press,
- )
- else:
- self._token_count_text = bui.textwidget(
- parent=self._container,
- scale=0.6,
- position=(
- self._c_width - 210 + 125,
- self._c_height - 44,
- ),
- color=(1.0, 0.6, 0.0),
- h_align='center',
- v_align='center',
- )
-
- # Set initial token count.
- self._update_currency_ui()
+ pass
v = self._c_height - 90
if hostingstate.party_code is None:
@@ -689,7 +656,7 @@ class PrivateGatherTab(GatherTab):
),
)
- v -= 100
+ v -= 90
if hostingstate.party_code is None:
# We've got no current party running; show options to set
# one up.
@@ -718,12 +685,12 @@ class PrivateGatherTab(GatherTab):
# If it appears we're coming back from playlist selection,
# re-select our playlist button.
- if bui.app.ui_v1.selecting_private_party_playlist:
+ if classic.selecting_private_party_playlist:
bui.containerwidget(
edit=self._container,
selected_child=self._host_playlist_button,
)
- bui.app.ui_v1.selecting_private_party_playlist = False
+ classic.selecting_private_party_playlist = False
else:
# We've got a current party; show its info.
bui.textwidget(
@@ -785,7 +752,7 @@ class PrivateGatherTab(GatherTab):
autoselect=True,
)
- v -= 120
+ v -= 110
# Line above the main action button:
@@ -951,6 +918,9 @@ class PrivateGatherTab(GatherTab):
)
def _playlist_press(self) -> None:
+ if bool(True):
+ bui.screenmessage('UNDER CONSTRUCTION')
+ return
assert self._host_playlist_button is not None
self.window.playlist_select(origin_widget=self._host_playlist_button)
diff --git a/src/assets/ba_data/python/bauiv1lib/gather/publictab.py b/src/assets/ba_data/python/bauiv1lib/gather/publictab.py
index d8b2f145..ca799188 100644
--- a/src/assets/ba_data/python/bauiv1lib/gather/publictab.py
+++ b/src/assets/ba_data/python/bauiv1lib/gather/publictab.py
@@ -584,7 +584,7 @@ class PublicGatherTab(GatherTab):
parent=self._container,
text=self._filter_value,
size=(350, 45),
- position=(290, v - 10),
+ position=(c_width * 0.5 - 150, v - 10),
h_align='left',
v_align='center',
editable=True,
@@ -596,7 +596,7 @@ class PublicGatherTab(GatherTab):
text=filter_txt,
parent=self._container,
size=(0, 0),
- position=(270, v + 13),
+ position=(c_width * 0.5 - 170, v + 13),
maxwidth=150,
scale=0.8,
color=(0.5, 0.46, 0.5),
@@ -609,7 +609,7 @@ class PublicGatherTab(GatherTab):
text=bui.Lstr(resource='nameText'),
parent=self._container,
size=(0, 0),
- position=(90, v - 8),
+ position=((c_width - sub_scroll_width) * 0.5 + 50, v - 8),
maxwidth=60,
scale=0.6,
color=(0.5, 0.46, 0.5),
@@ -621,7 +621,10 @@ class PublicGatherTab(GatherTab):
text=bui.Lstr(resource='gatherWindow.partySizeText'),
parent=self._container,
size=(0, 0),
- position=(755, v - 8),
+ position=(
+ c_width * 0.5 + sub_scroll_width * 0.5 - 110,
+ v - 8,
+ ),
maxwidth=60,
scale=0.6,
color=(0.5, 0.46, 0.5),
@@ -633,7 +636,10 @@ class PublicGatherTab(GatherTab):
text=bui.Lstr(resource='gatherWindow.pingText'),
parent=self._container,
size=(0, 0),
- position=(825, v - 8),
+ position=(
+ c_width * 0.5 + sub_scroll_width * 0.5 - 30,
+ v - 8,
+ ),
maxwidth=60,
scale=0.6,
color=(0.5, 0.46, 0.5),
@@ -811,6 +817,7 @@ class PublicGatherTab(GatherTab):
bui.widget(edit=self._host_name_text, down_widget=btn2)
bui.widget(edit=btn2, up_widget=self._host_name_text)
bui.widget(edit=btn1, up_widget=self._host_name_text)
+ assert self._join_text is not None
bui.widget(edit=self._join_text, down_widget=self._host_name_text)
v -= 10
self._host_status_text = bui.textwidget(
@@ -897,11 +904,6 @@ class PublicGatherTab(GatherTab):
plus = bui.app.plus
assert plus is not None
- # Special case: if a party-queue window is up, don't do any of this
- # (keeps things smoother).
- # if bui.app.ui.have_party_queue_window:
- # return
-
if self._sub_tab is SubTabType.JOIN:
# Keep our filter-text up to date from the UI.
text = self._filter_text
diff --git a/src/assets/ba_data/python/bauiv1lib/gettickets.py b/src/assets/ba_data/python/bauiv1lib/gettickets.py
deleted file mode 100644
index 3d40a4ac..00000000
--- a/src/assets/ba_data/python/bauiv1lib/gettickets.py
+++ /dev/null
@@ -1,881 +0,0 @@
-# Released under the MIT License. See LICENSE for details.
-#
-"""UI functionality for purchasing/acquiring currency."""
-
-from __future__ import annotations
-
-from typing import TYPE_CHECKING
-
-from efro.util import utc_now
-
-import bauiv1 as bui
-
-if TYPE_CHECKING:
- from typing import Any
-
-
-class GetTicketsWindow(bui.Window):
- """Window for purchasing/acquiring classic tickets."""
-
- def __init__(
- self,
- transition: str = 'in_right',
- from_modal_store: bool = False,
- modal: bool = False,
- origin_widget: bui.Widget | None = None,
- store_back_location: str | None = None,
- ):
- # pylint: disable=too-many-statements
- # pylint: disable=too-many-locals
-
- plus = bui.app.plus
- assert plus is not None
-
- bui.set_analytics_screen('Get Tickets Window')
-
- self._transitioning_out = False
- self._store_back_location = store_back_location # ew.
-
- self._ad_button_greyed = False
- self._smooth_update_timer: bui.AppTimer | None = None
- self._ad_button = None
- self._ad_label = None
- self._ad_image = None
- self._ad_time_text = None
-
- # If they provided an origin-widget, scale up from that.
- scale_origin: tuple[float, float] | None
- if origin_widget is not None:
- self._transition_out = 'out_scale'
- scale_origin = origin_widget.get_screen_space_center()
- transition = 'in_scale'
- else:
- self._transition_out = 'out_right'
- scale_origin = None
-
- assert bui.app.classic is not None
- uiscale = bui.app.ui_v1.uiscale
- self._width = 1000.0 if uiscale is bui.UIScale.SMALL else 800.0
- x_inset = 100.0 if uiscale is bui.UIScale.SMALL else 0.0
- self._height = 480.0
-
- self._modal = modal
- self._from_modal_store = from_modal_store
- self._r = 'getTicketsWindow'
-
- top_extra = 20 if uiscale is bui.UIScale.SMALL else 0
-
- super().__init__(
- root_widget=bui.containerwidget(
- size=(self._width, self._height + top_extra),
- transition=transition,
- scale_origin_stack_offset=scale_origin,
- color=(0.4, 0.37, 0.55),
- scale=(
- 1.63
- if uiscale is bui.UIScale.SMALL
- else 1.2 if uiscale is bui.UIScale.MEDIUM else 1.0
- ),
- stack_offset=(
- (0, -3) if uiscale is bui.UIScale.SMALL else (0, 0)
- ),
- )
- )
-
- btn = bui.buttonwidget(
- parent=self._root_widget,
- position=(55 + x_inset, self._height - 79),
- size=(140, 60),
- scale=1.0,
- autoselect=True,
- label=bui.Lstr(resource='doneText' if modal else 'backText'),
- button_type='regular' if modal else 'back',
- on_activate_call=self._back,
- )
-
- bui.containerwidget(edit=self._root_widget, cancel_button=btn)
-
- bui.textwidget(
- parent=self._root_widget,
- position=(self._width * 0.5 - 15, self._height - 47),
- size=(0, 0),
- color=bui.app.ui_v1.title_color,
- scale=1.2,
- h_align='right',
- v_align='center',
- text=bui.Lstr(resource=f'{self._r}.titleText'),
- # text='Testing really long text here blah blah',
- maxwidth=260,
- )
-
- # Get Tokens button
- bui.buttonwidget(
- parent=self._root_widget,
- position=(self._width * 0.5, self._height - 72),
- color=(0.65, 0.5, 0.7),
- textcolor=bui.app.ui_v1.title_color,
- size=(190, 50),
- autoselect=True,
- label=bui.Lstr(resource='tokens.getTokensText'),
- on_activate_call=self._get_tokens_press,
- )
-
- # 'New!' by tokens button
- bui.textwidget(
- parent=self._root_widget,
- text=bui.Lstr(resource='newExclaimText'),
- position=(self._width * 0.5 + 25, self._height - 32),
- size=(0, 0),
- color=(1, 1, 0, 1.0),
- rotate=22,
- shadow=1.0,
- maxwidth=150,
- h_align='center',
- v_align='center',
- scale=0.7,
- )
-
- if not modal:
- bui.buttonwidget(
- edit=btn,
- button_type='backSmall',
- size=(60, 60),
- label=bui.charstr(bui.SpecialChar.BACK),
- )
-
- b_size = (220.0, 180.0)
- v = self._height - b_size[1] - 80
- spacing = 1
-
- self._ad_button = None
-
- def _add_button(
- item: str,
- position: tuple[float, float],
- size: tuple[float, float],
- label: bui.Lstr,
- price: str | None = None,
- tex_name: str | None = None,
- tex_opacity: float = 1.0,
- tex_scale: float = 1.0,
- enabled: bool = True,
- text_scale: float = 1.0,
- ) -> bui.Widget:
- btn2 = bui.buttonwidget(
- parent=self._root_widget,
- position=position,
- button_type='square',
- size=size,
- label='',
- autoselect=True,
- color=None if enabled else (0.5, 0.5, 0.5),
- on_activate_call=(
- bui.Call(self._purchase, item)
- if enabled
- else self._disabled_press
- ),
- )
- txt = bui.textwidget(
- parent=self._root_widget,
- text=label,
- position=(
- position[0] + size[0] * 0.5,
- position[1] + size[1] * 0.3,
- ),
- scale=text_scale,
- maxwidth=size[0] * 0.75,
- size=(0, 0),
- h_align='center',
- v_align='center',
- draw_controller=btn2,
- color=(0.7, 0.9, 0.7, 1.0 if enabled else 0.2),
- )
- if price is not None and enabled:
- bui.textwidget(
- parent=self._root_widget,
- text=price,
- position=(
- position[0] + size[0] * 0.5,
- position[1] + size[1] * 0.17,
- ),
- scale=0.7,
- maxwidth=size[0] * 0.75,
- size=(0, 0),
- h_align='center',
- v_align='center',
- draw_controller=btn2,
- color=(0.4, 0.9, 0.4, 1.0),
- )
- i = None
- if tex_name is not None:
- tex_size = 90.0 * tex_scale
- i = bui.imagewidget(
- parent=self._root_widget,
- texture=bui.gettexture(tex_name),
- position=(
- position[0] + size[0] * 0.5 - tex_size * 0.5,
- position[1] + size[1] * 0.66 - tex_size * 0.5,
- ),
- size=(tex_size, tex_size),
- draw_controller=btn2,
- opacity=tex_opacity * (1.0 if enabled else 0.25),
- )
- if item == 'ad':
- self._ad_button = btn2
- self._ad_label = txt
- assert i is not None
- self._ad_image = i
- self._ad_time_text = bui.textwidget(
- parent=self._root_widget,
- text='1m 10s',
- position=(
- position[0] + size[0] * 0.5,
- position[1] + size[1] * 0.5,
- ),
- scale=text_scale * 1.2,
- maxwidth=size[0] * 0.85,
- size=(0, 0),
- h_align='center',
- v_align='center',
- draw_controller=btn2,
- color=(0.4, 0.9, 0.4, 1.0),
- )
- return btn2
-
- rsrc = f'{self._r}.ticketsText'
-
- c2txt = bui.Lstr(
- resource=rsrc,
- subs=[
- (
- '${COUNT}',
- str(
- plus.get_v1_account_misc_read_val('tickets2Amount', 500)
- ),
- )
- ],
- )
- c3txt = bui.Lstr(
- resource=rsrc,
- subs=[
- (
- '${COUNT}',
- str(
- plus.get_v1_account_misc_read_val(
- 'tickets3Amount', 1500
- )
- ),
- )
- ],
- )
- c4txt = bui.Lstr(
- resource=rsrc,
- subs=[
- (
- '${COUNT}',
- str(
- plus.get_v1_account_misc_read_val(
- 'tickets4Amount', 5000
- )
- ),
- )
- ],
- )
- c5txt = bui.Lstr(
- resource=rsrc,
- subs=[
- (
- '${COUNT}',
- str(
- plus.get_v1_account_misc_read_val(
- 'tickets5Amount', 15000
- )
- ),
- )
- ],
- )
-
- h = 110.0
-
- # Enable buttons if we have prices.
- tickets2_price = plus.get_price('tickets2')
- tickets3_price = plus.get_price('tickets3')
- tickets4_price = plus.get_price('tickets4')
- tickets5_price = plus.get_price('tickets5')
-
- # TEMP
- # tickets1_price = '$0.99'
- # tickets2_price = '$4.99'
- # tickets3_price = '$9.99'
- # tickets4_price = '$19.99'
- # tickets5_price = '$49.99'
-
- _add_button(
- 'tickets2',
- enabled=(tickets2_price is not None),
- position=(
- self._width * 0.5 - spacing * 1.5 - b_size[0] * 2.0 + h,
- v,
- ),
- size=b_size,
- label=c2txt,
- price=tickets2_price,
- tex_name='ticketsMore',
- ) # 0.99-ish
- _add_button(
- 'tickets3',
- enabled=(tickets3_price is not None),
- position=(
- self._width * 0.5 - spacing * 0.5 - b_size[0] * 1.0 + h,
- v,
- ),
- size=b_size,
- label=c3txt,
- price=tickets3_price,
- tex_name='ticketRoll',
- ) # 4.99-ish
- v -= b_size[1] - 5
- _add_button(
- 'tickets4',
- enabled=(tickets4_price is not None),
- position=(
- self._width * 0.5 - spacing * 1.5 - b_size[0] * 2.0 + h,
- v,
- ),
- size=b_size,
- label=c4txt,
- price=tickets4_price,
- tex_name='ticketRollBig',
- tex_scale=1.2,
- ) # 9.99-ish
- _add_button(
- 'tickets5',
- enabled=(tickets5_price is not None),
- position=(
- self._width * 0.5 - spacing * 0.5 - b_size[0] * 1.0 + h,
- v,
- ),
- size=b_size,
- label=c5txt,
- price=tickets5_price,
- tex_name='ticketRolls',
- tex_scale=1.2,
- ) # 19.99-ish
-
- self._enable_ad_button = plus.has_video_ads()
- h = self._width * 0.5 + 110.0
- v = self._height - b_size[1] - 115.0
-
- if self._enable_ad_button:
- h_offs = 35
- b_size_3 = (150, 120)
- cdb = _add_button(
- 'ad',
- position=(h + h_offs, v),
- size=b_size_3,
- label=bui.Lstr(
- resource=f'{self._r}.ticketsFromASponsorText',
- subs=[
- (
- '${COUNT}',
- str(
- plus.get_v1_account_misc_read_val(
- 'sponsorTickets', 5
- )
- ),
- )
- ],
- ),
- tex_name='ticketsMore',
- enabled=self._enable_ad_button,
- tex_opacity=0.6,
- tex_scale=0.7,
- text_scale=0.7,
- )
- bui.buttonwidget(edit=cdb, color=(0.65, 0.5, 0.7))
-
- self._ad_free_text = bui.textwidget(
- parent=self._root_widget,
- text=bui.Lstr(resource=f'{self._r}.freeText'),
- position=(
- h + h_offs + b_size_3[0] * 0.5,
- v + b_size_3[1] * 0.5 + 25,
- ),
- size=(0, 0),
- color=(1, 1, 0, 1.0),
- draw_controller=cdb,
- rotate=15,
- shadow=1.0,
- maxwidth=150,
- h_align='center',
- v_align='center',
- scale=1.0,
- )
- v -= 125
- else:
- v -= 20
-
- if bool(True):
- h_offs = 35
- b_size_3 = (150, 120)
- cdb = _add_button(
- 'app_invite',
- position=(h + h_offs, v),
- size=b_size_3,
- label=bui.Lstr(
- resource='gatherWindow.earnTicketsForRecommendingText',
- subs=[
- (
- '${COUNT}',
- str(
- plus.get_v1_account_misc_read_val(
- 'sponsorTickets', 5
- )
- ),
- )
- ],
- ),
- tex_name='ticketsMore',
- enabled=True,
- tex_opacity=0.6,
- tex_scale=0.7,
- text_scale=0.7,
- )
- bui.buttonwidget(edit=cdb, color=(0.65, 0.5, 0.7))
-
- bui.textwidget(
- parent=self._root_widget,
- text=bui.Lstr(resource=f'{self._r}.freeText'),
- position=(
- h + h_offs + b_size_3[0] * 0.5,
- v + b_size_3[1] * 0.5 + 25,
- ),
- size=(0, 0),
- color=(1, 1, 0, 1.0),
- draw_controller=cdb,
- rotate=15,
- shadow=1.0,
- maxwidth=150,
- h_align='center',
- v_align='center',
- scale=1.0,
- )
- tc_y_offs = 0
- else:
- tc_y_offs = 0
-
- h = self._width - (185 + x_inset)
- v = self._height - 105 + tc_y_offs
-
- txt1 = (
- bui.Lstr(resource=f'{self._r}.youHaveText')
- .evaluate()
- .partition('${COUNT}')[0]
- .strip()
- )
- txt2 = (
- bui.Lstr(resource=f'{self._r}.youHaveText')
- .evaluate()
- .rpartition('${COUNT}')[-1]
- .strip()
- )
-
- bui.textwidget(
- parent=self._root_widget,
- text=txt1,
- position=(h, v),
- size=(0, 0),
- color=(0.5, 0.5, 0.6),
- maxwidth=200,
- h_align='center',
- v_align='center',
- scale=0.8,
- )
- v -= 30
- self._ticket_count_text = bui.textwidget(
- parent=self._root_widget,
- position=(h, v),
- size=(0, 0),
- color=(0.2, 1.0, 0.2),
- maxwidth=200,
- h_align='center',
- v_align='center',
- scale=1.6,
- )
- v -= 30
- bui.textwidget(
- parent=self._root_widget,
- text=txt2,
- position=(h, v),
- size=(0, 0),
- color=(0.5, 0.5, 0.6),
- maxwidth=200,
- h_align='center',
- v_align='center',
- scale=0.8,
- )
-
- self._ticking_sound: bui.Sound | None = None
- self._smooth_ticket_count: float | None = None
- self._ticket_count = 0
- self._update()
- self._update_timer = bui.AppTimer(
- 1.0, bui.WeakCall(self._update), repeat=True
- )
- self._smooth_increase_speed = 1.0
-
- def __del__(self) -> None:
- if self._ticking_sound is not None:
- self._ticking_sound.stop()
- self._ticking_sound = None
-
- def _smooth_update(self) -> None:
- if not self._ticket_count_text:
- self._smooth_update_timer = None
- return
-
- finished = False
-
- # If we're going down, do it immediately.
- assert self._smooth_ticket_count is not None
- if int(self._smooth_ticket_count) >= self._ticket_count:
- self._smooth_ticket_count = float(self._ticket_count)
- finished = True
- else:
- # We're going up; start a sound if need be.
- self._smooth_ticket_count = min(
- self._smooth_ticket_count + 1.0 * self._smooth_increase_speed,
- self._ticket_count,
- )
- if int(self._smooth_ticket_count) >= self._ticket_count:
- finished = True
- self._smooth_ticket_count = float(self._ticket_count)
- elif self._ticking_sound is None:
- self._ticking_sound = bui.getsound('scoreIncrease')
- self._ticking_sound.play()
-
- bui.textwidget(
- edit=self._ticket_count_text,
- text=str(int(self._smooth_ticket_count)),
- )
-
- # If we've reached the target, kill the timer/sound/etc.
- if finished:
- self._smooth_update_timer = None
- if self._ticking_sound is not None:
- self._ticking_sound.stop()
- self._ticking_sound = None
- bui.getsound('cashRegister2').play()
-
- def _update(self) -> None:
- import datetime
-
- plus = bui.app.plus
- assert plus is not None
-
- # If we somehow get signed out, just die.
- if plus.get_v1_account_state() != 'signed_in':
- self._back()
- return
-
- self._ticket_count = plus.get_v1_account_ticket_count()
-
- # Update our incentivized ad button depending on whether ads are
- # available.
- if self._ad_button is not None:
- next_reward_ad_time = plus.get_v1_account_misc_read_val_2(
- 'nextRewardAdTime', None
- )
- if next_reward_ad_time is not None:
- next_reward_ad_time = datetime.datetime.fromtimestamp(
- next_reward_ad_time, datetime.UTC
- )
- now = utc_now()
- if plus.have_incentivized_ad() and (
- next_reward_ad_time is None or next_reward_ad_time <= now
- ):
- self._ad_button_greyed = False
- bui.buttonwidget(edit=self._ad_button, color=(0.65, 0.5, 0.7))
- bui.textwidget(edit=self._ad_label, color=(0.7, 0.9, 0.7, 1.0))
- bui.textwidget(edit=self._ad_free_text, color=(1, 1, 0, 1))
- bui.imagewidget(edit=self._ad_image, opacity=0.6)
- bui.textwidget(edit=self._ad_time_text, text='')
- else:
- self._ad_button_greyed = True
- bui.buttonwidget(edit=self._ad_button, color=(0.5, 0.5, 0.5))
- bui.textwidget(edit=self._ad_label, color=(0.7, 0.9, 0.7, 0.2))
- bui.textwidget(edit=self._ad_free_text, color=(1, 1, 0, 0.2))
- bui.imagewidget(edit=self._ad_image, opacity=0.6 * 0.25)
- sval: str | bui.Lstr
- if (
- next_reward_ad_time is not None
- and next_reward_ad_time > now
- ):
- sval = bui.timestring(
- (next_reward_ad_time - now).total_seconds(), centi=False
- )
- else:
- sval = ''
- bui.textwidget(edit=self._ad_time_text, text=sval)
-
- # If this is our first update, assign immediately; otherwise kick
- # off a smooth transition if the value has changed.
- if self._smooth_ticket_count is None:
- self._smooth_ticket_count = float(self._ticket_count)
- self._smooth_update() # will set the text widget
-
- elif (
- self._ticket_count != int(self._smooth_ticket_count)
- and self._smooth_update_timer is None
- ):
- self._smooth_update_timer = bui.AppTimer(
- 0.05, bui.WeakCall(self._smooth_update), repeat=True
- )
- diff = abs(float(self._ticket_count) - self._smooth_ticket_count)
- self._smooth_increase_speed = (
- diff / 100.0
- if diff >= 5000
- else (
- diff / 50.0
- if diff >= 1500
- else diff / 30.0 if diff >= 500 else diff / 15.0
- )
- )
-
- def _disabled_press(self) -> None:
- plus = bui.app.plus
- assert plus is not None
-
- # If we're on a platform without purchases, inform the user they
- # can link their accounts and buy stuff elsewhere.
- app = bui.app
- assert app.classic is not None
- if (
- app.env.test
- or (
- app.classic.platform == 'android'
- and app.classic.subplatform in ['oculus', 'cardboard']
- )
- ) and plus.get_v1_account_misc_read_val('allowAccountLinking2', False):
- bui.screenmessage(
- bui.Lstr(resource=f'{self._r}.unavailableLinkAccountText'),
- color=(1, 0.5, 0),
- )
- else:
- bui.screenmessage(
- bui.Lstr(resource=f'{self._r}.unavailableText'),
- color=(1, 0.5, 0),
- )
- bui.getsound('error').play()
-
- def _purchase(self, item: str) -> None:
- from bauiv1lib import account
- from bauiv1lib import appinvite
-
- plus = bui.app.plus
- assert plus is not None
-
- if bui.app.classic is None:
- raise RuntimeError('This requires classic support.')
-
- if item == 'app_invite':
- if plus.get_v1_account_state() != 'signed_in':
- account.show_sign_in_prompt()
- return
- appinvite.handle_app_invites_press()
- return
-
- # Here we ping the server to ask if it's valid for us to
- # purchase this.. (better to fail now than after we've paid
- # locally).
- app = bui.app
- assert app.classic is not None
- bui.app.classic.master_server_v1_get(
- 'bsAccountPurchaseCheck',
- {
- 'item': item,
- 'platform': app.classic.platform,
- 'subplatform': app.classic.subplatform,
- 'version': app.env.engine_version,
- 'buildNumber': app.env.engine_build_number,
- },
- callback=bui.WeakCall(self._purchase_check_result, item),
- )
-
- def _purchase_check_result(
- self, item: str, result: dict[str, Any] | None
- ) -> None:
- if result is None:
- bui.getsound('error').play()
- bui.screenmessage(
- bui.Lstr(resource='internal.unavailableNoConnectionText'),
- color=(1, 0, 0),
- )
- else:
- if result['allow']:
- self._do_purchase(item)
- else:
- if result['reason'] == 'versionTooOld':
- bui.getsound('error').play()
- bui.screenmessage(
- bui.Lstr(resource='getTicketsWindow.versionTooOldText'),
- color=(1, 0, 0),
- )
- else:
- bui.getsound('error').play()
- bui.screenmessage(
- bui.Lstr(resource='getTicketsWindow.unavailableText'),
- color=(1, 0, 0),
- )
-
- # Actually start the purchase locally.
- def _do_purchase(self, item: str) -> None:
- plus = bui.app.plus
- assert plus is not None
-
- if item == 'ad':
- import datetime
-
- # If ads are disabled until some time, error.
- next_reward_ad_time = plus.get_v1_account_misc_read_val_2(
- 'nextRewardAdTime', None
- )
- if next_reward_ad_time is not None:
- next_reward_ad_time = datetime.datetime.fromtimestamp(
- next_reward_ad_time, datetime.UTC
- )
- now = utc_now()
- if (
- next_reward_ad_time is not None and next_reward_ad_time > now
- ) or self._ad_button_greyed:
- bui.getsound('error').play()
- bui.screenmessage(
- bui.Lstr(
- resource='getTicketsWindow.unavailableTemporarilyText'
- ),
- color=(1, 0, 0),
- )
- elif self._enable_ad_button:
- assert bui.app.classic is not None
- bui.app.classic.ads.show_ad('tickets')
- else:
- plus.purchase(item)
-
- def _get_tokens_press(self) -> None:
- from functools import partial
-
- from bauiv1lib.gettokens import GetTokensWindow
-
- # 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:
- return
-
- bui.containerwidget(edit=self._root_widget, transition='out_left')
-
- # Note: Make sure we don't pass anything here that would
- # capture 'self'. (a lambda would implicitly do this by capturing
- # the stack frame).
- restorecall = partial(
- _restore_get_tickets_window,
- self._modal,
- self._from_modal_store,
- self._store_back_location,
- )
-
- window = GetTokensWindow(
- transition='in_right',
- restore_previous_call=restorecall,
- ).get_root_widget()
- if not self._modal and not self._from_modal_store:
- assert bui.app.classic is not None
- bui.app.ui_v1.set_main_menu_window(
- window, from_window=self._root_widget
- )
- self._transitioning_out = True
-
- def _back(self) -> None:
- 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:
- return
-
- bui.containerwidget(
- edit=self._root_widget, transition=self._transition_out
- )
- if not self._modal:
- window = browser.StoreBrowserWindow(
- transition='in_left',
- modal=self._from_modal_store,
- back_location=self._store_back_location,
- ).get_root_widget()
- if not self._from_modal_store:
- assert bui.app.classic is not None
- bui.app.ui_v1.set_main_menu_window(
- window, from_window=self._root_widget
- )
- self._transitioning_out = True
-
-
-# A call we can bundle up and pass to windows we open; allows them to
-# get back to us without having to explicitly know about us.
-def _restore_get_tickets_window(
- modal: bool,
- from_modal_store: bool,
- store_back_location: str | None,
- from_window: bui.Widget,
-) -> None:
- restored = GetTicketsWindow(
- transition='in_left',
- modal=modal,
- from_modal_store=from_modal_store,
- store_back_location=store_back_location,
- )
- if not modal and not from_modal_store:
- assert bui.app.classic is not None
- bui.app.ui_v1.set_main_menu_window(
- restored.get_root_widget(), from_window=from_window
- )
-
-
-def show_get_tickets_prompt() -> None:
- """Show a 'not enough tickets' prompt with an option to purchase more.
-
- Note that the purchase option may not always be available
- depending on the build of the game.
- """
- from bauiv1lib.confirm import ConfirmWindow
-
- assert bui.app.classic is not None
-
- if bui.app.classic.allow_ticket_purchases:
- ConfirmWindow(
- bui.Lstr(
- translate=(
- 'serverResponses',
- 'You don\'t have enough tickets for this!',
- )
- ),
- lambda: GetTicketsWindow(modal=True),
- ok_text=bui.Lstr(resource='getTicketsWindow.titleText'),
- width=460,
- height=130,
- )
- else:
- ConfirmWindow(
- bui.Lstr(
- translate=(
- 'serverResponses',
- 'You don\'t have enough tickets for this!',
- )
- ),
- cancel_button=False,
- width=460,
- height=130,
- )
diff --git a/src/assets/ba_data/python/bauiv1lib/gettokens.py b/src/assets/ba_data/python/bauiv1lib/gettokens.py
index 22bff444..0c3baeb8 100644
--- a/src/assets/ba_data/python/bauiv1lib/gettokens.py
+++ b/src/assets/ba_data/python/bauiv1lib/gettokens.py
@@ -325,56 +325,74 @@ class GetTokensWindow(bui.Window):
uiscale = bui.app.ui_v1.uiscale
self._width = 1000.0 if uiscale is bui.UIScale.SMALL else 800.0
- self._x_inset = 100.0 if uiscale is bui.UIScale.SMALL else 0.0
- self._height = 480.0
+ self._x_inset = 25.0 if uiscale is bui.UIScale.SMALL else 0.0
+ self._height = 550 if uiscale is bui.UIScale.SMALL else 480.0
+ self._y_offset = -60 if uiscale is bui.UIScale.SMALL else 0
self._r = 'getTokensWindow'
- top_extra = 20 if uiscale is bui.UIScale.SMALL else 0
-
super().__init__(
root_widget=bui.containerwidget(
- size=(self._width, self._height + top_extra),
+ size=(self._width, self._height),
transition=transition,
scale_origin_stack_offset=scale_origin,
color=(0.3, 0.23, 0.36),
scale=(
- 1.63
+ 1.5
if uiscale is bui.UIScale.SMALL
else 1.2 if uiscale is bui.UIScale.MEDIUM else 1.0
),
stack_offset=(
(0, -3) if uiscale is bui.UIScale.SMALL else (0, 0)
),
+ # toolbar_visibility='menu_minimal',
+ toolbar_visibility='get_tokens',
)
)
- self._back_button = btn = bui.buttonwidget(
- parent=self._root_widget,
- position=(45 + self._x_inset, self._height - 80),
- size=(
- (140, 60) if self._restore_previous_call is None else (60, 60)
- ),
- scale=1.0,
- autoselect=True,
- label=(
- bui.Lstr(resource='doneText')
- if self._restore_previous_call is None
- else bui.charstr(bui.SpecialChar.BACK)
- ),
- button_type=(
- 'regular'
- if self._restore_previous_call is None
- else 'backSmall'
- ),
- on_activate_call=self._back,
- )
-
- bui.containerwidget(edit=self._root_widget, cancel_button=btn)
+ if uiscale is bui.UIScale.SMALL:
+ bui.containerwidget(
+ edit=self._root_widget, on_cancel_call=self._back
+ )
+ self._back_button = bui.get_special_widget('back_button')
+ else:
+ self._back_button = bui.buttonwidget(
+ parent=self._root_widget,
+ position=(
+ 55 + self._x_inset,
+ self._height - 80 + self._y_offset,
+ ),
+ size=(
+ (140, 60)
+ if self._restore_previous_call is None
+ else (60, 60)
+ ),
+ scale=1.0,
+ autoselect=True,
+ label=(
+ bui.Lstr(resource='doneText')
+ if self._restore_previous_call is None
+ else bui.charstr(bui.SpecialChar.BACK)
+ ),
+ button_type=(
+ 'regular'
+ if self._restore_previous_call is None
+ else 'backSmall'
+ ),
+ on_activate_call=self._back,
+ )
+ # if uiscale is bui.UIScale.SMALL:
+ # bui.widget(
+ # edit=self._back_button,
+ # up_widget=bui.get_special_widget('tokens_meter'),
+ # )
+ bui.containerwidget(
+ edit=self._root_widget, cancel_button=self._back_button
+ )
self._title_text = bui.textwidget(
parent=self._root_widget,
- position=(self._width * 0.5, self._height - 47),
+ position=(self._width * 0.5, self._height - 42 + self._y_offset),
size=(0, 0),
color=self._textcolor,
flatness=0.0,
@@ -403,12 +421,12 @@ class GetTokensWindow(bui.Window):
self._status_text,
]
- self._token_count_widget: bui.Widget | None = None
- self._smooth_update_timer: bui.AppTimer | None = None
- self._smooth_token_count: float | None = None
- self._token_count: int = 0
- self._smooth_increase_speed = 1.0
- self._ticking_sound: bui.Sound | None = None
+ # self._token_count_widget: bui.Widget | None = None
+ # self._smooth_update_timer: bui.AppTimer | None = None
+ # self._smooth_token_count: float | None = None
+ # self._token_count: int = 0
+ # self._smooth_increase_speed = 1.0
+ # self._ticking_sound: bui.Sound | None = None
# Get all textures used by our buttons preloading so hopefully
# they'll be in place by the time we show them.
@@ -423,10 +441,10 @@ class GetTokensWindow(bui.Window):
)
self._update()
- def __del__(self) -> None:
- if self._ticking_sound is not None:
- self._ticking_sound.stop()
- self._ticking_sound = None
+ # def __del__(self) -> None:
+ # if self._ticking_sound is not None:
+ # self._ticking_sound.stop()
+ # self._ticking_sound = None
def _update(self) -> None:
# No-op if our underlying widget is dead or on its way out.
@@ -475,7 +493,7 @@ class GetTokensWindow(bui.Window):
return
# Ok, state is changing. Start by resetting to a blank slate.
- self._token_count_widget = None
+ # self._token_count_widget = None
for widget in self._root_widget.get_children():
if widget not in self._core_widgets:
widget.delete()
@@ -527,6 +545,8 @@ class GetTokensWindow(bui.Window):
# pylint: disable=too-many-locals
plus = bui.app.plus
+ uiscale = bui.app.ui_v1.uiscale
+
bui.textwidget(edit=self._status_text, text='')
xinset = 40
@@ -540,17 +560,23 @@ class GetTokensWindow(bui.Window):
# We currently don't handle the zero-button case.
assert self._buttondefs
- total_button_width = sum(
- b.width + b.prepad for b in self._buttondefs
- ) + buttonpadding * (len(self._buttondefs) - 1)
+ sidepad = 10.0
+ total_button_width = (
+ sum(b.width + b.prepad for b in self._buttondefs)
+ + buttonpadding * (len(self._buttondefs) - 1)
+ + 2 * sidepad
+ )
h_scroll = bui.hscrollwidget(
parent=self._root_widget,
size=(scrollwidth, scrollheight),
- position=(self._x_inset + xinset, 45),
+ position=(
+ self._x_inset + xinset,
+ self._height - 415 + self._y_offset,
+ ),
claims_left_right=True,
highlight=False,
- border_opacity=0.25,
+ border_opacity=0.3 if uiscale is bui.UIScale.SMALL else 1.0,
)
subcontainer = bui.containerwidget(
parent=h_scroll,
@@ -561,7 +587,10 @@ class GetTokensWindow(bui.Window):
parent=self._root_widget,
autoselect=True,
label=bui.Lstr(resource='learnMoreText'),
- position=(self._width * 0.5 - 75, self._height * 0.703),
+ position=(
+ self._width * 0.5 - 75,
+ self._height - 125 + self._y_offset,
+ ),
size=(180, 43),
scale=0.8,
color=(0.4, 0.25, 0.5),
@@ -570,8 +599,19 @@ class GetTokensWindow(bui.Window):
self._on_learn_more_press, response.token_info_url
),
)
+ if uiscale is bui.UIScale.SMALL:
+ bui.widget(
+ edit=tinfobtn,
+ left_widget=bui.get_special_widget('back_button'),
+ up_widget=bui.get_special_widget('back_button'),
+ )
- x = 0.0
+ bui.widget(
+ edit=tinfobtn,
+ right_widget=bui.get_special_widget('tokens_meter'),
+ )
+
+ x = sidepad
bwidgets: list[bui.Widget] = []
for i, buttondef in enumerate(self._buttondefs):
@@ -594,6 +634,10 @@ class GetTokensWindow(bui.Window):
),
)
bwidgets.append(btn)
+
+ if i == 0:
+ bui.widget(edit=btn, left_widget=self._back_button)
+
for imgdef in buttondef.imgdefs:
_img = bui.imagewidget(
parent=subcontainer,
@@ -645,7 +689,7 @@ class GetTokensWindow(bui.Window):
_tinfotxt = bui.textwidget(
parent=self._root_widget,
- position=(self._width * 0.5, self._height * 0.812),
+ position=(self._width * 0.5, self._height - 70 + self._y_offset),
color=self._textcolor,
shadow=1.0,
scale=0.7,
@@ -654,29 +698,35 @@ class GetTokensWindow(bui.Window):
v_align='center',
text=bui.Lstr(resource='tokens.shinyNewCurrencyText'),
)
- self._token_count_widget = bui.textwidget(
- parent=self._root_widget,
- position=(self._width - self._x_inset - 120.0, self._height - 48),
- color=(2.0, 0.7, 0.0),
- shadow=1.0,
- flatness=0.0,
- size=(0, 0),
- h_align='left',
- v_align='center',
- text='',
- )
- self._token_count = response.tokens
- self._smooth_token_count = float(self._token_count)
- self._smooth_update() # will set the text widget.
+ # self._token_count_widget = bui.textwidget(
+ # parent=self._root_widget,
+ # position=(
+ # self._width - self._x_inset - 120.0,
+ # self._height - 48 + self._y_offset,
+ # ),
+ # color=(2.0, 0.7, 0.0),
+ # shadow=1.0,
+ # flatness=0.0,
+ # size=(0, 0),
+ # h_align='left',
+ # v_align='center',
+ # text='',
+ # )
+ # self._token_count = response.tokens
+ # self._smooth_token_count = float(self._token_count)
+ # self._smooth_update() # will set the text widget.
- _tlabeltxt = bui.textwidget(
- parent=self._root_widget,
- position=(self._width - self._x_inset - 123.0, self._height - 48),
- size=(0, 0),
- h_align='right',
- v_align='center',
- text=bui.charstr(bui.SpecialChar.TOKEN),
- )
+ # _tlabeltxt = bui.textwidget(
+ # parent=self._root_widget,
+ # position=(
+ # self._width - self._x_inset - 123.0,
+ # self._height - 48 + self._y_offset,
+ # ),
+ # size=(0, 0),
+ # h_align='right',
+ # v_align='center',
+ # text=bui.charstr(bui.SpecialChar.TOKEN),
+ # )
def _purchase_press(self, itemid: str) -> None:
plus = bui.app.plus
@@ -700,70 +750,70 @@ class GetTokensWindow(bui.Window):
def _update_store_state(self) -> None:
"""Called to make minor updates to an already shown store."""
- assert self._token_count_widget is not None
+ # assert self._token_count_widget is not None
assert self._last_query_response is not None
- self._token_count = self._last_query_response.tokens
+ # self._token_count = self._last_query_response.tokens
# Kick off new smooth update if need be.
- assert self._smooth_token_count is not None
- if (
- self._token_count != int(self._smooth_token_count)
- and self._smooth_update_timer is None
- ):
- self._smooth_update_timer = bui.AppTimer(
- 0.05, bui.WeakCall(self._smooth_update), repeat=True
- )
- diff = abs(float(self._token_count) - self._smooth_token_count)
- self._smooth_increase_speed = (
- diff / 100.0
- if diff >= 5000
- else (
- diff / 50.0
- if diff >= 1500
- else diff / 30.0 if diff >= 500 else diff / 15.0
- )
- )
+ # assert self._smooth_token_count is not None
+ # if (
+ # self._token_count != int(self._smooth_token_count)
+ # and self._smooth_update_timer is None
+ # ):
+ # self._smooth_update_timer = bui.AppTimer(
+ # 0.05, bui.WeakCall(self._smooth_update), repeat=True
+ # )
+ # diff = abs(float(self._token_count) - self._smooth_token_count)
+ # self._smooth_increase_speed = (
+ # diff / 100.0
+ # if diff >= 5000
+ # else (
+ # diff / 50.0
+ # if diff >= 1500
+ # else diff / 30.0 if diff >= 500 else diff / 15.0
+ # )
+ # )
- def _smooth_update(self) -> None:
+ # def _smooth_update(self) -> None:
- # Stop if the count widget disappears.
- if not self._token_count_widget:
- self._smooth_update_timer = None
- return
+ # # Stop if the count widget disappears.
+ # if not self._token_count_widget:
+ # self._smooth_update_timer = None
+ # return
- finished = False
+ # finished = False
- # If we're going down, do it immediately.
- assert self._smooth_token_count is not None
- if int(self._smooth_token_count) >= self._token_count:
- self._smooth_token_count = float(self._token_count)
- finished = True
- else:
- # We're going up; start a sound if need be.
- self._smooth_token_count = min(
- self._smooth_token_count + 1.0 * self._smooth_increase_speed,
- self._token_count,
- )
- if int(self._smooth_token_count) >= self._token_count:
- finished = True
- self._smooth_token_count = float(self._token_count)
- elif self._ticking_sound is None:
- self._ticking_sound = bui.getsound('scoreIncrease')
- self._ticking_sound.play()
+ # # If we're going down, do it immediately.
+ # assert self._smooth_token_count is not None
+ # if int(self._smooth_token_count) >= self._token_count:
+ # self._smooth_token_count = float(self._token_count)
+ # finished = True
+ # else:
+ # # We're going up; start a sound if need be.
+ # self._smooth_token_count = min(
+ # self._smooth_token_count + 1.0 * self._smooth_increase_speed,
+ # self._token_count,
+ # )
+ # if int(self._smooth_token_count) >= self._token_count:
+ # finished = True
+ # self._smooth_token_count = float(self._token_count)
+ # elif self._ticking_sound is None:
+ # self._ticking_sound = bui.getsound('scoreIncrease')
+ # self._ticking_sound.play()
- bui.textwidget(
- edit=self._token_count_widget,
- text=str(int(self._smooth_token_count)),
- )
+ # bui.textwidget(
+ # edit=self._token_count_widget,
+ # text=str(int(self._smooth_token_count)),
+ # )
- # If we've reached the target, kill the timer/sound/etc.
- if finished:
- self._smooth_update_timer = None
- if self._ticking_sound is not None:
- self._ticking_sound.stop()
- self._ticking_sound = None
- bui.getsound('cashRegister2').play()
+ # # If we've reached the target, kill the timer/sound/etc.
+ # if finished:
+ # self._smooth_update_timer = None
+ # if self._ticking_sound is not None:
+ # self._ticking_sound.stop()
+ # self._ticking_sound = None
+ # bui.getsound('cashRegister2').play()
def _back(self) -> None:
diff --git a/src/assets/ba_data/python/bauiv1lib/helpui.py b/src/assets/ba_data/python/bauiv1lib/helpui.py
index 887f0050..ea4e8722 100644
--- a/src/assets/ba_data/python/bauiv1lib/helpui.py
+++ b/src/assets/ba_data/python/bauiv1lib/helpui.py
@@ -4,40 +4,33 @@
from __future__ import annotations
+from typing import override
+
import bauiv1 as bui
-class HelpWindow(bui.Window):
+class HelpWindow(bui.MainWindow):
"""A window providing help on how to play."""
def __init__(
- self, main_menu: bool = False, origin_widget: bui.Widget | None = None
+ self,
+ transition: str | None = 'in_right',
+ origin_widget: bui.Widget | None = None,
):
# pylint: disable=too-many-statements
# pylint: disable=too-many-locals
bui.set_analytics_screen('Help Window')
- # If they provided an origin-widget, scale up from that.
- scale_origin: tuple[float, float] | None
- if origin_widget is not None:
- self._transition_out = 'out_scale'
- scale_origin = origin_widget.get_screen_space_center()
- transition = 'in_scale'
- else:
- self._transition_out = 'out_right'
- scale_origin = None
- transition = 'in_right'
-
self._r = 'helpWindow'
getres = bui.app.lang.get_resource
- self._main_menu = main_menu
+ # self._main_menu = main_menu
assert bui.app.classic is not None
uiscale = bui.app.ui_v1.uiscale
width = 1050 if uiscale is bui.UIScale.SMALL else 750
- x_offs = 150 if uiscale is bui.UIScale.SMALL else 0
+ x_offs = 70 if uiscale is bui.UIScale.SMALL else 0
height = (
460
if uiscale is bui.UIScale.SMALL
@@ -47,20 +40,24 @@ class HelpWindow(bui.Window):
super().__init__(
root_widget=bui.containerwidget(
size=(width, height),
- transition=transition,
- toolbar_visibility='menu_minimal',
- scale_origin_stack_offset=scale_origin,
- scale=(
- 1.77
+ toolbar_visibility=(
+ 'menu_minimal'
if uiscale is bui.UIScale.SMALL
- else 1.25 if uiscale is bui.UIScale.MEDIUM else 1.0
+ else 'menu_full'
+ ),
+ scale=(
+ 1.55
+ if uiscale is bui.UIScale.SMALL
+ else 1.15 if uiscale is bui.UIScale.MEDIUM else 1.0
),
stack_offset=(
- (0, -30)
+ (0, -24)
if uiscale is bui.UIScale.SMALL
else (0, 15) if uiscale is bui.UIScale.MEDIUM else (0, 0)
),
- )
+ ),
+ transition=transition,
+ origin_widget=origin_widget,
)
bui.textwidget(
@@ -87,20 +84,19 @@ class HelpWindow(bui.Window):
capture_arrows=True,
)
- if bui.app.ui_v1.use_toolbars:
- bui.widget(
- edit=self._scrollwidget,
- right_widget=bui.get_special_widget('party_button'),
- )
+ bui.widget(
+ edit=self._scrollwidget,
+ right_widget=bui.get_special_widget('squad_button'),
+ )
bui.containerwidget(
edit=self._root_widget, selected_child=self._scrollwidget
)
# ugly: create this last so it gets first dibs at touch events (since
# we have it close to the scroll widget)
- if uiscale is bui.UIScale.SMALL and bui.app.ui_v1.use_toolbars:
+ if uiscale is bui.UIScale.SMALL:
bui.containerwidget(
- edit=self._root_widget, on_cancel_call=self._close
+ edit=self._root_widget, on_cancel_call=self.main_window_back
)
bui.widget(
edit=self._scrollwidget,
@@ -109,33 +105,18 @@ class HelpWindow(bui.Window):
else:
btn = bui.buttonwidget(
parent=self._root_widget,
- position=(
- x_offs + (40 + 0 if uiscale is bui.UIScale.SMALL else 70),
- height - (59 if uiscale is bui.UIScale.SMALL else 50),
- ),
- size=(140, 60),
- scale=0.7 if uiscale is bui.UIScale.SMALL else 0.8,
- label=(
- bui.Lstr(resource='backText')
- if self._main_menu
- else 'Close'
- ),
- button_type='back' if self._main_menu else None,
+ position=(x_offs + 50, height - 55),
+ size=(60, 55),
+ scale=0.8,
+ label=bui.charstr(bui.SpecialChar.BACK),
+ button_type='backSmall',
extra_touch_border_scale=2.0,
autoselect=True,
- on_activate_call=self._close,
+ on_activate_call=self.main_window_back,
)
bui.containerwidget(edit=self._root_widget, cancel_button=btn)
- if self._main_menu:
- bui.buttonwidget(
- edit=btn,
- button_type='backSmall',
- size=(60, 55),
- label=bui.charstr(bui.SpecialChar.BACK),
- )
-
- self._sub_width = 660
+ self._sub_width = 810 if uiscale is bui.UIScale.SMALL else 660
self._sub_height = (
1590
+ bui.app.lang.get_resource(f'{self._r}.someDaysExtraSpace')
@@ -639,20 +620,12 @@ class HelpWindow(bui.Window):
res_scale=0.5,
)
- def _close(self) -> None:
- # pylint: disable=cyclic-import
- 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(
- edit=self._root_widget, transition=self._transition_out
- )
- if self._main_menu:
- assert bui.app.classic is not None
- bui.app.ui_v1.set_main_menu_window(
- MainMenuWindow(transition='in_left').get_root_widget(),
- from_window=self._root_widget,
+ @override
+ def get_main_window_state(self) -> bui.MainWindowState:
+ # Support recreating our window for back/refresh purposes.
+ cls = type(self)
+ return bui.BasicMainWindowState(
+ create_call=lambda transition, origin_widget: cls(
+ transition=transition, origin_widget=origin_widget
)
+ )
diff --git a/src/assets/ba_data/python/bauiv1lib/inbox.py b/src/assets/ba_data/python/bauiv1lib/inbox.py
new file mode 100644
index 00000000..ee4b4108
--- /dev/null
+++ b/src/assets/ba_data/python/bauiv1lib/inbox.py
@@ -0,0 +1,119 @@
+# Released under the MIT License. See LICENSE for details.
+#
+"""Provides a popup window to view achievements."""
+
+from __future__ import annotations
+
+from typing import override
+
+from bauiv1lib.popup import PopupWindow
+import bauiv1 as bui
+
+
+class InboxWindow(PopupWindow):
+ """Popup window to show account messages."""
+
+ def __init__(
+ self, position: tuple[float, float], scale: float | None = None
+ ):
+ assert bui.app.classic is not None
+ uiscale = bui.app.ui_v1.uiscale
+ if scale is None:
+ scale = (
+ 2.3
+ if uiscale is bui.UIScale.SMALL
+ else 1.65 if uiscale is bui.UIScale.MEDIUM else 1.23
+ )
+ self._transitioning_out = False
+ self._width = 450
+ self._height = (
+ 300
+ if uiscale is bui.UIScale.SMALL
+ else 370 if uiscale is bui.UIScale.MEDIUM else 450
+ )
+ bg_color = (0.5, 0.4, 0.6)
+
+ # creates our _root_widget
+ super().__init__(
+ position=position,
+ size=(self._width, self._height),
+ scale=scale,
+ bg_color=bg_color,
+ edge_buffer_scale=4.0, # Try to keep button unobscured.
+ )
+
+ self._cancel_button = bui.buttonwidget(
+ parent=self.root_widget,
+ position=(50, self._height - 30),
+ size=(50, 50),
+ scale=0.5,
+ label='',
+ color=bg_color,
+ on_activate_call=self._on_cancel_press,
+ autoselect=True,
+ icon=bui.gettexture('crossOut'),
+ iconscale=1.2,
+ )
+
+ self._title_text = bui.textwidget(
+ parent=self.root_widget,
+ position=(self._width * 0.5, self._height - 20),
+ size=(0, 0),
+ h_align='center',
+ v_align='center',
+ scale=0.6,
+ text='INBOX (UNDER CONSTRUCTION)',
+ maxwidth=200,
+ color=bui.app.ui_v1.title_color,
+ )
+
+ self._scrollwidget = bui.scrollwidget(
+ parent=self.root_widget,
+ size=(self._width - 60, self._height - 70),
+ position=(30, 30),
+ capture_arrows=True,
+ simple_culling_v=10,
+ )
+ bui.widget(edit=self._scrollwidget, autoselect=True)
+
+ bui.containerwidget(
+ edit=self.root_widget, cancel_button=self._cancel_button
+ )
+
+ entries: list[str] = []
+ incr = 20
+ sub_width = self._width - 90
+ sub_height = 40 + len(entries) * incr
+
+ self._subcontainer = bui.containerwidget(
+ parent=self._scrollwidget,
+ size=(sub_width, sub_height),
+ background=False,
+ )
+
+ for i, entry in enumerate(entries):
+ bui.textwidget(
+ parent=self._subcontainer,
+ position=(sub_width * 0.08 - 5, sub_height - 20 - incr * i),
+ maxwidth=20,
+ scale=0.5,
+ flatness=1.0,
+ shadow=0.0,
+ text=entry,
+ size=(0, 0),
+ h_align='right',
+ v_align='center',
+ )
+
+ def _on_cancel_press(self) -> None:
+ self._transition_out()
+
+ def _transition_out(self) -> None:
+ if not self._transitioning_out:
+ self._transitioning_out = True
+ bui.containerwidget(edit=self.root_widget, transition='out_scale')
+
+ @override
+ def on_popup_cancel(self) -> None:
+ bui.getsound('swish').play()
+ self._transition_out()
diff --git a/src/assets/ba_data/python/bauiv1lib/ingamemenu.py b/src/assets/ba_data/python/bauiv1lib/ingamemenu.py
new file mode 100644
index 00000000..abb04027
--- /dev/null
+++ b/src/assets/ba_data/python/bauiv1lib/ingamemenu.py
@@ -0,0 +1,590 @@
+# Released under the MIT License. See LICENSE for details.
+#
+"""Implements the in-gmae menu window."""
+
+from __future__ import annotations
+
+from typing import TYPE_CHECKING, override
+import logging
+
+import bauiv1 as bui
+import bascenev1 as bs
+
+if TYPE_CHECKING:
+ from typing import Any, Callable
+
+
+class InGameMenuWindow(bui.MainWindow):
+ """The menu that can be invoked while in a game."""
+
+ def __init__(
+ self,
+ transition: str | None = 'in_right',
+ origin_widget: bui.Widget | None = None,
+ ):
+
+ # Make a vanilla container; we'll modify it to our needs in
+ # refresh.
+ super().__init__(
+ root_widget=bui.containerwidget(
+ toolbar_visibility=('menu_in_game')
+ ),
+ transition=transition,
+ origin_widget=origin_widget,
+ )
+
+ # Grab this stuff in case it changes.
+ self._is_demo = bui.app.env.demo
+ self._is_arcade = bui.app.env.arcade
+
+ self._p_index = 0
+ self._use_autoselect = True
+ self._button_width = 200.0
+ self._button_height = 45.0
+ self._width = 100.0
+ self._height = 100.0
+
+ self._refresh()
+
+ @override
+ def get_main_window_state(self) -> bui.MainWindowState:
+ # Support recreating our window for back/refresh purposes.
+ cls = type(self)
+ return bui.BasicMainWindowState(
+ create_call=lambda transition, origin_widget: cls(
+ transition=transition, origin_widget=origin_widget
+ )
+ )
+
+ def _refresh(self) -> None:
+
+ # Clear everything that was there.
+ children = self._root_widget.get_children()
+ for child in children:
+ child.delete()
+
+ self._r = 'mainMenu'
+
+ self._input_device = input_device = bs.get_ui_input_device()
+
+ # Are we connected to a local player?
+ self._input_player = input_device.player if input_device else None
+
+ # Are we connected to a remote player?.
+ self._connected_to_remote_player = (
+ input_device.is_attached_to_player()
+ if (input_device and self._input_player is None)
+ else False
+ )
+
+ positions: list[tuple[float, float, float]] = []
+ self._p_index = 0
+
+ self._refresh_in_game(positions)
+
+ h, v, scale = positions[self._p_index]
+ self._p_index += 1
+
+ # If we're in a replay, we have a 'Leave Replay' button.
+ if bs.is_in_replay():
+ bui.buttonwidget(
+ parent=self._root_widget,
+ position=(h - self._button_width * 0.5 * scale, v),
+ scale=scale,
+ size=(self._button_width, self._button_height),
+ autoselect=self._use_autoselect,
+ label=bui.Lstr(resource='replayEndText'),
+ on_activate_call=self._confirm_end_replay,
+ )
+ elif bs.get_foreground_host_session() is not None:
+ bui.buttonwidget(
+ parent=self._root_widget,
+ position=(h - self._button_width * 0.5 * scale, v),
+ scale=scale,
+ size=(self._button_width, self._button_height),
+ autoselect=self._use_autoselect,
+ label=bui.Lstr(
+ resource=self._r
+ + (
+ '.endTestText'
+ if self._is_benchmark()
+ else '.endGameText'
+ )
+ ),
+ on_activate_call=(
+ self._confirm_end_test
+ if self._is_benchmark()
+ else self._confirm_end_game
+ ),
+ )
+ else:
+ # Assume we're in a client-session.
+ bui.buttonwidget(
+ parent=self._root_widget,
+ position=(h - self._button_width * 0.5 * scale, v),
+ scale=scale,
+ size=(self._button_width, self._button_height),
+ autoselect=self._use_autoselect,
+ label=bui.Lstr(resource=f'{self._r}.leavePartyText'),
+ on_activate_call=self._confirm_leave_party,
+ )
+
+ # Add speed-up/slow-down buttons for replays. Ideally this
+ # should be part of a fading-out playback bar like most media
+ # players but this works for now.
+ if bs.is_in_replay():
+ b_size = 50.0
+ b_buffer_1 = 50.0
+ b_buffer_2 = 10.0
+ t_scale = 0.75
+ assert bui.app.classic is not None
+ uiscale = bui.app.ui_v1.uiscale
+ if uiscale is bui.UIScale.SMALL:
+ b_size *= 0.6
+ b_buffer_1 *= 0.8
+ b_buffer_2 *= 1.0
+ v_offs = -40
+ t_scale = 0.5
+ elif uiscale is bui.UIScale.MEDIUM:
+ v_offs = -70
+ else:
+ v_offs = -100
+ self._replay_speed_text = bui.textwidget(
+ parent=self._root_widget,
+ text=bui.Lstr(
+ resource='watchWindow.playbackSpeedText',
+ subs=[('${SPEED}', str(1.23))],
+ ),
+ position=(h, v + v_offs + 15 * t_scale),
+ h_align='center',
+ v_align='center',
+ size=(0, 0),
+ scale=t_scale,
+ )
+
+ # Update to current value.
+ self._change_replay_speed(0)
+
+ # Keep updating in a timer in case it gets changed elsewhere.
+ self._change_replay_speed_timer = bui.AppTimer(
+ 0.25, bui.WeakCall(self._change_replay_speed, 0), repeat=True
+ )
+ btn = bui.buttonwidget(
+ parent=self._root_widget,
+ position=(
+ h - b_size - b_buffer_1,
+ v - b_size - b_buffer_2 + v_offs,
+ ),
+ button_type='square',
+ size=(b_size, b_size),
+ label='',
+ autoselect=True,
+ on_activate_call=bui.Call(self._change_replay_speed, -1),
+ )
+ bui.textwidget(
+ parent=self._root_widget,
+ draw_controller=btn,
+ text='-',
+ position=(
+ h - b_size * 0.5 - b_buffer_1,
+ v - b_size * 0.5 - b_buffer_2 + 5 * t_scale + v_offs,
+ ),
+ h_align='center',
+ v_align='center',
+ size=(0, 0),
+ scale=3.0 * t_scale,
+ )
+ btn = bui.buttonwidget(
+ parent=self._root_widget,
+ position=(h + b_buffer_1, v - b_size - b_buffer_2 + v_offs),
+ button_type='square',
+ size=(b_size, b_size),
+ label='',
+ autoselect=True,
+ on_activate_call=bui.Call(self._change_replay_speed, 1),
+ )
+ bui.textwidget(
+ parent=self._root_widget,
+ draw_controller=btn,
+ text='+',
+ position=(
+ h + b_size * 0.5 + b_buffer_1,
+ v - b_size * 0.5 - b_buffer_2 + 5 * t_scale + v_offs,
+ ),
+ h_align='center',
+ v_align='center',
+ size=(0, 0),
+ scale=3.0 * t_scale,
+ )
+ self._pause_resume_button = btn = bui.buttonwidget(
+ parent=self._root_widget,
+ position=(h - b_size * 0.5, v - b_size - b_buffer_2 + v_offs),
+ button_type='square',
+ size=(b_size, b_size),
+ label=bui.charstr(
+ bui.SpecialChar.PLAY_BUTTON
+ if bs.is_replay_paused()
+ else bui.SpecialChar.PAUSE_BUTTON
+ ),
+ autoselect=True,
+ on_activate_call=bui.Call(self._pause_or_resume_replay),
+ )
+ btn = bui.buttonwidget(
+ parent=self._root_widget,
+ position=(
+ h - b_size * 1.5 - b_buffer_1 * 2,
+ v - b_size - b_buffer_2 + v_offs,
+ ),
+ button_type='square',
+ size=(b_size, b_size),
+ label='',
+ autoselect=True,
+ on_activate_call=bui.WeakCall(self._rewind_replay),
+ )
+ bui.textwidget(
+ parent=self._root_widget,
+ draw_controller=btn,
+ # text='<<',
+ text=bui.charstr(bui.SpecialChar.REWIND_BUTTON),
+ position=(
+ h - b_size - b_buffer_1 * 2,
+ v - b_size * 0.5 - b_buffer_2 + 5 * t_scale + v_offs,
+ ),
+ h_align='center',
+ v_align='center',
+ size=(0, 0),
+ scale=2.0 * t_scale,
+ )
+ btn = bui.buttonwidget(
+ parent=self._root_widget,
+ position=(
+ h + b_size * 0.5 + b_buffer_1 * 2,
+ v - b_size - b_buffer_2 + v_offs,
+ ),
+ button_type='square',
+ size=(b_size, b_size),
+ label='',
+ autoselect=True,
+ on_activate_call=bui.WeakCall(self._forward_replay),
+ )
+ bui.textwidget(
+ parent=self._root_widget,
+ draw_controller=btn,
+ # text='>>',
+ text=bui.charstr(bui.SpecialChar.FAST_FORWARD_BUTTON),
+ position=(
+ h + b_size + b_buffer_1 * 2,
+ v - b_size * 0.5 - b_buffer_2 + 5 * t_scale + v_offs,
+ ),
+ h_align='center',
+ v_align='center',
+ size=(0, 0),
+ scale=2.0 * t_scale,
+ )
+
+ def _rewind_replay(self) -> None:
+ bs.seek_replay(-2 * pow(2, bs.get_replay_speed_exponent()))
+
+ def _forward_replay(self) -> None:
+ bs.seek_replay(2 * pow(2, bs.get_replay_speed_exponent()))
+
+ def _refresh_in_game(
+ self, positions: list[tuple[float, float, float]]
+ ) -> tuple[float, float, float]:
+ # pylint: disable=too-many-branches
+ # pylint: disable=too-many-locals
+ # pylint: disable=too-many-statements
+ assert bui.app.classic is not None
+ custom_menu_entries: list[dict[str, Any]] = []
+ session = bs.get_foreground_host_session()
+ if session is not None:
+ try:
+ custom_menu_entries = session.get_custom_menu_entries()
+ for cme in custom_menu_entries:
+ cme_any: Any = cme # Type check may not hold true.
+ if (
+ not isinstance(cme_any, dict)
+ or 'label' not in cme
+ or not isinstance(cme['label'], (str, bui.Lstr))
+ or 'call' not in cme
+ or not callable(cme['call'])
+ ):
+ raise ValueError(
+ 'invalid custom menu entry: ' + str(cme)
+ )
+ except Exception:
+ custom_menu_entries = []
+ logging.exception(
+ 'Error getting custom menu entries for %s.', session
+ )
+ self._width = 250.0
+ self._height = 250.0 if self._input_player else 180.0
+ if (self._is_demo or self._is_arcade) and self._input_player:
+ self._height -= 40
+ # if not self._have_settings_button:
+ self._height -= 50
+ if self._connected_to_remote_player:
+ # In this case we have a leave *and* a disconnect button.
+ self._height += 50
+ self._height += 50 * (len(custom_menu_entries))
+ uiscale = bui.app.ui_v1.uiscale
+ bui.containerwidget(
+ edit=self._root_widget,
+ size=(self._width, self._height),
+ scale=(
+ 2.15
+ if uiscale is bui.UIScale.SMALL
+ else 1.6 if uiscale is bui.UIScale.MEDIUM else 1.0
+ ),
+ )
+ h = 125.0
+ v = self._height - 80.0 if self._input_player else self._height - 60
+ h_offset = 0
+ d_h_offset = 0
+ v_offset = -50
+ for _i in range(6 + len(custom_menu_entries)):
+ positions.append((h, v, 1.0))
+ v += v_offset
+ h += h_offset
+ h_offset += d_h_offset
+ # self._play_button = None
+ bui.app.classic.pause()
+
+ # Player name if applicable.
+ if self._input_player:
+ player_name = self._input_player.getname()
+ h, v, scale = positions[self._p_index]
+ v += 35
+ bui.textwidget(
+ parent=self._root_widget,
+ position=(h - self._button_width / 2, v),
+ size=(self._button_width, self._button_height),
+ color=(1, 1, 1, 0.5),
+ scale=0.7,
+ h_align='center',
+ text=bui.Lstr(value=player_name),
+ )
+ else:
+ player_name = ''
+ h, v, scale = positions[self._p_index]
+ self._p_index += 1
+ btn = bui.buttonwidget(
+ parent=self._root_widget,
+ position=(h - self._button_width / 2, v),
+ size=(self._button_width, self._button_height),
+ scale=scale,
+ label=bui.Lstr(resource=f'{self._r}.resumeText'),
+ autoselect=self._use_autoselect,
+ on_activate_call=self._resume,
+ )
+ bui.containerwidget(edit=self._root_widget, cancel_button=btn)
+
+ # Add any custom options defined by the current game.
+ for entry in custom_menu_entries:
+ h, v, scale = positions[self._p_index]
+ self._p_index += 1
+
+ # Ask the entry whether we should resume when we call
+ # it (defaults to true).
+ resume = bool(entry.get('resume_on_call', True))
+
+ if resume:
+ call = bui.Call(self._resume_and_call, entry['call'])
+ else:
+ call = bui.Call(entry['call'], bui.WeakCall(self._resume))
+
+ bui.buttonwidget(
+ parent=self._root_widget,
+ position=(h - self._button_width / 2, v),
+ size=(self._button_width, self._button_height),
+ scale=scale,
+ on_activate_call=call,
+ label=entry['label'],
+ autoselect=self._use_autoselect,
+ )
+
+ # Add a 'leave' button if the menu-owner has a player.
+ if (self._input_player or self._connected_to_remote_player) and not (
+ self._is_demo or self._is_arcade
+ ):
+ h, v, scale = positions[self._p_index]
+ self._p_index += 1
+ btn = bui.buttonwidget(
+ parent=self._root_widget,
+ position=(h - self._button_width / 2, v),
+ size=(self._button_width, self._button_height),
+ scale=scale,
+ on_activate_call=self._leave,
+ label='',
+ autoselect=self._use_autoselect,
+ )
+
+ if (
+ player_name != ''
+ and player_name[0] != '<'
+ and player_name[-1] != '>'
+ ):
+ txt = bui.Lstr(
+ resource=f'{self._r}.justPlayerText',
+ subs=[('${NAME}', player_name)],
+ )
+ else:
+ txt = bui.Lstr(value=player_name)
+ bui.textwidget(
+ parent=self._root_widget,
+ position=(
+ h,
+ v
+ + self._button_height
+ * (0.64 if player_name != '' else 0.5),
+ ),
+ size=(0, 0),
+ text=bui.Lstr(resource=f'{self._r}.leaveGameText'),
+ scale=(0.83 if player_name != '' else 1.0),
+ color=(0.75, 1.0, 0.7),
+ h_align='center',
+ v_align='center',
+ draw_controller=btn,
+ maxwidth=self._button_width * 0.9,
+ )
+ bui.textwidget(
+ parent=self._root_widget,
+ position=(h, v + self._button_height * 0.27),
+ size=(0, 0),
+ text=txt,
+ color=(0.75, 1.0, 0.7),
+ h_align='center',
+ v_align='center',
+ draw_controller=btn,
+ scale=0.45,
+ maxwidth=self._button_width * 0.9,
+ )
+ return h, v, scale
+
+ def _change_replay_speed(self, offs: int) -> None:
+ if not self._replay_speed_text:
+ if bui.do_once():
+ print('_change_replay_speed called without widget')
+ return
+ bs.set_replay_speed_exponent(bs.get_replay_speed_exponent() + offs)
+ actual_speed = pow(2.0, bs.get_replay_speed_exponent())
+ bui.textwidget(
+ edit=self._replay_speed_text,
+ text=bui.Lstr(
+ resource='watchWindow.playbackSpeedText',
+ subs=[('${SPEED}', str(actual_speed))],
+ ),
+ )
+
+ def _pause_or_resume_replay(self) -> None:
+ if bs.is_replay_paused():
+ bs.resume_replay()
+ bui.buttonwidget(
+ edit=self._pause_resume_button,
+ label=bui.charstr(bui.SpecialChar.PAUSE_BUTTON),
+ )
+ else:
+ bs.pause_replay()
+ bui.buttonwidget(
+ edit=self._pause_resume_button,
+ label=bui.charstr(bui.SpecialChar.PLAY_BUTTON),
+ )
+
+ def _is_benchmark(self) -> bool:
+ session = bs.get_foreground_host_session()
+ return getattr(session, 'benchmark_type', None) == 'cpu' or (
+ bui.app.classic is not None
+ and bui.app.classic.stress_test_update_timer is not None
+ )
+
+ def _confirm_end_game(self) -> None:
+ # pylint: disable=cyclic-import
+ from bauiv1lib.confirm import ConfirmWindow
+
+ # FIXME: Currently we crash calling this on client-sessions.
+
+ # Select cancel by default; this occasionally gets called by accident
+ # in a fit of button mashing and this will help reduce damage.
+ ConfirmWindow(
+ bui.Lstr(resource=f'{self._r}.exitToMenuText'),
+ self._end_game,
+ cancel_is_selected=True,
+ )
+
+ def _confirm_end_test(self) -> None:
+ # pylint: disable=cyclic-import
+ from bauiv1lib.confirm import ConfirmWindow
+
+ # Select cancel by default; this occasionally gets called by accident
+ # in a fit of button mashing and this will help reduce damage.
+ ConfirmWindow(
+ bui.Lstr(resource=f'{self._r}.exitToMenuText'),
+ self._end_game,
+ cancel_is_selected=True,
+ )
+
+ def _confirm_end_replay(self) -> None:
+ # pylint: disable=cyclic-import
+ from bauiv1lib.confirm import ConfirmWindow
+
+ # Select cancel by default; this occasionally gets called by accident
+ # in a fit of button mashing and this will help reduce damage.
+ ConfirmWindow(
+ bui.Lstr(resource=f'{self._r}.exitToMenuText'),
+ self._end_game,
+ cancel_is_selected=True,
+ )
+
+ def _confirm_leave_party(self) -> None:
+ # pylint: disable=cyclic-import
+ from bauiv1lib.confirm import ConfirmWindow
+
+ # Select cancel by default; this occasionally gets called by accident
+ # in a fit of button mashing and this will help reduce damage.
+ ConfirmWindow(
+ bui.Lstr(resource=f'{self._r}.leavePartyConfirmText'),
+ self._leave_party,
+ cancel_is_selected=True,
+ )
+
+ def _leave_party(self) -> None:
+ bs.disconnect_from_host()
+
+ def _end_game(self) -> 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.containerwidget(edit=self._root_widget, transition='out_left')
+ bui.app.classic.return_to_main_menu_session_gracefully(reset_ui=False)
+
+ def _leave(self) -> None:
+ if self._input_player:
+ self._input_player.remove_from_game()
+ elif self._connected_to_remote_player:
+ if self._input_device:
+ self._input_device.detach_from_player()
+ self._resume()
+
+ def _resume_and_call(self, call: Callable[[], Any]) -> None:
+ self._resume()
+ call()
+
+ def _resume(self) -> None:
+ classic = bui.app.classic
+
+ assert classic is not None
+ classic.resume()
+
+ bui.app.ui_v1.clear_main_window()
+
+ # If there's callbacks waiting for us to resume, call them.
+ for call in classic.main_menu_resume_callbacks:
+ try:
+ call()
+ except Exception:
+ logging.exception('Error in classic resume callback.')
+
+ classic.main_menu_resume_callbacks.clear()
diff --git a/src/assets/ba_data/python/bauiv1lib/inventory.py b/src/assets/ba_data/python/bauiv1lib/inventory.py
new file mode 100644
index 00000000..6297f3aa
--- /dev/null
+++ b/src/assets/ba_data/python/bauiv1lib/inventory.py
@@ -0,0 +1,127 @@
+# Released under the MIT License. See LICENSE for details.
+#
+"""Provides help related ui."""
+
+from __future__ import annotations
+
+from typing import override
+
+import bauiv1 as bui
+
+
+class InventoryWindow(bui.MainWindow):
+ """Shows what you got."""
+
+ def __init__(
+ self,
+ transition: str | None = 'in_right',
+ origin_widget: bui.Widget | None = None,
+ ):
+
+ bui.set_analytics_screen('Help Window')
+
+ assert bui.app.classic is not None
+ uiscale = bui.app.ui_v1.uiscale
+ width = 1050 if uiscale is bui.UIScale.SMALL else 750
+ height = (
+ 460
+ if uiscale is bui.UIScale.SMALL
+ else 530 if uiscale is bui.UIScale.MEDIUM else 600
+ )
+ x_offs = 70 if uiscale is bui.UIScale.SMALL else 0
+
+ super().__init__(
+ root_widget=bui.containerwidget(
+ size=(width, height),
+ toolbar_visibility=(
+ 'menu_minimal'
+ if uiscale is bui.UIScale.SMALL
+ else 'menu_full'
+ ),
+ scale=(
+ 1.55
+ if uiscale is bui.UIScale.SMALL
+ else 1.15 if uiscale is bui.UIScale.MEDIUM else 1.0
+ ),
+ stack_offset=(
+ (0, -24)
+ if uiscale is bui.UIScale.SMALL
+ else (0, 15) if uiscale is bui.UIScale.MEDIUM else (0, 0)
+ ),
+ ),
+ transition=transition,
+ origin_widget=origin_widget,
+ )
+
+ bui.textwidget(
+ parent=self._root_widget,
+ position=(0, height - (50 if uiscale is bui.UIScale.SMALL else 45)),
+ size=(width, 25),
+ text='INVENTORY',
+ color=bui.app.ui_v1.title_color,
+ h_align='center',
+ v_align='center',
+ )
+
+ if uiscale is bui.UIScale.SMALL:
+ bui.containerwidget(
+ edit=self._root_widget, on_cancel_call=self.main_window_back
+ )
+ else:
+ btn = bui.buttonwidget(
+ parent=self._root_widget,
+ position=(x_offs + 50, height - 55),
+ size=(60, 55),
+ scale=0.8,
+ label=bui.charstr(bui.SpecialChar.BACK),
+ button_type='backSmall',
+ extra_touch_border_scale=2.0,
+ autoselect=True,
+ on_activate_call=self.main_window_back,
+ )
+ bui.containerwidget(edit=self._root_widget, cancel_button=btn)
+
+ bui.textwidget(
+ parent=self._root_widget,
+ position=(0, height - 120),
+ size=(width, 25),
+ text='(under construction)',
+ scale=0.7,
+ h_align='center',
+ v_align='center',
+ )
+
+ button_width = 300
+ self._player_profiles_button = btn = bui.buttonwidget(
+ parent=self._root_widget,
+ position=((width - button_width) * 0.5, height - 200),
+ autoselect=True,
+ size=(button_width, 60),
+ label=bui.Lstr(resource='playerProfilesWindow.titleText'),
+ color=(0.55, 0.5, 0.6),
+ icon=bui.gettexture('cuteSpaz'),
+ textcolor=(0.75, 0.7, 0.8),
+ on_activate_call=self._player_profiles_press,
+ )
+
+ def _player_profiles_press(self) -> None:
+ # pylint: disable=cyclic-import
+ 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.main_window_replace(
+ ProfileBrowserWindow(origin_widget=self._player_profiles_button)
+ )
+
+ @override
+ def get_main_window_state(self) -> bui.MainWindowState:
+ # Support recreating our window for back/refresh purposes.
+ cls = type(self)
+ return bui.BasicMainWindowState(
+ create_call=lambda transition, origin_widget: cls(
+ transition=transition, origin_widget=origin_widget
+ )
+ )
diff --git a/src/assets/ba_data/python/bauiv1lib/kiosk.py b/src/assets/ba_data/python/bauiv1lib/kiosk.py
index 46475635..206569dc 100644
--- a/src/assets/ba_data/python/bauiv1lib/kiosk.py
+++ b/src/assets/ba_data/python/bauiv1lib/kiosk.py
@@ -8,10 +8,14 @@ import bascenev1 as bs
import bauiv1 as bui
-class KioskWindow(bui.Window):
+class KioskWindow(bui.MainWindow):
"""Kiosk mode window."""
- def __init__(self, transition: str = 'in_right'):
+ def __init__(
+ self,
+ transition: str | None = 'in_right',
+ origin_widget: bui.Widget | None = None,
+ ):
# pylint: disable=too-many-locals, too-many-statements
from bauiv1lib.confirm import QuitWindow
@@ -26,11 +30,13 @@ class KioskWindow(bui.Window):
super().__init__(
root_widget=bui.containerwidget(
size=(self._width, self._height),
- transition=transition,
+ # transition=transition,
on_cancel_call=_do_cancel,
background=False,
stack_offset=(0, -130),
- )
+ ),
+ transition=transition,
+ origin_widget=origin_widget,
)
self._r = 'kioskWindow'
@@ -501,6 +507,7 @@ class KioskWindow(bui.Window):
bui.containerwidget(edit=self._root_widget, transition='out_left')
def _do_full_menu(self) -> None:
+ # pylint: disable=cyclic-import
from bauiv1lib.mainmenu import MainMenuWindow
# no-op if our underlying widget is dead or on its way out.
@@ -512,6 +519,4 @@ class KioskWindow(bui.Window):
self._save_state()
bui.containerwidget(edit=self._root_widget, transition='out_left')
bui.app.classic.did_menu_intro = True # prevent delayed transition-in
- bui.app.ui_v1.set_main_menu_window(
- MainMenuWindow().get_root_widget(), from_window=self._root_widget
- )
+ bui.app.ui_v1.set_main_window(MainMenuWindow(), from_window=self)
diff --git a/src/assets/ba_data/python/bauiv1lib/league/rankwindow.py b/src/assets/ba_data/python/bauiv1lib/league/rankwindow.py
index 7e6d8d1e..b4e235e6 100644
--- a/src/assets/ba_data/python/bauiv1lib/league/rankwindow.py
+++ b/src/assets/ba_data/python/bauiv1lib/league/rankwindow.py
@@ -16,14 +16,14 @@ if TYPE_CHECKING:
from typing import Any
-class LeagueRankWindow(bui.Window):
+class LeagueRankWindow(bui.MainWindow):
"""Window for showing league rank."""
def __init__(
self,
- transition: str = 'in_right',
- modal: bool = False,
+ transition: str | None = 'in_right',
origin_widget: bui.Widget | None = None,
+ modal: bool = False,
):
# pylint: disable=too-many-statements
plus = bui.app.plus
@@ -46,22 +46,18 @@ class LeagueRankWindow(bui.Window):
self._to_ranked_text: bui.Widget | None = None
self._trophy_counts_reset_text: bui.Widget | None = None
- # If they provided an origin-widget, scale up from that.
- scale_origin: tuple[float, float] | None
+ # Need to handle transitioning out ourself for modal case
if origin_widget is not None:
self._transition_out = 'out_scale'
- scale_origin = origin_widget.get_screen_space_center()
- transition = 'in_scale'
else:
self._transition_out = 'out_right'
- scale_origin = None
assert bui.app.classic is not None
uiscale = bui.app.ui_v1.uiscale
- self._width = 1320 if uiscale is bui.UIScale.SMALL else 1120
+ self._width = 1490 if uiscale is bui.UIScale.SMALL else 1120
x_inset = 100 if uiscale is bui.UIScale.SMALL else 0
self._height = (
- 657
+ 660
if uiscale is bui.UIScale.SMALL
else 710 if uiscale is bui.UIScale.MEDIUM else 800
)
@@ -69,6 +65,8 @@ class LeagueRankWindow(bui.Window):
self._rdict = bui.app.lang.get_resource(self._r)
top_extra = 20 if uiscale is bui.UIScale.SMALL else 0
+ self._xoffs = 80.0 if uiscale is bui.UIScale.SMALL else 0
+
self._league_url_arg = ''
self._is_current_season = False
@@ -78,37 +76,62 @@ class LeagueRankWindow(bui.Window):
root_widget=bui.containerwidget(
size=(self._width, self._height + top_extra),
stack_offset=(
- (0, -15)
+ (0, 0)
if uiscale is bui.UIScale.SMALL
else (0, 10) if uiscale is bui.UIScale.MEDIUM else (0, 0)
),
- transition=transition,
- scale_origin_stack_offset=scale_origin,
scale=(
- 1.2
+ 1.08
if uiscale is bui.UIScale.SMALL
else 0.93 if uiscale is bui.UIScale.MEDIUM else 0.8
),
- )
+ toolbar_visibility=(
+ 'menu_minimal'
+ if uiscale is bui.UIScale.SMALL
+ else 'menu_full'
+ ),
+ ),
+ transition=transition,
+ origin_widget=origin_widget,
)
- self._back_button = btn = bui.buttonwidget(
- parent=self._root_widget,
- position=(
- 75 + x_inset,
- self._height - 87 - (4 if uiscale is bui.UIScale.SMALL else 0),
- ),
- size=(120, 60),
- scale=1.2,
- autoselect=True,
- label=bui.Lstr(resource='doneText' if self._modal else 'backText'),
- button_type=None if self._modal else 'back',
- on_activate_call=self._back,
- )
+ if uiscale is bui.UIScale.SMALL:
+ self._back_button = bui.get_special_widget('back_button')
+ bui.containerwidget(
+ edit=self._root_widget, on_cancel_call=self._back
+ )
+ else:
+ self._back_button = btn = bui.buttonwidget(
+ parent=self._root_widget,
+ position=(75 + x_inset, self._height - 87),
+ size=(120, 60),
+ scale=1.2,
+ autoselect=True,
+ label=bui.Lstr(
+ resource='doneText' if self._modal else 'backText'
+ ),
+ button_type=None if self._modal else 'back',
+ on_activate_call=self._back,
+ )
+ bui.buttonwidget(
+ edit=btn,
+ button_type='backSmall',
+ position=(75 + x_inset, self._height - 87),
+ size=(60, 55),
+ label=bui.charstr(bui.SpecialChar.BACK),
+ )
+ bui.containerwidget(
+ edit=self._root_widget,
+ cancel_button=self._back_button,
+ selected_child=self._back_button,
+ )
self._title_text = bui.textwidget(
parent=self._root_widget,
- position=(self._width * 0.5, self._height - 56),
+ position=(
+ self._width * 0.5,
+ self._height - (66 if uiscale is bui.UIScale.SMALL else 56),
+ ),
size=(0, 0),
text=bui.Lstr(
resource='league.leagueRankText',
@@ -121,17 +144,6 @@ class LeagueRankWindow(bui.Window):
v_align='center',
)
- bui.buttonwidget(
- edit=btn,
- button_type='backSmall',
- position=(
- 75 + x_inset,
- self._height - 87 - (2 if uiscale is bui.UIScale.SMALL else 0),
- ),
- size=(60, 55),
- label=bui.charstr(bui.SpecialChar.BACK),
- )
-
self._scroll_width = self._width - (130 + 2 * x_inset)
self._scroll_height = self._height - 160
self._scrollwidget = bui.scrollwidget(
@@ -143,11 +155,6 @@ class LeagueRankWindow(bui.Window):
)
bui.widget(edit=self._scrollwidget, autoselect=True)
bui.containerwidget(edit=self._scrollwidget, claims_left_right=True)
- bui.containerwidget(
- edit=self._root_widget,
- cancel_button=self._back_button,
- selected_child=self._back_button,
- )
self._last_power_ranking_query_time: float | None = None
self._doing_power_ranking_query = False
@@ -374,7 +381,7 @@ class LeagueRankWindow(bui.Window):
bui.textwidget(
parent=w_parent,
- position=(h2 - 60, v2 + 106),
+ position=(self._xoffs + h2 - 60, v2 + 106),
size=(0, 0),
flatness=1.0,
shadow=0.0,
@@ -388,7 +395,7 @@ class LeagueRankWindow(bui.Window):
self._power_ranking_achievements_button = bui.buttonwidget(
parent=w_parent,
- position=(h2 - 60, v2 + 10),
+ position=(self._xoffs + h2 - 60, v2 + 10),
size=(200, 80),
icon=bui.gettexture('achievementsIcon'),
autoselect=True,
@@ -402,7 +409,7 @@ class LeagueRankWindow(bui.Window):
self._power_ranking_achievement_total_text = bui.textwidget(
parent=w_parent,
- position=(h2 + h_offs_tally, v2 + 45),
+ position=(self._xoffs + h2 + h_offs_tally, v2 + 45),
size=(0, 0),
flatness=1.0,
shadow=0.0,
@@ -418,7 +425,7 @@ class LeagueRankWindow(bui.Window):
self._power_ranking_trophies_button = bui.buttonwidget(
parent=w_parent,
- position=(h2 - 60, v2 + 10),
+ position=(self._xoffs + h2 - 60, v2 + 10),
size=(200, 80),
icon=bui.gettexture('medalSilver'),
autoselect=True,
@@ -430,7 +437,7 @@ class LeagueRankWindow(bui.Window):
)
self._power_ranking_trophies_total_text = bui.textwidget(
parent=w_parent,
- position=(h2 + h_offs_tally, v2 + 45),
+ position=(self._xoffs + h2 + h_offs_tally, v2 + 45),
size=(0, 0),
flatness=1.0,
shadow=0.0,
@@ -446,7 +453,7 @@ class LeagueRankWindow(bui.Window):
bui.textwidget(
parent=w_parent,
- position=(h2 - 60, v2 + 86),
+ position=(self._xoffs + h2 - 60, v2 + 86),
size=(0, 0),
flatness=1.0,
shadow=0.0,
@@ -462,7 +469,7 @@ class LeagueRankWindow(bui.Window):
if plus.get_v1_account_misc_read_val('act', False):
self._activity_mult_button = bui.buttonwidget(
parent=w_parent,
- position=(h2 - 60, v2 + 10),
+ position=(self._xoffs + h2 - 60, v2 + 10),
size=(200, 60),
icon=bui.gettexture('heart'),
icon_color=(0.5, 0, 0.5),
@@ -476,7 +483,7 @@ class LeagueRankWindow(bui.Window):
self._activity_mult_text = bui.textwidget(
parent=w_parent,
- position=(h2 + h_offs_tally, v2 + 40),
+ position=(self._xoffs + h2 + h_offs_tally, v2 + 40),
size=(0, 0),
flatness=1.0,
shadow=0.0,
@@ -493,7 +500,7 @@ class LeagueRankWindow(bui.Window):
self._pro_mult_button = bui.buttonwidget(
parent=w_parent,
- position=(h2 - 60, v2 + 10),
+ position=(self._xoffs + h2 - 60, v2 + 10),
size=(200, 60),
icon=bui.gettexture('logo'),
icon_color=(0.3, 0, 0.3),
@@ -510,7 +517,7 @@ class LeagueRankWindow(bui.Window):
self._pro_mult_text = bui.textwidget(
parent=w_parent,
- position=(h2 + h_offs_tally, v2 + 40),
+ position=(self._xoffs + h2 + h_offs_tally, v2 + 40),
size=(0, 0),
flatness=1.0,
shadow=0.0,
@@ -526,7 +533,7 @@ class LeagueRankWindow(bui.Window):
v2 -= spc
bui.textwidget(
parent=w_parent,
- position=(h2 + h_offs_tally - 10 - 40, v2 + 35),
+ position=(self._xoffs + h2 + h_offs_tally - 10 - 40, v2 + 35),
size=(0, 0),
flatness=1.0,
shadow=0.0,
@@ -539,7 +546,7 @@ class LeagueRankWindow(bui.Window):
)
self._power_ranking_total_text = bui.textwidget(
parent=w_parent,
- position=(h2 + h_offs_tally - 40, v2 + 35),
+ position=(self._xoffs + h2 + h_offs_tally - 40, v2 + 35),
size=(0, 0),
flatness=1.0,
shadow=0.0,
@@ -553,7 +560,7 @@ class LeagueRankWindow(bui.Window):
self._season_show_text = bui.textwidget(
parent=w_parent,
- position=(390 - 15, v - 20),
+ position=(self._xoffs + 390 - 15, v - 20),
size=(0, 0),
color=(0.6, 0.6, 0.7),
maxwidth=200,
@@ -567,7 +574,7 @@ class LeagueRankWindow(bui.Window):
self._league_title_text = bui.textwidget(
parent=w_parent,
- position=(470, v - 97),
+ position=(self._xoffs + 470, v - 97),
size=(0, 0),
color=(0.6, 0.6, 0.7),
maxwidth=230,
@@ -583,7 +590,7 @@ class LeagueRankWindow(bui.Window):
self._league_text_maxwidth = 210
self._league_text = bui.textwidget(
parent=w_parent,
- position=(470, v - 140),
+ position=(self._xoffs + 470, v - 140),
size=(0, 0),
color=(1, 1, 1),
maxwidth=self._league_text_maxwidth,
@@ -597,7 +604,7 @@ class LeagueRankWindow(bui.Window):
self._league_number_base_pos = (470, v - 140)
self._league_number_text = bui.textwidget(
parent=w_parent,
- position=(470, v - 140),
+ position=(self._xoffs + 470, v - 140),
size=(0, 0),
color=(1, 1, 1),
maxwidth=100,
@@ -611,7 +618,7 @@ class LeagueRankWindow(bui.Window):
self._your_power_ranking_text = bui.textwidget(
parent=w_parent,
- position=(470, v - 142 - 70),
+ position=(self._xoffs + 470, v - 142 - 70),
size=(0, 0),
color=(0.6, 0.6, 0.7),
maxwidth=230,
@@ -625,7 +632,7 @@ class LeagueRankWindow(bui.Window):
self._to_ranked_text = bui.textwidget(
parent=w_parent,
- position=(470, v - 250 - 70),
+ position=(self._xoffs + 470, v - 250 - 70),
size=(0, 0),
color=(0.6, 0.6, 0.7),
maxwidth=230,
@@ -639,7 +646,7 @@ class LeagueRankWindow(bui.Window):
self._power_ranking_rank_text = bui.textwidget(
parent=w_parent,
- position=(473, v - 210 - 70),
+ position=(self._xoffs + 473, v - 210 - 70),
size=(0, 0),
big=False,
text='-',
@@ -650,7 +657,7 @@ class LeagueRankWindow(bui.Window):
self._season_ends_text = bui.textwidget(
parent=w_parent,
- position=(470, v - 380),
+ position=(self._xoffs + 470, v - 380),
size=(0, 0),
color=(0.6, 0.6, 0.6),
maxwidth=230,
@@ -663,7 +670,7 @@ class LeagueRankWindow(bui.Window):
)
self._trophy_counts_reset_text = bui.textwidget(
parent=w_parent,
- position=(470, v - 410),
+ position=(self._xoffs + 470, v - 410),
size=(0, 0),
color=(0.5, 0.5, 0.5),
maxwidth=230,
@@ -685,7 +692,7 @@ class LeagueRankWindow(bui.Window):
self._see_more_button = bui.buttonwidget(
parent=w_parent,
label=self._rdict.seeMoreText,
- position=(h, v),
+ position=(self._xoffs + h, v),
color=(0.5, 0.5, 0.6),
textcolor=(0.7, 0.7, 0.8),
size=(230, 60),
@@ -732,6 +739,7 @@ class LeagueRankWindow(bui.Window):
if not self._root_widget:
return
plus = bui.app.plus
+ uiscale = bui.app.ui_v1.uiscale
assert plus is not None
assert bui.app.classic is not None
accounts = bui.app.classic.accounts
@@ -825,7 +833,7 @@ class LeagueRankWindow(bui.Window):
assert self._subcontainer
self._season_popup_menu = PopupMenu(
parent=self._subcontainer,
- position=(390, v - 45),
+ position=(self._xoffs + 390, v - 45),
width=150,
button_size=(200, 50),
choices=season_choices,
@@ -843,11 +851,12 @@ class LeagueRankWindow(bui.Window):
edit=self._season_popup_menu.get_button(),
up_widget=self._back_button,
)
- bui.widget(
- edit=self._back_button,
- down_widget=self._power_ranking_achievements_button,
- right_widget=self._season_popup_menu.get_button(),
- )
+ if uiscale is not bui.UIScale.SMALL:
+ bui.widget(
+ edit=self._back_button,
+ down_widget=self._power_ranking_achievements_button,
+ right_widget=self._season_popup_menu.get_button(),
+ )
bui.textwidget(
edit=self._league_title_text,
@@ -928,7 +937,10 @@ class LeagueRankWindow(bui.Window):
text=lnum,
color=lcolor,
position=(
- self._league_number_base_pos[0] + l_text_width * 0.5 + 8,
+ self._xoffs
+ + self._league_number_base_pos[0]
+ + l_text_width * 0.5
+ + 8,
self._league_number_base_pos[1] + 10,
),
)
@@ -957,7 +969,7 @@ class LeagueRankWindow(bui.Window):
bui.textwidget(
edit=self._power_ranking_rank_text,
- position=(473, v - 70 - (170 if do_percent else 220)),
+ position=(self._xoffs + 473, v - 70 - (170 if do_percent else 220)),
text=status_text,
big=(in_top or do_percent),
scale=(
@@ -1080,7 +1092,7 @@ class LeagueRankWindow(bui.Window):
self._power_ranking_score_widgets.append(
bui.textwidget(
parent=w_parent,
- position=(h2 - 20, v2),
+ position=(self._xoffs + h2 - 20, v2),
size=(0, 0),
color=(1, 1, 1) if is_us else (0.6, 0.6, 0.7),
maxwidth=40,
@@ -1095,7 +1107,7 @@ class LeagueRankWindow(bui.Window):
self._power_ranking_score_widgets.append(
bui.textwidget(
parent=w_parent,
- position=(h2 + 20, v2),
+ position=(self._xoffs + h2 + 20, v2),
size=(0, 0),
color=(1, 1, 1) if is_us else tally_color,
maxwidth=60,
@@ -1109,7 +1121,7 @@ class LeagueRankWindow(bui.Window):
)
txt = bui.textwidget(
parent=w_parent,
- position=(h2 + 60, v2 - (28 * 0.5) / 0.9),
+ position=(self._xoffs + h2 + 60, v2 - (28 * 0.5) / 0.9),
size=(210 / 0.9, 28),
color=(1, 1, 1) if is_us else (0.6, 0.6, 0.6),
maxwidth=210,
@@ -1155,19 +1167,15 @@ class LeagueRankWindow(bui.Window):
pass
def _back(self) -> None:
- 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()
- bui.containerwidget(
- edit=self._root_widget, transition=self._transition_out
- )
- if not self._modal:
- assert bui.app.classic is not None
- bui.app.ui_v1.set_main_menu_window(
- CoopBrowserWindow(transition='in_left').get_root_widget(),
- from_window=self._root_widget,
+ if self._modal:
+ bui.containerwidget(
+ edit=self._root_widget, transition=self._transition_out
)
+ else:
+ self.main_window_back()
diff --git a/src/assets/ba_data/python/bauiv1lib/mainmenu.py b/src/assets/ba_data/python/bauiv1lib/mainmenu.py
index cc793476..ea7fd0e0 100644
--- a/src/assets/ba_data/python/bauiv1lib/mainmenu.py
+++ b/src/assets/ba_data/python/bauiv1lib/mainmenu.py
@@ -1,11 +1,10 @@
# Released under the MIT License. See LICENSE for details.
#
"""Implements the main menu window."""
-# pylint: disable=too-many-lines
from __future__ import annotations
-from typing import TYPE_CHECKING
+from typing import TYPE_CHECKING, override
import logging
import bauiv1 as bui
@@ -15,41 +14,30 @@ if TYPE_CHECKING:
from typing import Any, Callable
-class MainMenuWindow(bui.Window):
- """The main menu window, both in-game and in the main menu session."""
+class MainMenuWindow(bui.MainWindow):
+ """The main menu window."""
- def __init__(self, transition: str | None = 'in_right'):
- # pylint: disable=cyclic-import
- import threading
- from bascenev1lib.mainmenu import MainMenuSession
-
- plus = bui.app.plus
- assert plus is not None
-
- self._in_game = not isinstance(
- bs.get_foreground_host_session(),
- MainMenuSession,
- )
+ def __init__(
+ self,
+ transition: str | None = 'in_right',
+ origin_widget: bui.Widget | None = None,
+ ):
# Preload some modules we use in a background thread so we won't
# have a visual hitch when the user taps them.
- threading.Thread(target=self._preload_modules).start()
+ bui.app.threadpool_submit_no_wait(self._preload_modules)
- if not self._in_game:
- bui.set_analytics_screen('Main Menu')
- self._show_remote_app_info_on_first_launch()
+ bui.set_analytics_screen('Main Menu')
+ self._show_remote_app_info_on_first_launch()
# Make a vanilla container; we'll modify it to our needs in
# refresh.
super().__init__(
root_widget=bui.containerwidget(
- transition=transition,
- toolbar_visibility=(
- 'menu_minimal_no_back'
- if self._in_game
- else 'menu_minimal_no_back'
- ),
- )
+ toolbar_visibility=('menu_full_no_back')
+ ),
+ transition=transition,
+ origin_widget=origin_widget,
)
# Grab this stuff in case it changes.
@@ -59,7 +47,6 @@ class MainMenuWindow(bui.Window):
self._tdelay = 0.0
self._t_delay_inc = 0.02
self._t_delay_play = 1.7
- self._p_index = 0
self._use_autoselect = True
self._button_width = 200.0
self._button_height = 45.0
@@ -67,41 +54,43 @@ class MainMenuWindow(bui.Window):
self._height = 100.0
self._demo_menu_button: bui.Widget | None = None
self._gather_button: bui.Widget | None = None
- self._start_button: bui.Widget | None = None
+ self._play_button: bui.Widget | None = None
self._watch_button: bui.Widget | None = None
- self._account_button: bui.Widget | None = None
self._how_to_play_button: bui.Widget | None = None
self._credits_button: bui.Widget | None = None
- self._settings_button: bui.Widget | None = None
- self._next_refresh_allow_time = 0.0
-
- self._store_char_tex = self._get_store_char_tex()
self._refresh()
+
self._restore_state()
- # Keep an eye on a few things and refresh if they change.
- self._account_state = plus.get_v1_account_state()
- self._account_state_num = plus.get_v1_account_state_num()
- self._account_type = (
- plus.get_v1_account_type()
- if self._account_state == 'signed_in'
- else None
- )
- self._refresh_timer = bui.AppTimer(
- 0.27, bui.WeakCall(self._check_refresh), repeat=True
+ @override
+ def on_main_window_close(self) -> None:
+ self._save_state()
+
+ @override
+ def get_main_window_state(self) -> bui.MainWindowState:
+ # Support recreating our window for back/refresh purposes.
+ return self.do_get_main_window_state()
+
+ @classmethod
+ def do_get_main_window_state(cls) -> bui.MainWindowState:
+ """Classmethod to gen a windowstate for the main menu."""
+ return bui.BasicMainWindowState(
+ create_call=lambda transition, origin_widget: cls(
+ transition=transition, origin_widget=origin_widget
+ )
)
@staticmethod
def _preload_modules() -> None:
"""Preload modules we use; avoids hitches (called in bg thread)."""
+ # pylint: disable=cyclic-import
import bauiv1lib.getremote as _unused
import bauiv1lib.confirm as _unused2
import bauiv1lib.store.button as _unused3
- import bauiv1lib.kiosk as _unused4
import bauiv1lib.account.settings as _unused5
import bauiv1lib.store.browser as _unused6
- import bauiv1lib.creditslist as _unused7
+ import bauiv1lib.credits as _unused7
import bauiv1lib.helpui as _unused8
import bauiv1lib.settings.allsettings as _unused9
import bauiv1lib.gather as _unused10
@@ -111,8 +100,8 @@ class MainMenuWindow(bui.Window):
def _show_remote_app_info_on_first_launch(self) -> None:
app = bui.app
assert app.classic is not None
- # The first time the non-in-game menu pops up, we might wanna show
- # a 'get-remote-app' dialog in front of it.
+ # The first time the non-in-game menu pops up, we might wanna
+ # show a 'get-remote-app' dialog in front of it.
if app.classic.first_main_menu:
app.classic.first_main_menu = False
try:
@@ -138,67 +127,16 @@ class MainMenuWindow(bui.Window):
except Exception:
logging.exception('Error showing get-remote-app info.')
- def _get_store_char_tex(self) -> str:
- plus = bui.app.plus
- assert plus is not None
- return (
- 'storeCharacterXmas'
- if plus.get_v1_account_misc_read_val('xmas', False)
- else (
- 'storeCharacterEaster'
- if plus.get_v1_account_misc_read_val('easter', False)
- else 'storeCharacter'
- )
- )
-
- def _check_refresh(self) -> None:
- plus = bui.app.plus
- assert plus is not None
-
- if not self._root_widget:
- return
-
- now = bui.apptime()
- if now < self._next_refresh_allow_time:
- return
-
- # Don't refresh for the first few seconds the game is up so we
- # don't interrupt the transition in.
-
- # bui.app.main_menu_window_refresh_check_count += 1
- # if bui.app.main_menu_window_refresh_check_count < 4:
- # return
-
- store_char_tex = self._get_store_char_tex()
- account_state_num = plus.get_v1_account_state_num()
- if (
- account_state_num != self._account_state_num
- or store_char_tex != self._store_char_tex
- ):
- self._store_char_tex = store_char_tex
- self._account_state_num = account_state_num
- account_state = self._account_state = plus.get_v1_account_state()
- self._account_type = (
- plus.get_v1_account_type()
- if account_state == 'signed_in'
- else None
- )
- self._save_state()
- self._refresh()
- self._restore_state()
-
def get_play_button(self) -> bui.Widget | None:
"""Return the play button."""
- return self._start_button
+ return self._play_button
def _refresh(self) -> None:
- # pylint: disable=too-many-branches
- # pylint: disable=too-many-locals
# pylint: disable=too-many-statements
- from bauiv1lib.store.button import StoreButton
+ # pylint: disable=too-many-locals
- plus = bui.app.plus
- assert plus is not None
+ classic = bui.app.classic
+ assert classic is not None
# Clear everything that was there.
children = self._root_widget.get_children()
@@ -215,484 +153,63 @@ class MainMenuWindow(bui.Window):
app = bui.app
assert app.classic is not None
- self._have_quit_button = app.ui_v1.uiscale is bui.UIScale.LARGE or (
- app.classic.platform == 'windows'
- and app.classic.subplatform == 'oculus'
+
+ self._have_quit_button = app.classic.platform in (
+ 'windows',
+ 'mac',
+ 'linux',
)
- self._have_store_button = not self._in_game
-
- self._have_settings_button = (
- not self._in_game or not app.ui_v1.use_toolbars
- ) and not (self._is_demo or self._is_arcade)
-
- self._input_device = input_device = bs.get_ui_input_device()
-
- # Are we connected to a local player?
- self._input_player = input_device.player if input_device else None
-
- # Are we connected to a remote player?.
- self._connected_to_remote_player = (
- input_device.is_attached_to_player()
- if (input_device and self._input_player is None)
- else False
- )
-
- positions: list[tuple[float, float, float]] = []
- self._p_index = 0
-
- if self._in_game:
- h, v, scale = self._refresh_in_game(positions)
- else:
- h, v, scale = self._refresh_not_in_game(positions)
-
- if self._have_settings_button:
- h, v, scale = positions[self._p_index]
- self._p_index += 1
- self._settings_button = bui.buttonwidget(
- parent=self._root_widget,
- position=(h - self._button_width * 0.5 * scale, v),
- size=(self._button_width, self._button_height),
- scale=scale,
- autoselect=self._use_autoselect,
- label=bui.Lstr(resource=f'{self._r}.settingsText'),
- transition_delay=self._tdelay,
- on_activate_call=self._settings,
- )
-
- # Scattered eggs on easter.
- if (
- plus.get_v1_account_misc_read_val('easter', False)
- and not self._in_game
- ):
- icon_size = 34
- bui.imagewidget(
- parent=self._root_widget,
- position=(
- h - icon_size * 0.5 - 15,
- v + self._button_height * scale - icon_size * 0.24 + 1.5,
- ),
- transition_delay=self._tdelay,
- size=(icon_size, icon_size),
- texture=bui.gettexture('egg3'),
- tilt_scale=0.0,
- )
-
- self._tdelay += self._t_delay_inc
-
- if self._in_game:
- h, v, scale = positions[self._p_index]
- self._p_index += 1
-
- # If we're in a replay, we have a 'Leave Replay' button.
- if bs.is_in_replay():
- bui.buttonwidget(
- parent=self._root_widget,
- position=(h - self._button_width * 0.5 * scale, v),
- scale=scale,
- size=(self._button_width, self._button_height),
- autoselect=self._use_autoselect,
- label=bui.Lstr(resource='replayEndText'),
- on_activate_call=self._confirm_end_replay,
- )
- elif bs.get_foreground_host_session() is not None:
- bui.buttonwidget(
- parent=self._root_widget,
- position=(h - self._button_width * 0.5 * scale, v),
- scale=scale,
- size=(self._button_width, self._button_height),
- autoselect=self._use_autoselect,
- label=bui.Lstr(
- resource=self._r
- + (
- '.endTestText'
- if self._is_benchmark()
- else '.endGameText'
- )
- ),
- on_activate_call=(
- self._confirm_end_test
- if self._is_benchmark()
- else self._confirm_end_game
- ),
- )
- else:
- # Assume we're in a client-session.
- bui.buttonwidget(
- parent=self._root_widget,
- position=(h - self._button_width * 0.5 * scale, v),
- scale=scale,
- size=(self._button_width, self._button_height),
- autoselect=self._use_autoselect,
- label=bui.Lstr(resource=f'{self._r}.leavePartyText'),
- on_activate_call=self._confirm_leave_party,
- )
-
- self._store_button: bui.Widget | None
- if self._have_store_button:
- this_b_width = self._button_width
- h, v, scale = positions[self._p_index]
- self._p_index += 1
-
- sbtn = self._store_button_instance = StoreButton(
- parent=self._root_widget,
- position=(h - this_b_width * 0.5 * scale, v),
- size=(this_b_width, self._button_height),
- scale=scale,
- on_activate_call=bui.WeakCall(self._on_store_pressed),
- sale_scale=1.3,
- transition_delay=self._tdelay,
- )
- self._store_button = store_button = sbtn.get_button()
- assert bui.app.classic is not None
- uiscale = bui.app.ui_v1.uiscale
- icon_size = (
- 55
- if uiscale is bui.UIScale.SMALL
- else 55 if uiscale is bui.UIScale.MEDIUM else 70
- )
- bui.imagewidget(
- parent=self._root_widget,
- position=(
- h - icon_size * 0.5,
- v + self._button_height * scale - icon_size * 0.23,
- ),
- transition_delay=self._tdelay,
- size=(icon_size, icon_size),
- texture=bui.gettexture(self._store_char_tex),
- tilt_scale=0.0,
- draw_controller=store_button,
- )
- self._tdelay += self._t_delay_inc
- else:
- self._store_button = None
-
- self._quit_button: bui.Widget | None
- if not self._in_game and self._have_quit_button:
- h, v, scale = positions[self._p_index]
- self._p_index += 1
- self._quit_button = quit_button = bui.buttonwidget(
- parent=self._root_widget,
- autoselect=self._use_autoselect,
- position=(h - self._button_width * 0.5 * scale, v),
- size=(self._button_width, self._button_height),
- scale=scale,
- label=bui.Lstr(
- resource=self._r
- + (
- '.quitText'
- if 'Mac' in app.classic.legacy_user_agent_string
- else '.exitGameText'
- )
- ),
- on_activate_call=self._quit,
- transition_delay=self._tdelay,
- )
-
- # Scattered eggs on easter.
- if plus.get_v1_account_misc_read_val('easter', False):
- icon_size = 30
- bui.imagewidget(
- parent=self._root_widget,
- position=(
- h - icon_size * 0.5 + 25,
- v
- + self._button_height * scale
- - icon_size * 0.24
- + 1.5,
- ),
- transition_delay=self._tdelay,
- size=(icon_size, icon_size),
- texture=bui.gettexture('egg1'),
- tilt_scale=0.0,
- )
-
- bui.containerwidget(
- edit=self._root_widget, cancel_button=quit_button
- )
- self._tdelay += self._t_delay_inc
- else:
- self._quit_button = None
-
- # If we're not in-game, have no quit button, and this is
- # android, we want back presses to quit our activity.
- if (
- not self._in_game
- and not self._have_quit_button
- and app.classic.platform == 'android'
- ):
-
- def _do_quit() -> None:
- bui.quit(confirm=True, quit_type=bui.QuitType.BACK)
-
- bui.containerwidget(
- edit=self._root_widget, on_cancel_call=_do_quit
- )
-
- # Add speed-up/slow-down buttons for replays. Ideally this
- # should be part of a fading-out playback bar like most media
- # players but this works for now.
- if bs.is_in_replay():
- b_size = 50.0
- b_buffer_1 = 50.0
- b_buffer_2 = 10.0
- t_scale = 0.75
- assert bui.app.classic is not None
- uiscale = bui.app.ui_v1.uiscale
- if uiscale is bui.UIScale.SMALL:
- b_size *= 0.6
- b_buffer_1 *= 0.8
- b_buffer_2 *= 1.0
- v_offs = -40
- t_scale = 0.5
- elif uiscale is bui.UIScale.MEDIUM:
- v_offs = -70
- else:
- v_offs = -100
- self._replay_speed_text = bui.textwidget(
- parent=self._root_widget,
- text=bui.Lstr(
- resource='watchWindow.playbackSpeedText',
- subs=[('${SPEED}', str(1.23))],
- ),
- position=(h, v + v_offs + 15 * t_scale),
- h_align='center',
- v_align='center',
- size=(0, 0),
- scale=t_scale,
- )
-
- # Update to current value.
- self._change_replay_speed(0)
-
- # Keep updating in a timer in case it gets changed elsewhere.
- self._change_replay_speed_timer = bui.AppTimer(
- 0.25, bui.WeakCall(self._change_replay_speed, 0), repeat=True
- )
- btn = bui.buttonwidget(
- parent=self._root_widget,
- position=(
- h - b_size - b_buffer_1,
- v - b_size - b_buffer_2 + v_offs,
- ),
- button_type='square',
- size=(b_size, b_size),
- label='',
- autoselect=True,
- on_activate_call=bui.Call(self._change_replay_speed, -1),
- )
- bui.textwidget(
- parent=self._root_widget,
- draw_controller=btn,
- text='-',
- position=(
- h - b_size * 0.5 - b_buffer_1,
- v - b_size * 0.5 - b_buffer_2 + 5 * t_scale + v_offs,
- ),
- h_align='center',
- v_align='center',
- size=(0, 0),
- scale=3.0 * t_scale,
- )
- btn = bui.buttonwidget(
- parent=self._root_widget,
- position=(h + b_buffer_1, v - b_size - b_buffer_2 + v_offs),
- button_type='square',
- size=(b_size, b_size),
- label='',
- autoselect=True,
- on_activate_call=bui.Call(self._change_replay_speed, 1),
- )
- bui.textwidget(
- parent=self._root_widget,
- draw_controller=btn,
- text='+',
- position=(
- h + b_size * 0.5 + b_buffer_1,
- v - b_size * 0.5 - b_buffer_2 + 5 * t_scale + v_offs,
- ),
- h_align='center',
- v_align='center',
- size=(0, 0),
- scale=3.0 * t_scale,
- )
- self._pause_resume_button = btn = bui.buttonwidget(
- parent=self._root_widget,
- position=(h - b_size * 0.5, v - b_size - b_buffer_2 + v_offs),
- button_type='square',
- size=(b_size, b_size),
- label=bui.charstr(
- bui.SpecialChar.PLAY_BUTTON
- if bs.is_replay_paused()
- else bui.SpecialChar.PAUSE_BUTTON
- ),
- autoselect=True,
- on_activate_call=bui.Call(self._pause_or_resume_replay),
- )
- btn = bui.buttonwidget(
- parent=self._root_widget,
- position=(
- h - b_size * 1.5 - b_buffer_1 * 2,
- v - b_size - b_buffer_2 + v_offs,
- ),
- button_type='square',
- size=(b_size, b_size),
- label='',
- autoselect=True,
- on_activate_call=bui.WeakCall(self._rewind_replay),
- )
- bui.textwidget(
- parent=self._root_widget,
- draw_controller=btn,
- # text='<<',
- text=bui.charstr(bui.SpecialChar.REWIND_BUTTON),
- position=(
- h - b_size - b_buffer_1 * 2,
- v - b_size * 0.5 - b_buffer_2 + 5 * t_scale + v_offs,
- ),
- h_align='center',
- v_align='center',
- size=(0, 0),
- scale=2.0 * t_scale,
- )
- btn = bui.buttonwidget(
- parent=self._root_widget,
- position=(
- h + b_size * 0.5 + b_buffer_1 * 2,
- v - b_size - b_buffer_2 + v_offs,
- ),
- button_type='square',
- size=(b_size, b_size),
- label='',
- autoselect=True,
- on_activate_call=bui.WeakCall(self._forward_replay),
- )
- bui.textwidget(
- parent=self._root_widget,
- draw_controller=btn,
- # text='>>',
- text=bui.charstr(bui.SpecialChar.FAST_FORWARD_BUTTON),
- position=(
- h + b_size + b_buffer_1 * 2,
- v - b_size * 0.5 - b_buffer_2 + 5 * t_scale + v_offs,
- ),
- h_align='center',
- v_align='center',
- size=(0, 0),
- scale=2.0 * t_scale,
- )
-
- def _rewind_replay(self) -> None:
- bs.seek_replay(-2 * pow(2, bs.get_replay_speed_exponent()))
-
- def _forward_replay(self) -> None:
- bs.seek_replay(2 * pow(2, bs.get_replay_speed_exponent()))
-
- def _refresh_not_in_game(
- self, positions: list[tuple[float, float, float]]
- ) -> tuple[float, float, float]:
- # pylint: disable=too-many-branches
- # pylint: disable=too-many-locals
- # pylint: disable=too-many-statements
- plus = bui.app.plus
- assert plus is not None
-
- assert bui.app.classic is not None
- if not bui.app.classic.did_menu_intro:
- self._tdelay = 2.0
- self._t_delay_inc = 0.02
+ if not classic.did_menu_intro:
+ self._tdelay = 1.7
+ self._t_delay_inc = 0.05
self._t_delay_play = 1.7
+ classic.did_menu_intro = True
- def _set_allow_time() -> None:
- self._next_refresh_allow_time = bui.apptime() + 2.5
-
- # Slight hack: widget transitions currently only progress
- # when frames are being drawn, but this tends to get called
- # before frame drawing even starts, meaning we don't know
- # exactly how long we should wait before refreshing to avoid
- # interrupting the transition. To make things a bit better,
- # let's do a redundant set of the time in a deferred call
- # which hopefully happens closer to actual frame draw times.
- _set_allow_time()
- bui.pushcall(_set_allow_time)
-
- bui.app.classic.did_menu_intro = True
self._width = 400.0
self._height = 200.0
- enable_account_button = True
- account_type_name: str | bui.Lstr
- if plus.get_v1_account_state() == 'signed_in':
- account_type_name = plus.get_v1_account_display_string()
- account_type_icon = None
- account_textcolor = (1.0, 1.0, 1.0)
- else:
- account_type_name = bui.Lstr(
- resource='notSignedInText',
- fallback_resource='accountSettingsWindow.titleText',
- )
- account_type_icon = None
- account_textcolor = (1.0, 0.2, 0.2)
- account_type_icon_color = (1.0, 1.0, 1.0)
- account_type_call = self._show_account_window
- account_type_enable_button_sound = True
- b_count = 3 # play, help, credits
- if self._have_settings_button:
- b_count += 1
- if enable_account_button:
- b_count += 1
- if self._have_quit_button:
- b_count += 1
- if self._have_store_button:
- b_count += 1
+
+ play_button_width = self._button_width * 0.65
+ play_button_height = self._button_height * 1.1
+ play_button_scale = 1.7
+ hspace = 20.0
+ side_button_width = self._button_width * 0.4
+ side_button_height = side_button_width
+ side_button_scale = 0.95
+ side_button_y_offs = 5.0
+ hspace2 = 15.0
+ side_button_2_width = self._button_width * 1.0
+ side_button_2_height = side_button_2_width * 0.3
+ side_button_2_y_offs = 10.0
+ side_button_2_scale = 0.5
+
uiscale = bui.app.ui_v1.uiscale
if uiscale is bui.UIScale.SMALL:
- root_widget_scale = 1.6
- play_button_width = self._button_width * 0.65
- play_button_height = self._button_height * 1.1
- small_button_scale = 0.51 if b_count > 6 else 0.63
+ root_widget_scale = 1.3
button_y_offs = -20.0
- button_y_offs2 = -60.0
self._button_height *= 1.3
- button_spacing = 1.04
elif uiscale is bui.UIScale.MEDIUM:
root_widget_scale = 1.3
- play_button_width = self._button_width * 0.65
- play_button_height = self._button_height * 1.1
- small_button_scale = 0.6
button_y_offs = -55.0
- button_y_offs2 = -75.0
self._button_height *= 1.25
- button_spacing = 1.1
else:
root_widget_scale = 1.0
- play_button_width = self._button_width * 0.65
- play_button_height = self._button_height * 1.1
- small_button_scale = 0.75
- button_y_offs = -80.0
- button_y_offs2 = -100.0
+ button_y_offs = -90.0
self._button_height *= 1.2
- button_spacing = 1.1
- spc = self._button_width * small_button_scale * button_spacing
+
bui.containerwidget(
edit=self._root_widget,
size=(self._width, self._height),
background=False,
scale=root_widget_scale,
)
- assert not positions
- positions.append((self._width * 0.5, button_y_offs, 1.7))
- x_offs = self._width * 0.5 - (spc * (b_count - 1) * 0.5) + (spc * 0.5)
- for i in range(b_count - 1):
- positions.append(
- (
- x_offs + spc * i - 1.0,
- button_y_offs + button_y_offs2,
- small_button_scale,
- )
- )
+
# In kiosk mode, provide a button to get back to the kiosk menu.
if bui.app.env.demo or bui.app.env.arcade:
- h, v, scale = positions[self._p_index]
+ # h, v, scale = positions[self._p_index]
+ h = self._width * 0.5
+ v = button_y_offs
+ scale = 1.0
this_b_width = self._button_width * 0.4 * scale
demo_menu_delay = (
0.0
@@ -714,70 +231,90 @@ class MainMenuWindow(bui.Window):
)
),
transition_delay=demo_menu_delay,
- on_activate_call=self._demo_menu_press,
+ on_activate_call=self.main_window_back,
)
else:
self._demo_menu_button = None
- uiscale = bui.app.ui_v1.uiscale
- foof = (
- -1
- if uiscale is bui.UIScale.SMALL
- else 1 if uiscale is bui.UIScale.MEDIUM else 3
+
+ # Gather button
+ h = self._width * 0.5
+ h = (
+ self._width * 0.5
+ - play_button_width * play_button_scale * 0.5
+ - hspace
+ - side_button_width * side_button_scale * 0.5
)
- h, v, scale = positions[self._p_index]
- v = v + foof
- gather_delay = (
- 0.0
- if self._t_delay_play == 0.0
- else max(0.0, self._t_delay_play + 0.1)
- )
- assert play_button_width is not None
- assert play_button_height is not None
- this_h = h - play_button_width * 0.5 * scale - 40 * scale
- this_b_width = self._button_width * 0.25 * scale
- this_b_height = self._button_height * 0.82 * scale
+ v = button_y_offs + side_button_y_offs
self._gather_button = btn = bui.buttonwidget(
parent=self._root_widget,
- position=(this_h - this_b_width * 0.5, v),
- size=(this_b_width, this_b_height),
+ position=(h - side_button_width * side_button_scale * 0.5, v),
+ size=(side_button_width, side_button_height),
+ scale=side_button_scale,
autoselect=self._use_autoselect,
button_type='square',
label='',
- transition_delay=gather_delay,
+ transition_delay=self._tdelay,
on_activate_call=self._gather_press,
)
bui.textwidget(
parent=self._root_widget,
- position=(this_h, v + self._button_height * 0.33),
+ position=(h, v + side_button_height * side_button_scale * 0.25),
size=(0, 0),
scale=0.75,
- transition_delay=gather_delay,
+ transition_delay=self._tdelay,
draw_controller=btn,
color=(0.75, 1.0, 0.7),
- maxwidth=self._button_width * 0.33,
+ maxwidth=side_button_width * side_button_scale * 0.8,
text=bui.Lstr(resource='gatherWindow.titleText'),
h_align='center',
v_align='center',
)
- icon_size = this_b_width * 0.6
+ icon_size = side_button_width * side_button_scale * 0.63
bui.imagewidget(
parent=self._root_widget,
size=(icon_size, icon_size),
draw_controller=btn,
- transition_delay=gather_delay,
- position=(this_h - 0.5 * icon_size, v + 0.31 * this_b_height),
+ transition_delay=self._tdelay,
+ position=(
+ h - 0.5 * icon_size,
+ v
+ + 0.65 * side_button_height * side_button_scale
+ - 0.5 * icon_size,
+ ),
texture=bui.gettexture('usersButton'),
)
- # Play button.
- h, v, scale = positions[self._p_index]
- self._p_index += 1
- self._start_button = start_button = bui.buttonwidget(
+ h -= (
+ side_button_width * side_button_scale * 0.5
+ + hspace2
+ + side_button_2_width * side_button_2_scale
+ )
+ v = button_y_offs + side_button_2_y_offs
+
+ btn = bui.buttonwidget(
parent=self._root_widget,
- position=(h - play_button_width * 0.5 * scale, v),
+ position=(h, v),
+ autoselect=self._use_autoselect,
+ size=(side_button_2_width, side_button_2_height * 2.0),
+ button_type='square',
+ scale=side_button_2_scale,
+ label=bui.Lstr(resource=f'{self._r}.howToPlayText'),
+ transition_delay=self._tdelay,
+ on_activate_call=self._howtoplay,
+ )
+ self._how_to_play_button = btn
+
+ # Play button.
+ h = self._width * 0.5
+ v = button_y_offs
+ assert play_button_width is not None
+ assert play_button_height is not None
+ self._play_button = start_button = bui.buttonwidget(
+ parent=self._root_widget,
+ position=(h - play_button_width * 0.5 * play_button_scale, v),
size=(play_button_width, play_button_height),
autoselect=self._use_autoselect,
- scale=scale,
+ scale=play_button_scale,
text_res_scale=2.0,
label=bui.Lstr(resource='playText'),
transition_delay=self._t_delay_play,
@@ -788,346 +325,125 @@ class MainMenuWindow(bui.Window):
start_button=start_button,
selected_child=start_button,
)
- v = v + foof
- watch_delay = (
- 0.0
- if self._t_delay_play == 0.0
- else max(0.0, self._t_delay_play - 0.1)
+
+ self._tdelay += self._t_delay_inc
+
+ h = (
+ self._width * 0.5
+ + play_button_width * play_button_scale * 0.5
+ + hspace
+ + side_button_width * side_button_scale * 0.5
)
- this_h = h + play_button_width * 0.5 * scale + 40 * scale
- this_b_width = self._button_width * 0.25 * scale
- this_b_height = self._button_height * 0.82 * scale
+ v = button_y_offs + side_button_y_offs
self._watch_button = btn = bui.buttonwidget(
parent=self._root_widget,
- position=(this_h - this_b_width * 0.5, v),
- size=(this_b_width, this_b_height),
+ position=(h - side_button_width * side_button_scale * 0.5, v),
+ size=(side_button_width, side_button_height),
+ scale=side_button_scale,
autoselect=self._use_autoselect,
button_type='square',
label='',
- transition_delay=watch_delay,
+ transition_delay=self._tdelay,
on_activate_call=self._watch_press,
)
bui.textwidget(
parent=self._root_widget,
- position=(this_h, v + self._button_height * 0.33),
+ position=(h, v + side_button_height * side_button_scale * 0.25),
size=(0, 0),
scale=0.75,
- transition_delay=watch_delay,
+ transition_delay=self._tdelay,
color=(0.75, 1.0, 0.7),
draw_controller=btn,
- maxwidth=self._button_width * 0.33,
+ maxwidth=side_button_width * side_button_scale * 0.8,
text=bui.Lstr(resource='watchWindow.titleText'),
h_align='center',
v_align='center',
)
- icon_size = this_b_width * 0.55
+ icon_size = side_button_width * side_button_scale * 0.63
bui.imagewidget(
parent=self._root_widget,
size=(icon_size, icon_size),
draw_controller=btn,
- transition_delay=watch_delay,
- position=(this_h - 0.5 * icon_size, v + 0.33 * this_b_height),
+ transition_delay=self._tdelay,
+ position=(
+ h - 0.5 * icon_size,
+ v
+ + 0.65 * side_button_height * side_button_scale
+ - 0.5 * icon_size,
+ ),
texture=bui.gettexture('tv'),
)
- if not self._in_game and enable_account_button:
- this_b_width = self._button_width
- h, v, scale = positions[self._p_index]
- self._p_index += 1
- self._account_button = bui.buttonwidget(
- parent=self._root_widget,
- position=(h - this_b_width * 0.5 * scale, v),
- size=(this_b_width, self._button_height),
- scale=scale,
- label=account_type_name,
- autoselect=self._use_autoselect,
- on_activate_call=account_type_call,
- textcolor=account_textcolor,
- icon=account_type_icon,
- icon_color=account_type_icon_color,
- transition_delay=self._tdelay,
- enable_sound=account_type_enable_button_sound,
- )
- # Scattered eggs on easter.
- if (
- plus.get_v1_account_misc_read_val('easter', False)
- and not self._in_game
- ):
- icon_size = 32
- bui.imagewidget(
- parent=self._root_widget,
- position=(
- h - icon_size * 0.5 + 35,
- v
- + self._button_height * scale
- - icon_size * 0.24
- + 1.5,
- ),
- transition_delay=self._tdelay,
- size=(icon_size, icon_size),
- texture=bui.gettexture('egg2'),
- tilt_scale=0.0,
- )
- self._tdelay += self._t_delay_inc
- else:
- self._account_button = None
-
- # How-to-play button.
- h, v, scale = positions[self._p_index]
- self._p_index += 1
- btn = bui.buttonwidget(
- parent=self._root_widget,
- position=(h - self._button_width * 0.5 * scale, v),
- scale=scale,
- autoselect=self._use_autoselect,
- size=(self._button_width, self._button_height),
- label=bui.Lstr(resource=f'{self._r}.howToPlayText'),
- transition_delay=self._tdelay,
- on_activate_call=self._howtoplay,
- )
- self._how_to_play_button = btn
-
- # Scattered eggs on easter.
- if (
- plus.get_v1_account_misc_read_val('easter', False)
- and not self._in_game
- ):
- icon_size = 28
- bui.imagewidget(
- parent=self._root_widget,
- position=(
- h - icon_size * 0.5 + 30,
- v + self._button_height * scale - icon_size * 0.24 + 1.5,
- ),
- transition_delay=self._tdelay,
- size=(icon_size, icon_size),
- texture=bui.gettexture('egg4'),
- tilt_scale=0.0,
- )
# Credits button.
self._tdelay += self._t_delay_inc
- h, v, scale = positions[self._p_index]
- self._p_index += 1
+
+ h += side_button_width * side_button_scale * 0.5 + hspace2
+ v = button_y_offs + side_button_2_y_offs
+
+ if self._have_quit_button:
+ v += 1.17 * side_button_2_height * side_button_2_scale
+
self._credits_button = bui.buttonwidget(
parent=self._root_widget,
- position=(h - self._button_width * 0.5 * scale, v),
- size=(self._button_width, self._button_height),
+ position=(h, v),
+ button_type=None if self._have_quit_button else 'square',
+ size=(
+ side_button_2_width,
+ side_button_2_height * (1.0 if self._have_quit_button else 2.0),
+ ),
+ scale=side_button_2_scale,
autoselect=self._use_autoselect,
label=bui.Lstr(resource=f'{self._r}.creditsText'),
- scale=scale,
transition_delay=self._tdelay,
on_activate_call=self._credits,
)
self._tdelay += self._t_delay_inc
- return h, v, scale
- def _refresh_in_game(
- self, positions: list[tuple[float, float, float]]
- ) -> tuple[float, float, float]:
- # pylint: disable=too-many-branches
- # pylint: disable=too-many-locals
- # pylint: disable=too-many-statements
- assert bui.app.classic is not None
- custom_menu_entries: list[dict[str, Any]] = []
- session = bs.get_foreground_host_session()
- if session is not None:
- try:
- custom_menu_entries = session.get_custom_menu_entries()
- for cme in custom_menu_entries:
- cme_any: Any = cme # Type check may not hold true.
- if (
- not isinstance(cme_any, dict)
- or 'label' not in cme
- or not isinstance(cme['label'], (str, bui.Lstr))
- or 'call' not in cme
- or not callable(cme['call'])
- ):
- raise ValueError(
- 'invalid custom menu entry: ' + str(cme)
- )
- except Exception:
- custom_menu_entries = []
- logging.exception(
- 'Error getting custom menu entries for %s.', session
- )
- self._width = 250.0
- self._height = 250.0 if self._input_player else 180.0
- if (self._is_demo or self._is_arcade) and self._input_player:
- self._height -= 40
- if not self._have_settings_button:
- self._height -= 50
- if self._connected_to_remote_player:
- # In this case we have a leave *and* a disconnect button.
- self._height += 50
- self._height += 50 * (len(custom_menu_entries))
- uiscale = bui.app.ui_v1.uiscale
- bui.containerwidget(
- edit=self._root_widget,
- size=(self._width, self._height),
- scale=(
- 2.15
- if uiscale is bui.UIScale.SMALL
- else 1.6 if uiscale is bui.UIScale.MEDIUM else 1.0
- ),
- )
- h = 125.0
- v = self._height - 80.0 if self._input_player else self._height - 60
- h_offset = 0
- d_h_offset = 0
- v_offset = -50
- for _i in range(6 + len(custom_menu_entries)):
- positions.append((h, v, 1.0))
- v += v_offset
- h += h_offset
- h_offset += d_h_offset
- self._start_button = None
- bui.app.classic.pause()
-
- # Player name if applicable.
- if self._input_player:
- player_name = self._input_player.getname()
- h, v, scale = positions[self._p_index]
- v += 35
- bui.textwidget(
+ self._quit_button: bui.Widget | None
+ if self._have_quit_button:
+ v -= 1.1 * side_button_2_height * side_button_2_scale
+ self._quit_button = quit_button = bui.buttonwidget(
parent=self._root_widget,
- position=(h - self._button_width / 2, v),
- size=(self._button_width, self._button_height),
- color=(1, 1, 1, 0.5),
- scale=0.7,
- h_align='center',
- text=bui.Lstr(value=player_name),
- )
- else:
- player_name = ''
- h, v, scale = positions[self._p_index]
- self._p_index += 1
- btn = bui.buttonwidget(
- parent=self._root_widget,
- position=(h - self._button_width / 2, v),
- size=(self._button_width, self._button_height),
- scale=scale,
- label=bui.Lstr(resource=f'{self._r}.resumeText'),
- autoselect=self._use_autoselect,
- on_activate_call=self._resume,
- )
- bui.containerwidget(edit=self._root_widget, cancel_button=btn)
-
- # Add any custom options defined by the current game.
- for entry in custom_menu_entries:
- h, v, scale = positions[self._p_index]
- self._p_index += 1
-
- # Ask the entry whether we should resume when we call
- # it (defaults to true).
- resume = bool(entry.get('resume_on_call', True))
-
- if resume:
- call = bui.Call(self._resume_and_call, entry['call'])
- else:
- call = bui.Call(entry['call'], bui.WeakCall(self._resume))
-
- bui.buttonwidget(
- parent=self._root_widget,
- position=(h - self._button_width / 2, v),
- size=(self._button_width, self._button_height),
- scale=scale,
- on_activate_call=call,
- label=entry['label'],
autoselect=self._use_autoselect,
- )
- # Add a 'leave' button if the menu-owner has a player.
- if (self._input_player or self._connected_to_remote_player) and not (
- self._is_demo or self._is_arcade
- ):
- h, v, scale = positions[self._p_index]
- self._p_index += 1
- btn = bui.buttonwidget(
- parent=self._root_widget,
- position=(h - self._button_width / 2, v),
- size=(self._button_width, self._button_height),
- scale=scale,
- on_activate_call=self._leave,
- label='',
- autoselect=self._use_autoselect,
- )
-
- if (
- player_name != ''
- and player_name[0] != '<'
- and player_name[-1] != '>'
- ):
- txt = bui.Lstr(
- resource=f'{self._r}.justPlayerText',
- subs=[('${NAME}', player_name)],
- )
- else:
- txt = bui.Lstr(value=player_name)
- bui.textwidget(
- parent=self._root_widget,
- position=(
- h,
- v
- + self._button_height
- * (0.64 if player_name != '' else 0.5),
+ position=(h, v),
+ size=(side_button_2_width, side_button_2_height),
+ scale=side_button_2_scale,
+ label=bui.Lstr(
+ resource=self._r
+ + (
+ '.quitText'
+ if 'Mac' in app.classic.legacy_user_agent_string
+ else '.exitGameText'
+ )
),
- size=(0, 0),
- text=bui.Lstr(resource=f'{self._r}.leaveGameText'),
- scale=(0.83 if player_name != '' else 1.0),
- color=(0.75, 1.0, 0.7),
- h_align='center',
- v_align='center',
- draw_controller=btn,
- maxwidth=self._button_width * 0.9,
+ on_activate_call=self._quit,
+ transition_delay=self._tdelay,
)
- bui.textwidget(
- parent=self._root_widget,
- position=(h, v + self._button_height * 0.27),
- size=(0, 0),
- text=txt,
- color=(0.75, 1.0, 0.7),
- h_align='center',
- v_align='center',
- draw_controller=btn,
- scale=0.45,
- maxwidth=self._button_width * 0.9,
- )
- return h, v, scale
- def _change_replay_speed(self, offs: int) -> None:
- if not self._replay_speed_text:
- if bui.do_once():
- print('_change_replay_speed called without widget')
- return
- bs.set_replay_speed_exponent(bs.get_replay_speed_exponent() + offs)
- actual_speed = pow(2.0, bs.get_replay_speed_exponent())
- bui.textwidget(
- edit=self._replay_speed_text,
- text=bui.Lstr(
- resource='watchWindow.playbackSpeedText',
- subs=[('${SPEED}', str(actual_speed))],
- ),
- )
-
- def _pause_or_resume_replay(self) -> None:
- if bs.is_replay_paused():
- bs.resume_replay()
- bui.buttonwidget(
- edit=self._pause_resume_button,
- label=bui.charstr(bui.SpecialChar.PAUSE_BUTTON),
+ bui.containerwidget(
+ edit=self._root_widget, cancel_button=quit_button
)
+ self._tdelay += self._t_delay_inc
else:
- bs.pause_replay()
- bui.buttonwidget(
- edit=self._pause_resume_button,
- label=bui.charstr(bui.SpecialChar.PLAY_BUTTON),
- )
+ self._quit_button = None
+
+ # If we're not in-game, have no quit button, and this is
+ # android, we want back presses to quit our activity.
+ if app.classic.platform == 'android':
+
+ def _do_quit() -> None:
+ bui.quit(confirm=True, quit_type=bui.QuitType.BACK)
+
+ bui.containerwidget(
+ edit=self._root_widget, on_cancel_call=_do_quit
+ )
def _quit(self) -> None:
# pylint: disable=cyclic-import
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:
+ # no-op if we're not currently in control.
+ if not self.can_change_main_window():
return
# Note: Normally we should go through bui.quit(confirm=True) but
@@ -1135,334 +451,120 @@ class MainMenuWindow(bui.Window):
# button.
QuitWindow(origin_widget=self._quit_button)
- def _demo_menu_press(self) -> None:
- # pylint: disable=cyclic-import
- 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()
- bui.containerwidget(edit=self._root_widget, transition='out_right')
- assert bui.app.classic is not None
- bui.app.ui_v1.set_main_menu_window(
- KioskWindow(transition='in_left').get_root_widget(),
- from_window=self._root_widget,
- )
-
- def _show_account_window(self) -> None:
- # pylint: disable=cyclic-import
- 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()
- bui.containerwidget(edit=self._root_widget, transition='out_left')
- assert bui.app.classic is not None
- bui.app.ui_v1.set_main_menu_window(
- AccountSettingsWindow(
- origin_widget=self._account_button
- ).get_root_widget(),
- from_window=self._root_widget,
- )
-
- def _on_store_pressed(self) -> None:
- # pylint: disable=cyclic-import
- from bauiv1lib.store.browser import StoreBrowserWindow
- 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
- assert plus is not None
-
- if plus.get_v1_account_state() != 'signed_in':
- show_sign_in_prompt()
- return
- self._save_state()
- bui.containerwidget(edit=self._root_widget, transition='out_left')
- assert bui.app.classic is not None
- bui.app.ui_v1.set_main_menu_window(
- StoreBrowserWindow(
- origin_widget=self._store_button
- ).get_root_widget(),
- from_window=self._root_widget,
- )
-
- def _is_benchmark(self) -> bool:
- session = bs.get_foreground_host_session()
- return getattr(session, 'benchmark_type', None) == 'cpu' or (
- bui.app.classic is not None
- and bui.app.classic.stress_test_update_timer is not None
- )
-
- def _confirm_end_game(self) -> None:
- # pylint: disable=cyclic-import
- from bauiv1lib.confirm import ConfirmWindow
-
- # FIXME: Currently we crash calling this on client-sessions.
-
- # Select cancel by default; this occasionally gets called by accident
- # in a fit of button mashing and this will help reduce damage.
- ConfirmWindow(
- bui.Lstr(resource=f'{self._r}.exitToMenuText'),
- self._end_game,
- cancel_is_selected=True,
- )
-
- def _confirm_end_test(self) -> None:
- # pylint: disable=cyclic-import
- from bauiv1lib.confirm import ConfirmWindow
-
- # Select cancel by default; this occasionally gets called by accident
- # in a fit of button mashing and this will help reduce damage.
- ConfirmWindow(
- bui.Lstr(resource=f'{self._r}.exitToMenuText'),
- self._end_game,
- cancel_is_selected=True,
- )
-
- def _confirm_end_replay(self) -> None:
- # pylint: disable=cyclic-import
- from bauiv1lib.confirm import ConfirmWindow
-
- # Select cancel by default; this occasionally gets called by accident
- # in a fit of button mashing and this will help reduce damage.
- ConfirmWindow(
- bui.Lstr(resource=f'{self._r}.exitToMenuText'),
- self._end_game,
- cancel_is_selected=True,
- )
-
- def _confirm_leave_party(self) -> None:
- # pylint: disable=cyclic-import
- from bauiv1lib.confirm import ConfirmWindow
-
- # Select cancel by default; this occasionally gets called by accident
- # in a fit of button mashing and this will help reduce damage.
- ConfirmWindow(
- bui.Lstr(resource=f'{self._r}.leavePartyConfirmText'),
- self._leave_party,
- cancel_is_selected=True,
- )
-
- def _leave_party(self) -> None:
- bs.disconnect_from_host()
-
- def _end_game(self) -> 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.containerwidget(edit=self._root_widget, transition='out_left')
- bui.app.classic.return_to_main_menu_session_gracefully(reset_ui=False)
-
- def _leave(self) -> None:
- if self._input_player:
- self._input_player.remove_from_game()
- elif self._connected_to_remote_player:
- if self._input_device:
- self._input_device.detach_from_player()
- self._resume()
-
def _credits(self) -> None:
# pylint: disable=cyclic-import
- from bauiv1lib.creditslist import CreditsListWindow
+ from bauiv1lib.credits import CreditsWindow
- # no-op if our underlying widget is dead or on its way out.
- if not self._root_widget or self._root_widget.transitioning_out:
+ # no-op if we're not currently in control.
+ if not self.can_change_main_window():
return
- self._save_state()
- bui.containerwidget(edit=self._root_widget, transition='out_left')
- assert bui.app.classic is not None
- bui.app.ui_v1.set_main_menu_window(
- CreditsListWindow(
- origin_widget=self._credits_button
- ).get_root_widget(),
- from_window=self._root_widget,
+ self.main_window_replace(
+ CreditsWindow(origin_widget=self._credits_button),
+ # group_id='mainmenutop',
)
def _howtoplay(self) -> None:
# pylint: disable=cyclic-import
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:
+ # no-op if we're not currently in control.
+ if not self.can_change_main_window():
return
- self._save_state()
- bui.containerwidget(edit=self._root_widget, transition='out_left')
- assert bui.app.classic is not None
- bui.app.ui_v1.set_main_menu_window(
- HelpWindow(
- main_menu=True, origin_widget=self._how_to_play_button
- ).get_root_widget(),
- from_window=self._root_widget,
+ self.main_window_replace(
+ HelpWindow(origin_widget=self._how_to_play_button),
)
- def _settings(self) -> None:
- # pylint: disable=cyclic-import
- 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()
- bui.containerwidget(edit=self._root_widget, transition='out_left')
- assert bui.app.classic is not None
- bui.app.ui_v1.set_main_menu_window(
- AllSettingsWindow(
- origin_widget=self._settings_button
- ).get_root_widget(),
- from_window=self._root_widget,
- )
-
- def _resume_and_call(self, call: Callable[[], Any]) -> None:
- self._resume()
- call()
-
- def _do_game_service_press(self) -> None:
- self._save_state()
- if bui.app.plus is not None:
- bui.app.plus.show_game_service_ui()
- else:
- logging.warning(
- 'plus feature-set is required to show game service ui'
- )
-
def _save_state(self) -> None:
- # Don't do this for the in-game menu.
- if self._in_game:
- return
- assert bui.app.classic is not None
- ui = bui.app.ui_v1
- sel = self._root_widget.get_selected_child()
- if sel == self._start_button:
- ui.main_menu_selection = 'Start'
- elif sel == self._gather_button:
- ui.main_menu_selection = 'Gather'
- elif sel == self._watch_button:
- ui.main_menu_selection = 'Watch'
- elif sel == self._how_to_play_button:
- ui.main_menu_selection = 'HowToPlay'
- elif sel == self._credits_button:
- ui.main_menu_selection = 'Credits'
- elif sel == self._settings_button:
- ui.main_menu_selection = 'Settings'
- elif sel == self._account_button:
- ui.main_menu_selection = 'Account'
- elif sel == self._store_button:
- ui.main_menu_selection = 'Store'
- elif sel == self._quit_button:
- ui.main_menu_selection = 'Quit'
- elif sel == self._demo_menu_button:
- ui.main_menu_selection = 'DemoMenu'
- else:
- print('unknown widget in main menu store selection:', sel)
- ui.main_menu_selection = 'Start'
+ try:
+ sel = self._root_widget.get_selected_child()
+ if sel == self._play_button:
+ sel_name = 'Start'
+ elif sel == self._gather_button:
+ sel_name = 'Gather'
+ elif sel == self._watch_button:
+ sel_name = 'Watch'
+ elif sel == self._how_to_play_button:
+ sel_name = 'HowToPlay'
+ elif sel == self._credits_button:
+ sel_name = 'Credits'
+ elif sel == self._quit_button:
+ sel_name = 'Quit'
+ elif sel == self._demo_menu_button:
+ sel_name = 'DemoMenu'
+ else:
+ print(f'Unknown widget in main menu selection: {sel}.')
+ sel_name = 'Start'
+ bui.app.ui_v1.window_states[type(self)] = {'sel_name': sel_name}
+ except Exception:
+ logging.exception('Error saving state for %s.', self)
def _restore_state(self) -> None:
- # pylint: disable=too-many-branches
+ try:
- # Don't do this for the in-game menu.
- if self._in_game:
- return
- assert bui.app.classic is not None
- sel_name = bui.app.ui_v1.main_menu_selection
- sel: bui.Widget | None
- if sel_name is None:
- sel_name = 'Start'
- if sel_name == 'HowToPlay':
- sel = self._how_to_play_button
- elif sel_name == 'Gather':
- sel = self._gather_button
- elif sel_name == 'Watch':
- sel = self._watch_button
- elif sel_name == 'Credits':
- sel = self._credits_button
- elif sel_name == 'Settings':
- sel = self._settings_button
- elif sel_name == 'Account':
- sel = self._account_button
- elif sel_name == 'Store':
- sel = self._store_button
- elif sel_name == 'Quit':
- sel = self._quit_button
- elif sel_name == 'DemoMenu':
- sel = self._demo_menu_button
- else:
- sel = self._start_button
- if sel is not None:
- bui.containerwidget(edit=self._root_widget, selected_child=sel)
+ sel: bui.Widget | None
+
+ sel_name = bui.app.ui_v1.window_states.get(type(self), {}).get(
+ 'sel_name'
+ )
+ assert isinstance(sel_name, (str, type(None)))
+ if sel_name is None:
+ sel_name = 'Start'
+ if sel_name == 'HowToPlay':
+ sel = self._how_to_play_button
+ elif sel_name == 'Gather':
+ sel = self._gather_button
+ elif sel_name == 'Watch':
+ sel = self._watch_button
+ elif sel_name == 'Credits':
+ sel = self._credits_button
+ elif sel_name == 'Quit':
+ sel = self._quit_button
+ elif sel_name == 'DemoMenu':
+ sel = self._demo_menu_button
+ else:
+ sel = self._play_button
+ if sel is not None:
+ bui.containerwidget(edit=self._root_widget, selected_child=sel)
+
+ except Exception:
+ logging.exception('Error restoring state for %s.', self)
def _gather_press(self) -> None:
# pylint: disable=cyclic-import
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:
+ # no-op if we're not currently in control.
+ if not self.can_change_main_window():
return
- self._save_state()
- bui.containerwidget(edit=self._root_widget, transition='out_left')
- assert bui.app.classic is not None
- bui.app.ui_v1.set_main_menu_window(
- GatherWindow(origin_widget=self._gather_button).get_root_widget(),
- from_window=self._root_widget,
+ self.main_window_replace(
+ GatherWindow(origin_widget=self._gather_button)
)
def _watch_press(self) -> None:
# pylint: disable=cyclic-import
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:
+ # no-op if we're not currently in control.
+ if not self.can_change_main_window():
return
- self._save_state()
- bui.containerwidget(edit=self._root_widget, transition='out_left')
- assert bui.app.classic is not None
- bui.app.ui_v1.set_main_menu_window(
- WatchWindow(origin_widget=self._watch_button).get_root_widget(),
- from_window=self._root_widget,
+ self.main_window_replace(
+ WatchWindow(origin_widget=self._watch_button),
)
def _play_press(self) -> None:
# pylint: disable=cyclic-import
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:
+ # no-op if we're not currently in control.
+ if not self.can_change_main_window():
return
- self._save_state()
- bui.containerwidget(edit=self._root_widget, transition='out_left')
+ classic = bui.app.classic
+ if classic is not None:
+ classic.selecting_private_party_playlist = False
- assert bui.app.classic is not None
- bui.app.ui_v1.selecting_private_party_playlist = False
- bui.app.ui_v1.set_main_menu_window(
- PlayWindow(origin_widget=self._start_button).get_root_widget(),
- from_window=self._root_widget,
- )
-
- def _resume(self) -> None:
- assert bui.app.classic is not None
- bui.app.classic.resume()
- # if self._root_widget:
- # bui.containerwidget(edit=self._root_widget,
- # transition='out_right')
- bui.app.ui_v1.clear_main_menu_window(transition='out_right')
-
- # If there's callbacks waiting for this window to go away, call them.
- for call in bui.app.ui_v1.main_menu_resume_callbacks:
- call()
- del bui.app.ui_v1.main_menu_resume_callbacks[:]
+ self.main_window_replace(PlayWindow(origin_widget=self._play_button))
diff --git a/src/assets/ba_data/python/bauiv1lib/party.py b/src/assets/ba_data/python/bauiv1lib/party.py
index 199db42f..c40a6432 100644
--- a/src/assets/ba_data/python/bauiv1lib/party.py
+++ b/src/assets/ba_data/python/bauiv1lib/party.py
@@ -48,15 +48,15 @@ class PartyWindow(bui.Window):
on_outside_click_call=self.close_with_sound,
scale_origin_stack_offset=origin,
scale=(
- 2.0
+ 1.6
if uiscale is bui.UIScale.SMALL
- else 1.35 if uiscale is bui.UIScale.MEDIUM else 1.0
+ else 1.3 if uiscale is bui.UIScale.MEDIUM else 0.9
),
stack_offset=(
- (0, -10)
+ (200, -10)
if uiscale is bui.UIScale.SMALL
else (
- (240, 0) if uiscale is bui.UIScale.MEDIUM else (330, 20)
+ (260, 0) if uiscale is bui.UIScale.MEDIUM else (370, 60)
)
),
)
diff --git a/src/assets/ba_data/python/bauiv1lib/partyqueue.py b/src/assets/ba_data/python/bauiv1lib/partyqueue.py
index ef514291..8435562d 100644
--- a/src/assets/ba_data/python/bauiv1lib/partyqueue.py
+++ b/src/assets/ba_data/python/bauiv1lib/partyqueue.py
@@ -223,7 +223,6 @@ class PartyQueueWindow(bui.Window):
def __init__(self, queue_id: str, address: str, port: int):
assert bui.app.classic is not None
- bui.app.ui_v1.have_party_queue_window = True
self._address = address
self._port = port
self._queue_id = queue_id
@@ -329,8 +328,6 @@ class PartyQueueWindow(bui.Window):
plus = bui.app.plus
assert plus is not None
- assert bui.app.classic is not None
- bui.app.ui_v1.have_party_queue_window = False
plus.add_v1_account_transaction(
{'type': 'PARTY_QUEUE_REMOVE', 'q': self._queue_id}
)
@@ -564,7 +561,8 @@ class PartyQueueWindow(bui.Window):
def on_boost_press(self) -> None:
"""Boost was pressed."""
from bauiv1lib import account
- from bauiv1lib import gettickets
+
+ # from bauiv1lib import gettickets
plus = bui.app.plus
assert plus is not None
@@ -575,7 +573,7 @@ class PartyQueueWindow(bui.Window):
if plus.get_v1_account_ticket_count() < self._boost_tickets:
bui.getsound('error').play()
- gettickets.show_get_tickets_prompt()
+ # gettickets.show_get_tickets_prompt()
return
bui.getsound('laserReverse').play()
diff --git a/src/assets/ba_data/python/bauiv1lib/play.py b/src/assets/ba_data/python/bauiv1lib/play.py
index f33ffe61..5ec63a2c 100644
--- a/src/assets/ba_data/python/bauiv1lib/play.py
+++ b/src/assets/ba_data/python/bauiv1lib/play.py
@@ -5,31 +5,35 @@
from __future__ import annotations
import logging
+from typing import override
import bascenev1 as bs
import bauiv1 as bui
-class PlayWindow(bui.Window):
+class PlayWindow(bui.MainWindow):
"""Window for selecting overall play type."""
def __init__(
self,
- transition: str = 'in_right',
+ transition: str | None = 'in_right',
origin_widget: bui.Widget | None = None,
):
# pylint: disable=too-many-statements
# pylint: disable=too-many-locals
- import threading
# Preload some modules we use in a background thread so we won't
# have a visual hitch when the user taps them.
- threading.Thread(target=self._preload_modules).start()
+ bui.app.threadpool_submit_no_wait(self._preload_modules)
- # We can currently be used either for main menu duty or for selecting
- # playlists (should make this more elegant/general).
- assert bui.app.classic is not None
- self._is_main_menu = not bui.app.ui_v1.selecting_private_party_playlist
+ classic = bui.app.classic
+ assert classic is not None
+
+ # We can currently be used either for main window duty or
+ # modally for selecting playlists. Ideally we should clean
+ # things up and make playlist selection go through the
+ # main-window mechanism.
+ self._is_main_menu = not classic.selecting_private_party_playlist
uiscale = bui.app.ui_v1.uiscale
width = 1100 if uiscale is bui.UIScale.SMALL else 800
@@ -37,30 +41,31 @@ class PlayWindow(bui.Window):
height = 550
button_width = 400
- scale_origin: tuple[float, float] | None
if origin_widget is not None:
+
+ # Need to store this ourself since we can function as a
+ # non-main window.
self._transition_out = 'out_scale'
- scale_origin = origin_widget.get_screen_space_center()
- transition = 'in_scale'
else:
self._transition_out = 'out_right'
- scale_origin = None
self._r = 'playWindow'
super().__init__(
root_widget=bui.containerwidget(
size=(width, height),
- transition=transition,
toolbar_visibility='menu_full',
- scale_origin_stack_offset=scale_origin,
scale=(
- 1.6
+ 1.35
if uiscale is bui.UIScale.SMALL
else 0.9 if uiscale is bui.UIScale.MEDIUM else 0.8
),
- stack_offset=(0, 0) if uiscale is bui.UIScale.SMALL else (0, 0),
- )
+ stack_offset=(
+ (0, 20) if uiscale is bui.UIScale.SMALL else (0, 0)
+ ),
+ ),
+ transition=transition,
+ origin_widget=origin_widget,
)
self._back_button = back_button = btn = bui.buttonwidget(
parent=self._root_widget,
@@ -101,18 +106,14 @@ class PlayWindow(bui.Window):
size=(60, 60),
label=bui.charstr(bui.SpecialChar.BACK),
)
- if bui.app.ui_v1.use_toolbars and uiscale is bui.UIScale.SMALL:
+ if uiscale is bui.UIScale.SMALL:
bui.textwidget(edit=txt, text='')
v = height - (110 if self._is_main_menu else 90)
v -= 100
clr = (0.6, 0.7, 0.6, 1.0)
v -= 280 if self._is_main_menu else 180
- v += (
- 30
- if bui.app.ui_v1.use_toolbars and uiscale is bui.UIScale.SMALL
- else 0
- )
+ v += 30 if uiscale is bui.UIScale.SMALL else 0
hoffs = x_offs + 80 if self._is_main_menu else x_offs - 100
scl = 1.13 if self._is_main_menu else 0.68
@@ -153,7 +154,7 @@ class PlayWindow(bui.Window):
on_activate_call=self._coop,
)
- if bui.app.ui_v1.use_toolbars and uiscale is bui.UIScale.SMALL:
+ if uiscale is bui.UIScale.SMALL:
bui.widget(
edit=btn,
left_widget=bui.get_special_widget('back_button'),
@@ -256,12 +257,11 @@ class PlayWindow(bui.Window):
on_activate_call=self._team_tourney,
)
- if bui.app.ui_v1.use_toolbars:
- bui.widget(
- edit=btn,
- up_widget=bui.get_special_widget('tickets_plus_button'),
- right_widget=bui.get_special_widget('party_button'),
- )
+ bui.widget(
+ edit=btn,
+ up_widget=bui.get_special_widget('get_tokens_button'),
+ right_widget=bui.get_special_widget('squad_button'),
+ )
xxx = -14
self._draw_dude(
@@ -489,11 +489,12 @@ class PlayWindow(bui.Window):
color=clr,
)
- if bui.app.ui_v1.use_toolbars and uiscale is bui.UIScale.SMALL:
+ if uiscale is bui.UIScale.SMALL:
back_button.delete()
bui.containerwidget(
edit=self._root_widget,
on_cancel_call=self._back,
+ # cancel_button=bui.get_special_widget('back_button'),
selected_child=(
self._coop_button
if self._is_main_menu
@@ -514,7 +515,20 @@ class PlayWindow(bui.Window):
self._restore_state()
- # noinspection PyUnresolvedReferences
+ @override
+ def get_main_window_state(self) -> bui.MainWindowState:
+ # Support recreating our window for back/refresh purposes.
+ cls = type(self)
+ return bui.BasicMainWindowState(
+ create_call=lambda transition, origin_widget: cls(
+ transition=transition, origin_widget=origin_widget
+ )
+ )
+
+ @override
+ def on_main_window_close(self) -> None:
+ self._save_state()
+
@staticmethod
def _preload_modules() -> None:
"""Preload modules we use; avoids hitches (called in bg thread)."""
@@ -526,30 +540,22 @@ class PlayWindow(bui.Window):
def _back(self) -> None:
# 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:
+ # no-op if we're not currently in control.
+ if not self.can_change_main_window():
return
if self._is_main_menu:
- from bauiv1lib.mainmenu import MainMenuWindow
-
- self._save_state()
- assert bui.app.classic is not None
- bui.app.ui_v1.set_main_menu_window(
- MainMenuWindow(transition='in_left').get_root_widget(),
- from_window=self._root_widget,
- )
- bui.containerwidget(
- edit=self._root_widget, transition=self._transition_out
- )
+ self.main_window_back()
else:
from bauiv1lib.gather import GatherWindow
- self._save_state()
assert bui.app.classic is not None
- bui.app.ui_v1.set_main_menu_window(
- GatherWindow(transition='in_left').get_root_widget(),
- from_window=self._root_widget,
+
+ self._save_state()
+ bui.app.ui_v1.set_main_window(
+ GatherWindow(transition='in_left'),
+ from_window=self,
+ is_back=True,
)
bui.containerwidget(
edit=self._root_widget, transition=self._transition_out
@@ -560,8 +566,8 @@ class PlayWindow(bui.Window):
from bauiv1lib.account import show_sign_in_prompt
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:
+ # no-op if we're not currently in control.
+ if not self.can_change_main_window():
return
plus = bui.app.plus
@@ -573,48 +579,43 @@ class PlayWindow(bui.Window):
self._save_state()
bui.containerwidget(edit=self._root_widget, transition='out_left')
assert bui.app.classic is not None
- bui.app.ui_v1.set_main_menu_window(
- CoopBrowserWindow(
- origin_widget=self._coop_button
- ).get_root_widget(),
- from_window=self._root_widget,
+ bui.app.ui_v1.set_main_window(
+ CoopBrowserWindow(origin_widget=self._coop_button), from_window=self
)
def _team_tourney(self) -> None:
# pylint: disable=cyclic-import
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:
+ # no-op if we're not currently in control.
+ if not self.can_change_main_window():
return
self._save_state()
- bui.containerwidget(edit=self._root_widget, transition='out_left')
- assert bui.app.classic is not None
- bui.app.ui_v1.set_main_menu_window(
+
+ self.main_window_replace(
PlaylistBrowserWindow(
origin_widget=self._teams_button, sessiontype=bs.DualTeamSession
- ).get_root_widget(),
- from_window=self._root_widget,
+ )
)
def _free_for_all(self) -> None:
# pylint: disable=cyclic-import
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:
+ # no-op if we're not currently in control.
+ if not self.can_change_main_window():
return
self._save_state()
bui.containerwidget(edit=self._root_widget, transition='out_left')
assert bui.app.classic is not None
- bui.app.ui_v1.set_main_menu_window(
+ bui.app.ui_v1.set_main_window(
PlaylistBrowserWindow(
origin_widget=self._free_for_all_button,
sessiontype=bs.FreeForAllSession,
- ).get_root_widget(),
- from_window=self._root_widget,
+ ),
+ from_window=self,
)
def _draw_dude(
diff --git a/src/assets/ba_data/python/bauiv1lib/playlist/addgame.py b/src/assets/ba_data/python/bauiv1lib/playlist/addgame.py
index 317c9e9e..02ef065a 100644
--- a/src/assets/ba_data/python/bauiv1lib/playlist/addgame.py
+++ b/src/assets/ba_data/python/bauiv1lib/playlist/addgame.py
@@ -4,7 +4,7 @@
from __future__ import annotations
-from typing import TYPE_CHECKING
+from typing import TYPE_CHECKING, override
import bascenev1 as bs
import bauiv1 as bui
@@ -13,13 +13,14 @@ if TYPE_CHECKING:
from bauiv1lib.playlist.editcontroller import PlaylistEditController
-class PlaylistAddGameWindow(bui.Window):
+class PlaylistAddGameWindow(bui.MainWindow):
"""Window for selecting a game type to add to a playlist."""
def __init__(
self,
editcontroller: PlaylistEditController,
- transition: str = 'in_right',
+ transition: str | None = 'in_right',
+ origin_widget: bui.Widget | None = None,
):
self._editcontroller = editcontroller
self._r = 'addGameWindow'
@@ -38,27 +39,32 @@ class PlaylistAddGameWindow(bui.Window):
super().__init__(
root_widget=bui.containerwidget(
size=(self._width, self._height + top_extra),
- transition=transition,
scale=(
- 2.17
+ 1.95
if uiscale is bui.UIScale.SMALL
else 1.5 if uiscale is bui.UIScale.MEDIUM else 1.0
),
stack_offset=(0, 1) if uiscale is bui.UIScale.SMALL else (0, 0),
- )
+ toolbar_visibility='menu_minimal',
+ ),
+ transition=transition,
+ origin_widget=origin_widget,
)
- self._back_button = bui.buttonwidget(
- parent=self._root_widget,
- position=(58 + x_inset, self._height - 53),
- size=(165, 70),
- scale=0.75,
- text_scale=1.2,
- label=bui.Lstr(resource='backText'),
- autoselect=True,
- button_type='back',
- on_activate_call=self._back,
- )
+ if uiscale is bui.UIScale.SMALL:
+ self._back_button = bui.get_special_widget('back_button')
+ else:
+ self._back_button = bui.buttonwidget(
+ parent=self._root_widget,
+ position=(58 + x_inset, self._height - 53),
+ size=(165, 70),
+ scale=0.75,
+ text_scale=1.2,
+ label=bui.Lstr(resource='backText'),
+ autoselect=True,
+ button_type='back',
+ on_activate_call=self.main_window_back,
+ )
self._select_button = select_button = bui.buttonwidget(
parent=self._root_widget,
position=(self._width - (172 + x_inset), self._height - 50),
@@ -70,11 +76,10 @@ class PlaylistAddGameWindow(bui.Window):
on_activate_call=self._add,
)
- if bui.app.ui_v1.use_toolbars:
- bui.widget(
- edit=select_button,
- right_widget=bui.get_special_widget('party_button'),
- )
+ bui.widget(
+ edit=select_button,
+ right_widget=bui.get_special_widget('squad_button'),
+ )
bui.textwidget(
parent=self._root_widget,
@@ -130,11 +135,18 @@ class PlaylistAddGameWindow(bui.Window):
self._column: bui.Widget | None = None
v -= 35
- bui.containerwidget(
- edit=self._root_widget,
- cancel_button=self._back_button,
- start_button=select_button,
- )
+
+ if uiscale is bui.UIScale.SMALL:
+ bui.containerwidget(
+ edit=self._root_widget, on_cancel_call=self.main_window_back
+ )
+ else:
+ bui.containerwidget(
+ edit=self._root_widget,
+ cancel_button=self._back_button,
+ )
+ bui.containerwidget(edit=self._root_widget, start_button=select_button)
+
self._selected_game_type: type[bs.GameActivity] | None = None
bui.containerwidget(
@@ -154,6 +166,23 @@ class PlaylistAddGameWindow(bui.Window):
# game loading is complete.
self._refresh()
+ @override
+ def get_main_window_state(self) -> bui.MainWindowState:
+ # Support recreating our window for back/refresh purposes.
+ cls = type(self)
+
+ # Avoid dereferencing self from the lambda or we'll keep
+ # ourself alive indefinitely.
+ editcontroller = self._editcontroller
+
+ return bui.BasicMainWindowState(
+ create_call=lambda transition, origin_widget: cls(
+ transition=transition,
+ origin_widget=origin_widget,
+ editcontroller=editcontroller,
+ )
+ )
+
def _on_game_types_loaded(
self, gametypes: list[type[bs.GameActivity]]
) -> None:
@@ -262,5 +291,5 @@ class PlaylistAddGameWindow(bui.Window):
),
)
- def _back(self) -> None:
- self._editcontroller.add_game_cancelled()
+ # def _back(self) -> None:
+ # self._editcontroller.add_game_cancelled()
diff --git a/src/assets/ba_data/python/bauiv1lib/playlist/browser.py b/src/assets/ba_data/python/bauiv1lib/playlist/browser.py
index f8b1a73f..ef0fcf57 100644
--- a/src/assets/ba_data/python/bauiv1lib/playlist/browser.py
+++ b/src/assets/ba_data/python/bauiv1lib/playlist/browser.py
@@ -4,15 +4,16 @@
from __future__ import annotations
-import logging
import copy
import math
+import logging
+from typing import override
import bascenev1 as bs
import bauiv1 as bui
-class PlaylistBrowserWindow(bui.Window):
+class PlaylistBrowserWindow(bui.MainWindow):
"""Window for starting teams games."""
def __init__(
@@ -21,28 +22,13 @@ class PlaylistBrowserWindow(bui.Window):
transition: str | None = 'in_right',
origin_widget: bui.Widget | None = None,
):
- # pylint: disable=too-many-statements
# pylint: disable=cyclic-import
from bauiv1lib.playlist import PlaylistTypeVars
- # If they provided an origin-widget, scale up from that.
- scale_origin: tuple[float, float] | None
- if origin_widget is not None:
- self._transition_out = 'out_scale'
- scale_origin = origin_widget.get_screen_space_center()
- transition = 'in_scale'
- else:
- self._transition_out = 'out_right'
- scale_origin = None
-
- assert bui.app.classic is not None
-
# Store state for when we exit the next game.
if issubclass(sessiontype, bs.DualTeamSession):
- bui.app.ui_v1.set_main_menu_location('Team Game Select')
bui.set_analytics_screen('Teams Window')
elif issubclass(sessiontype, bs.FreeForAllSession):
- bui.app.ui_v1.set_main_menu_location('Free-for-All Game Select')
bui.set_analytics_screen('FreeForAll Window')
else:
raise TypeError(f'Invalid sessiontype: {sessiontype}.')
@@ -65,7 +51,7 @@ class PlaylistBrowserWindow(bui.Window):
self._width = 1100.0 if uiscale is bui.UIScale.SMALL else 800.0
x_inset = 150 if uiscale is bui.UIScale.SMALL else 0
self._height = (
- 480
+ 440
if uiscale is bui.UIScale.SMALL
else 510 if uiscale is bui.UIScale.MEDIUM else 580
)
@@ -75,18 +61,22 @@ class PlaylistBrowserWindow(bui.Window):
super().__init__(
root_widget=bui.containerwidget(
size=(self._width, self._height + top_extra),
- transition=transition,
- toolbar_visibility='menu_full',
- scale_origin_stack_offset=scale_origin,
+ toolbar_visibility=(
+ 'menu_minimal'
+ if uiscale is bui.UIScale.SMALL
+ else 'menu_full'
+ ),
scale=(
- 1.69
+ 1.83
if uiscale is bui.UIScale.SMALL
else 1.05 if uiscale is bui.UIScale.MEDIUM else 0.9
),
stack_offset=(
- (0, -26) if uiscale is bui.UIScale.SMALL else (0, 0)
+ (0, -56) if uiscale is bui.UIScale.SMALL else (0, 0)
),
- )
+ ),
+ transition=transition,
+ origin_widget=origin_widget,
)
self._back_button: bui.Widget | None = bui.buttonwidget(
@@ -102,19 +92,22 @@ class PlaylistBrowserWindow(bui.Window):
bui.containerwidget(
edit=self._root_widget, cancel_button=self._back_button
)
- txt = self._title_text = bui.textwidget(
+ self._title_text = bui.textwidget(
parent=self._root_widget,
- position=(self._width * 0.5, self._height - 41),
+ position=(
+ self._width * 0.5,
+ self._height - (32 if uiscale is bui.UIScale.SMALL else 41),
+ ),
size=(0, 0),
text=self._pvars.window_title_name,
- scale=1.3,
+ scale=(0.8 if uiscale is bui.UIScale.SMALL else 1.3),
res_scale=1.5,
color=bui.app.ui_v1.heading_color,
h_align='center',
v_align='center',
)
- if uiscale is bui.UIScale.SMALL and bui.app.ui_v1.use_toolbars:
- bui.textwidget(edit=txt, text='')
+ # if uiscale is bui.UIScale.SMALL and bui.app.ui_v1.use_toolbars:
+ # bui.textwidget(edit=txt, text='')
bui.buttonwidget(
edit=self._back_button,
@@ -124,7 +117,7 @@ class PlaylistBrowserWindow(bui.Window):
label=bui.charstr(bui.SpecialChar.BACK),
)
- if uiscale is bui.UIScale.SMALL and bui.app.ui_v1.use_toolbars:
+ if uiscale is bui.UIScale.SMALL:
self._back_button.delete()
self._back_button = None
bui.containerwidget(
@@ -135,9 +128,7 @@ class PlaylistBrowserWindow(bui.Window):
scroll_offs = 0
self._scroll_width = self._width - (100 + 2 * x_inset)
self._scroll_height = self._height - (
- 146
- if uiscale is bui.UIScale.SMALL and bui.app.ui_v1.use_toolbars
- else 136
+ 146 if uiscale is bui.UIScale.SMALL else 136
)
self._scrollwidget = bui.scrollwidget(
parent=self._root_widget,
@@ -160,6 +151,27 @@ class PlaylistBrowserWindow(bui.Window):
1.0, bui.WeakCall(self._update), repeat=True
)
+ @override
+ def get_main_window_state(self) -> bui.MainWindowState:
+ # Support recreating our window for back/refresh purposes.
+ cls = type(self)
+
+ # Pull things out of self here; if we do it below in the lambda
+ # then we keep self alive.
+ sessiontype = self._sessiontype
+
+ return bui.BasicMainWindowState(
+ create_call=lambda transition, origin_widget: cls(
+ transition=transition,
+ origin_widget=origin_widget,
+ sessiontype=sessiontype,
+ )
+ )
+
+ @override
+ def on_main_window_close(self) -> None:
+ self._save_state()
+
def _ensure_standard_playlists_exist(self) -> None:
plus = bui.app.plus
assert plus is not None
@@ -418,23 +430,15 @@ class PlaylistBrowserWindow(bui.Window):
position=pos,
)
- if (
- x == 0
- and bui.app.ui_v1.use_toolbars
- and uiscale is bui.UIScale.SMALL
- ):
+ if x == 0 and uiscale is bui.UIScale.SMALL:
bui.widget(
edit=btn,
left_widget=bui.get_special_widget('back_button'),
)
- if (
- x == columns - 1
- and bui.app.ui_v1.use_toolbars
- and uiscale is bui.UIScale.SMALL
- ):
+ if x == columns - 1 and uiscale is bui.UIScale.SMALL:
bui.widget(
edit=btn,
- right_widget=bui.get_special_widget('party_button'),
+ right_widget=bui.get_special_widget('squad_button'),
)
bui.buttonwidget(
edit=btn,
@@ -687,17 +691,17 @@ class PlaylistBrowserWindow(bui.Window):
self._save_state()
bui.containerwidget(edit=self._root_widget, transition='out_left')
assert bui.app.classic is not None
- bui.app.ui_v1.set_main_menu_window(
+ bui.app.ui_v1.set_main_window(
PlaylistCustomizeBrowserWindow(
origin_widget=self._customize_button,
sessiontype=self._sessiontype,
- ).get_root_widget(),
- from_window=self._root_widget,
+ ),
+ from_window=self,
)
def _on_back_press(self) -> None:
# 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:
@@ -715,15 +719,16 @@ class PlaylistBrowserWindow(bui.Window):
)
cfg.commit()
- self._save_state()
- bui.containerwidget(
- edit=self._root_widget, transition=self._transition_out
- )
- assert bui.app.classic is not None
- bui.app.ui_v1.set_main_menu_window(
- PlayWindow(transition='in_left').get_root_widget(),
- from_window=self._root_widget,
- )
+ self.main_window_back()
+
+ # self._save_state()
+ # bui.containerwidget(
+ # edit=self._root_widget, transition=self._transition_out
+ # )
+ # assert bui.app.classic is not None
+ # bui.app.ui_v1.set_main_window(
+ # PlayWindow(transition='in_left'), from_window=self, is_back=True
+ # )
def _save_state(self) -> None:
try:
diff --git a/src/assets/ba_data/python/bauiv1lib/playlist/customizebrowser.py b/src/assets/ba_data/python/bauiv1lib/playlist/customizebrowser.py
index 789e53fd..08658d5f 100644
--- a/src/assets/ba_data/python/bauiv1lib/playlist/customizebrowser.py
+++ b/src/assets/ba_data/python/bauiv1lib/playlist/customizebrowser.py
@@ -7,7 +7,7 @@ from __future__ import annotations
import copy
import time
import logging
-from typing import TYPE_CHECKING
+from typing import TYPE_CHECKING, override
import bascenev1 as bs
import bauiv1 as bui
@@ -15,16 +15,21 @@ import bauiv1 as bui
if TYPE_CHECKING:
from typing import Any
+REQUIRE_PRO = False
-class PlaylistCustomizeBrowserWindow(bui.Window):
+# TEMP
+UNDER_CONSTRUCTION = True
+
+
+class PlaylistCustomizeBrowserWindow(bui.MainWindow):
"""Window for viewing a playlist."""
def __init__(
self,
sessiontype: type[bs.Session],
- transition: str = 'in_right',
- select_playlist: str | None = None,
+ transition: str | None = 'in_right',
origin_widget: bui.Widget | None = None,
+ select_playlist: str | None = None,
):
# Yes this needs tidying.
# pylint: disable=too-many-locals
@@ -32,22 +37,13 @@ class PlaylistCustomizeBrowserWindow(bui.Window):
# pylint: disable=cyclic-import
from bauiv1lib import playlist
- scale_origin: tuple[float, float] | None
- if origin_widget is not None:
- self._transition_out = 'out_scale'
- scale_origin = origin_widget.get_screen_space_center()
- transition = 'in_scale'
- else:
- self._transition_out = 'out_right'
- scale_origin = None
-
self._sessiontype = sessiontype
self._pvars = playlist.PlaylistTypeVars(sessiontype)
self._max_playlists = 30
self._r = 'gameListWindow'
assert bui.app.classic is not None
uiscale = bui.app.ui_v1.uiscale
- self._width = 850.0 if uiscale is bui.UIScale.SMALL else 650.0
+ self._width = 970.0 if uiscale is bui.UIScale.SMALL else 650.0
x_inset = 100.0 if uiscale is bui.UIScale.SMALL else 0.0
self._height = (
380.0
@@ -59,29 +55,47 @@ class PlaylistCustomizeBrowserWindow(bui.Window):
super().__init__(
root_widget=bui.containerwidget(
size=(self._width, self._height + top_extra),
- transition=transition,
- scale_origin_stack_offset=scale_origin,
scale=(
- 2.05
+ 1.83
if uiscale is bui.UIScale.SMALL
else 1.5 if uiscale is bui.UIScale.MEDIUM else 1.0
),
+ toolbar_visibility=(
+ 'menu_minimal'
+ if uiscale is bui.UIScale.SMALL
+ else 'menu_full'
+ ),
stack_offset=(
(0, -10) if uiscale is bui.UIScale.SMALL else (0, 0)
),
- )
+ ),
+ transition=transition,
+ origin_widget=origin_widget,
)
- self._back_button = back_button = btn = bui.buttonwidget(
- parent=self._root_widget,
- position=(43 + x_inset, self._height - 60),
- size=(160, 68),
- scale=0.77,
- autoselect=True,
- text_scale=1.3,
- label=bui.Lstr(resource='backText'),
- button_type='back',
- )
+ self._back_button: bui.Widget | None
+ if uiscale is bui.UIScale.SMALL:
+ self._back_button = None
+ bui.containerwidget(
+ edit=self._root_widget, on_cancel_call=self._back
+ )
+ else:
+ self._back_button = bui.buttonwidget(
+ parent=self._root_widget,
+ position=(43 + x_inset, self._height - 60),
+ size=(160, 68),
+ scale=0.77,
+ autoselect=True,
+ text_scale=1.3,
+ label=bui.Lstr(resource='backText'),
+ button_type='back',
+ )
+ bui.buttonwidget(
+ edit=self._back_button,
+ button_type='backSmall',
+ size=(60, 60),
+ label=bui.charstr(bui.SpecialChar.BACK),
+ )
bui.textwidget(
parent=self._root_widget,
@@ -97,13 +111,6 @@ class PlaylistCustomizeBrowserWindow(bui.Window):
v_align='center',
)
- bui.buttonwidget(
- edit=btn,
- button_type='backSmall',
- size=(60, 60),
- label=bui.charstr(bui.SpecialChar.BACK),
- )
-
v = self._height - 59.0
h = 41 + x_inset
b_color = (0.6, 0.53, 0.63)
@@ -261,7 +268,9 @@ class PlaylistCustomizeBrowserWindow(bui.Window):
size=(self._width - (180 + 2 * x_inset), self._scroll_height + 10),
highlight=False,
)
- bui.widget(edit=back_button, right_widget=scrollwidget)
+ if self._back_button is not None:
+ bui.widget(edit=self._back_button, right_widget=scrollwidget)
+
self._columnwidget = bui.columnwidget(
parent=scrollwidget, border=2, margin=0
)
@@ -279,11 +288,7 @@ class PlaylistCustomizeBrowserWindow(bui.Window):
bui.widget(
edit=scrollwidget,
left_widget=new_button,
- right_widget=(
- bui.get_special_widget('party_button')
- if bui.app.ui_v1.use_toolbars
- else None
- ),
+ right_widget=bui.get_special_widget('squad_button'),
)
# make sure config exists
@@ -298,8 +303,13 @@ class PlaylistCustomizeBrowserWindow(bui.Window):
self._refresh(select_playlist=select_playlist)
- bui.buttonwidget(edit=back_button, on_activate_call=self._back)
- bui.containerwidget(edit=self._root_widget, cancel_button=back_button)
+ if self._back_button is not None:
+ bui.buttonwidget(
+ edit=self._back_button, on_activate_call=self._back
+ )
+ bui.containerwidget(
+ edit=self._root_widget, cancel_button=self._back_button
+ )
bui.containerwidget(edit=self._root_widget, selected_child=scrollwidget)
@@ -309,15 +319,38 @@ class PlaylistCustomizeBrowserWindow(bui.Window):
)
self._update()
+ @override
+ def get_main_window_state(self) -> bui.MainWindowState:
+ # Support recreating our window for back/refresh purposes.
+ cls = type(self)
+
+ # Avoid dereferencing self within the lambda or we'll keep
+ # ourself alive indefinitely.
+ stype = self._sessiontype
+
+ return bui.BasicMainWindowState(
+ create_call=lambda transition, origin_widget: cls(
+ transition=transition,
+ origin_widget=origin_widget,
+ sessiontype=stype,
+ )
+ )
+
+ # @override
+ # def on_main_window_close(self) -> None:
+ # self._save_state()
+
def _update(self) -> None:
assert bui.app.classic is not None
have = bui.app.classic.accounts.have_pro_options()
for lock in self._lock_images:
- bui.imagewidget(edit=lock, opacity=0.0 if have else 1.0)
+ bui.imagewidget(
+ edit=lock, opacity=0.0 if (have or not REQUIRE_PRO) else 1.0
+ )
def _back(self) -> None:
# 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:
@@ -330,16 +363,18 @@ class PlaylistCustomizeBrowserWindow(bui.Window):
)
cfg.commit()
- bui.containerwidget(
- edit=self._root_widget, transition=self._transition_out
- )
- assert bui.app.classic is not None
- bui.app.ui_v1.set_main_menu_window(
- browser.PlaylistBrowserWindow(
- transition='in_left', sessiontype=self._sessiontype
- ).get_root_widget(),
- from_window=self._root_widget,
- )
+ self.main_window_back()
+ # bui.containerwidget(
+ # edit=self._root_widget, transition=self._transition_out
+ # )
+ # assert bui.app.classic is not None
+ # bui.app.ui_v1.set_main_window(
+ # browser.PlaylistBrowserWindow(
+ # transition='in_left', sessiontype=self._sessiontype
+ # ),
+ # from_window=self,
+ # is_back=True,
+ # )
def _select(self, name: str, index: int) -> None:
self._selected_playlist_name = name
@@ -418,7 +453,14 @@ class PlaylistCustomizeBrowserWindow(bui.Window):
# Hitting up from top widget should jump to 'back'
if index == 0:
- bui.widget(edit=txtw, up_widget=self._back_button)
+ bui.widget(
+ edit=txtw,
+ up_widget=(
+ self._back_button
+ if self._back_button is not None
+ else bui.get_special_widget('back_button')
+ ),
+ )
self._playlist_widgets.append(txtw)
@@ -469,8 +511,12 @@ class PlaylistCustomizeBrowserWindow(bui.Window):
from bauiv1lib.playlist.editcontroller import PlaylistEditController
from bauiv1lib.purchase import PurchaseWindow
+ if UNDER_CONSTRUCTION:
+ bui.screenmessage('UNDER CONSTRUCTION')
+ return
+
assert bui.app.classic is not None
- if not bui.app.classic.accounts.have_pro_options():
+ if REQUIRE_PRO and not bui.app.classic.accounts.have_pro_options():
PurchaseWindow(items=['pro'])
return
@@ -500,8 +546,12 @@ class PlaylistCustomizeBrowserWindow(bui.Window):
from bauiv1lib.playlist.editcontroller import PlaylistEditController
from bauiv1lib.purchase import PurchaseWindow
+ if UNDER_CONSTRUCTION:
+ bui.screenmessage('UNDER CONSTRUCTION')
+ return
+
assert bui.app.classic is not None
- if not bui.app.classic.accounts.have_pro_options():
+ if REQUIRE_PRO and not bui.app.classic.accounts.have_pro_options():
PurchaseWindow(items=['pro'])
return
if self._selected_playlist_name is None:
@@ -584,7 +634,7 @@ class PlaylistCustomizeBrowserWindow(bui.Window):
assert plus is not None
assert bui.app.classic is not None
- if not bui.app.classic.accounts.have_pro_options():
+ if REQUIRE_PRO and not bui.app.classic.accounts.have_pro_options():
PurchaseWindow(items=['pro'])
return
@@ -626,7 +676,7 @@ class PlaylistCustomizeBrowserWindow(bui.Window):
from bauiv1lib.confirm import ConfirmWindow
assert bui.app.classic is not None
- if not bui.app.classic.accounts.have_pro_options():
+ if REQUIRE_PRO and not bui.app.classic.accounts.have_pro_options():
PurchaseWindow(items=['pro'])
return
@@ -666,7 +716,7 @@ class PlaylistCustomizeBrowserWindow(bui.Window):
assert plus is not None
assert bui.app.classic is not None
- if not bui.app.classic.accounts.have_pro_options():
+ if REQUIRE_PRO and not bui.app.classic.accounts.have_pro_options():
PurchaseWindow(items=['pro'])
return
if self._selected_playlist_name is None:
diff --git a/src/assets/ba_data/python/bauiv1lib/playlist/edit.py b/src/assets/ba_data/python/bauiv1lib/playlist/edit.py
index 3a3fccab..43a39cfa 100644
--- a/src/assets/ba_data/python/bauiv1lib/playlist/edit.py
+++ b/src/assets/ba_data/python/bauiv1lib/playlist/edit.py
@@ -5,7 +5,7 @@
from __future__ import annotations
import logging
-from typing import TYPE_CHECKING, cast
+from typing import TYPE_CHECKING, cast, override
import bascenev1 as bs
import bauiv1 as bui
@@ -14,13 +14,14 @@ if TYPE_CHECKING:
from bauiv1lib.playlist.editcontroller import PlaylistEditController
-class PlaylistEditWindow(bui.Window):
+class PlaylistEditWindow(bui.MainWindow):
"""Window for editing an individual game playlist."""
def __init__(
self,
editcontroller: PlaylistEditController,
- transition: str = 'in_right',
+ transition: str | None = 'in_right',
+ origin_widget: bui.Widget | None = None,
):
# pylint: disable=too-many-statements
# pylint: disable=too-many-locals
@@ -43,16 +44,17 @@ class PlaylistEditWindow(bui.Window):
super().__init__(
root_widget=bui.containerwidget(
size=(self._width, self._height + top_extra),
- transition=transition,
scale=(
- 2.0
+ 1.8
if uiscale is bui.UIScale.SMALL
else 1.3 if uiscale is bui.UIScale.MEDIUM else 1.0
),
stack_offset=(
(0, -16) if uiscale is bui.UIScale.SMALL else (0, 0)
),
- )
+ ),
+ transition=transition,
+ origin_widget=origin_widget,
)
cancel_button = bui.buttonwidget(
parent=self._root_widget,
@@ -74,11 +76,10 @@ class PlaylistEditWindow(bui.Window):
text_scale=1.2,
)
- if bui.app.ui_v1.use_toolbars:
- bui.widget(
- edit=btn,
- right_widget=bui.get_special_widget('party_button'),
- )
+ bui.widget(
+ edit=btn,
+ right_widget=bui.get_special_widget('squad_button'),
+ )
bui.widget(
edit=cancel_button,
@@ -270,31 +271,49 @@ class PlaylistEditWindow(bui.Window):
edit=self._root_widget, selected_child=scrollwidget
)
+ @override
+ def get_main_window_state(self) -> bui.MainWindowState:
+ # Support recreating our window for back/refresh purposes.
+ cls = type(self)
+
+ editcontroller = self._editcontroller
+
+ return bui.BasicMainWindowState(
+ create_call=lambda transition, origin_widget: cls(
+ transition=transition,
+ origin_widget=origin_widget,
+ editcontroller=editcontroller,
+ )
+ )
+
def _set_ui_selection(self, selection: str) -> None:
self._editcontroller.set_edit_ui_selection(selection)
def _cancel(self) -> None:
- from bauiv1lib.playlist.customizebrowser import (
- PlaylistCustomizeBrowserWindow,
- )
+ # from bauiv1lib.playlist.customizebrowser import (
+ # 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.containerwidget(edit=self._root_widget, transition='out_right')
- assert bui.app.classic is not None
- bui.app.ui_v1.set_main_menu_window(
- PlaylistCustomizeBrowserWindow(
- transition='in_left',
- sessiontype=self._editcontroller.get_session_type(),
- select_playlist=(
- self._editcontroller.get_existing_playlist_name()
- ),
- ).get_root_widget(),
- from_window=self._root_widget,
- )
+ self.main_window_back()
+
+ # bui.containerwidget(edit=self._root_widget, transition='out_right')
+ # assert bui.app.classic is not None
+ # bui.app.ui_v1.set_main_window(
+ # PlaylistCustomizeBrowserWindow(
+ # transition='in_left',
+ # sessiontype=self._editcontroller.get_session_type(),
+ # select_playlist=(
+ # self._editcontroller.get_existing_playlist_name()
+ # ),
+ # ),
+ # from_window=self,
+ # is_back=True,
+ # )
def _add(self) -> None:
# Store list name then tell the session to perform an add.
@@ -379,13 +398,14 @@ class PlaylistEditWindow(bui.Window):
bui.containerwidget(edit=self._root_widget, transition='out_right')
bui.getsound('gunCocking').play()
assert bui.app.classic is not None
- bui.app.ui_v1.set_main_menu_window(
+ bui.app.ui_v1.set_main_window(
PlaylistCustomizeBrowserWindow(
transition='in_left',
sessiontype=self._editcontroller.get_session_type(),
select_playlist=new_name,
- ).get_root_widget(),
- from_window=self._root_widget,
+ ),
+ from_window=self,
+ is_back=True,
)
def _save_press_with_sound(self) -> None:
diff --git a/src/assets/ba_data/python/bauiv1lib/playlist/editcontroller.py b/src/assets/ba_data/python/bauiv1lib/playlist/editcontroller.py
index 7ed9a92a..2fb584c6 100644
--- a/src/assets/ba_data/python/bauiv1lib/playlist/editcontroller.py
+++ b/src/assets/ba_data/python/bauiv1lib/playlist/editcontroller.py
@@ -89,10 +89,8 @@ class PlaylistEditController:
self._edit_ui_selection = 'add_button'
assert bui.app.classic is not None
- bui.app.ui_v1.set_main_menu_window(
- PlaylistEditWindow(
- editcontroller=self, transition=transition
- ).get_root_widget(),
+ bui.app.ui_v1.set_main_window(
+ PlaylistEditWindow(editcontroller=self, transition=transition),
from_window=False, # Disable this check.
)
@@ -149,10 +147,9 @@ class PlaylistEditController:
from bauiv1lib.playlist.addgame import PlaylistAddGameWindow
assert bui.app.classic is not None
- bui.app.ui_v1.clear_main_menu_window(transition='out_left')
- bui.app.ui_v1.set_main_menu_window(
- PlaylistAddGameWindow(editcontroller=self).get_root_widget(),
- from_window=None,
+ bui.app.ui_v1.clear_main_window()
+ bui.app.ui_v1.set_main_window(
+ PlaylistAddGameWindow(editcontroller=self), from_window=None
)
def edit_game_pressed(self) -> None:
@@ -168,18 +165,17 @@ class PlaylistEditController:
settings=self._playlist[self._selected_index],
)
- def add_game_cancelled(self) -> None:
- """(internal)"""
- from bauiv1lib.playlist.edit import PlaylistEditWindow
+ # def add_game_cancelled(self) -> None:
+ # """(internal)"""
+ # from bauiv1lib.playlist.edit import PlaylistEditWindow
- assert bui.app.classic is not None
- bui.app.ui_v1.clear_main_menu_window(transition='out_right')
- bui.app.ui_v1.set_main_menu_window(
- PlaylistEditWindow(
- editcontroller=self, transition='in_left'
- ).get_root_widget(),
- from_window=None,
- )
+ # assert bui.app.classic is not None
+ # bui.app.ui_v1.clear_main_window(transition='out_right')
+ # bui.app.ui_v1.set_main_window(
+ # PlaylistEditWindow(editcontroller=self, transition='in_left'),
+ # from_window=None,
+ # is_back=True,
+ # )
def _show_edit_ui(
self, gametype: type[bs.GameActivity], settings: dict[str, Any] | None
@@ -204,22 +200,24 @@ class PlaylistEditController:
# If we were editing, go back to our list.
if self._editing_game:
bui.getsound('powerdown01').play()
- bui.app.ui_v1.clear_main_menu_window(transition='out_right')
- bui.app.ui_v1.set_main_menu_window(
+ bui.app.ui_v1.clear_main_window()
+ bui.app.ui_v1.set_main_window(
PlaylistEditWindow(
editcontroller=self, transition='in_left'
- ).get_root_widget(),
+ ),
from_window=None,
+ is_back=True,
)
# Otherwise we were adding; go back to the add type choice list.
else:
- bui.app.ui_v1.clear_main_menu_window(transition='out_right')
- bui.app.ui_v1.set_main_menu_window(
+ bui.app.ui_v1.clear_main_window()
+ bui.app.ui_v1.set_main_window(
PlaylistAddGameWindow(
editcontroller=self, transition='in_left'
- ).get_root_widget(),
+ ),
from_window=None,
+ is_back=True,
)
else:
# Make sure type is in there.
@@ -237,10 +235,9 @@ class PlaylistEditController:
self._selected_index = insert_index
bui.getsound('gunCocking').play()
- bui.app.ui_v1.clear_main_menu_window(transition='out_right')
- bui.app.ui_v1.set_main_menu_window(
- PlaylistEditWindow(
- editcontroller=self, transition='in_left'
- ).get_root_widget(),
+ bui.app.ui_v1.clear_main_window()
+ bui.app.ui_v1.set_main_window(
+ PlaylistEditWindow(editcontroller=self, transition='in_left'),
from_window=None,
+ is_back=True,
)
diff --git a/src/assets/ba_data/python/bauiv1lib/playlist/editgame.py b/src/assets/ba_data/python/bauiv1lib/playlist/editgame.py
index 993aac37..5dfbe182 100644
--- a/src/assets/ba_data/python/bauiv1lib/playlist/editgame.py
+++ b/src/assets/ba_data/python/bauiv1lib/playlist/editgame.py
@@ -7,7 +7,7 @@ from __future__ import annotations
import copy
import random
import logging
-from typing import TYPE_CHECKING, cast
+from typing import TYPE_CHECKING, cast, override
import bascenev1 as bs
import bauiv1 as bui
@@ -16,7 +16,7 @@ if TYPE_CHECKING:
from typing import Any, Callable
-class PlaylistEditGameWindow(bui.Window):
+class PlaylistEditGameWindow(bui.MainWindow):
"""Window for editing a game config."""
def __init__(
@@ -26,7 +26,8 @@ class PlaylistEditGameWindow(bui.Window):
config: dict[str, Any] | None,
completion_call: Callable[[dict[str, Any] | None], Any],
default_selection: str | None = None,
- transition: str = 'in_right',
+ transition: str | None = 'in_right',
+ origin_widget: bui.Widget | None = None,
edit_info: dict[str, Any] | None = None,
):
# pylint: disable=too-many-branches
@@ -64,6 +65,7 @@ class PlaylistEditGameWindow(bui.Window):
bui.screenmessage(bui.Lstr(resource='noValidMapsErrorText'))
raise RuntimeError('No valid maps found.')
+ self._config = config
self._settings_defs = gametype.get_available_settings(sessiontype)
self._completion_call = completion_call
@@ -123,16 +125,17 @@ class PlaylistEditGameWindow(bui.Window):
super().__init__(
root_widget=bui.containerwidget(
size=(width, height + top_extra),
- transition=transition,
scale=(
- 2.19
+ 1.95
if uiscale is bui.UIScale.SMALL
else 1.35 if uiscale is bui.UIScale.MEDIUM else 1.0
),
stack_offset=(
(0, -17) if uiscale is bui.UIScale.SMALL else (0, 0)
),
- )
+ ),
+ transition=transition,
+ origin_widget=origin_widget,
)
btn = bui.buttonwidget(
@@ -161,13 +164,12 @@ class PlaylistEditGameWindow(bui.Window):
label=(
bui.Lstr(resource=f'{self._r}.addGameText')
if is_add
- else bui.Lstr(resource='doneText')
+ else bui.Lstr(resource='applyText')
),
)
- if bui.app.ui_v1.use_toolbars:
- pbtn = bui.get_special_widget('party_button')
- bui.widget(edit=add_button, right_widget=pbtn, up_widget=pbtn)
+ pbtn = bui.get_special_widget('squad_button')
+ bui.widget(edit=add_button, right_widget=pbtn, up_widget=pbtn)
bui.textwidget(
parent=self._root_widget,
@@ -509,6 +511,29 @@ class PlaylistEditGameWindow(bui.Window):
edit=self._subcontainer, selected_child=map_button
)
+ @override
+ def get_main_window_state(self) -> bui.MainWindowState:
+ # Support recreating our window for back/refresh purposes.
+ cls = type(self)
+
+ # Pull things out of self here so we don't refer to self in the
+ # lambda below which would keep us alive.
+ gametype = self._gametype
+ sessiontype = self._sessiontype
+ config = self._config
+ completion_call = self._completion_call
+
+ return bui.BasicMainWindowState(
+ create_call=lambda transition, origin_widget: cls(
+ transition=transition,
+ origin_widget=origin_widget,
+ gametype=gametype,
+ sessiontype=sessiontype,
+ config=config,
+ completion_call=completion_call,
+ )
+ )
+
def _get_localized_setting_name(self, name: str) -> bui.Lstr:
return bui.Lstr(translate=('settingNames', name))
@@ -523,15 +548,15 @@ class PlaylistEditGameWindow(bui.Window):
# Replace ourself with the map-select UI.
bui.containerwidget(edit=self._root_widget, transition='out_left')
assert bui.app.classic is not None
- bui.app.ui_v1.set_main_menu_window(
+ bui.app.ui_v1.set_main_window(
PlaylistMapSelectWindow(
self._gametype,
self._sessiontype,
copy.deepcopy(self._getconfig()),
self._edit_info,
self._completion_call,
- ).get_root_widget(),
- from_window=self._root_widget,
+ ),
+ from_window=self,
)
def _choice_inc(
diff --git a/src/assets/ba_data/python/bauiv1lib/playlist/mapselect.py b/src/assets/ba_data/python/bauiv1lib/playlist/mapselect.py
index 8e2cc8ea..6b7fc547 100644
--- a/src/assets/ba_data/python/bauiv1lib/playlist/mapselect.py
+++ b/src/assets/ba_data/python/bauiv1lib/playlist/mapselect.py
@@ -15,7 +15,7 @@ if TYPE_CHECKING:
import bascenev1 as bs
-class PlaylistMapSelectWindow(bui.Window):
+class PlaylistMapSelectWindow(bui.MainWindow):
"""Window to select a map."""
def __init__(
@@ -25,8 +25,11 @@ class PlaylistMapSelectWindow(bui.Window):
config: dict[str, Any],
edit_info: dict[str, Any],
completion_call: Callable[[dict[str, Any] | None], Any],
- transition: str = 'in_right',
+ transition: str | None = 'in_right',
+ origin_widget: bui.Widget | None = None,
):
+ # pylint: disable=too-many-locals
+
from bascenev1 import get_filtered_map_name
self._gametype = gametype
@@ -56,16 +59,18 @@ class PlaylistMapSelectWindow(bui.Window):
super().__init__(
root_widget=bui.containerwidget(
size=(width, height + top_extra),
- transition=transition,
+ # transition=transition,
scale=(
- 2.17
+ 1.95
if uiscale is bui.UIScale.SMALL
else 1.3 if uiscale is bui.UIScale.MEDIUM else 1.0
),
stack_offset=(
(0, -27) if uiscale is bui.UIScale.SMALL else (0, 0)
),
- )
+ ),
+ transition=transition,
+ origin_widget=origin_widget,
)
self._cancel_button = btn = bui.buttonwidget(
@@ -76,7 +81,7 @@ class PlaylistMapSelectWindow(bui.Window):
text_scale=1.0,
autoselect=True,
label=bui.Lstr(resource='cancelText'),
- on_activate_call=self._cancel,
+ on_activate_call=self.main_window_back,
)
bui.containerwidget(edit=self._root_widget, cancel_button=btn)
@@ -198,10 +203,10 @@ class PlaylistMapSelectWindow(bui.Window):
bui.widget(edit=btn, left_widget=self._cancel_button)
if y == 0:
bui.widget(edit=btn, up_widget=self._cancel_button)
- if x == columns - 1 and bui.app.ui_v1.use_toolbars:
+ if x == columns - 1:
bui.widget(
edit=btn,
- right_widget=bui.get_special_widget('party_button'),
+ right_widget=bui.get_special_widget('squad_button'),
)
bui.widget(edit=btn, show_buffer_top=60, show_buffer_bottom=60)
@@ -267,51 +272,53 @@ class PlaylistMapSelectWindow(bui.Window):
self._refresh(select_get_more_maps_button=True)
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
- bui.containerwidget(edit=self._root_widget, transition='out_right')
- assert bui.app.classic is not None
- bui.app.ui_v1.set_main_menu_window(
- PlaylistEditGameWindow(
- self._gametype,
- self._sessiontype,
- self._config,
- self._completion_call,
- default_selection='map',
- transition='in_left',
- edit_info=self._edit_info,
- ).get_root_widget(),
- from_window=self._root_widget,
- )
+ self.main_window_back()
+ # bui.containerwidget(edit=self._root_widget, transition='out_right')
+ # assert bui.app.classic is not None
+ # bui.app.ui_v1.set_main_window(
+ # PlaylistEditGameWindow(
+ # self._gametype,
+ # self._sessiontype,
+ # self._config,
+ # self._completion_call,
+ # default_selection='map',
+ # transition='in_left',
+ # edit_info=self._edit_info,
+ # ),
+ # from_window=self,
+ # )
def _select_with_delay(self, map_name: str) -> None:
bui.lock_all_input()
bui.apptimer(0.1, bui.unlock_all_input)
bui.apptimer(0.1, bui.WeakCall(self._select, map_name))
- def _cancel(self) -> None:
- from bauiv1lib.playlist.editgame import PlaylistEditGameWindow
+ # def _cancel(self) -> None:
+ # 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
+ # # 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')
- assert bui.app.classic is not None
- bui.app.ui_v1.set_main_menu_window(
- PlaylistEditGameWindow(
- self._gametype,
- self._sessiontype,
- self._config,
- self._completion_call,
- default_selection='map',
- transition='in_left',
- edit_info=self._edit_info,
- ).get_root_widget(),
- from_window=self._root_widget,
- )
+ # bui.containerwidget(edit=self._root_widget, transition='out_right')
+ # assert bui.app.classic is not None
+ # bui.app.ui_v1.set_main_window(
+ # PlaylistEditGameWindow(
+ # self._gametype,
+ # self._sessiontype,
+ # self._config,
+ # self._completion_call,
+ # default_selection='map',
+ # transition='in_left',
+ # edit_info=self._edit_info,
+ # ),
+ # from_window=self,
+ # is_back=True,
+ # )
diff --git a/src/assets/ba_data/python/bauiv1lib/playoptions.py b/src/assets/ba_data/python/bauiv1lib/playoptions.py
index cc69b3d4..2b81de6a 100644
--- a/src/assets/ba_data/python/bauiv1lib/playoptions.py
+++ b/src/assets/ba_data/python/bauiv1lib/playoptions.py
@@ -41,8 +41,9 @@ class PlayOptionsWindow(PopupWindow):
# We behave differently if we're being used for playlist selection
# vs starting a game directly (should make this more elegant).
- assert bui.app.classic is not None
- self._selecting_mode = bui.app.ui_v1.selecting_private_party_playlist
+ classic = bui.app.classic
+ assert classic is not None
+ self._selecting_mode = classic.selecting_private_party_playlist
self._do_randomize_val = bui.app.config.get(
self._pvars.config_name + ' Playlist Randomize', 0
@@ -512,8 +513,8 @@ class PlayOptionsWindow(PopupWindow):
# 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(
- GatherWindow(transition='in_right').get_root_widget(),
+ bui.app.ui_v1.set_main_window(
+ GatherWindow(transition='in_right'),
from_window=False, # Disable this test.
)
self._transition_out(transition='out_left')
diff --git a/src/assets/ba_data/python/bauiv1lib/popup.py b/src/assets/ba_data/python/bauiv1lib/popup.py
index ef830f85..df9d111c 100644
--- a/src/assets/ba_data/python/bauiv1lib/popup.py
+++ b/src/assets/ba_data/python/bauiv1lib/popup.py
@@ -10,7 +10,7 @@ from typing import TYPE_CHECKING, override
import bauiv1 as bui
if TYPE_CHECKING:
- from typing import Any, Sequence, Callable
+ from typing import Any, Sequence, Callable, Literal
class PopupWindow:
@@ -27,7 +27,12 @@ class PopupWindow:
bg_color: tuple[float, float, float] = (0.35, 0.55, 0.15),
focus_position: tuple[float, float] = (0, 0),
focus_size: tuple[float, float] | None = None,
- toolbar_visibility: str = 'menu_minimal_no_back',
+ toolbar_visibility: Literal[
+ 'inherit',
+ 'menu_minimal_no_back',
+ 'menu_store_no_back',
+ ] = 'menu_minimal_no_back',
+ edge_buffer_scale: float = 1.0,
):
# pylint: disable=too-many-locals
if focus_size is None:
@@ -45,7 +50,7 @@ class PopupWindow:
# we now need to ensure that we're all onscreen by scaling down if
# need be and clamping it to the UI bounds.
bounds = bui.uibounds()
- edge_buffer = 15
+ edge_buffer = 15 * edge_buffer_scale
bounds_width = bounds[1] - bounds[0] - edge_buffer * 2
bounds_height = bounds[3] - bounds[2] - edge_buffer * 2
diff --git a/src/assets/ba_data/python/bauiv1lib/profile/browser.py b/src/assets/ba_data/python/bauiv1lib/profile/browser.py
index 4529cdb6..95464e03 100644
--- a/src/assets/ba_data/python/bauiv1lib/profile/browser.py
+++ b/src/assets/ba_data/python/bauiv1lib/profile/browser.py
@@ -5,7 +5,7 @@
from __future__ import annotations
import logging
-from typing import TYPE_CHECKING
+from typing import TYPE_CHECKING, override
import bauiv1 as bui
import bascenev1 as bs
@@ -14,18 +14,17 @@ if TYPE_CHECKING:
from typing import Any
-class ProfileBrowserWindow(bui.Window):
+class ProfileBrowserWindow(bui.MainWindow):
"""Window for browsing player profiles."""
def __init__(
self,
- transition: str = 'in_right',
+ transition: str | None = 'in_right',
in_main_menu: bool = True,
selected_profile: str | None = None,
origin_widget: bui.Widget | None = None,
):
# pylint: disable=too-many-statements
- # pylint: disable=too-many-locals
self._in_main_menu = in_main_menu
if self._in_main_menu:
back_label = bui.Lstr(resource='backText')
@@ -46,15 +45,11 @@ class ProfileBrowserWindow(bui.Window):
assert bui.app.classic is not None
bui.app.classic.pause()
- # If they provided an origin-widget, scale up from that.
- scale_origin: tuple[float, float] | None
+ # Need to handle out-transitions ourself for modal mode.
if origin_widget is not None:
self._transition_out = 'out_scale'
- scale_origin = origin_widget.get_screen_space_center()
- transition = 'in_scale'
else:
self._transition_out = 'out_right'
- scale_origin = None
self._r = 'playerProfilesWindow'
@@ -67,30 +62,48 @@ class ProfileBrowserWindow(bui.Window):
super().__init__(
root_widget=bui.containerwidget(
size=(self._width, self._height + top_extra),
- transition=transition,
- scale_origin_stack_offset=scale_origin,
- scale=(
- 2.2
+ toolbar_visibility=(
+ 'menu_minimal'
if uiscale is bui.UIScale.SMALL
- else 1.6 if uiscale is bui.UIScale.MEDIUM else 1.0
+ else 'menu_full'
+ ),
+ scale=(
+ 2.0
+ if uiscale is bui.UIScale.SMALL
+ else 1.5 if uiscale is bui.UIScale.MEDIUM else 1.0
),
stack_offset=(
(0, -14) if uiscale is bui.UIScale.SMALL else (0, 0)
),
- )
+ ),
+ transition=transition,
+ origin_widget=origin_widget,
)
- self._back_button = btn = bui.buttonwidget(
- parent=self._root_widget,
- position=(40 + x_inset, self._height - 59),
- size=(120, 60),
- scale=0.8,
- label=back_label,
- button_type='back' if self._in_main_menu else None,
- autoselect=True,
- on_activate_call=self._back,
- )
- bui.containerwidget(edit=self._root_widget, cancel_button=btn)
+ if bui.app.ui_v1.uiscale is bui.UIScale.SMALL:
+ self._back_button = bui.get_special_widget('back_button')
+ bui.containerwidget(
+ edit=self._root_widget, on_cancel_call=self._back
+ )
+ else:
+ self._back_button = btn = bui.buttonwidget(
+ parent=self._root_widget,
+ position=(40 + x_inset, self._height - 59),
+ size=(120, 60),
+ scale=0.8,
+ label=back_label,
+ button_type='back' if self._in_main_menu else None,
+ autoselect=True,
+ on_activate_call=self._back,
+ )
+ bui.containerwidget(edit=self._root_widget, cancel_button=btn)
+ if self._in_main_menu:
+ bui.buttonwidget(
+ edit=btn,
+ button_type='backSmall',
+ size=(60, 60),
+ label=bui.charstr(bui.SpecialChar.BACK),
+ )
bui.textwidget(
parent=self._root_widget,
@@ -104,14 +117,6 @@ class ProfileBrowserWindow(bui.Window):
v_align='center',
)
- if self._in_main_menu:
- bui.buttonwidget(
- edit=btn,
- button_type='backSmall',
- size=(60, 60),
- label=bui.charstr(bui.SpecialChar.BACK),
- )
-
scroll_height = self._height - 140.0
self._scroll_width = self._width - (188 + x_inset * 2)
v = self._height - 84.0
@@ -203,6 +208,20 @@ class ProfileBrowserWindow(bui.Window):
self._refresh()
self._restore_state()
+ @override
+ def get_main_window_state(self) -> bui.MainWindowState:
+ # Support recreating our window for back/refresh purposes.
+ cls = type(self)
+ return bui.BasicMainWindowState(
+ create_call=lambda transition, origin_widget: cls(
+ transition=transition, origin_widget=origin_widget
+ )
+ )
+
+ @override
+ def on_main_window_close(self) -> None:
+ self._save_state()
+
def _new_profile(self) -> None:
# pylint: disable=cyclic-import
from bauiv1lib.profile.edit import EditProfileWindow
@@ -249,11 +268,11 @@ class ProfileBrowserWindow(bui.Window):
self._save_state()
bui.containerwidget(edit=self._root_widget, transition='out_left')
- bui.app.ui_v1.set_main_menu_window(
+ bui.app.ui_v1.set_main_window(
EditProfileWindow(
existing_profile=None, in_main_menu=self._in_main_menu
- ).get_root_widget(),
- from_window=self._root_widget if self._in_main_menu else False,
+ ),
+ from_window=self if self._in_main_menu else False,
)
def _delete_profile(self) -> None:
@@ -315,11 +334,11 @@ class ProfileBrowserWindow(bui.Window):
self._save_state()
bui.containerwidget(edit=self._root_widget, transition='out_left')
assert bui.app.classic is not None
- bui.app.ui_v1.set_main_menu_window(
+ bui.app.ui_v1.set_main_window(
EditProfileWindow(
self._selected_profile, in_main_menu=self._in_main_menu
- ).get_root_widget(),
- from_window=self._root_widget if self._in_main_menu else False,
+ ),
+ from_window=self if self._in_main_menu else False,
)
def _select(self, name: str, index: int) -> None:
@@ -328,7 +347,10 @@ class ProfileBrowserWindow(bui.Window):
def _back(self) -> None:
# pylint: disable=cyclic-import
- from bauiv1lib.account.settings import AccountSettingsWindow
+
+ if self._in_main_menu:
+ self.main_window_back()
+ return
# no-op if our underlying widget is dead or on its way out.
if not self._root_widget or self._root_widget.transitioning_out:
@@ -340,16 +362,17 @@ class ProfileBrowserWindow(bui.Window):
bui.containerwidget(
edit=self._root_widget, transition=self._transition_out
)
- if self._in_main_menu:
- assert bui.app.classic is not None
- bui.app.ui_v1.set_main_menu_window(
- AccountSettingsWindow(transition='in_left').get_root_widget(),
- from_window=self._root_widget,
- )
+ # if self._in_main_menu:
+ # assert bui.app.classic is not None
+ # bui.app.ui_v1.set_main_window(
+ # AccountSettingsWindow(transition='in_left'),
+ # from_window=self,
+ # is_back=True,
+ # )
- # If we're being called up standalone, handle pause/resume ourself.
- else:
- bui.app.classic.resume()
+ # # If we're being called up standalone, handle pause/resume ourself.
+ # else:
+ bui.app.classic.resume()
def _refresh(self) -> None:
# pylint: disable=too-many-locals
diff --git a/src/assets/ba_data/python/bauiv1lib/profile/edit.py b/src/assets/ba_data/python/bauiv1lib/profile/edit.py
index b4747769..982d143b 100644
--- a/src/assets/ba_data/python/bauiv1lib/profile/edit.py
+++ b/src/assets/ba_data/python/bauiv1lib/profile/edit.py
@@ -5,17 +5,16 @@
from __future__ import annotations
import random
-from typing import cast
+from typing import cast, override
from bauiv1lib.colorpicker import ColorPicker
import bauiv1 as bui
import bascenev1 as bs
-class EditProfileWindow(bui.Window):
+class EditProfileWindow(bui.MainWindow):
"""Window for editing a player profile."""
- # FIXME: WILL NEED TO CHANGE THIS FOR UILOCATION.
def reload_window(self) -> None:
"""Transitions out and recreates ourself."""
@@ -25,18 +24,18 @@ class EditProfileWindow(bui.Window):
bui.containerwidget(edit=self._root_widget, transition='out_left')
assert bui.app.classic is not None
- bui.app.ui_v1.set_main_menu_window(
- EditProfileWindow(
- self.getname(), self._in_main_menu
- ).get_root_widget(),
- from_window=self._root_widget,
+ bui.app.ui_v1.set_main_window(
+ EditProfileWindow(self.getname(), self._in_main_menu),
+ from_window=self,
+ is_back=True,
)
def __init__(
self,
existing_profile: str | None,
in_main_menu: bool,
- transition: str = 'in_right',
+ transition: str | None = 'in_right',
+ origin_widget: bui.Widget | None = None,
):
# FIXME: Tidy this up a bit.
# pylint: disable=too-many-branches
@@ -63,26 +62,32 @@ class EditProfileWindow(bui.Window):
self._width = width = 880.0 if uiscale is bui.UIScale.SMALL else 680.0
self._x_inset = x_inset = 100.0 if uiscale is bui.UIScale.SMALL else 0.0
self._height = height = (
- 350.0
+ 450.0
if uiscale is bui.UIScale.SMALL
else 400.0 if uiscale is bui.UIScale.MEDIUM else 450.0
)
spacing = 40
self._base_scale = (
- 2.05
+ 1.6
if uiscale is bui.UIScale.SMALL
- else 1.5 if uiscale is bui.UIScale.MEDIUM else 1.0
+ else 1.35 if uiscale is bui.UIScale.MEDIUM else 1.0
)
- top_extra = 15 if uiscale is bui.UIScale.SMALL else 15
+ top_extra = 70 if uiscale is bui.UIScale.SMALL else 15
super().__init__(
root_widget=bui.containerwidget(
size=(width, height + top_extra),
- transition=transition,
scale=self._base_scale,
stack_offset=(
- (0, 15) if uiscale is bui.UIScale.SMALL else (0, 0)
+ (0, -40) if uiscale is bui.UIScale.SMALL else (0, 0)
),
- )
+ toolbar_visibility=(
+ 'menu_minimal'
+ if uiscale is bui.UIScale.SMALL
+ else 'menu_full'
+ ),
+ ),
+ transition=transition,
+ origin_widget=origin_widget,
)
cancel_button = btn = bui.buttonwidget(
parent=self._root_widget,
@@ -512,6 +517,19 @@ class EditProfileWindow(bui.Window):
)
self._update_character()
+ @override
+ def get_main_window_state(self) -> bui.MainWindowState:
+ # Support recreating our window for back/refresh purposes.
+ cls = type(self)
+ return bui.BasicMainWindowState(
+ create_call=lambda transition, origin_widget: cls(
+ transition=transition,
+ origin_widget=origin_widget,
+ existing_profile=self._existing_profile,
+ in_main_menu=self._in_main_menu,
+ )
+ )
+
def assign_random_name(self) -> None:
"""Assigning a random name to the player."""
names = bs.get_random_names()
@@ -672,22 +690,24 @@ class EditProfileWindow(bui.Window):
)
def _cancel(self) -> None:
- from bauiv1lib.profile.browser import ProfileBrowserWindow
+ self.main_window_back()
+ # 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
+ # # 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')
- assert bui.app.classic is not None
- bui.app.ui_v1.set_main_menu_window(
- ProfileBrowserWindow(
- 'in_left',
- selected_profile=self._existing_profile,
- in_main_menu=self._in_main_menu,
- ).get_root_widget(),
- from_window=self._root_widget,
- )
+ # bui.containerwidget(edit=self._root_widget, transition='out_right')
+ # assert bui.app.classic is not None
+ # bui.app.ui_v1.set_main_window(
+ # ProfileBrowserWindow(
+ # 'in_left',
+ # selected_profile=self._existing_profile,
+ # in_main_menu=self._in_main_menu,
+ # ),
+ # from_window=self,
+ # is_back=True,
+ # )
def _set_color(self, color: tuple[float, float, float]) -> None:
self._color = color
@@ -783,7 +803,6 @@ class EditProfileWindow(bui.Window):
def save(self, transition_out: bool = True) -> bool:
"""Save has been selected."""
- 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:
@@ -840,14 +859,17 @@ class EditProfileWindow(bui.Window):
if transition_out:
plus.run_v1_account_transactions()
- bui.containerwidget(edit=self._root_widget, transition='out_right')
- assert bui.app.classic is not None
- bui.app.ui_v1.set_main_menu_window(
- ProfileBrowserWindow(
- 'in_left',
- selected_profile=new_name,
- in_main_menu=self._in_main_menu,
- ).get_root_widget(),
- from_window=self._root_widget,
- )
+ self.main_window_back()
+ # bui.containerwidget(edit=self._root_widget,
+ # transition='out_right')
+ # assert bui.app.classic is not None
+ # bui.app.ui_v1.set_main_window(
+ # ProfileBrowserWindow(
+ # 'in_left',
+ # selected_profile=new_name,
+ # in_main_menu=self._in_main_menu,
+ # ),
+ # from_window=self,
+ # is_back=True,
+ # )
return True
diff --git a/src/assets/ba_data/python/bauiv1lib/profile/upgrade.py b/src/assets/ba_data/python/bauiv1lib/profile/upgrade.py
index 89340bb4..eadc9903 100644
--- a/src/assets/ba_data/python/bauiv1lib/profile/upgrade.py
+++ b/src/assets/ba_data/python/bauiv1lib/profile/upgrade.py
@@ -32,12 +32,12 @@ class ProfileUpgradeWindow(bui.Window):
self._r = 'editProfileWindow'
- self._width = 680
+ uiscale = bui.app.ui_v1.uiscale
+ self._width = 750 if uiscale is bui.UIScale.SMALL else 680
self._height = 350
assert bui.app.classic is not None
- uiscale = bui.app.ui_v1.uiscale
self._base_scale = (
- 2.05
+ 1.9
if uiscale is bui.UIScale.SMALL
else 1.5 if uiscale is bui.UIScale.MEDIUM else 1.2
)
@@ -49,17 +49,17 @@ class ProfileUpgradeWindow(bui.Window):
super().__init__(
root_widget=bui.containerwidget(
size=(self._width, self._height + top_extra),
- toolbar_visibility='menu_currency',
+ toolbar_visibility='menu_store_no_back',
transition=transition,
scale=self._base_scale,
stack_offset=(
- (0, 15) if uiscale is bui.UIScale.SMALL else (0, 0)
+ (0, -30) if uiscale is bui.UIScale.SMALL else (0, 0)
),
)
)
cancel_button = bui.buttonwidget(
parent=self._root_widget,
- position=(52, 30),
+ position=(52, 60),
size=(155, 60),
scale=0.8,
autoselect=True,
@@ -68,7 +68,7 @@ class ProfileUpgradeWindow(bui.Window):
)
self._upgrade_button = bui.buttonwidget(
parent=self._root_widget,
- position=(self._width - 190, 30),
+ position=(self._width - 190, 60),
size=(155, 60),
scale=0.8,
autoselect=True,
@@ -136,20 +136,20 @@ class ProfileUpgradeWindow(bui.Window):
)
self._tickets_text: bui.Widget | None
- if not bui.app.ui_v1.use_toolbars:
- self._tickets_text = bui.textwidget(
- parent=self._root_widget,
- position=(self._width * 0.9 - 5, self._height - 30),
- size=(0, 0),
- text=bui.charstr(bui.SpecialChar.TICKET) + '123',
- color=(0.2, 1, 0.2),
- maxwidth=100,
- scale=0.5,
- h_align='right',
- v_align='center',
- )
- else:
- self._tickets_text = None
+ # if not bui.app.ui_v1.use_toolbars:
+ # self._tickets_text = bui.textwidget(
+ # parent=self._root_widget,
+ # position=(self._width * 0.9 - 5, self._height - 30),
+ # size=(0, 0),
+ # text=bui.charstr(bui.SpecialChar.TICKET) + '123',
+ # color=(0.2, 1, 0.2),
+ # maxwidth=100,
+ # scale=0.5,
+ # h_align='right',
+ # v_align='center',
+ # )
+ # else:
+ self._tickets_text = None
bui.app.classic.master_server_v1_get(
'bsGlobalProfileCheck',
@@ -210,7 +210,7 @@ class ProfileUpgradeWindow(bui.Window):
)
def _on_upgrade_press(self) -> None:
- from bauiv1lib import gettickets
+ # from bauiv1lib import gettickets
if self._status is None:
plus = bui.app.plus
@@ -220,7 +220,8 @@ class ProfileUpgradeWindow(bui.Window):
tickets = plus.get_v1_account_ticket_count()
if tickets < self._cost:
bui.getsound('error').play()
- gettickets.show_get_tickets_prompt()
+ print('FIXME - show not-enough-tickets msg.')
+ # gettickets.show_get_tickets_prompt()
return
bui.screenmessage(
bui.Lstr(resource='purchasingText'), color=(0, 1, 0)
diff --git a/src/assets/ba_data/python/bauiv1lib/purchase.py b/src/assets/ba_data/python/bauiv1lib/purchase.py
index 60f97b0e..fbb74d75 100644
--- a/src/assets/ba_data/python/bauiv1lib/purchase.py
+++ b/src/assets/ba_data/python/bauiv1lib/purchase.py
@@ -44,7 +44,7 @@ class PurchaseWindow(bui.Window):
root_widget=bui.containerwidget(
size=(self._width, self._height),
transition=transition,
- toolbar_visibility='menu_currency',
+ toolbar_visibility='menu_store',
scale=(
1.2
if uiscale is bui.UIScale.SMALL
@@ -162,7 +162,7 @@ class PurchaseWindow(bui.Window):
bui.containerwidget(edit=self._root_widget, transition='out_left')
def _purchase(self) -> None:
- from bauiv1lib import gettickets
+ # from bauiv1lib import gettickets
plus = bui.app.plus
assert plus is not None
@@ -176,7 +176,8 @@ class PurchaseWindow(bui.Window):
except Exception:
ticket_count = None
if ticket_count is not None and ticket_count < self._price:
- gettickets.show_get_tickets_prompt()
+ # gettickets.show_get_tickets_prompt()
+ print('FIXME - show not-enough-tickets msg')
bui.getsound('error').play()
return
diff --git a/src/assets/ba_data/python/bauiv1lib/resourcetypeinfo.py b/src/assets/ba_data/python/bauiv1lib/resourcetypeinfo.py
index a3e39ebe..9ca597c1 100644
--- a/src/assets/ba_data/python/bauiv1lib/resourcetypeinfo.py
+++ b/src/assets/ba_data/python/bauiv1lib/resourcetypeinfo.py
@@ -4,22 +4,29 @@
from __future__ import annotations
-from typing import override
+from typing import override, TYPE_CHECKING, assert_never
from bauiv1lib.popup import PopupWindow
import bauiv1 as bui
+if TYPE_CHECKING:
+ from typing import Literal
+
class ResourceTypeInfoWindow(PopupWindow):
"""Popup window providing info about resource types."""
- def __init__(self, origin_widget: bui.Widget):
+ def __init__(
+ self,
+ resource_type: Literal['tickets', 'tokens', 'trophies', 'xp'],
+ origin_widget: bui.Widget,
+ ):
assert bui.app.classic is not None
uiscale = bui.app.ui_v1.uiscale
scale = (
- 2.3
+ 2.0
if uiscale is bui.UIScale.SMALL
- else 1.65 if uiscale is bui.UIScale.MEDIUM else 1.23
+ else 1.5 if uiscale is bui.UIScale.MEDIUM else 1.0
)
self._transitioning_out = False
self._width = 570
@@ -31,12 +38,13 @@ class ResourceTypeInfoWindow(PopupWindow):
scale=scale,
bg_color=bg_color,
position=origin_widget.get_screen_space_center(),
+ edge_buffer_scale=4.0,
)
self._cancel_button = bui.buttonwidget(
parent=self.root_widget,
- position=(50, self._height - 30),
+ position=(40, self._height - 40),
size=(50, 50),
- scale=0.5,
+ scale=0.7,
label='',
color=bg_color,
on_activate_call=self._on_cancel_press,
@@ -45,6 +53,26 @@ class ResourceTypeInfoWindow(PopupWindow):
iconscale=1.2,
)
+ if resource_type == 'tickets':
+ rdesc = 'Will describe tickets.'
+ elif resource_type == 'tokens':
+ rdesc = 'Will describe tokens.'
+ elif resource_type == 'trophies':
+ rdesc = 'Will show trophies & league rankings.'
+ elif resource_type == 'xp':
+ rdesc = 'Will describe xp/levels.'
+ else:
+ assert_never(resource_type)
+
+ bui.textwidget(
+ parent=self.root_widget,
+ h_align='center',
+ v_align='center',
+ size=(0, 0),
+ position=(self._width * 0.5, self._height * 0.5),
+ text=(f'UNDER CONSTRUCTION.\n({rdesc})'),
+ )
+
def _on_cancel_press(self) -> None:
self._transition_out()
diff --git a/src/assets/ba_data/python/bauiv1lib/sendinfo.py b/src/assets/ba_data/python/bauiv1lib/sendinfo.py
index 0a1cf59e..97899259 100644
--- a/src/assets/ba_data/python/bauiv1lib/sendinfo.py
+++ b/src/assets/ba_data/python/bauiv1lib/sendinfo.py
@@ -14,26 +14,25 @@ if TYPE_CHECKING:
from typing import Any
-class SendInfoWindow(bui.Window):
+class SendInfoWindow(bui.MainWindow):
"""Window for sending info to the developer."""
def __init__(
self,
modal: bool = False,
legacy_code_mode: bool = False,
+ transition: str | None = 'in_scale',
origin_widget: bui.Widget | None = None,
):
self._legacy_code_mode = legacy_code_mode
- scale_origin: tuple[float, float] | None
+ # scale_origin: tuple[float, float] | None
+
+ # Need to wrangle our own transition-out in modal mode.
if origin_widget is not None:
self._transition_out = 'out_scale'
- scale_origin = origin_widget.get_screen_space_center()
- transition = 'in_scale'
else:
self._transition_out = 'out_right'
- scale_origin = None
- transition = 'in_right'
width = 450 if legacy_code_mode else 600
height = 200 if legacy_code_mode else 300
@@ -46,15 +45,19 @@ class SendInfoWindow(bui.Window):
super().__init__(
root_widget=bui.containerwidget(
size=(width, height),
- transition=transition,
- toolbar_visibility='menu_minimal_no_back',
- scale_origin_stack_offset=scale_origin,
+ toolbar_visibility=(
+ 'menu_minimal_no_back'
+ if uiscale is bui.UIScale.SMALL or modal
+ else 'menu_full'
+ ),
scale=(
2.0
if uiscale is bui.UIScale.SMALL
else 1.5 if uiscale is bui.UIScale.MEDIUM else 1.0
),
- )
+ ),
+ transition=transition,
+ origin_widget=origin_widget,
)
btn = bui.buttonwidget(
@@ -165,7 +168,12 @@ class SendInfoWindow(bui.Window):
def _do_back(self) -> None:
# pylint: disable=cyclic-import
- from bauiv1lib.settings.advanced import AdvancedSettingsWindow
+
+ if not self._modal:
+ self.main_window_back()
+ return
+
+ # 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:
@@ -174,12 +182,13 @@ class SendInfoWindow(bui.Window):
bui.containerwidget(
edit=self._root_widget, transition=self._transition_out
)
- if not self._modal:
- assert bui.app.classic is not None
- bui.app.ui_v1.set_main_menu_window(
- AdvancedSettingsWindow(transition='in_left').get_root_widget(),
- from_window=self._root_widget,
- )
+ # if not self._modal:
+ # assert bui.app.classic is not None
+ # bui.app.ui_v1.set_main_window(
+ # AdvancedSettingsWindow(transition='in_left'),
+ # from_window=self,
+ # is_back=True,
+ # )
def _activate_enter_button(self) -> None:
self._enter_button.activate()
@@ -200,9 +209,8 @@ class SendInfoWindow(bui.Window):
)
if not self._modal:
assert bui.app.classic is not None
- bui.app.ui_v1.set_main_menu_window(
- AdvancedSettingsWindow(transition='in_left').get_root_widget(),
- from_window=self._root_widget,
+ bui.app.ui_v1.set_main_window(
+ AdvancedSettingsWindow(transition='in_left'), from_window=self
)
description: Any = bui.textwidget(query=self._text_field)
diff --git a/src/assets/ba_data/python/bauiv1lib/settings/advanced.py b/src/assets/ba_data/python/bauiv1lib/settings/advanced.py
index 521762ac..b43b95b5 100644
--- a/src/assets/ba_data/python/bauiv1lib/settings/advanced.py
+++ b/src/assets/ba_data/python/bauiv1lib/settings/advanced.py
@@ -6,7 +6,7 @@ from __future__ import annotations
import os
import logging
-from typing import TYPE_CHECKING
+from typing import TYPE_CHECKING, override
from bauiv1lib.popup import PopupMenu
import bauiv1 as bui
@@ -15,39 +15,28 @@ if TYPE_CHECKING:
from typing import Any
-class AdvancedSettingsWindow(bui.Window):
+class AdvancedSettingsWindow(bui.MainWindow):
"""Window for editing advanced app settings."""
def __init__(
self,
- transition: str = 'in_right',
+ transition: str | None = 'in_right',
origin_widget: bui.Widget | None = None,
):
# pylint: disable=too-many-statements
- import threading
if bui.app.classic is None:
raise RuntimeError('This requires classic support.')
# Preload some modules we use in a background thread so we won't
# have a visual hitch when the user taps them.
- threading.Thread(target=self._preload_modules).start()
+ bui.app.threadpool_submit_no_wait(self._preload_modules)
app = bui.app
assert app.classic is not None
- # If they provided an origin-widget, scale up from that.
- scale_origin: tuple[float, float] | None
- if origin_widget is not None:
- self._transition_out = 'out_scale'
- scale_origin = origin_widget.get_screen_space_center()
- transition = 'in_scale'
- else:
- self._transition_out = 'out_right'
- scale_origin = None
-
uiscale = bui.app.ui_v1.uiscale
- self._width = 970.0 if uiscale is bui.UIScale.SMALL else 670.0
+ self._width = 1030.0 if uiscale is bui.UIScale.SMALL else 670.0
x_inset = 150 if uiscale is bui.UIScale.SMALL else 0
self._height = (
390.0
@@ -63,18 +52,22 @@ class AdvancedSettingsWindow(bui.Window):
super().__init__(
root_widget=bui.containerwidget(
size=(self._width, self._height + top_extra),
- transition=transition,
- toolbar_visibility='menu_minimal',
- scale_origin_stack_offset=scale_origin,
+ toolbar_visibility=(
+ 'menu_minimal'
+ if uiscale is bui.UIScale.SMALL
+ else 'menu_full'
+ ),
scale=(
- 2.06
+ 2.04
if uiscale is bui.UIScale.SMALL
else 1.4 if uiscale is bui.UIScale.MEDIUM else 1.0
),
stack_offset=(
- (0, -25) if uiscale is bui.UIScale.SMALL else (0, 0)
+ (0, 10) if uiscale is bui.UIScale.SMALL else (0, 0)
),
- )
+ ),
+ transition=transition,
+ origin_widget=origin_widget,
)
self._prev_lang = ''
@@ -112,9 +105,9 @@ class AdvancedSettingsWindow(bui.Window):
self._r = 'settingsWindowAdvanced'
- if app.ui_v1.use_toolbars and uiscale is bui.UIScale.SMALL:
+ if uiscale is bui.UIScale.SMALL:
bui.containerwidget(
- edit=self._root_widget, on_cancel_call=self._do_back
+ edit=self._root_widget, on_cancel_call=self.main_window_back
)
self._back_button = None
else:
@@ -126,7 +119,7 @@ class AdvancedSettingsWindow(bui.Window):
autoselect=True,
label=bui.Lstr(resource='backText'),
button_type='back',
- on_activate_call=self._do_back,
+ on_activate_call=self.main_window_back,
)
bui.containerwidget(
edit=self._root_widget, cancel_button=self._back_button
@@ -134,12 +127,16 @@ class AdvancedSettingsWindow(bui.Window):
self._title_text = bui.textwidget(
parent=self._root_widget,
- position=(0, self._height - 52),
- size=(self._width, 25),
+ position=(
+ self._width * 0.5,
+ self._height - (57 if uiscale is bui.UIScale.SMALL else 40),
+ ),
+ size=(0, 0),
+ scale=0.5 if uiscale is bui.UIScale.SMALL else 1.0,
text=bui.Lstr(resource=f'{self._r}.titleText'),
color=app.ui_v1.title_color,
h_align='center',
- v_align='top',
+ v_align='center',
)
if self._back_button is not None:
@@ -180,10 +177,23 @@ class AdvancedSettingsWindow(bui.Window):
callback=bui.WeakCall(self._completed_langs_cb),
)
- # noinspection PyUnresolvedReferences
+ @override
+ def get_main_window_state(self) -> bui.MainWindowState:
+ # Support recreating our window for back/refresh purposes.
+ cls = type(self)
+ return bui.BasicMainWindowState(
+ create_call=lambda transition, origin_widget: cls(
+ transition=transition, origin_widget=origin_widget
+ )
+ )
+
+ @override
+ def on_main_window_close(self) -> None:
+ self._save_state()
+
@staticmethod
def _preload_modules() -> None:
- """Preload modules we use; avoids hitches (called in bg thread)."""
+ """Preload stuff in bg thread to avoid hitches in logic thread"""
from babase import modutils as _unused2
from bauiv1lib import config as _unused1
from bauiv1lib.settings import vrtesting as _unused3
@@ -191,7 +201,7 @@ class AdvancedSettingsWindow(bui.Window):
from bauiv1lib import appinvite as _unused5
from bauiv1lib import account as _unused6
from bauiv1lib import sendinfo as _unused7
- from bauiv1lib import debug as _unused8
+ from bauiv1lib import benchmarks as _unused8
from bauiv1lib.settings import plugins as _unused9
from bauiv1lib.settings import devtools as _unused10
@@ -684,14 +694,13 @@ class AdvancedSettingsWindow(bui.Window):
for child in self._subcontainer.get_children():
bui.widget(edit=child, show_buffer_bottom=30, show_buffer_top=20)
- if bui.app.ui_v1.use_toolbars:
- pbtn = bui.get_special_widget('party_button')
- bui.widget(edit=self._scrollwidget, right_widget=pbtn)
- if self._back_button is None:
- bui.widget(
- edit=self._scrollwidget,
- left_widget=bui.get_special_widget('back_button'),
- )
+ pbtn = bui.get_special_widget('squad_button')
+ bui.widget(edit=self._scrollwidget, right_widget=pbtn)
+ if self._back_button is None:
+ bui.widget(
+ edit=self._scrollwidget,
+ left_widget=bui.get_special_widget('back_button'),
+ )
self._restore_state()
@@ -719,9 +728,8 @@ class AdvancedSettingsWindow(bui.Window):
self._save_state()
bui.containerwidget(edit=self._root_widget, transition='out_left')
assert bui.app.classic is not None
- bui.app.ui_v1.set_main_menu_window(
- VRTestingWindow(transition='in_right').get_root_widget(),
- from_window=self._root_widget,
+ bui.app.ui_v1.set_main_window(
+ VRTestingWindow(transition='in_right'), from_window=self
)
def _on_net_test_press(self) -> None:
@@ -736,9 +744,8 @@ class AdvancedSettingsWindow(bui.Window):
self._save_state()
bui.containerwidget(edit=self._root_widget, transition='out_left')
assert bui.app.classic is not None
- bui.app.ui_v1.set_main_menu_window(
- NetTestingWindow(transition='in_right').get_root_widget(),
- from_window=self._root_widget,
+ bui.app.ui_v1.set_main_window(
+ NetTestingWindow(transition='in_right'), from_window=self
)
def _on_friend_promo_code_press(self) -> None:
@@ -763,9 +770,8 @@ class AdvancedSettingsWindow(bui.Window):
self._save_state()
bui.containerwidget(edit=self._root_widget, transition='out_left')
assert bui.app.classic is not None
- bui.app.ui_v1.set_main_menu_window(
- PluginWindow(origin_widget=self._plugins_button).get_root_widget(),
- from_window=self._root_widget,
+ bui.app.ui_v1.set_main_window(
+ PluginWindow(origin_widget=self._plugins_button), from_window=self
)
def _on_dev_tools_button_press(self) -> None:
@@ -779,11 +785,9 @@ class AdvancedSettingsWindow(bui.Window):
self._save_state()
bui.containerwidget(edit=self._root_widget, transition='out_left')
assert bui.app.classic is not None
- bui.app.ui_v1.set_main_menu_window(
- DevToolsWindow(
- origin_widget=self._dev_tools_button
- ).get_root_widget(),
- from_window=self._root_widget,
+ bui.app.ui_v1.set_main_window(
+ DevToolsWindow(origin_widget=self._dev_tools_button),
+ from_window=self,
)
def _on_send_info_press(self) -> None:
@@ -799,15 +803,13 @@ class AdvancedSettingsWindow(bui.Window):
self._save_state()
bui.containerwidget(edit=self._root_widget, transition='out_left')
assert bui.app.classic is not None
- bui.app.ui_v1.set_main_menu_window(
- SendInfoWindow(
- origin_widget=self._send_info_button
- ).get_root_widget(),
- from_window=self._root_widget,
+ bui.app.ui_v1.set_main_window(
+ SendInfoWindow(origin_widget=self._send_info_button),
+ from_window=self,
)
def _on_benchmark_press(self) -> None:
- from bauiv1lib.debug import DebugWindow
+ from bauiv1lib.benchmarks import BenchmarksAndStressTestsWindow
# no-op if our underlying widget is dead or on its way out.
if not self._root_widget or self._root_widget.transitioning_out:
@@ -816,9 +818,9 @@ class AdvancedSettingsWindow(bui.Window):
self._save_state()
bui.containerwidget(edit=self._root_widget, transition='out_left')
assert bui.app.classic is not None
- bui.app.ui_v1.set_main_menu_window(
- DebugWindow(transition='in_right').get_root_widget(),
- from_window=self._root_widget,
+ bui.app.ui_v1.set_main_window(
+ BenchmarksAndStressTestsWindow(transition='in_right'),
+ from_window=self,
)
def _save_state(self) -> None:
@@ -974,19 +976,20 @@ class AdvancedSettingsWindow(bui.Window):
self._complete_langs_error = True
bui.apptimer(0.001, bui.WeakCall(self._update_lang_status))
- def _do_back(self) -> None:
- from bauiv1lib.settings.allsettings import AllSettingsWindow
+ # def _do_back(self) -> None:
+ # 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
+ # # 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()
- bui.containerwidget(
- edit=self._root_widget, transition=self._transition_out
- )
- assert bui.app.classic is not None
- bui.app.ui_v1.set_main_menu_window(
- AllSettingsWindow(transition='in_left').get_root_widget(),
- from_window=self._root_widget,
- )
+ # self._save_state()
+ # bui.containerwidget(
+ # edit=self._root_widget, transition=self._transition_out
+ # )
+ # assert bui.app.classic is not None
+ # bui.app.ui_v1.set_main_window(
+ # AllSettingsWindow(transition='in_left'),
+ # from_window=self,
+ # is_back=True,
+ # )
diff --git a/src/assets/ba_data/python/bauiv1lib/settings/allsettings.py b/src/assets/ba_data/python/bauiv1lib/settings/allsettings.py
index 746000a9..11df974d 100644
--- a/src/assets/ba_data/python/bauiv1lib/settings/allsettings.py
+++ b/src/assets/ba_data/python/bauiv1lib/settings/allsettings.py
@@ -4,8 +4,7 @@
from __future__ import annotations
-from typing import TYPE_CHECKING
-from threading import Thread
+from typing import TYPE_CHECKING, override
import logging
import bauiv1 as bui
@@ -14,12 +13,12 @@ if TYPE_CHECKING:
pass
-class AllSettingsWindow(bui.Window):
+class AllSettingsWindow(bui.MainWindow):
"""Window for selecting a settings category."""
def __init__(
self,
- transition: str = 'in_right',
+ transition: str | None = 'in_right',
origin_widget: bui.Widget | None = None,
):
# pylint: disable=too-many-statements
@@ -27,17 +26,9 @@ class AllSettingsWindow(bui.Window):
# Preload some modules we use in a background thread so we won't
# have a visual hitch when the user taps them.
- Thread(target=self._preload_modules).start()
+ bui.app.threadpool_submit_no_wait(self._preload_modules)
bui.set_analytics_screen('Settings Window')
- scale_origin: tuple[float, float] | None
- if origin_widget is not None:
- self._transition_out = 'out_scale'
- scale_origin = origin_widget.get_screen_space_center()
- transition = 'in_scale'
- else:
- self._transition_out = 'out_right'
- scale_origin = None
assert bui.app.classic is not None
uiscale = bui.app.ui_v1.uiscale
width = 1000 if uiscale is bui.UIScale.SMALL else 580
@@ -50,24 +41,28 @@ class AllSettingsWindow(bui.Window):
super().__init__(
root_widget=bui.containerwidget(
size=(width, height + top_extra),
- transition=transition,
- toolbar_visibility='menu_minimal',
- scale_origin_stack_offset=scale_origin,
- scale=(
- 1.75
+ toolbar_visibility=(
+ 'menu_minimal'
if uiscale is bui.UIScale.SMALL
- else 1.35 if uiscale is bui.UIScale.MEDIUM else 1.0
+ else 'menu_full'
+ ),
+ scale=(
+ 1.5
+ if uiscale is bui.UIScale.SMALL
+ else 1.25 if uiscale is bui.UIScale.MEDIUM else 1.0
),
stack_offset=(
- (0, -8) if uiscale is bui.UIScale.SMALL else (0, 0)
+ (0, 0) if uiscale is bui.UIScale.SMALL else (0, 0)
),
- )
+ ),
+ transition=transition,
+ origin_widget=origin_widget,
)
- if bui.app.ui_v1.use_toolbars and uiscale is bui.UIScale.SMALL:
+ if uiscale is bui.UIScale.SMALL:
self._back_button = None
bui.containerwidget(
- edit=self._root_widget, on_cancel_call=self._do_back
+ edit=self._root_widget, on_cancel_call=self.main_window_back
)
else:
self._back_button = btn = bui.buttonwidget(
@@ -79,7 +74,7 @@ class AllSettingsWindow(bui.Window):
text_scale=1.2,
label=bui.Lstr(resource='backText'),
button_type='back',
- on_activate_call=self._do_back,
+ on_activate_call=self.main_window_back,
)
bui.containerwidget(edit=self._root_widget, cancel_button=btn)
@@ -139,7 +134,7 @@ class AllSettingsWindow(bui.Window):
label='',
on_activate_call=self._do_controllers,
)
- if bui.app.ui_v1.use_toolbars and self._back_button is None:
+ if self._back_button is None:
bbtn = bui.get_special_widget('back_button')
bui.widget(edit=ctb, left_widget=bbtn)
_b_title(
@@ -163,9 +158,8 @@ class AllSettingsWindow(bui.Window):
label='',
on_activate_call=self._do_graphics,
)
- if bui.app.ui_v1.use_toolbars:
- pbtn = bui.get_special_widget('party_button')
- bui.widget(edit=gfxb, up_widget=pbtn, right_widget=pbtn)
+ pbtn = bui.get_special_widget('squad_button')
+ bui.widget(edit=gfxb, up_widget=pbtn, right_widget=pbtn)
_b_title(x_offs3, v, gfxb, bui.Lstr(resource=f'{self._r}.graphicsText'))
imgw = imgh = 110
bui.imagewidget(
@@ -219,7 +213,20 @@ class AllSettingsWindow(bui.Window):
)
self._restore_state()
- # noinspection PyUnresolvedReferences
+ @override
+ def get_main_window_state(self) -> bui.MainWindowState:
+ # Support recreating our window for back/refresh purposes.
+ cls = type(self)
+ return bui.BasicMainWindowState(
+ create_call=lambda transition, origin_widget: cls(
+ transition=transition, origin_widget=origin_widget
+ )
+ )
+
+ @override
+ def on_main_window_close(self) -> None:
+ self._save_state()
+
@staticmethod
def _preload_modules() -> None:
"""Preload modules we use; avoids hitches (called in bg thread)."""
@@ -229,24 +236,6 @@ class AllSettingsWindow(bui.Window):
import bauiv1lib.settings.audio as _unused4
import bauiv1lib.settings.advanced as _unused5
- def _do_back(self) -> None:
- # pylint: disable=cyclic-import
- 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()
- bui.containerwidget(
- edit=self._root_widget, transition=self._transition_out
- )
- assert bui.app.classic is not None
- bui.app.ui_v1.set_main_menu_window(
- MainMenuWindow(transition='in_left').get_root_widget(),
- from_window=self._root_widget,
- )
-
def _do_controllers(self) -> None:
# pylint: disable=cyclic-import
from bauiv1lib.settings.controls import ControlsSettingsWindow
@@ -258,11 +247,9 @@ class AllSettingsWindow(bui.Window):
self._save_state()
bui.containerwidget(edit=self._root_widget, transition='out_left')
assert bui.app.classic is not None
- bui.app.ui_v1.set_main_menu_window(
- ControlsSettingsWindow(
- origin_widget=self._controllers_button
- ).get_root_widget(),
- from_window=self._root_widget,
+ bui.app.ui_v1.set_main_window(
+ ControlsSettingsWindow(origin_widget=self._controllers_button),
+ from_window=self,
)
def _do_graphics(self) -> None:
@@ -276,11 +263,9 @@ class AllSettingsWindow(bui.Window):
self._save_state()
bui.containerwidget(edit=self._root_widget, transition='out_left')
assert bui.app.classic is not None
- bui.app.ui_v1.set_main_menu_window(
- GraphicsSettingsWindow(
- origin_widget=self._graphics_button
- ).get_root_widget(),
- from_window=self._root_widget,
+ bui.app.ui_v1.set_main_window(
+ GraphicsSettingsWindow(origin_widget=self._graphics_button),
+ from_window=self,
)
def _do_audio(self) -> None:
@@ -294,11 +279,9 @@ class AllSettingsWindow(bui.Window):
self._save_state()
bui.containerwidget(edit=self._root_widget, transition='out_left')
assert bui.app.classic is not None
- bui.app.ui_v1.set_main_menu_window(
- AudioSettingsWindow(
- origin_widget=self._audio_button
- ).get_root_widget(),
- from_window=self._root_widget,
+ bui.app.ui_v1.set_main_window(
+ AudioSettingsWindow(origin_widget=self._audio_button),
+ from_window=self,
)
def _do_advanced(self) -> None:
@@ -312,11 +295,9 @@ class AllSettingsWindow(bui.Window):
self._save_state()
bui.containerwidget(edit=self._root_widget, transition='out_left')
assert bui.app.classic is not None
- bui.app.ui_v1.set_main_menu_window(
- AdvancedSettingsWindow(
- origin_widget=self._advanced_button
- ).get_root_widget(),
- from_window=self._root_widget,
+ bui.app.ui_v1.set_main_window(
+ AdvancedSettingsWindow(origin_widget=self._advanced_button),
+ from_window=self,
)
def _save_state(self) -> None:
diff --git a/src/assets/ba_data/python/bauiv1lib/settings/audio.py b/src/assets/ba_data/python/bauiv1lib/settings/audio.py
index d04cdf68..71d242a9 100644
--- a/src/assets/ba_data/python/bauiv1lib/settings/audio.py
+++ b/src/assets/ba_data/python/bauiv1lib/settings/audio.py
@@ -4,7 +4,7 @@
from __future__ import annotations
-from typing import TYPE_CHECKING
+from typing import TYPE_CHECKING, override
import logging
import bauiv1 as bui
@@ -13,12 +13,12 @@ if TYPE_CHECKING:
pass
-class AudioSettingsWindow(bui.Window):
+class AudioSettingsWindow(bui.MainWindow):
"""Window for editing audio settings."""
def __init__(
self,
- transition: str = 'in_right',
+ transition: str | None = 'in_right',
origin_widget: bui.Widget | None = None,
):
# pylint: disable=too-many-statements
@@ -30,16 +30,6 @@ class AudioSettingsWindow(bui.Window):
assert bui.app.classic is not None
music = bui.app.classic.music
- # If they provided an origin-widget, scale up from that.
- scale_origin: tuple[float, float] | None
- if origin_widget is not None:
- self._transition_out = 'out_scale'
- scale_origin = origin_widget.get_screen_space_center()
- transition = 'in_scale'
- else:
- self._transition_out = 'out_right'
- scale_origin = None
-
self._r = 'audioSettingsWindow'
spacing = 50.0
@@ -70,13 +60,18 @@ class AudioSettingsWindow(bui.Window):
super().__init__(
root_widget=bui.containerwidget(
size=(width, height),
- transition=transition,
scale=base_scale,
- scale_origin_stack_offset=scale_origin,
stack_offset=(
(0, -20) if uiscale is bui.UIScale.SMALL else (0, 0)
),
- )
+ toolbar_visibility=(
+ 'menu_minimal'
+ if uiscale is bui.UIScale.SMALL
+ else 'menu_full'
+ ),
+ ),
+ transition=transition,
+ origin_widget=origin_widget,
)
self._back_button = back_button = btn = bui.buttonwidget(
@@ -87,7 +82,7 @@ class AudioSettingsWindow(bui.Window):
text_scale=1.2,
label=bui.Lstr(resource='backText'),
button_type='back',
- on_activate_call=self._back,
+ on_activate_call=self.main_window_back,
autoselect=True,
)
bui.containerwidget(edit=self._root_widget, cancel_button=btn)
@@ -122,11 +117,10 @@ class AudioSettingsWindow(bui.Window):
increment=0.05,
as_percent=True,
)
- if bui.app.ui_v1.use_toolbars:
- bui.widget(
- edit=svne.plusbutton,
- right_widget=bui.get_special_widget('party_button'),
- )
+ bui.widget(
+ edit=svne.plusbutton,
+ right_widget=bui.get_special_widget('squad_button'),
+ )
v -= spacing
self._music_volume_numedit = ConfigNumberEdit(
parent=self._root_widget,
@@ -226,6 +220,20 @@ class AudioSettingsWindow(bui.Window):
self._restore_state()
+ @override
+ def get_main_window_state(self) -> bui.MainWindowState:
+ # Support recreating our window for back/refresh purposes.
+ cls = type(self)
+ return bui.BasicMainWindowState(
+ create_call=lambda transition, origin_widget: cls(
+ transition=transition, origin_widget=origin_widget
+ )
+ )
+
+ @override
+ def on_main_window_close(self) -> None:
+ self._save_state()
+
def _set_vr_head_relative_audio(self, val: str) -> None:
cfg = bui.app.config
cfg['VR Head Relative Audio'] = val
@@ -255,31 +263,9 @@ class AudioSettingsWindow(bui.Window):
self._save_state()
bui.containerwidget(edit=self._root_widget, transition='out_left')
assert bui.app.classic is not None
- bui.app.ui_v1.set_main_menu_window(
- stb.SoundtrackBrowserWindow(
- origin_widget=self._soundtrack_button
- ).get_root_widget(),
- from_window=self._root_widget,
- )
-
- def _back(self) -> None:
- # pylint: disable=cyclic-import
- 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()
- bui.containerwidget(
- edit=self._root_widget, transition=self._transition_out
- )
- assert bui.app.classic is not None
- bui.app.ui_v1.set_main_menu_window(
- allsettings.AllSettingsWindow(
- transition='in_left'
- ).get_root_widget(),
- from_window=self._root_widget,
+ bui.app.ui_v1.set_main_window(
+ stb.SoundtrackBrowserWindow(origin_widget=self._soundtrack_button),
+ from_window=self,
)
def _save_state(self) -> None:
diff --git a/src/assets/ba_data/python/bauiv1lib/settings/controls.py b/src/assets/ba_data/python/bauiv1lib/settings/controls.py
index 6a85dfdf..85620b48 100644
--- a/src/assets/ba_data/python/bauiv1lib/settings/controls.py
+++ b/src/assets/ba_data/python/bauiv1lib/settings/controls.py
@@ -4,17 +4,19 @@
from __future__ import annotations
+from typing import override
+
from bauiv1lib.popup import PopupMenu
import bascenev1 as bs
import bauiv1 as bui
-class ControlsSettingsWindow(bui.Window):
+class ControlsSettingsWindow(bui.MainWindow):
"""Top level control settings window."""
def __init__(
self,
- transition: str = 'in_right',
+ transition: str | None = 'in_right',
origin_widget: bui.Widget | None = None,
):
# FIXME: should tidy up here.
@@ -25,30 +27,22 @@ class ControlsSettingsWindow(bui.Window):
self._have_selected_child = False
- scale_origin: tuple[float, float] | None
-
- # If they provided an origin-widget, scale up from that.
- if origin_widget is not None:
- self._transition_out = 'out_scale'
- scale_origin = origin_widget.get_screen_space_center()
- transition = 'in_scale'
- else:
- self._transition_out = 'out_right'
- scale_origin = None
-
self._r = 'configControllersWindow'
+ uiscale = bui.app.ui_v1.uiscale
app = bui.app
assert app.classic is not None
spacing = 50.0
button_width = 350.0
- width = 460.0
- height = 130.0
+ width = 800.0 if uiscale is bui.UIScale.SMALL else 460.0
+ height = 300 if uiscale is bui.UIScale.SMALL else 130.0
+ yoffs = -60 if uiscale is bui.UIScale.SMALL else 0
space_height = spacing * 0.3
- # FIXME: should create vis settings under platform or app-adapter
- # to determine whether to show this stuff; not hard code it.
+ # FIXME: should create vis settings under platform or
+ # app-adapter to determine whether to show this stuff; not hard
+ # code it.
show_gamepads = False
platform = app.classic.platform
@@ -111,13 +105,10 @@ class ControlsSettingsWindow(bui.Window):
height += spacing
assert bui.app.classic is not None
- uiscale = bui.app.ui_v1.uiscale
smallscale = 1.7 if show_keyboard else 2.2
super().__init__(
root_widget=bui.containerwidget(
size=(width, height),
- transition=transition,
- scale_origin_stack_offset=scale_origin,
stack_offset=(
(0, -10) if uiscale is bui.UIScale.SMALL else (0, 0)
),
@@ -126,20 +117,41 @@ class ControlsSettingsWindow(bui.Window):
if uiscale is bui.UIScale.SMALL
else 1.5 if uiscale is bui.UIScale.MEDIUM else 1.0
),
+ toolbar_visibility=(
+ 'menu_minimal'
+ if uiscale is bui.UIScale.SMALL
+ else 'menu_full'
+ ),
+ ),
+ transition=transition,
+ origin_widget=origin_widget,
+ )
+
+ self._back_button: bui.Widget | None
+ if uiscale is bui.UIScale.SMALL:
+ bui.containerwidget(
+ edit=self._root_widget, on_cancel_call=self.main_window_back
+ )
+ self._back_button = None
+ else:
+ self._back_button = btn = bui.buttonwidget(
+ parent=self._root_widget,
+ position=(35, height - 60),
+ size=(140, 65),
+ scale=0.8,
+ text_scale=1.2,
+ autoselect=True,
+ label=bui.Lstr(resource='backText'),
+ button_type='back',
+ on_activate_call=self.main_window_back,
+ )
+ bui.containerwidget(edit=self._root_widget, cancel_button=btn)
+ bui.buttonwidget(
+ edit=btn,
+ button_type='backSmall',
+ size=(60, 60),
+ label=bui.charstr(bui.SpecialChar.BACK),
)
- )
- self._back_button = btn = bui.buttonwidget(
- parent=self._root_widget,
- position=(35, height - 60),
- size=(140, 65),
- scale=0.8,
- text_scale=1.2,
- autoselect=True,
- label=bui.Lstr(resource='backText'),
- button_type='back',
- on_activate_call=self._back,
- )
- bui.containerwidget(edit=self._root_widget, cancel_button=btn)
# We need these vars to exist even if the buttons don't.
self._gamepads_button: bui.Widget | None = None
@@ -150,21 +162,15 @@ class ControlsSettingsWindow(bui.Window):
bui.textwidget(
parent=self._root_widget,
- position=(0, height - 49),
+ position=(0, height - 49 + yoffs),
size=(width, 25),
text=bui.Lstr(resource=f'{self._r}.titleText'),
color=bui.app.ui_v1.title_color,
h_align='center',
v_align='top',
)
- bui.buttonwidget(
- edit=btn,
- button_type='backSmall',
- size=(60, 60),
- label=bui.charstr(bui.SpecialChar.BACK),
- )
- v = height - 75
+ v = height - 75 + yoffs
v -= spacing
if show_touch:
@@ -176,18 +182,18 @@ class ControlsSettingsWindow(bui.Window):
label=bui.Lstr(resource=f'{self._r}.configureTouchText'),
on_activate_call=self._do_touchscreen,
)
- if bui.app.ui_v1.use_toolbars:
- bui.widget(
- edit=btn,
- right_widget=bui.get_special_widget('party_button'),
- )
+ bui.widget(
+ edit=btn,
+ right_widget=bui.get_special_widget('squad_button'),
+ )
if not self._have_selected_child:
bui.containerwidget(
edit=self._root_widget, selected_child=self._touch_button
)
- bui.widget(
- edit=self._back_button, down_widget=self._touch_button
- )
+ if self._back_button is not None:
+ bui.widget(
+ edit=self._back_button, down_widget=self._touch_button
+ )
self._have_selected_child = True
v -= spacing
@@ -200,18 +206,19 @@ class ControlsSettingsWindow(bui.Window):
label=bui.Lstr(resource=f'{self._r}.configureControllersText'),
on_activate_call=self._do_gamepads,
)
- if bui.app.ui_v1.use_toolbars:
- bui.widget(
- edit=btn,
- right_widget=bui.get_special_widget('party_button'),
- )
+ bui.widget(
+ edit=btn,
+ right_widget=bui.get_special_widget('squad_button'),
+ )
if not self._have_selected_child:
bui.containerwidget(
edit=self._root_widget, selected_child=self._gamepads_button
)
- bui.widget(
- edit=self._back_button, down_widget=self._gamepads_button
- )
+ if self._back_button is not None:
+ bui.widget(
+ edit=self._back_button,
+ down_widget=self._gamepads_button,
+ )
self._have_selected_child = True
v -= spacing
else:
@@ -232,18 +239,19 @@ class ControlsSettingsWindow(bui.Window):
bui.widget(
edit=self._keyboard_button, left_widget=self._keyboard_button
)
- if bui.app.ui_v1.use_toolbars:
- bui.widget(
- edit=btn,
- right_widget=bui.get_special_widget('party_button'),
- )
+ bui.widget(
+ edit=btn,
+ right_widget=bui.get_special_widget('squad_button'),
+ )
if not self._have_selected_child:
bui.containerwidget(
edit=self._root_widget, selected_child=self._keyboard_button
)
- bui.widget(
- edit=self._back_button, down_widget=self._keyboard_button
- )
+ if self._back_button is not None:
+ bui.widget(
+ edit=self._back_button,
+ down_widget=self._keyboard_button,
+ )
self._have_selected_child = True
v -= spacing
if show_keyboard_p2:
@@ -274,18 +282,19 @@ class ControlsSettingsWindow(bui.Window):
bui.widget(
edit=self._idevices_button, left_widget=self._idevices_button
)
- if bui.app.ui_v1.use_toolbars:
- bui.widget(
- edit=btn,
- right_widget=bui.get_special_widget('party_button'),
- )
+ bui.widget(
+ edit=btn,
+ right_widget=bui.get_special_widget('squad_button'),
+ )
if not self._have_selected_child:
bui.containerwidget(
edit=self._root_widget, selected_child=self._idevices_button
)
- bui.widget(
- edit=self._back_button, down_widget=self._idevices_button
- )
+ if self._back_button is not None:
+ bui.widget(
+ edit=self._back_button,
+ down_widget=self._idevices_button,
+ )
self._have_selected_child = True
v -= spacing
@@ -371,6 +380,20 @@ class ControlsSettingsWindow(bui.Window):
self._restore_state()
+ @override
+ def get_main_window_state(self) -> bui.MainWindowState:
+ # Support recreating our window for back/refresh purposes.
+ cls = type(self)
+ return bui.BasicMainWindowState(
+ create_call=lambda transition, origin_widget: cls(
+ transition=transition, origin_widget=origin_widget
+ )
+ )
+
+ @override
+ def on_main_window_close(self) -> None:
+ self._save_state()
+
def _set_mac_controller_subsystem(self, val: str) -> None:
cfg = bui.app.config
cfg['Mac Controller Subsystem'] = val
@@ -387,11 +410,9 @@ class ControlsSettingsWindow(bui.Window):
self._save_state()
bui.containerwidget(edit=self._root_widget, transition='out_left')
assert bui.app.classic is not None
- bui.app.ui_v1.set_main_menu_window(
- ConfigKeyboardWindow(
- bs.getinputdevice('Keyboard', '#1')
- ).get_root_widget(),
- from_window=self._root_widget,
+ bui.app.ui_v1.set_main_window(
+ ConfigKeyboardWindow(bs.getinputdevice('Keyboard', '#1')),
+ from_window=self,
)
def _config_keyboard2(self) -> None:
@@ -405,11 +426,9 @@ class ControlsSettingsWindow(bui.Window):
self._save_state()
bui.containerwidget(edit=self._root_widget, transition='out_left')
assert bui.app.classic is not None
- bui.app.ui_v1.set_main_menu_window(
- ConfigKeyboardWindow(
- bs.getinputdevice('Keyboard', '#2')
- ).get_root_widget(),
- from_window=self._root_widget,
+ bui.app.ui_v1.set_main_window(
+ ConfigKeyboardWindow(bs.getinputdevice('Keyboard', '#2')),
+ from_window=self,
)
def _do_mobile_devices(self) -> None:
@@ -423,9 +442,8 @@ class ControlsSettingsWindow(bui.Window):
self._save_state()
bui.containerwidget(edit=self._root_widget, transition='out_left')
assert bui.app.classic is not None
- bui.app.ui_v1.set_main_menu_window(
- RemoteAppSettingsWindow().get_root_widget(),
- from_window=self._root_widget,
+ bui.app.ui_v1.set_main_window(
+ RemoteAppSettingsWindow(), from_window=self
)
def _do_gamepads(self) -> None:
@@ -439,10 +457,7 @@ class ControlsSettingsWindow(bui.Window):
self._save_state()
bui.containerwidget(edit=self._root_widget, transition='out_left')
assert bui.app.classic is not None
- bui.app.ui_v1.set_main_menu_window(
- GamepadSelectWindow().get_root_widget(),
- from_window=self._root_widget,
- )
+ bui.app.ui_v1.set_main_window(GamepadSelectWindow(), from_window=self)
def _do_touchscreen(self) -> None:
# pylint: disable=cyclic-import
@@ -455,9 +470,8 @@ class ControlsSettingsWindow(bui.Window):
self._save_state()
bui.containerwidget(edit=self._root_widget, transition='out_left')
assert bui.app.classic is not None
- bui.app.ui_v1.set_main_menu_window(
- TouchscreenSettingsWindow().get_root_widget(),
- from_window=self._root_widget,
+ bui.app.ui_v1.set_main_window(
+ TouchscreenSettingsWindow(), from_window=self
)
def _save_state(self) -> None:
@@ -500,20 +514,21 @@ class ControlsSettingsWindow(bui.Window):
)
bui.containerwidget(edit=self._root_widget, selected_child=sel)
- def _back(self) -> None:
- # pylint: disable=cyclic-import
- from bauiv1lib.settings.allsettings import AllSettingsWindow
+ # def _back(self) -> None:
+ # # pylint: disable=cyclic-import
+ # 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
+ # # 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()
- bui.containerwidget(
- edit=self._root_widget, transition=self._transition_out
- )
- assert bui.app.classic is not None
- bui.app.ui_v1.set_main_menu_window(
- AllSettingsWindow(transition='in_left').get_root_widget(),
- from_window=self._root_widget,
- )
+ # self._save_state()
+ # bui.containerwidget(
+ # edit=self._root_widget, transition=self._transition_out
+ # )
+ # assert bui.app.classic is not None
+ # bui.app.ui_v1.set_main_window(
+ # AllSettingsWindow(transition='in_left'),
+ # from_window=self,
+ # is_back=True,
+ # )
diff --git a/src/assets/ba_data/python/bauiv1lib/settings/devtools.py b/src/assets/ba_data/python/bauiv1lib/settings/devtools.py
index 2db6e9c2..4c97422a 100644
--- a/src/assets/ba_data/python/bauiv1lib/settings/devtools.py
+++ b/src/assets/ba_data/python/bauiv1lib/settings/devtools.py
@@ -4,6 +4,8 @@
from __future__ import annotations
+from typing import override
+
import babase
import bauiv1 as bui
from bauiv1lib.popup import PopupMenu
@@ -11,33 +13,23 @@ from bauiv1lib.confirm import ConfirmWindow
from bauiv1lib.config import ConfigCheckBox
-class DevToolsWindow(bui.Window):
+class DevToolsWindow(bui.MainWindow):
"""Window for accessing modding tools."""
def __init__(
self,
- transition: str = 'in_right',
+ transition: str | None = 'in_right',
origin_widget: bui.Widget | None = None,
):
app = bui.app
assert app.classic is not None
- # If they provided an origin-widget, scale up from that.
- scale_origin: tuple[float, float] | None
- if origin_widget is not None:
- self._transition_out = 'out_scale'
- scale_origin = origin_widget.get_screen_space_center()
- transition = 'in_scale'
- else:
- self._transition_out = 'out_right'
- scale_origin = None
-
uiscale = app.ui_v1.uiscale
- self._width = 970.0 if uiscale is bui.UIScale.SMALL else 670.0
+ self._width = 1000.0 if uiscale is bui.UIScale.SMALL else 670.0
x_inset = 150 if uiscale is bui.UIScale.SMALL else 0
self._height = (
- 390.0
+ 370.0
if uiscale is bui.UIScale.SMALL
else 450.0 if uiscale is bui.UIScale.MEDIUM else 520.0
)
@@ -53,25 +45,29 @@ class DevToolsWindow(bui.Window):
super().__init__(
root_widget=bui.containerwidget(
size=(self._width, self._height + top_extra),
- transition=transition,
- toolbar_visibility='menu_minimal',
- scale_origin_stack_offset=scale_origin,
+ toolbar_visibility=(
+ 'menu_minimal'
+ if uiscale is bui.UIScale.SMALL
+ else 'menu_full'
+ ),
scale=(
- 2.06
+ 2.13
if uiscale is bui.UIScale.SMALL
else 1.4 if uiscale is bui.UIScale.MEDIUM else 1.0
),
stack_offset=(
- (0, -25) if uiscale is bui.UIScale.SMALL else (0, 0)
+ (0, 0) if uiscale is bui.UIScale.SMALL else (0, 0)
),
- )
+ ),
+ transition=transition,
+ origin_widget=origin_widget,
)
self._r = 'settingsDevTools'
- if app.ui_v1.use_toolbars and uiscale is bui.UIScale.SMALL:
+ if uiscale is bui.UIScale.SMALL:
bui.containerwidget(
- edit=self._root_widget, on_cancel_call=self._do_back
+ edit=self._root_widget, on_cancel_call=self.main_window_back
)
self._back_button = None
else:
@@ -83,7 +79,7 @@ class DevToolsWindow(bui.Window):
autoselect=True,
label=bui.Lstr(resource='backText'),
button_type='back',
- on_activate_call=self._do_back,
+ on_activate_call=self.main_window_back,
)
bui.containerwidget(
edit=self._root_widget, cancel_button=self._back_button
@@ -91,8 +87,12 @@ class DevToolsWindow(bui.Window):
self._title_text = bui.textwidget(
parent=self._root_widget,
- position=(self._width * 0.5, self._height - 48),
+ position=(
+ self._width * 0.5,
+ self._height - (64 if uiscale is bui.UIScale.SMALL else 48),
+ ),
size=(0, 25),
+ scale=(0.6 if uiscale is bui.UIScale.SMALL else 1.0),
maxwidth=self._width - 200,
text=bui.Lstr(resource='settingsWindowAdvanced.devToolsText'),
color=app.ui_v1.title_color,
@@ -201,6 +201,16 @@ class DevToolsWindow(bui.Window):
on_value_change_call=self._set_uiscale,
)
+ @override
+ def get_main_window_state(self) -> bui.MainWindowState:
+ # Support recreating our window for back/refresh purposes.
+ cls = type(self)
+ return bui.BasicMainWindowState(
+ create_call=lambda transition, origin_widget: cls(
+ transition=transition, origin_widget=origin_widget
+ )
+ )
+
def _set_uiscale(self, val: str) -> None:
cfg = bui.app.config
cfg['UI Scale'] = val
@@ -210,19 +220,3 @@ class DevToolsWindow(bui.Window):
bui.Lstr(resource='settingsWindowAdvanced.mustRestartText'),
color=(1.0, 0.5, 0.0),
)
-
- def _do_back(self) -> None:
- 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=self._transition_out
- )
- assert bui.app.classic is not None
- bui.app.ui_v1.set_main_menu_window(
- AdvancedSettingsWindow(transition='in_left').get_root_widget(),
- from_window=self._root_widget,
- )
diff --git a/src/assets/ba_data/python/bauiv1lib/settings/gamepad.py b/src/assets/ba_data/python/bauiv1lib/settings/gamepad.py
index 390af52a..1a0cba67 100644
--- a/src/assets/ba_data/python/bauiv1lib/settings/gamepad.py
+++ b/src/assets/ba_data/python/bauiv1lib/settings/gamepad.py
@@ -17,7 +17,7 @@ if TYPE_CHECKING:
from bauiv1lib.popup import PopupWindow
-class GamepadSettingsWindow(bui.Window):
+class GamepadSettingsWindow(bui.MainWindow):
"""Window for configuring a gamepad."""
# pylint: disable=too-many-public-methods
@@ -28,6 +28,7 @@ class GamepadSettingsWindow(bui.Window):
is_main_menu: bool = True,
transition: str = 'in_right',
transition_out: str = 'out_right',
+ origin_widget: bui.Widget | None = None,
settings: dict | None = None,
):
self._input = gamepad
@@ -63,7 +64,9 @@ class GamepadSettingsWindow(bui.Window):
(-20, -16) if uiscale is bui.UIScale.SMALL else (0, 0)
),
transition=transition,
- )
+ ),
+ transition=transition,
+ origin_widget=origin_widget,
)
self._settings: dict[str, int] = {}
@@ -792,7 +795,7 @@ class GamepadSettingsWindow(bui.Window):
return btn
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:
@@ -802,11 +805,13 @@ class GamepadSettingsWindow(bui.Window):
edit=self._root_widget, transition=self._transition_out
)
if self._is_main_menu:
- assert bui.app.classic is not None
- bui.app.ui_v1.set_main_menu_window(
- ControlsSettingsWindow(transition='in_left').get_root_widget(),
- from_window=self._root_widget,
- )
+ self.main_window_back()
+ # assert bui.app.classic is not None
+ # bui.app.ui_v1.set_main_window(
+ # ControlsSettingsWindow(transition='in_left'),
+ # from_window=self,
+ # is_back=True,
+ # )
def _reset(self) -> None:
from bauiv1lib.confirm import ConfirmWindow
@@ -937,9 +942,10 @@ class GamepadSettingsWindow(bui.Window):
from bauiv1lib.settings.controls import ControlsSettingsWindow
assert bui.app.classic is not None
- bui.app.ui_v1.set_main_menu_window(
- ControlsSettingsWindow(transition='in_left').get_root_widget(),
- from_window=self._root_widget,
+ bui.app.ui_v1.set_main_window(
+ ControlsSettingsWindow(transition='in_left'),
+ from_window=self,
+ is_back=True,
)
diff --git a/src/assets/ba_data/python/bauiv1lib/settings/gamepadselect.py b/src/assets/ba_data/python/bauiv1lib/settings/gamepadselect.py
index ab0e7e1c..9040c81d 100644
--- a/src/assets/ba_data/python/bauiv1lib/settings/gamepadselect.py
+++ b/src/assets/ba_data/python/bauiv1lib/settings/gamepadselect.py
@@ -5,7 +5,7 @@
from __future__ import annotations
import logging
-from typing import TYPE_CHECKING
+from typing import TYPE_CHECKING, override
import bascenev1 as bs
import bauiv1 as bui
@@ -16,15 +16,20 @@ if TYPE_CHECKING:
def gamepad_configure_callback(event: dict[str, Any]) -> None:
"""Respond to a gamepad button press during config selection."""
- from bauiv1lib.settings import gamepad
+ from bauiv1lib.settings.gamepad import GamepadSettingsWindow
# Ignore all but button-presses.
if event['type'] not in ['BUTTONDOWN', 'HATMOTION']:
return
bs.release_gamepad_input()
+
+ if bool(True):
+ bui.screenmessage('UNDER CONSTRUCTION')
+ return
+
assert bui.app.classic is not None
try:
- bui.app.ui_v1.clear_main_menu_window(transition='out_left')
+ bui.app.ui_v1.clear_main_window()
except Exception:
logging.exception('Error transitioning out main_menu_window.')
bui.getsound('activateBeep').play()
@@ -32,25 +37,34 @@ def gamepad_configure_callback(event: dict[str, Any]) -> None:
device = event['input_device']
assert isinstance(device, bs.InputDevice)
if device.allows_configuring:
- bui.app.ui_v1.set_main_menu_window(
- gamepad.GamepadSettingsWindow(device).get_root_widget(),
- from_window=None,
+ bui.app.ui_v1.set_main_window(
+ GamepadSettingsWindow(device), from_window=None
)
else:
width = 700
height = 200
button_width = 80
uiscale = bui.app.ui_v1.uiscale
- dlg = bui.containerwidget(
- scale=(
- 1.7
- if uiscale is bui.UIScale.SMALL
- else 1.4 if uiscale is bui.UIScale.MEDIUM else 1.0
- ),
- size=(width, height),
- transition='in_right',
- )
- bui.app.ui_v1.set_main_menu_window(dlg, from_window=None)
+
+ class _Window(bui.MainWindow):
+ def __init__(self) -> None:
+ super().__init__(
+ root_widget=bui.containerwidget(
+ scale=(
+ 1.7
+ if uiscale is bui.UIScale.SMALL
+ else 1.4 if uiscale is bui.UIScale.MEDIUM else 1.0
+ ),
+ size=(width, height),
+ ),
+ transition='in_right',
+ origin_widget=None,
+ )
+
+ win = _Window()
+ dlg = win.get_root_widget()
+
+ bui.app.ui_v1.set_main_window(win, from_window=None)
if device.allows_configuring_in_system_settings:
msg = bui.Lstr(
@@ -86,11 +100,10 @@ def gamepad_configure_callback(event: dict[str, Any]) -> None:
bui.containerwidget(edit=dlg, transition='out_right')
assert bui.app.classic is not None
- bui.app.ui_v1.set_main_menu_window(
- controls.ControlsSettingsWindow(
- transition='in_left'
- ).get_root_widget(),
- from_window=dlg,
+ bui.app.ui_v1.set_main_window(
+ controls.ControlsSettingsWindow(transition='in_left'),
+ from_window=win,
+ is_back=True,
)
bui.buttonwidget(
@@ -102,10 +115,14 @@ def gamepad_configure_callback(event: dict[str, Any]) -> None:
)
-class GamepadSelectWindow(bui.Window):
+class GamepadSelectWindow(bui.MainWindow):
"""Window for selecting a gamepad to configure."""
- def __init__(self) -> None:
+ def __init__(
+ self,
+ transition: str | None = 'in_right',
+ origin_widget: bui.Widget | None = None,
+ ) -> None:
from typing import cast
width = 480
@@ -123,8 +140,9 @@ class GamepadSelectWindow(bui.Window):
else 1.5 if uiscale is bui.UIScale.MEDIUM else 1.0
),
size=(width, height),
- transition='in_right',
- )
+ ),
+ transition=transition,
+ origin_widget=origin_widget,
)
btn = bui.buttonwidget(
@@ -134,8 +152,9 @@ class GamepadSelectWindow(bui.Window):
label=bui.Lstr(resource='backText'),
button_type='back',
scale=0.8,
- on_activate_call=self._back,
+ on_activate_call=self.main_window_back,
)
+
# Let's not have anything selected by default; its misleading looking
# for the controller getting configured.
bui.containerwidget(
@@ -190,19 +209,16 @@ class GamepadSelectWindow(bui.Window):
bs.capture_gamepad_input(gamepad_configure_callback)
- def _back(self) -> None:
- 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()
- bui.containerwidget(edit=self._root_widget, transition='out_right')
- assert bui.app.classic is not None
- bui.app.ui_v1.set_main_menu_window(
- controls.ControlsSettingsWindow(
- transition='in_left'
- ).get_root_widget(),
- from_window=self._root_widget,
+ @override
+ def get_main_window_state(self) -> bui.MainWindowState:
+ # Support recreating our window for back/refresh purposes.
+ cls = type(self)
+ return bui.BasicMainWindowState(
+ create_call=lambda transition, origin_widget: cls(
+ transition=transition, origin_widget=origin_widget
+ )
)
+
+ @override
+ def on_main_window_close(self) -> None:
+ bs.release_gamepad_input()
diff --git a/src/assets/ba_data/python/bauiv1lib/settings/graphics.py b/src/assets/ba_data/python/bauiv1lib/settings/graphics.py
index 650d3cd4..3088476d 100644
--- a/src/assets/ba_data/python/bauiv1lib/settings/graphics.py
+++ b/src/assets/ba_data/python/bauiv1lib/settings/graphics.py
@@ -4,7 +4,7 @@
from __future__ import annotations
-from typing import TYPE_CHECKING, cast
+from typing import TYPE_CHECKING, cast, override
from bauiv1lib.popup import PopupMenu
from bauiv1lib.config import ConfigCheckBox
@@ -14,28 +14,18 @@ if TYPE_CHECKING:
from typing import Any
-class GraphicsSettingsWindow(bui.Window):
+class GraphicsSettingsWindow(bui.MainWindow):
"""Window for graphics settings."""
def __init__(
self,
- transition: str = 'in_right',
+ transition: str | None = 'in_right',
origin_widget: bui.Widget | None = None,
):
# pylint: disable=too-many-locals
# pylint: disable=too-many-branches
# pylint: disable=too-many-statements
- # if they provided an origin-widget, scale up from that
- scale_origin: tuple[float, float] | None
- if origin_widget is not None:
- self._transition_out = 'out_scale'
- scale_origin = origin_widget.get_screen_space_center()
- transition = 'in_scale'
- else:
- self._transition_out = 'out_right'
- scale_origin = None
-
self._r = 'graphicsSettingsWindow'
app = bui.app
assert app.classic is not None
@@ -73,9 +63,9 @@ class GraphicsSettingsWindow(bui.Window):
assert bui.app.classic is not None
uiscale = bui.app.ui_v1.uiscale
base_scale = (
- 2.0
+ 1.5
if uiscale is bui.UIScale.SMALL
- else 1.5 if uiscale is bui.UIScale.MEDIUM else 1.0
+ else 1.3 if uiscale is bui.UIScale.MEDIUM else 1.0
)
popup_menu_scale = base_scale * 1.2
v = height - 50
@@ -83,13 +73,18 @@ class GraphicsSettingsWindow(bui.Window):
super().__init__(
root_widget=bui.containerwidget(
size=(width, height),
- transition=transition,
- scale_origin_stack_offset=scale_origin,
scale=base_scale,
stack_offset=(
- (0, -30) if uiscale is bui.UIScale.SMALL else (0, 0)
+ (0, -10) if uiscale is bui.UIScale.SMALL else (0, 0)
),
- )
+ toolbar_visibility=(
+ 'menu_minimal'
+ if uiscale is bui.UIScale.SMALL
+ else 'menu_full'
+ ),
+ ),
+ transition=transition,
+ origin_widget=origin_widget,
)
back_button = bui.buttonwidget(
@@ -102,7 +97,7 @@ class GraphicsSettingsWindow(bui.Window):
autoselect=True,
label=bui.charstr(bui.SpecialChar.BACK),
button_type='backSmall',
- on_activate_call=self._back,
+ on_activate_call=self.main_window_back,
)
bui.containerwidget(edit=self._root_widget, cancel_button=back_button)
@@ -215,11 +210,10 @@ class GraphicsSettingsWindow(bui.Window):
current_choice=bui.app.config.resolve('Texture Quality'),
on_value_change_call=self._set_textures,
)
- if bui.app.ui_v1.use_toolbars:
- bui.widget(
- edit=textures_popup.get_button(),
- right_widget=bui.get_special_widget('party_button'),
- )
+ bui.widget(
+ edit=textures_popup.get_button(),
+ right_widget=bui.get_special_widget('squad_button'),
+ )
v -= 80
h_offs = 0
@@ -433,28 +427,20 @@ class GraphicsSettingsWindow(bui.Window):
0.25, bui.WeakCall(self._update_controls), repeat=True
)
- def _back(self) -> None:
- from bauiv1lib.settings import allsettings
+ @override
+ def get_main_window_state(self) -> bui.MainWindowState:
+ # Support recreating our window for back/refresh purposes.
+ cls = type(self)
+ return bui.BasicMainWindowState(
+ create_call=lambda transition, origin_widget: cls(
+ transition=transition, origin_widget=origin_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
-
- # Applying max-fps takes a few moments. Apply if it hasn't been
- # yet.
+ @override
+ def on_main_window_close(self) -> None:
self._apply_max_fps()
- bui.containerwidget(
- edit=self._root_widget, transition=self._transition_out
- )
- assert bui.app.classic is not None
- bui.app.ui_v1.set_main_menu_window(
- allsettings.AllSettingsWindow(
- transition='in_left'
- ).get_root_widget(),
- from_window=self._root_widget,
- )
-
def _set_quality(self, quality: str) -> None:
cfg = bui.app.config
cfg['Graphics Quality'] = quality
diff --git a/src/assets/ba_data/python/bauiv1lib/settings/keyboard.py b/src/assets/ba_data/python/bauiv1lib/settings/keyboard.py
index 9bda0fb4..3226f7ae 100644
--- a/src/assets/ba_data/python/bauiv1lib/settings/keyboard.py
+++ b/src/assets/ba_data/python/bauiv1lib/settings/keyboard.py
@@ -4,7 +4,7 @@
from __future__ import annotations
-from typing import TYPE_CHECKING
+from typing import TYPE_CHECKING, override
from bauiv1lib.popup import PopupMenuWindow
import bauiv1 as bui
@@ -12,13 +12,19 @@ import bascenev1 as bs
if TYPE_CHECKING:
from typing import Any
+
from bauiv1lib.popup import PopupWindow
-class ConfigKeyboardWindow(bui.Window):
+class ConfigKeyboardWindow(bui.MainWindow):
"""Window for configuring keyboards."""
- def __init__(self, c: bs.InputDevice, transition: str = 'in_right'):
+ def __init__(
+ self,
+ c: bs.InputDevice,
+ transition: str | None = 'in_right',
+ origin_widget: bui.Widget | None = None,
+ ):
self._r = 'configKeyboardWindow'
self._input = c
self._name = self._input.name
@@ -39,13 +45,15 @@ class ConfigKeyboardWindow(bui.Window):
root_widget=bui.containerwidget(
size=(self._width, self._height),
scale=(
- 1.6
+ 1.4
if uiscale is bui.UIScale.SMALL
else 1.3 if uiscale is bui.UIScale.MEDIUM else 1.0
),
stack_offset=(0, 5) if uiscale is bui.UIScale.SMALL else (0, 0),
transition=transition,
- )
+ ),
+ transition=transition,
+ origin_widget=origin_widget,
)
self._settings: dict[str, int] = {}
@@ -53,6 +61,23 @@ class ConfigKeyboardWindow(bui.Window):
self._rebuild_ui()
+ @override
+ def get_main_window_state(self) -> bui.MainWindowState:
+ # Support recreating our window for back/refresh purposes.
+ cls = type(self)
+
+ # Pull things from self here; if we do it within the lambda
+ # we'll keep self alive which is bad.
+ inputdevice = self._input
+
+ return bui.BasicMainWindowState(
+ create_call=lambda transition, origin_widget: cls(
+ transition=transition,
+ origin_widget=origin_widget,
+ c=inputdevice,
+ )
+ )
+
def _get_config_mapping(self, default: bool = False) -> None:
for button in [
'buttonJump',
@@ -87,7 +112,7 @@ class ConfigKeyboardWindow(bui.Window):
size=(170, 60),
label=bui.Lstr(resource='cancelText'),
scale=0.9,
- on_activate_call=self._cancel,
+ on_activate_call=self.main_window_back,
)
save_button = bui.buttonwidget(
parent=self._root_widget,
@@ -287,20 +312,6 @@ class ConfigKeyboardWindow(bui.Window):
bui.pushcall(doit)
- def _cancel(self) -> None:
- 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')
- assert bui.app.classic is not None
- bui.app.ui_v1.set_main_menu_window(
- ControlsSettingsWindow(transition='in_left').get_root_widget(),
- from_window=self._root_widget,
- )
-
def _reset(self) -> None:
from bauiv1lib.confirm import ConfirmWindow
@@ -366,14 +377,14 @@ class ConfigKeyboardWindow(bui.Window):
"""Called when the popup is closing."""
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
- bui.containerwidget(edit=self._root_widget, transition='out_right')
+ # bui.containerwidget(edit=self._root_widget, transition='out_right')
bui.getsound('gunCocking').play()
# There's a chance the device disappeared; handle that gracefully.
@@ -405,10 +416,13 @@ class ConfigKeyboardWindow(bui.Window):
},
)
bui.app.config.apply_and_commit()
- bui.app.ui_v1.set_main_menu_window(
- ControlsSettingsWindow(transition='in_left').get_root_widget(),
- from_window=self._root_widget,
- )
+
+ self.main_window_back()
+ # bui.app.ui_v1.set_main_window(
+ # ControlsSettingsWindow(transition='in_left'),
+ # from_window=self,
+ # is_back=True,
+ # )
class AwaitKeyboardInputWindow(bui.Window):
diff --git a/src/assets/ba_data/python/bauiv1lib/settings/nettesting.py b/src/assets/ba_data/python/bauiv1lib/settings/nettesting.py
index 2e7f7e8c..9542c07f 100644
--- a/src/assets/ba_data/python/bauiv1lib/settings/nettesting.py
+++ b/src/assets/ba_data/python/bauiv1lib/settings/nettesting.py
@@ -8,7 +8,7 @@ import time
import copy
import weakref
from threading import Thread
-from typing import TYPE_CHECKING
+from typing import TYPE_CHECKING, override
from efro.error import CleanError
from bauiv1lib.settings.testing import TestingWindow
@@ -22,40 +22,53 @@ if TYPE_CHECKING:
MAX_TEST_SECONDS = 60 * 2
-class NetTestingWindow(bui.Window):
+class NetTestingWindow(bui.MainWindow):
"""Window that runs a networking test suite to help diagnose issues."""
- def __init__(self, transition: str = 'in_right'):
+ def __init__(
+ self,
+ transition: str | None = 'in_right',
+ origin_widget: bui.Widget | None = None,
+ ):
+ uiscale = bui.app.ui_v1.uiscale
self._width = 820
- self._height = 500
+ self._height = 400 if uiscale is bui.UIScale.SMALL else 500
self._printed_lines: list[str] = []
assert bui.app.classic is not None
- uiscale = bui.app.ui_v1.uiscale
super().__init__(
root_widget=bui.containerwidget(
size=(self._width, self._height),
scale=(
- 1.56
+ 1.75
if uiscale is bui.UIScale.SMALL
else 1.2 if uiscale is bui.UIScale.MEDIUM else 0.8
),
- stack_offset=(0.0, -7 if uiscale is bui.UIScale.SMALL else 0.0),
- transition=transition,
- )
+ stack_offset=(0, -4 if uiscale is bui.UIScale.SMALL else 0.0),
+ toolbar_visibility=(
+ 'menu_minimal'
+ if uiscale is bui.UIScale.SMALL
+ else 'menu_full'
+ ),
+ ),
+ transition=transition,
+ origin_widget=origin_widget,
)
- self._done_button = bui.buttonwidget(
+ self._done_button: bui.Widget | None = bui.buttonwidget(
parent=self._root_widget,
- position=(40, self._height - 77),
+ position=(46, self._height - 77),
size=(120, 60),
scale=0.8,
autoselect=True,
label=bui.Lstr(resource='doneText'),
- on_activate_call=self._done,
+ on_activate_call=self.main_window_back,
)
+ # Avoid squads button on small mode.
+ xinset = -50 if uiscale is bui.UIScale.SMALL else 0
+
self._copy_button = bui.buttonwidget(
parent=self._root_widget,
- position=(self._width - 200, self._height - 77),
+ position=(self._width - 200 + xinset, self._height - 77),
size=(100, 60),
scale=0.8,
autoselect=True,
@@ -65,7 +78,7 @@ class NetTestingWindow(bui.Window):
self._settings_button = bui.buttonwidget(
parent=self._root_widget,
- position=(self._width - 100, self._height - 77),
+ position=(self._width - 100 + xinset, self._height - 77),
size=(60, 60),
scale=0.8,
autoselect=True,
@@ -73,7 +86,7 @@ class NetTestingWindow(bui.Window):
on_activate_call=self._show_val_testing,
)
- twidth = self._width - 450
+ twidth = self._width - 540
bui.textwidget(
parent=self._root_widget,
position=(self._width * 0.5, self._height - 55),
@@ -94,9 +107,16 @@ class NetTestingWindow(bui.Window):
)
self._rows = bui.columnwidget(parent=self._scroll)
- bui.containerwidget(
- edit=self._root_widget, cancel_button=self._done_button
- )
+ if uiscale is bui.UIScale.SMALL:
+ bui.containerwidget(
+ edit=self._root_widget, on_cancel_call=self.main_window_back
+ )
+ self._done_button.delete()
+ self._done_button = None
+ else:
+ bui.containerwidget(
+ edit=self._root_widget, cancel_button=self._done_button
+ )
# Now kick off the tests.
# Pass a weak-ref to this window so we don't keep it alive
@@ -107,6 +127,16 @@ class NetTestingWindow(bui.Window):
target=bui.Call(_run_diagnostics, weakref.ref(self)),
).start()
+ @override
+ def get_main_window_state(self) -> bui.MainWindowState:
+ # Support recreating our window for back/refresh purposes.
+ cls = type(self)
+ return bui.BasicMainWindowState(
+ create_call=lambda transition, origin_widget: cls(
+ transition=transition, origin_widget=origin_widget
+ )
+ )
+
def print(self, text: str, color: tuple[float, float, float]) -> None:
"""Print text to our console thingie."""
for line in text.splitlines():
@@ -138,26 +168,24 @@ class NetTestingWindow(bui.Window):
if not self._root_widget or self._root_widget.transitioning_out:
return
- bui.app.ui_v1.set_main_menu_window(
- NetValTestingWindow().get_root_widget(),
- from_window=self._root_widget,
- )
+ bui.app.ui_v1.set_main_window(NetValTestingWindow(), from_window=self)
bui.containerwidget(edit=self._root_widget, transition='out_left')
- def _done(self) -> None:
- # pylint: disable=cyclic-import
- from bauiv1lib.settings.advanced import AdvancedSettingsWindow
+ # def _done(self) -> None:
+ # # pylint: disable=cyclic-import
+ # 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
+ # # 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
- bui.app.ui_v1.set_main_menu_window(
- AdvancedSettingsWindow(transition='in_left').get_root_widget(),
- from_window=self._root_widget,
- )
- bui.containerwidget(edit=self._root_widget, transition='out_right')
+ # assert bui.app.classic is not None
+ # bui.app.ui_v1.set_main_window(
+ # AdvancedSettingsWindow(transition='in_left'),
+ # from_window=self,
+ # is_back=True,
+ # )
+ # bui.containerwidget(edit=self._root_widget, transition='out_right')
def _run_diagnostics(weakwin: weakref.ref[NetTestingWindow]) -> None:
@@ -471,5 +499,4 @@ class NetValTestingWindow(TestingWindow):
title=bui.Lstr(resource='settingsWindowAdvanced.netTestingText'),
entries=entries,
transition=transition,
- back_call=lambda: NetTestingWindow(transition='in_left'),
)
diff --git a/src/assets/ba_data/python/bauiv1lib/settings/plugins.py b/src/assets/ba_data/python/bauiv1lib/settings/plugins.py
index b33216b6..d5f4fd81 100644
--- a/src/assets/ba_data/python/bauiv1lib/settings/plugins.py
+++ b/src/assets/ba_data/python/bauiv1lib/settings/plugins.py
@@ -4,9 +4,9 @@
from __future__ import annotations
-from enum import Enum
import logging
-from typing import TYPE_CHECKING, assert_never
+from enum import Enum
+from typing import TYPE_CHECKING, assert_never, override
import bauiv1 as bui
from bauiv1lib import popup
@@ -28,35 +28,24 @@ class Category(Enum):
return f'{self.value}Text'
-class PluginWindow(bui.Window):
+class PluginWindow(bui.MainWindow):
"""Window for configuring plugins."""
def __init__(
self,
- transition: str = 'in_right',
+ transition: str | None = 'in_right',
origin_widget: bui.Widget | None = None,
):
- # pylint: disable=too-many-statements
app = bui.app
self._category = Category.ALL
- # If they provided an origin-widget, scale up from that.
- scale_origin: tuple[float, float] | None
- if origin_widget is not None:
- self._transition_out = 'out_scale'
- scale_origin = origin_widget.get_screen_space_center()
- transition = 'in_scale'
- else:
- self._transition_out = 'out_right'
- scale_origin = None
-
assert bui.app.classic is not None
uiscale = bui.app.ui_v1.uiscale
self._width = 870.0 if uiscale is bui.UIScale.SMALL else 670.0
x_inset = 100 if uiscale is bui.UIScale.SMALL else 0
self._height = (
- 390.0
+ 370.0
if uiscale is bui.UIScale.SMALL
else 450.0 if uiscale is bui.UIScale.MEDIUM else 520.0
)
@@ -64,18 +53,22 @@ class PluginWindow(bui.Window):
super().__init__(
root_widget=bui.containerwidget(
size=(self._width, self._height + top_extra),
- transition=transition,
- toolbar_visibility='menu_minimal',
- scale_origin_stack_offset=scale_origin,
+ toolbar_visibility=(
+ 'menu_minimal'
+ if uiscale is bui.UIScale.SMALL
+ else 'menu_full'
+ ),
scale=(
- 2.06
+ 1.9
if uiscale is bui.UIScale.SMALL
else 1.4 if uiscale is bui.UIScale.MEDIUM else 1.0
),
stack_offset=(
(0, -25) if uiscale is bui.UIScale.SMALL else (0, 0)
),
- )
+ ),
+ transition=transition,
+ origin_widget=origin_widget,
)
self._scroll_width = self._width - (100 + 2 * x_inset)
@@ -84,9 +77,9 @@ class PluginWindow(bui.Window):
self._sub_height = 724.0
assert app.classic is not None
- if app.ui_v1.use_toolbars and uiscale is bui.UIScale.SMALL:
+ if uiscale is bui.UIScale.SMALL:
bui.containerwidget(
- edit=self._root_widget, on_cancel_call=self._do_back
+ edit=self._root_widget, on_cancel_call=self.main_window_back
)
self._back_button = None
else:
@@ -98,7 +91,7 @@ class PluginWindow(bui.Window):
autoselect=True,
label=bui.Lstr(resource='backText'),
button_type='back',
- on_activate_call=self._do_back,
+ on_activate_call=self.main_window_back,
)
bui.containerwidget(
edit=self._root_widget, cancel_button=self._back_button
@@ -213,6 +206,20 @@ class PluginWindow(bui.Window):
)
self._restore_state()
+ @override
+ def get_main_window_state(self) -> bui.MainWindowState:
+ # Support recreating our window for back/refresh purposes.
+ cls = type(self)
+ return bui.BasicMainWindowState(
+ create_call=lambda transition, origin_widget: cls(
+ transition=transition, origin_widget=origin_widget
+ )
+ )
+
+ @override
+ def on_main_window_close(self) -> None:
+ self._save_state()
+
def _check_value_changed(self, plug: bui.PluginSpec, value: bool) -> None:
bui.screenmessage(
bui.Lstr(resource='settingsWindowAdvanced.mustRestartText'),
@@ -235,9 +242,8 @@ class PluginWindow(bui.Window):
self._save_state()
bui.containerwidget(edit=self._root_widget, transition='out_left')
assert bui.app.classic is not None
- bui.app.ui_v1.set_main_menu_window(
- PluginSettingsWindow(transition='in_right').get_root_widget(),
- from_window=self._root_widget,
+ bui.app.ui_v1.set_main_window(
+ PluginSettingsWindow(transition='in_right'), from_window=self
)
def _show_category_options(self) -> None:
@@ -451,21 +457,3 @@ class PluginWindow(bui.Window):
bui.containerwidget(edit=self._root_widget, selected_child=sel)
except Exception:
logging.exception('Error restoring state for %s.', self)
-
- def _do_back(self) -> None:
- # pylint: disable=cyclic-import
- 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()
- bui.containerwidget(
- edit=self._root_widget, transition=self._transition_out
- )
- assert bui.app.classic is not None
- bui.app.ui_v1.set_main_menu_window(
- AdvancedSettingsWindow(transition='in_left').get_root_widget(),
- from_window=self._root_widget,
- )
diff --git a/src/assets/ba_data/python/bauiv1lib/settings/pluginsettings.py b/src/assets/ba_data/python/bauiv1lib/settings/pluginsettings.py
index d59554ff..1f3aefda 100644
--- a/src/assets/ba_data/python/bauiv1lib/settings/pluginsettings.py
+++ b/src/assets/ba_data/python/bauiv1lib/settings/pluginsettings.py
@@ -4,21 +4,24 @@
from __future__ import annotations
+from typing import override
+
import bauiv1 as bui
from bauiv1lib.confirm import ConfirmWindow
-class PluginSettingsWindow(bui.Window):
+class PluginSettingsWindow(bui.MainWindow):
"""Plugin Settings Window"""
- def __init__(self, transition: str = 'in_right'):
- scale_origin: tuple[float, float] | None
- self._transition_out = 'out_right'
- scale_origin = None
+ def __init__(
+ self,
+ transition: str | None = 'in_right',
+ origin_widget: bui.Widget | None = None,
+ ):
assert bui.app.classic is not None
uiscale = bui.app.ui_v1.uiscale
- width = 470.0 if uiscale is bui.UIScale.SMALL else 470.0
+ width = 670.0 if uiscale is bui.UIScale.SMALL else 470.0
height = (
365.0
if uiscale is bui.UIScale.SMALL
@@ -29,9 +32,11 @@ class PluginSettingsWindow(bui.Window):
super().__init__(
root_widget=bui.containerwidget(
size=(width, height + top_extra),
- transition=transition,
- toolbar_visibility='menu_minimal',
- scale_origin_stack_offset=scale_origin,
+ toolbar_visibility=(
+ 'menu_minimal'
+ if uiscale is bui.UIScale.SMALL
+ else 'menu_full'
+ ),
scale=(
2.06
if uiscale is bui.UIScale.SMALL
@@ -40,37 +45,50 @@ class PluginSettingsWindow(bui.Window):
stack_offset=(
(0, -25) if uiscale is bui.UIScale.SMALL else (0, 0)
),
- )
+ ),
+ transition=transition,
+ origin_widget=origin_widget,
)
- self._back_button = bui.buttonwidget(
- parent=self._root_widget,
- position=(53, height - 60),
- size=(60, 60),
- scale=0.8,
- autoselect=True,
- label=bui.charstr(bui.SpecialChar.BACK),
- button_type='backSmall',
- on_activate_call=self._do_back,
- )
- bui.containerwidget(
- edit=self._root_widget, cancel_button=self._back_button
- )
+ if uiscale is bui.UIScale.SMALL:
+ xoffs = 90
+ self._back_button = bui.get_special_widget('back_button')
+ bui.containerwidget(
+ edit=self._root_widget, on_cancel_call=self.main_window_back
+ )
+ else:
+ xoffs = 0
+ self._back_button = bui.buttonwidget(
+ parent=self._root_widget,
+ position=(53, height - 60),
+ size=(60, 60),
+ scale=0.8,
+ autoselect=True,
+ label=bui.charstr(bui.SpecialChar.BACK),
+ button_type='backSmall',
+ on_activate_call=self.main_window_back,
+ )
+ bui.containerwidget(
+ edit=self._root_widget, cancel_button=self._back_button
+ )
self._title_text = bui.textwidget(
parent=self._root_widget,
- position=(0, height - 52),
- size=(width, 25),
+ position=(
+ width * 0.5,
+ height - (45 if uiscale is bui.UIScale.SMALL else 35),
+ ),
+ size=(0, 0),
text=bui.Lstr(resource='pluginSettingsText'),
color=bui.app.ui_v1.title_color,
h_align='center',
- v_align='top',
+ v_align='center',
)
self._y_position = 170 if uiscale is bui.UIScale.MEDIUM else 205
self._enable_plugins_button = bui.buttonwidget(
parent=self._root_widget,
- position=(65, self._y_position),
+ position=(xoffs + 65, self._y_position),
size=(350, 60),
autoselect=True,
label=bui.Lstr(resource='pluginsEnableAllText'),
@@ -83,7 +101,7 @@ class PluginSettingsWindow(bui.Window):
self._y_position -= 70
self._disable_plugins_button = bui.buttonwidget(
parent=self._root_widget,
- position=(65, self._y_position),
+ position=(xoffs + 65, self._y_position),
size=(350, 60),
autoselect=True,
label=bui.Lstr(resource='pluginsDisableAllText'),
@@ -96,7 +114,7 @@ class PluginSettingsWindow(bui.Window):
self._y_position -= 70
self._enable_new_plugins_check_box = bui.checkboxwidget(
parent=self._root_widget,
- position=(65, self._y_position),
+ position=(xoffs + 65, self._y_position),
size=(350, 60),
value=bui.app.config.get(
bui.app.plugins.AUTO_ENABLE_NEW_PLUGINS_CONFIG_KEY,
@@ -108,9 +126,10 @@ class PluginSettingsWindow(bui.Window):
on_value_change_call=self._update_value,
)
- bui.widget(
- edit=self._back_button, down_widget=self._enable_plugins_button
- )
+ if uiscale is not bui.UIScale.SMALL:
+ bui.widget(
+ edit=self._back_button, down_widget=self._enable_plugins_button
+ )
bui.widget(
edit=self._disable_plugins_button,
@@ -124,6 +143,16 @@ class PluginSettingsWindow(bui.Window):
down_widget=self._enable_new_plugins_check_box,
)
+ @override
+ def get_main_window_state(self) -> bui.MainWindowState:
+ # Support recreating our window for back/refresh purposes.
+ cls = type(self)
+ return bui.BasicMainWindowState(
+ create_call=lambda transition, origin_widget: cls(
+ transition=transition, origin_widget=origin_widget
+ )
+ )
+
def _enable_all_plugins(self) -> None:
cfg = bui.app.config
plugs: dict[str, dict] = cfg.setdefault('Plugins', {})
@@ -152,20 +181,3 @@ class PluginSettingsWindow(bui.Window):
cfg = bui.app.config
cfg[bui.app.plugins.AUTO_ENABLE_NEW_PLUGINS_CONFIG_KEY] = val
cfg.apply_and_commit()
-
- def _do_back(self) -> None:
- # pylint: disable=cyclic-import
- 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(
- edit=self._root_widget, transition=self._transition_out
- )
- assert bui.app.classic is not None
- bui.app.ui_v1.set_main_menu_window(
- PluginWindow(transition='in_left').get_root_widget(),
- from_window=self._root_widget,
- )
diff --git a/src/assets/ba_data/python/bauiv1lib/settings/remoteapp.py b/src/assets/ba_data/python/bauiv1lib/settings/remoteapp.py
index f6a8216e..97af8c44 100644
--- a/src/assets/ba_data/python/bauiv1lib/settings/remoteapp.py
+++ b/src/assets/ba_data/python/bauiv1lib/settings/remoteapp.py
@@ -4,45 +4,70 @@
from __future__ import annotations
+from typing import override
+
import bauiv1 as bui
-class RemoteAppSettingsWindow(bui.Window):
+class RemoteAppSettingsWindow(bui.MainWindow):
"""Window showing info/settings related to the remote app."""
- def __init__(self) -> None:
+ def __init__(
+ self,
+ transition: str | None = 'in_right',
+ origin_widget: bui.Widget | None = None,
+ ) -> None:
self._r = 'connectMobileDevicesWindow'
- width = 700
+ app = bui.app
+ uiscale = app.ui_v1.uiscale
+ width = 800 if uiscale is bui.UIScale.SMALL else 700
height = 390
spacing = 40
assert bui.app.classic is not None
- uiscale = bui.app.ui_v1.uiscale
super().__init__(
root_widget=bui.containerwidget(
size=(width, height),
- transition='in_right',
+ toolbar_visibility=(
+ 'menu_minimal'
+ if uiscale is bui.UIScale.SMALL
+ else 'menu_full'
+ ),
scale=(
- 1.85
+ 1.76
if uiscale is bui.UIScale.SMALL
else 1.3 if uiscale is bui.UIScale.MEDIUM else 1.0
),
stack_offset=(
(-10, 0) if uiscale is bui.UIScale.SMALL else (0, 0)
),
+ ),
+ transition=transition,
+ origin_widget=origin_widget,
+ )
+ if uiscale is bui.UIScale.SMALL:
+ bui.containerwidget(
+ edit=self.get_root_widget(),
+ on_cancel_call=self.main_window_back,
+ )
+ else:
+ btn = bui.buttonwidget(
+ parent=self._root_widget,
+ position=(40, height - 67),
+ size=(140, 65),
+ scale=0.8,
+ label=bui.Lstr(resource='backText'),
+ button_type='back',
+ text_scale=1.1,
+ autoselect=True,
+ on_activate_call=self.main_window_back,
+ )
+ bui.containerwidget(edit=self._root_widget, cancel_button=btn)
+ bui.buttonwidget(
+ edit=btn,
+ button_type='backSmall',
+ size=(60, 60),
+ label=bui.charstr(bui.SpecialChar.BACK),
)
- )
- btn = bui.buttonwidget(
- parent=self._root_widget,
- position=(40, height - 67),
- size=(140, 65),
- scale=0.8,
- label=bui.Lstr(resource='backText'),
- button_type='back',
- text_scale=1.1,
- autoselect=True,
- on_activate_call=self._back,
- )
- bui.containerwidget(edit=self._root_widget, cancel_button=btn)
bui.textwidget(
parent=self._root_widget,
@@ -56,13 +81,6 @@ class RemoteAppSettingsWindow(bui.Window):
v_align='center',
)
- bui.buttonwidget(
- edit=btn,
- button_type='backSmall',
- size=(60, 60),
- label=bui.charstr(bui.SpecialChar.BACK),
- )
-
v = height - 70.0
v -= spacing * 1.2
bui.textwidget(
@@ -125,23 +143,17 @@ class RemoteAppSettingsWindow(bui.Window):
on_value_change_call=self._on_check_changed,
)
+ @override
+ def get_main_window_state(self) -> bui.MainWindowState:
+ # Support recreating our window for back/refresh purposes.
+ cls = type(self)
+ return bui.BasicMainWindowState(
+ create_call=lambda transition, origin_widget: cls(
+ transition=transition, origin_widget=origin_widget
+ )
+ )
+
def _on_check_changed(self, value: bool) -> None:
cfg = bui.app.config
cfg['Enable Remote App'] = not value
cfg.apply_and_commit()
-
- def _back(self) -> None:
- 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')
- assert bui.app.classic is not None
- bui.app.ui_v1.set_main_menu_window(
- controls.ControlsSettingsWindow(
- transition='in_left'
- ).get_root_widget(),
- from_window=self._root_widget,
- )
diff --git a/src/assets/ba_data/python/bauiv1lib/settings/testing.py b/src/assets/ba_data/python/bauiv1lib/settings/testing.py
index 5af029bb..de396332 100644
--- a/src/assets/ba_data/python/bauiv1lib/settings/testing.py
+++ b/src/assets/ba_data/python/bauiv1lib/settings/testing.py
@@ -13,50 +13,73 @@ if TYPE_CHECKING:
from typing import Any, Callable
-class TestingWindow(bui.Window):
+class TestingWindow(bui.MainWindow):
"""Window for conveniently testing various settings."""
def __init__(
self,
title: bui.Lstr,
entries: list[dict[str, Any]],
- transition: str = 'in_right',
- back_call: Callable[[], bui.Window] | None = None,
+ transition: str | None = 'in_right',
+ origin_widget: bui.Widget | None = None,
):
assert bui.app.classic is not None
uiscale = bui.app.ui_v1.uiscale
- self._width = 600
+ self._width = 700 if uiscale is bui.UIScale.SMALL else 600
self._height = 324 if uiscale is bui.UIScale.SMALL else 400
self._entries = copy.deepcopy(entries)
- self._back_call = back_call
super().__init__(
root_widget=bui.containerwidget(
size=(self._width, self._height),
- transition=transition,
scale=(
- 2.5
+ 2.27
if uiscale is bui.UIScale.SMALL
else 1.2 if uiscale is bui.UIScale.MEDIUM else 1.0
),
stack_offset=(
- (0, -28) if uiscale is bui.UIScale.SMALL else (0, 0)
+ (0, -20) if uiscale is bui.UIScale.SMALL else (0, 0)
),
+ toolbar_visibility=(
+ 'menu_minimal'
+ if uiscale is bui.UIScale.SMALL
+ else 'menu_full'
+ ),
+ ),
+ transition=transition,
+ origin_widget=origin_widget,
+ )
+
+ if uiscale is bui.UIScale.SMALL:
+ self._back_button = bui.get_special_widget('back_button')
+ bui.containerwidget(
+ edit=self._root_widget, on_cancel_call=self.main_window_back
)
- )
- self._back_button = btn = bui.buttonwidget(
- parent=self._root_widget,
- autoselect=True,
- position=(65, self._height - 59),
- size=(130, 60),
- scale=0.8,
- text_scale=1.2,
- label=bui.Lstr(resource='backText'),
- button_type='back',
- on_activate_call=self._do_back,
- )
+ else:
+ self._back_button = btn = bui.buttonwidget(
+ parent=self._root_widget,
+ autoselect=True,
+ position=(65, self._height - 59),
+ size=(130, 60),
+ scale=0.8,
+ text_scale=1.2,
+ label=bui.Lstr(resource='backText'),
+ button_type='back',
+ on_activate_call=self.main_window_back,
+ )
+ bui.buttonwidget(
+ edit=self._back_button,
+ button_type='backSmall',
+ size=(60, 60),
+ label=bui.charstr(bui.SpecialChar.BACK),
+ )
+ bui.containerwidget(edit=self._root_widget, cancel_button=btn)
+
bui.textwidget(
parent=self._root_widget,
- position=(self._width * 0.5, self._height - 35),
+ position=(
+ self._width * 0.5,
+ self._height - (42 if uiscale is bui.UIScale.SMALL else 35),
+ ),
size=(0, 0),
color=bui.app.ui_v1.title_color,
h_align='center',
@@ -65,16 +88,12 @@ class TestingWindow(bui.Window):
text=title,
)
- bui.buttonwidget(
- edit=self._back_button,
- button_type='backSmall',
- size=(60, 60),
- label=bui.charstr(bui.SpecialChar.BACK),
- )
-
bui.textwidget(
parent=self._root_widget,
- position=(self._width * 0.5, self._height - 75),
+ position=(
+ self._width * 0.5,
+ self._height - (80 if uiscale is bui.UIScale.SMALL else 80),
+ ),
size=(0, 0),
color=bui.app.ui_v1.infotextcolor,
h_align='center',
@@ -82,7 +101,6 @@ class TestingWindow(bui.Window):
maxwidth=self._width * 0.75,
text=bui.Lstr(resource='settingsWindowAdvanced.forTestingText'),
)
- bui.containerwidget(edit=self._root_widget, cancel_button=btn)
self._scroll_width = self._width - 130
self._scroll_height = self._height - 140
self._scrollwidget = bui.scrollwidget(
@@ -138,7 +156,6 @@ class TestingWindow(bui.Window):
)
if i == 0:
bui.widget(edit=btn, up_widget=self._back_button)
- # pylint: disable=consider-using-f-string
entry['widget'] = bui.textwidget(
parent=self._subcontainer,
position=(h + 100, v),
@@ -146,7 +163,7 @@ class TestingWindow(bui.Window):
h_align='center',
v_align='center',
maxwidth=60,
- text='%.4g' % bui.app.classic.value_test(entry_name),
+ text=f'{bui.app.classic.value_test(entry_name):.4g}',
)
btn = bui.buttonwidget(
parent=self._subcontainer,
@@ -185,10 +202,9 @@ class TestingWindow(bui.Window):
entry['name'],
absolute=bui.app.classic.value_test_defaults[entry['name']],
)
- # pylint: disable=consider-using-f-string
bui.textwidget(
edit=entry['widget'],
- text='%.4g' % bui.app.classic.value_test(entry['name']),
+ text=f'{bui.app.classic.value_test(entry['name']):.4g}',
)
def _on_minus_press(self, entry_name: str) -> None:
@@ -210,22 +226,3 @@ class TestingWindow(bui.Window):
edit=entry['widget'],
text='%.4g' % bui.app.classic.value_test(entry['name']),
)
-
- def _do_back(self) -> None:
- # pylint: disable=cyclic-import
- 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')
- backwin = (
- self._back_call()
- if self._back_call is not None
- else AdvancedSettingsWindow(transition='in_left')
- )
- assert bui.app.classic is not None
- bui.app.ui_v1.set_main_menu_window(
- backwin.get_root_widget(), from_window=self._root_widget
- )
diff --git a/src/assets/ba_data/python/bauiv1lib/settings/touchscreen.py b/src/assets/ba_data/python/bauiv1lib/settings/touchscreen.py
index 38aec5f9..ea965829 100644
--- a/src/assets/ba_data/python/bauiv1lib/settings/touchscreen.py
+++ b/src/assets/ba_data/python/bauiv1lib/settings/touchscreen.py
@@ -3,11 +3,13 @@
"""UI settings functionality related to touchscreens."""
from __future__ import annotations
+from typing import override
+
import bauiv1 as bui
import bascenev1 as bs
-class TouchscreenSettingsWindow(bui.Window):
+class TouchscreenSettingsWindow(bui.MainWindow):
"""Settings window for touchscreens."""
def __del__(self) -> None:
@@ -18,7 +20,11 @@ class TouchscreenSettingsWindow(bui.Window):
# thing that exists.
bs.set_touchscreen_editing(False)
- def __init__(self) -> None:
+ def __init__(
+ self,
+ transition: str | None = 'in_right',
+ origin_widget: bui.Widget | None = None,
+ ) -> None:
self._width = 650
self._height = 380
self._spacing = 40
@@ -31,13 +37,14 @@ class TouchscreenSettingsWindow(bui.Window):
super().__init__(
root_widget=bui.containerwidget(
size=(self._width, self._height),
- transition='in_right',
scale=(
1.9
if uiscale is bui.UIScale.SMALL
else 1.55 if uiscale is bui.UIScale.MEDIUM else 1.2
),
- )
+ ),
+ transition=transition,
+ origin_widget=origin_widget,
)
btn = bui.buttonwidget(
@@ -95,6 +102,16 @@ class TouchscreenSettingsWindow(bui.Window):
)
self._build_gui()
+ @override
+ def get_main_window_state(self) -> bui.MainWindowState:
+ # Support recreating our window for back/refresh purposes.
+ cls = type(self)
+ return bui.BasicMainWindowState(
+ create_call=lambda transition, origin_widget: cls(
+ transition=transition, origin_widget=origin_widget
+ )
+ )
+
def _build_gui(self) -> None:
from bauiv1lib.config import ConfigNumberEdit, ConfigCheckBox
from bauiv1lib.radiogroup import make_radio_group
@@ -280,10 +297,9 @@ class TouchscreenSettingsWindow(bui.Window):
bui.containerwidget(edit=self._root_widget, transition='out_right')
assert bui.app.classic is not None
- bui.app.ui_v1.set_main_menu_window(
- controls.ControlsSettingsWindow(
- transition='in_left'
- ).get_root_widget(),
- from_window=self._root_widget,
+ bui.app.ui_v1.set_main_window(
+ controls.ControlsSettingsWindow(transition='in_left'),
+ from_window=self,
+ is_back=True,
)
bs.set_touchscreen_editing(False)
diff --git a/src/assets/ba_data/python/bauiv1lib/soundtrack/browser.py b/src/assets/ba_data/python/bauiv1lib/soundtrack/browser.py
index f16073bd..a052b075 100644
--- a/src/assets/ba_data/python/bauiv1lib/soundtrack/browser.py
+++ b/src/assets/ba_data/python/bauiv1lib/soundtrack/browser.py
@@ -6,35 +6,29 @@ from __future__ import annotations
import copy
import logging
-from typing import TYPE_CHECKING
+from typing import TYPE_CHECKING, override
import bauiv1 as bui
if TYPE_CHECKING:
from typing import Any
+REQUIRE_PRO = False
-class SoundtrackBrowserWindow(bui.Window):
+# Temp.
+UNDER_CONSTRUCTION = True
+
+
+class SoundtrackBrowserWindow(bui.MainWindow):
"""Window for browsing soundtracks."""
def __init__(
self,
- transition: str = 'in_right',
+ transition: str | None = 'in_right',
origin_widget: bui.Widget | None = None,
):
- # pylint: disable=too-many-locals
# pylint: disable=too-many-statements
- # If they provided an origin-widget, scale up from that.
- scale_origin: tuple[float, float] | None
- if origin_widget is not None:
- self._transition_out = 'out_scale'
- scale_origin = origin_widget.get_screen_space_center()
- transition = 'in_scale'
- else:
- self._transition_out = 'out_right'
- scale_origin = None
-
self._r = 'editSoundtrackWindow'
assert bui.app.classic is not None
uiscale = bui.app.ui_v1.uiscale
@@ -52,22 +46,26 @@ class SoundtrackBrowserWindow(bui.Window):
super().__init__(
root_widget=bui.containerwidget(
size=(self._width, self._height),
- transition=transition,
- toolbar_visibility='menu_minimal',
- scale_origin_stack_offset=scale_origin,
+ toolbar_visibility=(
+ 'menu_minimal'
+ if uiscale is bui.UIScale.SMALL
+ else 'menu_full'
+ ),
scale=(
- 2.3
+ 2.1
if uiscale is bui.UIScale.SMALL
else 1.6 if uiscale is bui.UIScale.MEDIUM else 1.0
),
stack_offset=(
(0, -18) if uiscale is bui.UIScale.SMALL else (0, 0)
),
- )
+ ),
+ transition=transition,
+ origin_widget=origin_widget,
)
assert bui.app.classic is not None
- if bui.app.ui_v1.use_toolbars and uiscale is bui.UIScale.SMALL:
+ if uiscale is bui.UIScale.SMALL:
self._back_button = None
else:
self._back_button = bui.buttonwidget(
@@ -239,11 +237,7 @@ class SoundtrackBrowserWindow(bui.Window):
bui.widget(
edit=self._scrollwidget,
left_widget=self._new_button,
- right_widget=(
- bui.get_special_widget('party_button')
- if bui.app.ui_v1.use_toolbars
- else self._scrollwidget
- ),
+ right_widget=bui.get_special_widget('squad_button'),
)
self._col = bui.columnwidget(parent=scrollwidget, border=2, margin=0)
@@ -255,23 +249,39 @@ class SoundtrackBrowserWindow(bui.Window):
self._refresh()
if self._back_button is not None:
bui.buttonwidget(
- edit=self._back_button, on_activate_call=self._back
+ edit=self._back_button, on_activate_call=self.main_window_back
)
bui.containerwidget(
edit=self._root_widget, cancel_button=self._back_button
)
else:
bui.containerwidget(
- edit=self._root_widget, on_cancel_call=self._back
+ edit=self._root_widget, on_cancel_call=self.main_window_back
)
+ @override
+ def get_main_window_state(self) -> bui.MainWindowState:
+ # Support recreating our window for back/refresh purposes.
+ cls = type(self)
+ return bui.BasicMainWindowState(
+ create_call=lambda transition, origin_widget: cls(
+ transition=transition, origin_widget=origin_widget
+ )
+ )
+
+ @override
+ def on_main_window_close(self) -> None:
+ self._save_state()
+
def _update(self) -> None:
- have = (
+ have_pro = (
bui.app.classic is None
or bui.app.classic.accounts.have_pro_options()
)
for lock in self._lock_images:
- bui.imagewidget(edit=lock, opacity=0.0 if have else 1.0)
+ bui.imagewidget(
+ edit=lock, opacity=0.0 if (have_pro or not REQUIRE_PRO) else 1.0
+ )
def _do_delete_soundtrack(self) -> None:
cfg = bui.app.config
@@ -292,7 +302,7 @@ class SoundtrackBrowserWindow(bui.Window):
from bauiv1lib.purchase import PurchaseWindow
from bauiv1lib.confirm import ConfirmWindow
- if (
+ if REQUIRE_PRO and (
bui.app.classic is not None
and not bui.app.classic.accounts.have_pro_options()
):
@@ -321,7 +331,7 @@ class SoundtrackBrowserWindow(bui.Window):
# pylint: disable=cyclic-import
from bauiv1lib.purchase import PurchaseWindow
- if (
+ if REQUIRE_PRO and (
bui.app.classic is not None
and not bui.app.classic.accounts.have_pro_options()
):
@@ -387,29 +397,30 @@ class SoundtrackBrowserWindow(bui.Window):
music.music_types[bui.app.classic.MusicPlayMode.REGULAR]
)
- def _back(self) -> None:
- # pylint: disable=cyclic-import
- from bauiv1lib.settings.audio import AudioSettingsWindow
+ # def _back(self) -> None:
+ # # pylint: disable=cyclic-import
+ # 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
+ # # 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()
- bui.containerwidget(
- edit=self._root_widget, transition=self._transition_out
- )
- assert bui.app.classic is not None
- bui.app.ui_v1.set_main_menu_window(
- AudioSettingsWindow(transition='in_left').get_root_widget(),
- from_window=self._root_widget,
- )
+ # self._save_state()
+ # bui.containerwidget(
+ # edit=self._root_widget, transition=self._transition_out
+ # )
+ # assert bui.app.classic is not None
+ # bui.app.ui_v1.set_main_window(
+ # AudioSettingsWindow(transition='in_left'),
+ # from_window=self,
+ # is_back=True,
+ # )
def _edit_soundtrack_with_sound(self) -> None:
# pylint: disable=cyclic-import
from bauiv1lib.purchase import PurchaseWindow
- if (
+ if REQUIRE_PRO and (
bui.app.classic is not None
and not bui.app.classic.accounts.have_pro_options()
):
@@ -423,11 +434,15 @@ class SoundtrackBrowserWindow(bui.Window):
from bauiv1lib.purchase import PurchaseWindow
from bauiv1lib.soundtrack.edit import SoundtrackEditWindow
+ if UNDER_CONSTRUCTION:
+ bui.screenmessage('UNDER CONSTRUCTION')
+ return
+
# 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 REQUIRE_PRO and (
bui.app.classic is not None
and not bui.app.classic.accounts.have_pro_options()
):
@@ -446,11 +461,9 @@ class SoundtrackBrowserWindow(bui.Window):
self._save_state()
bui.containerwidget(edit=self._root_widget, transition='out_left')
assert bui.app.classic is not None
- bui.app.ui_v1.set_main_menu_window(
- SoundtrackEditWindow(
- existing_soundtrack=self._selected_soundtrack
- ).get_root_widget(),
- from_window=self._root_widget,
+ bui.app.ui_v1.set_main_window(
+ SoundtrackEditWindow(existing_soundtrack=self._selected_soundtrack),
+ from_window=self,
)
def _get_soundtrack_display_name(self, soundtrack: str) -> bui.Lstr:
@@ -541,7 +554,11 @@ class SoundtrackBrowserWindow(bui.Window):
from bauiv1lib.purchase import PurchaseWindow
from bauiv1lib.soundtrack.edit import SoundtrackEditWindow
- if (
+ if UNDER_CONSTRUCTION:
+ bui.screenmessage('UNDER CONSTRUCTION')
+ return
+
+ if REQUIRE_PRO and (
bui.app.classic is not None
and not bui.app.classic.accounts.have_pro_options()
):
@@ -549,9 +566,8 @@ class SoundtrackBrowserWindow(bui.Window):
return
self._save_state()
bui.containerwidget(edit=self._root_widget, transition='out_left')
- bui.app.ui_v1.set_main_menu_window(
- SoundtrackEditWindow(existing_soundtrack=None).get_root_widget(),
- from_window=self._root_widget,
+ bui.app.ui_v1.set_main_window(
+ SoundtrackEditWindow(existing_soundtrack=None), from_window=self
)
def _create_done(self, new_soundtrack: str) -> None:
diff --git a/src/assets/ba_data/python/bauiv1lib/soundtrack/edit.py b/src/assets/ba_data/python/bauiv1lib/soundtrack/edit.py
index 9a155a51..77bcae57 100644
--- a/src/assets/ba_data/python/bauiv1lib/soundtrack/edit.py
+++ b/src/assets/ba_data/python/bauiv1lib/soundtrack/edit.py
@@ -6,7 +6,7 @@ from __future__ import annotations
import copy
import os
-from typing import TYPE_CHECKING, cast
+from typing import TYPE_CHECKING, cast, override
import bascenev1 as bs
import bauiv1 as bui
@@ -15,22 +15,26 @@ if TYPE_CHECKING:
from typing import Any
-class SoundtrackEditWindow(bui.Window):
+class SoundtrackEditWindow(bui.MainWindow):
"""Window for editing a soundtrack."""
def __init__(
self,
existing_soundtrack: str | dict[str, Any] | None,
- transition: str = 'in_right',
+ transition: str | None = 'in_right',
+ origin_widget: bui.Widget | None = None,
):
# pylint: disable=too-many-statements
+
+ print('SPAWNING EDIT')
+
appconfig = bui.app.config
self._r = 'editSoundtrackWindow'
self._folder_tex = bui.gettexture('folder')
self._file_tex = bui.gettexture('file')
assert bui.app.classic is not None
uiscale = bui.app.ui_v1.uiscale
- self._width = 848 if uiscale is bui.UIScale.SMALL else 648
+ self._width = 900 if uiscale is bui.UIScale.SMALL else 648
x_inset = 100 if uiscale is bui.UIScale.SMALL else 0
self._height = (
395
@@ -40,18 +44,19 @@ class SoundtrackEditWindow(bui.Window):
super().__init__(
root_widget=bui.containerwidget(
size=(self._width, self._height),
- transition=transition,
scale=(
- 2.08
+ 1.8
if uiscale is bui.UIScale.SMALL
else 1.5 if uiscale is bui.UIScale.MEDIUM else 1.0
),
stack_offset=(
- (0, -48)
+ (0, -37)
if uiscale is bui.UIScale.SMALL
else (0, 15) if uiscale is bui.UIScale.MEDIUM else (0, 0)
),
- )
+ ),
+ transition=transition,
+ origin_widget=origin_widget,
)
cancel_button = bui.buttonwidget(
parent=self._root_widget,
@@ -93,6 +98,7 @@ class SoundtrackEditWindow(bui.Window):
appconfig['Soundtracks'] = {}
self._soundtrack_name: str | None
+ self._existing_soundtrack = existing_soundtrack
self._existing_soundtrack_name: str | None
if existing_soundtrack is not None:
# if they passed just a name, pull info from that soundtrack
@@ -189,6 +195,22 @@ class SoundtrackEditWindow(bui.Window):
bui.widget(edit=self._text_field, up_widget=cancel_button)
bui.widget(edit=cancel_button, down_widget=self._text_field)
+ @override
+ def get_main_window_state(self) -> bui.MainWindowState:
+ # Support recreating our window for back/refresh purposes.
+ cls = type(self)
+
+ # Pull this out of self here; if we do it in the lambda we'll
+ # keep our window alive due to the 'self' reference.
+ existing_soundtrack = self._existing_soundtrack
+ return bui.BasicMainWindowState(
+ create_call=lambda transition, origin_widget: cls(
+ transition=transition,
+ origin_widget=origin_widget,
+ existing_soundtrack=existing_soundtrack,
+ )
+ )
+
def _refresh(self) -> None:
for widget in self._col.get_children():
widget.delete()
@@ -326,6 +348,8 @@ class SoundtrackEditWindow(bui.Window):
assert bui.app.classic is not None
music = bui.app.classic.music
+ print('GoT RESTORE', state, musictype, entry)
+
# Apply the change and recreate the window.
soundtrack = state['soundtrack']
existing_entry = (
@@ -346,11 +370,6 @@ class SoundtrackEditWindow(bui.Window):
else:
soundtrack[musictype] = entry
- bui.app.ui_v1.set_main_menu_window(
- cls(state, transition='in_left').get_root_widget(),
- from_window=False, # Disable check here.
- )
-
def _get_entry(
self, song_type: str, entry: Any, selection_target_name: str
) -> None:
@@ -369,17 +388,23 @@ class SoundtrackEditWindow(bui.Window):
'soundtrack': self._soundtrack,
'last_edited_song_type': song_type,
}
- bui.containerwidget(edit=self._root_widget, transition='out_left')
- bui.app.ui_v1.set_main_menu_window(
- music.get_music_player()
- .select_entry(
+ self.main_window_replace(
+ music.get_music_player().select_entry(
bui.Call(self._restore_editor, state, song_type),
entry,
selection_target_name,
- )
- .get_root_widget(),
- from_window=self._root_widget,
+ ),
+ group_id='soundtrackentryselect',
)
+ # bui.containerwidget(edit=self._root_widget, transition='out_left')
+ # bui.app.ui_v1.set_main_window(
+ # music.get_music_player().select_entry(
+ # bui.Call(self._restore_editor, state, song_type),
+ # entry,
+ # selection_target_name,
+ # ),
+ # from_window=self,
+ # )
def _test(self, song_type: bs.MusicType) -> None:
assert bui.app.classic is not None
@@ -423,7 +448,7 @@ class SoundtrackEditWindow(bui.Window):
return None
def _cancel(self) -> None:
- from bauiv1lib.soundtrack.browser import SoundtrackBrowserWindow
+ # from bauiv1lib.soundtrack.browser import SoundtrackBrowserWindow
# no-op if our underlying widget is dead or on its way out.
if not self._root_widget or self._root_widget.transitioning_out:
@@ -434,14 +459,17 @@ class SoundtrackEditWindow(bui.Window):
# Resets music back to normal.
music.set_music_play_mode(bui.app.classic.MusicPlayMode.REGULAR)
- bui.containerwidget(edit=self._root_widget, transition='out_right')
- bui.app.ui_v1.set_main_menu_window(
- SoundtrackBrowserWindow(transition='in_left').get_root_widget(),
- from_window=self._root_widget,
- )
+
+ self.main_window_back()
+ # bui.containerwidget(edit=self._root_widget, transition='out_right')
+ # bui.app.ui_v1.set_main_window(
+ # SoundtrackBrowserWindow(transition='in_left'),
+ # from_window=self,
+ # is_back=True,
+ # )
def _do_it(self) -> None:
- from bauiv1lib.soundtrack.browser import SoundtrackBrowserWindow
+ # from bauiv1lib.soundtrack.browser import SoundtrackBrowserWindow
# no-op if our underlying widget is dead or on its way out.
if not self._root_widget or self._root_widget.transitioning_out:
@@ -487,17 +515,19 @@ class SoundtrackEditWindow(bui.Window):
cfg.commit()
bui.getsound('gunCocking').play()
- bui.containerwidget(edit=self._root_widget, transition='out_right')
+ # bui.containerwidget(edit=self._root_widget, transition='out_right')
# Resets music back to normal.
music.set_music_play_mode(
bui.app.classic.MusicPlayMode.REGULAR, force_restart=True
)
- bui.app.ui_v1.set_main_menu_window(
- SoundtrackBrowserWindow(transition='in_left').get_root_widget(),
- from_window=self._root_widget,
- )
+ self.main_window_back()
+ # bui.app.ui_v1.set_main_window(
+ # SoundtrackBrowserWindow(transition='in_left'),
+ # from_window=self,
+ # is_back=True,
+ # )
def _do_it_with_sound(self) -> None:
bui.getsound('swish').play()
diff --git a/src/assets/ba_data/python/bauiv1lib/soundtrack/entrytypeselect.py b/src/assets/ba_data/python/bauiv1lib/soundtrack/entrytypeselect.py
index d0e6547f..4556e00f 100644
--- a/src/assets/ba_data/python/bauiv1lib/soundtrack/entrytypeselect.py
+++ b/src/assets/ba_data/python/bauiv1lib/soundtrack/entrytypeselect.py
@@ -4,7 +4,7 @@
from __future__ import annotations
import copy
-from typing import TYPE_CHECKING
+from typing import TYPE_CHECKING, override
import bauiv1 as bui
@@ -12,7 +12,7 @@ if TYPE_CHECKING:
from typing import Any, Callable
-class SoundtrackEntryTypeSelectWindow(bui.Window):
+class SoundtrackEntryTypeSelectWindow(bui.MainWindow):
"""Window for selecting a soundtrack entry type."""
def __init__(
@@ -20,12 +20,15 @@ class SoundtrackEntryTypeSelectWindow(bui.Window):
callback: Callable[[Any], Any],
current_entry: Any,
selection_target_name: str,
- transition: str = 'in_right',
+ transition: str | None = 'in_right',
+ origin_widget: bui.Widget | None = None,
):
+ # pylint: disable=too-many-locals
assert bui.app.classic is not None
music = bui.app.classic.music
self._r = 'editSoundtrackWindow'
+ self._selection_target_name = selection_target_name
self._callback = callback
self._current_entry = copy.deepcopy(current_entry)
@@ -53,13 +56,12 @@ class SoundtrackEntryTypeSelectWindow(bui.Window):
# NOTE: When something is selected, we close our UI and kick off
# another window which then calls us back when its done, so the
- # standard UI-cleanup-check complains that something is holding on
- # to our instance after its ui is gone. Should restructure in a
- # cleaner way, but just disabling that check for now.
+ # standard UI-cleanup-check complains that something is holding
+ # on to our instance after its ui is gone. Should restructure in
+ # a cleaner way, but just disabling that check for now.
super().__init__(
root_widget=bui.containerwidget(
size=(self._width, self._height),
- transition=transition,
scale=(
1.7
if uiscale is bui.UIScale.SMALL
@@ -67,6 +69,8 @@ class SoundtrackEntryTypeSelectWindow(bui.Window):
),
),
cleanupcheck=False,
+ transition=transition,
+ origin_widget=origin_widget,
)
btn = bui.buttonwidget(
parent=self._root_widget,
@@ -157,6 +161,27 @@ class SoundtrackEntryTypeSelectWindow(bui.Window):
bui.containerwidget(edit=self._root_widget, selected_child=btn)
v -= spacing
+ @override
+ def get_main_window_state(self) -> bui.MainWindowState:
+ # Support recreating our window for back/refresh purposes.
+ cls = type(self)
+
+ # Pull these out of self here; if we reference self in the
+ # lambda we'll keep our window alive which is bad.
+ current_entry = self._current_entry
+ callback = self._callback
+ selection_target_name = self._selection_target_name
+
+ return bui.BasicMainWindowState(
+ create_call=lambda transition, origin_widget: cls(
+ transition=transition,
+ origin_widget=origin_widget,
+ current_entry=current_entry,
+ callback=callback,
+ selection_target_name=selection_target_name,
+ )
+ )
+
def _on_mac_music_app_playlist_press(self) -> None:
assert bui.app.classic is not None
music = bui.app.classic.music
@@ -168,7 +193,7 @@ class SoundtrackEntryTypeSelectWindow(bui.Window):
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
if (
@@ -180,12 +205,18 @@ class SoundtrackEntryTypeSelectWindow(bui.Window):
)
else:
current_playlist_entry = None
- bui.app.ui_v1.set_main_menu_window(
+
+ self.main_window_replace(
MacMusicAppPlaylistSelectWindow(
self._callback, current_playlist_entry, self._current_entry
- ).get_root_widget(),
- from_window=self._root_widget,
+ ),
+ group_id='soundtrackentryselect',
)
+ # MacMusicAppPlaylistSelectWindow(
+ # self._callback, current_playlist_entry, self._current_entry
+ # ),
+ # from_window=self,
+ # )
def _on_music_file_press(self) -> None:
from babase import android_get_external_files_dir
@@ -196,10 +227,11 @@ class SoundtrackEntryTypeSelectWindow(bui.Window):
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()
assert bui.app.classic is not None
- bui.app.ui_v1.set_main_menu_window(
+
+ self.main_window_replace(
FileSelectorWindow(
base_path,
callback=self._music_file_selector_cb,
@@ -208,9 +240,21 @@ class SoundtrackEntryTypeSelectWindow(bui.Window):
OSMusicPlayer.get_valid_music_file_extensions()
),
allow_folders=False,
- ).get_root_widget(),
- from_window=self._root_widget,
+ ),
+ group_id='soundtrackentryselect',
)
+ # bui.app.ui_v1.set_main_window(
+ # FileSelectorWindow(
+ # base_path,
+ # callback=self._music_file_selector_cb,
+ # show_base_path=False,
+ # valid_file_extensions=(
+ # OSMusicPlayer.get_valid_music_file_extensions()
+ # ),
+ # allow_folders=False,
+ # ),
+ # from_window=self,
+ # )
def _on_music_folder_press(self) -> None:
from bauiv1lib.fileselector import FileSelectorWindow
@@ -220,19 +264,30 @@ class SoundtrackEntryTypeSelectWindow(bui.Window):
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()
assert bui.app.classic is not None
- bui.app.ui_v1.set_main_menu_window(
+
+ self.main_window_replace(
FileSelectorWindow(
base_path,
callback=self._music_folder_selector_cb,
show_base_path=False,
valid_file_extensions=[],
allow_folders=True,
- ).get_root_widget(),
- from_window=self._root_widget,
+ ),
+ group_id='soundtrackentryselect',
)
+ # bui.app.ui_v1.set_main_window(
+ # FileSelectorWindow(
+ # base_path,
+ # callback=self._music_folder_selector_cb,
+ # show_base_path=False,
+ # valid_file_extensions=[],
+ # allow_folders=True,
+ # ),
+ # from_window=self,
+ # )
def _music_file_selector_cb(self, result: str | None) -> None:
if result is None:
@@ -247,9 +302,11 @@ class SoundtrackEntryTypeSelectWindow(bui.Window):
self._callback({'type': 'musicFolder', 'name': result})
def _on_default_press(self) -> None:
- bui.containerwidget(edit=self._root_widget, transition='out_right')
+ self.main_window_back()
+ # bui.containerwidget(edit=self._root_widget, transition='out_right')
self._callback(None)
def _on_cancel_press(self) -> None:
- bui.containerwidget(edit=self._root_widget, transition='out_right')
+ self.main_window_back()
+ # bui.containerwidget(edit=self._root_widget, transition='out_right')
self._callback(self._current_entry)
diff --git a/src/assets/ba_data/python/bauiv1lib/soundtrack/macmusicapp.py b/src/assets/ba_data/python/bauiv1lib/soundtrack/macmusicapp.py
index 99c60a69..b4c34ef8 100644
--- a/src/assets/ba_data/python/bauiv1lib/soundtrack/macmusicapp.py
+++ b/src/assets/ba_data/python/bauiv1lib/soundtrack/macmusicapp.py
@@ -13,7 +13,7 @@ if TYPE_CHECKING:
from typing import Any, Callable
-class MacMusicAppPlaylistSelectWindow(bui.Window):
+class MacMusicAppPlaylistSelectWindow(bui.MainWindow):
"""Window for selecting an iTunes playlist."""
def __init__(
@@ -21,6 +21,8 @@ class MacMusicAppPlaylistSelectWindow(bui.Window):
callback: Callable[[Any], Any],
existing_playlist: str | None,
existing_entry: Any,
+ transition: str | None = 'in_right',
+ origin_widget: bui.Widget | None = None,
):
from baclassic.macmusicapp import MacMusicAppMusicPlayer
@@ -34,9 +36,9 @@ class MacMusicAppPlaylistSelectWindow(bui.Window):
v = self._height - 90.0
v -= self._spacing * 1.0
super().__init__(
- root_widget=bui.containerwidget(
- size=(self._width, self._height), transition='in_right'
- )
+ root_widget=bui.containerwidget(size=(self._width, self._height)),
+ transition=transition,
+ origin_widget=origin_widget,
)
btn = bui.buttonwidget(
parent=self._root_widget,
@@ -112,9 +114,12 @@ class MacMusicAppPlaylistSelectWindow(bui.Window):
def _sel(self, selection: str) -> None:
if self._root_widget:
- bui.containerwidget(edit=self._root_widget, transition='out_right')
+ # bui.containerwidget(
+ # edit=self._root_widget, transition='out_right')
self._callback({'type': 'iTunesPlaylist', 'name': selection})
+ self.main_window_back()
def _back(self) -> None:
- bui.containerwidget(edit=self._root_widget, transition='out_right')
+ # bui.containerwidget(edit=self._root_widget, transition='out_right')
+ self.main_window_back()
self._callback(self._existing_entry)
diff --git a/src/assets/ba_data/python/bauiv1lib/specialoffer.py b/src/assets/ba_data/python/bauiv1lib/specialoffer.py
index 33853420..8d092e3d 100644
--- a/src/assets/ba_data/python/bauiv1lib/specialoffer.py
+++ b/src/assets/ba_data/python/bauiv1lib/specialoffer.py
@@ -283,24 +283,24 @@ class SpecialOfferWindow(bui.Window):
)
# Add ticket button only if this is ticket-purchasable.
- if isinstance(offer.get('price'), int):
- self._get_tickets_button = bui.buttonwidget(
- parent=self._root_widget,
- position=(self._width - 125, self._height - 68),
- size=(90, 55),
- scale=1.0,
- button_type='square',
- color=(0.7, 0.5, 0.85),
- textcolor=(0.2, 1, 0.2),
- autoselect=True,
- label=bui.Lstr(resource='getTicketsWindow.titleText'),
- on_activate_call=self._on_get_more_tickets_press,
- )
+ # if isinstance(offer.get('price'), int):
+ # self._get_tickets_button = bui.buttonwidget(
+ # parent=self._root_widget,
+ # position=(self._width - 125, self._height - 68),
+ # size=(90, 55),
+ # scale=1.0,
+ # button_type='square',
+ # color=(0.7, 0.5, 0.85),
+ # textcolor=(0.2, 1, 0.2),
+ # autoselect=True,
+ # label=bui.Lstr(resource='getTicketsWindow.titleText'),
+ # on_activate_call=self._on_get_more_tickets_press,
+ # )
- self._ticket_text_update_timer = bui.AppTimer(
- 1.0, bui.WeakCall(self._update_tickets_text), repeat=True
- )
- self._update_tickets_text()
+ # self._ticket_text_update_timer = bui.AppTimer(
+ # 1.0, bui.WeakCall(self._update_tickets_text), repeat=True
+ # )
+ # self._update_tickets_text()
self._update_timer = bui.AppTimer(
1.0, bui.WeakCall(self._update), repeat=True
@@ -421,37 +421,37 @@ class SpecialOfferWindow(bui.Window):
bui.containerwidget(edit=self._root_widget, transition=transition)
- def _update_tickets_text(self) -> None:
- from babase import SpecialChar
+ # def _update_tickets_text(self) -> None:
+ # from babase import SpecialChar
- plus = bui.app.plus
- assert plus is not None
+ # plus = bui.app.plus
+ # assert plus is not None
- if not self._root_widget:
- return
- sval: str | bui.Lstr
- if plus.get_v1_account_state() == 'signed_in':
- sval = bui.charstr(SpecialChar.TICKET) + str(
- plus.get_v1_account_ticket_count()
- )
- else:
- sval = bui.Lstr(resource='getTicketsWindow.titleText')
- bui.buttonwidget(edit=self._get_tickets_button, label=sval)
+ # if not self._root_widget:
+ # return
+ # sval: str | bui.Lstr
+ # if plus.get_v1_account_state() == 'signed_in':
+ # sval = bui.charstr(SpecialChar.TICKET) + str(
+ # plus.get_v1_account_ticket_count()
+ # )
+ # else:
+ # sval = bui.Lstr(resource='getTicketsWindow.titleText')
+ # bui.buttonwidget(edit=self._get_tickets_button, label=sval)
- def _on_get_more_tickets_press(self) -> None:
- from bauiv1lib import account
- from bauiv1lib import gettickets
+ # def _on_get_more_tickets_press(self) -> None:
+ # from bauiv1lib import account
+ # from bauiv1lib import gettickets
- plus = bui.app.plus
- assert plus is not None
+ # plus = bui.app.plus
+ # assert plus is not None
- if plus.get_v1_account_state() != 'signed_in':
- account.show_sign_in_prompt()
- return
- gettickets.GetTicketsWindow(modal=True).get_root_widget()
+ # if plus.get_v1_account_state() != 'signed_in':
+ # account.show_sign_in_prompt()
+ # return
+ # gettickets.GetTicketsWindow(modal=True).get_root_widget()
def _purchase(self) -> None:
- from bauiv1lib import gettickets
+ # from bauiv1lib import gettickets
from bauiv1lib import confirm
plus = bui.app.plus
@@ -474,7 +474,7 @@ class SpecialOfferWindow(bui.Window):
except Exception:
ticket_count = None
if ticket_count is not None and ticket_count < self._offer['price']:
- gettickets.show_get_tickets_prompt()
+ # gettickets.show_get_tickets_prompt()
bui.getsound('error').play()
return
diff --git a/src/assets/ba_data/python/bauiv1lib/store/browser.py b/src/assets/ba_data/python/bauiv1lib/store/browser.py
index 8ab52102..6a41feed 100644
--- a/src/assets/ba_data/python/bauiv1lib/store/browser.py
+++ b/src/assets/ba_data/python/bauiv1lib/store/browser.py
@@ -13,7 +13,7 @@ import weakref
import datetime
from enum import Enum
from threading import Thread
-from typing import TYPE_CHECKING
+from typing import TYPE_CHECKING, override
from efro.util import utc_now
from efro.error import CommunicationError
@@ -26,7 +26,7 @@ if TYPE_CHECKING:
MERCH_LINK_KEY = 'Merch Link'
-class StoreBrowserWindow(bui.Window):
+class StoreBrowserWindow(bui.MainWindow):
"""Window for browsing the store."""
class TabID(Enum):
@@ -40,7 +40,7 @@ class StoreBrowserWindow(bui.Window):
def __init__(
self,
- transition: str = 'in_right',
+ transition: str | None = 'in_right',
modal: bool = False,
show_tab: StoreBrowserWindow.TabID | None = None,
on_close_call: Callable[[], Any] | None = None,
@@ -58,16 +58,11 @@ class StoreBrowserWindow(bui.Window):
bui.set_analytics_screen('Store Window')
- scale_origin: tuple[float, float] | None
-
- # If they provided an origin-widget, scale up from that.
+ # Need to store this ourself for modal mode.
if origin_widget is not None:
self._transition_out = 'out_scale'
- scale_origin = origin_widget.get_screen_space_center()
- transition = 'in_scale'
else:
self._transition_out = 'out_right'
- scale_origin = None
self.button_infos: dict[str, dict[str, Any]] | None = None
self.update_buttons_timer: bui.AppTimer | None = None
@@ -77,10 +72,10 @@ class StoreBrowserWindow(bui.Window):
self._on_close_call = on_close_call
self._show_tab = show_tab
self._modal = modal
- self._width = 1440 if uiscale is bui.UIScale.SMALL else 1040
- self._x_inset = x_inset = 200 if uiscale is bui.UIScale.SMALL else 0
+ self._width = 1670 if uiscale is bui.UIScale.SMALL else 1040
+ self._x_inset = x_inset = 310 if uiscale is bui.UIScale.SMALL else 0
self._height = (
- 578
+ 538
if uiscale is bui.UIScale.SMALL
else 645 if uiscale is bui.UIScale.MEDIUM else 800
)
@@ -94,20 +89,24 @@ class StoreBrowserWindow(bui.Window):
super().__init__(
root_widget=bui.containerwidget(
size=(self._width, self._height + extra_top),
- transition=transition,
- toolbar_visibility='menu_full',
+ toolbar_visibility=(
+ 'menu_store'
+ if uiscale is bui.UIScale.SMALL
+ else 'menu_full'
+ ),
scale=(
1.3
if uiscale is bui.UIScale.SMALL
else 0.9 if uiscale is bui.UIScale.MEDIUM else 0.8
),
- scale_origin_stack_offset=scale_origin,
stack_offset=(
- (0, -5)
+ (0, 10)
if uiscale is bui.UIScale.SMALL
else (0, 0) if uiscale is bui.UIScale.MEDIUM else (0, 0)
),
- )
+ ),
+ transition=transition,
+ origin_widget=origin_widget,
)
self._back_button = btn = bui.buttonwidget(
@@ -120,48 +119,60 @@ class StoreBrowserWindow(bui.Window):
button_type=None if self._modal else 'back',
on_activate_call=self._back,
)
- bui.containerwidget(edit=self._root_widget, cancel_button=btn)
- self._ticket_count_text: bui.Widget | None = None
- self._get_tickets_button: bui.Widget | None = None
-
- if app.classic.allow_ticket_purchases:
- self._get_tickets_button = bui.buttonwidget(
- parent=self._root_widget,
- size=(210, 65),
- on_activate_call=self._on_get_more_tickets_press,
- autoselect=True,
- scale=0.9,
- text_scale=1.4,
- left_widget=self._back_button,
- color=(0.7, 0.5, 0.85),
- textcolor=(0.2, 1.0, 0.2),
- label=bui.Lstr(resource='getTicketsWindow.titleText'),
+ if uiscale is bui.UIScale.SMALL:
+ self._back_button.delete()
+ bui.containerwidget(
+ edit=self._root_widget, on_cancel_call=self._back
)
+ # backbutton = bui.get_special_widget('back_button')
+ backbuttonspecial = True
else:
- self._ticket_count_text = bui.textwidget(
- parent=self._root_widget,
- size=(210, 64),
- color=(0.2, 1.0, 0.2),
- h_align='center',
- v_align='center',
- )
+ bui.containerwidget(edit=self._root_widget, cancel_button=btn)
+ # backbutton = self._back_button
+ backbuttonspecial = False
+
+ # self._ticket_count_text: bui.Widget | None = None
+ # self._get_tickets_button: bui.Widget | None = None
+
+ # if bool(False):
+ # if app.classic.allow_ticket_purchases:
+ # self._get_tickets_button = bui.buttonwidget(
+ # parent=self._root_widget,
+ # size=(210, 65),
+ # on_activate_call=self._on_get_more_tickets_press,
+ # autoselect=True,
+ # scale=0.9,
+ # text_scale=1.4,
+ # left_widget=backbutton,
+ # color=(0.7, 0.5, 0.85),
+ # textcolor=(0.2, 1.0, 0.2),
+ # label=bui.Lstr(resource='getTicketsWindow.titleText'),
+ # )
+ # else:
+ # self._ticket_count_text = bui.textwidget(
+ # parent=self._root_widget,
+ # size=(210, 64),
+ # color=(0.2, 1.0, 0.2),
+ # h_align='center',
+ # v_align='center',
+ # )
# Move this dynamically to keep it out of the way of the party icon.
- self._update_get_tickets_button_pos()
- self._get_ticket_pos_update_timer = bui.AppTimer(
- 1.0,
- bui.WeakCall(self._update_get_tickets_button_pos),
- repeat=True,
- )
- if self._get_tickets_button:
- bui.widget(
- edit=self._back_button, right_widget=self._get_tickets_button
- )
- self._ticket_text_update_timer = bui.AppTimer(
- 1.0, bui.WeakCall(self._update_tickets_text), repeat=True
- )
- self._update_tickets_text()
+ # self._update_get_tickets_button_pos()
+ # self._get_ticket_pos_update_timer = bui.AppTimer(
+ # 1.0,
+ # bui.WeakCall(self._update_get_tickets_button_pos),
+ # repeat=True,
+ # )
+ # if self._get_tickets_button and not backbuttonspecial:
+ # bui.widget(
+ # edit=self._back_button, right_widget=self._get_tickets_button
+ # )
+ # self._ticket_text_update_timer = bui.AppTimer(
+ # 1.0, bui.WeakCall(self._update_tickets_text), repeat=True
+ # )
+ # self._update_tickets_text()
if (
app.classic.platform in ['mac', 'ios']
@@ -183,17 +194,20 @@ class StoreBrowserWindow(bui.Window):
bui.textwidget(
parent=self._root_widget,
- position=(self._width * 0.5, self._height - 44),
+ position=(
+ self._width * 0.5,
+ self._height - (53 if uiscale is bui.UIScale.SMALL else 44),
+ ),
size=(0, 0),
color=app.ui_v1.title_color,
scale=1.5,
h_align='center',
v_align='center',
text=bui.Lstr(resource='storeText'),
- maxwidth=420,
+ maxwidth=290,
)
- if not self._modal:
+ if not self._modal and not backbuttonspecial:
bui.buttonwidget(
edit=self._back_button,
button_type='backSmall',
@@ -302,15 +316,27 @@ class StoreBrowserWindow(bui.Window):
)
self._update_tabs()
- if self._get_tickets_button:
+ # if self._get_tickets_button:
+ # last_tab_button = self._tab_row.tabs[tabs_def[-1][0]].button
+ # bui.widget(
+ # edit=self._get_tickets_button, down_widget=last_tab_button
+ # )
+ # bui.widget(
+ # edit=last_tab_button,
+ # up_widget=self._get_tickets_button,
+ # right_widget=self._get_tickets_button,
+ # )
+
+ if uiscale is bui.UIScale.SMALL:
+ first_tab_button = self._tab_row.tabs[tabs_def[0][0]].button
last_tab_button = self._tab_row.tabs[tabs_def[-1][0]].button
bui.widget(
- edit=self._get_tickets_button, down_widget=last_tab_button
+ edit=first_tab_button,
+ left_widget=bui.get_special_widget('back_button'),
)
bui.widget(
edit=last_tab_button,
- up_widget=self._get_tickets_button,
- right_widget=self._get_tickets_button,
+ right_widget=bui.get_special_widget('squad_button'),
)
self._scroll_width = self._width - scroll_buffer_h
@@ -320,27 +346,27 @@ class StoreBrowserWindow(bui.Window):
self._status_textwidget: bui.Widget | None = None
self._restore_state()
- def _update_get_tickets_button_pos(self) -> None:
- assert bui.app.classic is not None
- uiscale = bui.app.ui_v1.uiscale
- pos = (
- self._width
- - 252
- - (
- self._x_inset
- + (
- 47
- if uiscale is bui.UIScale.SMALL
- and bui.is_party_icon_visible()
- else 0
- )
- ),
- self._height - 70,
- )
- if self._get_tickets_button:
- bui.buttonwidget(edit=self._get_tickets_button, position=pos)
- if self._ticket_count_text:
- bui.textwidget(edit=self._ticket_count_text, position=pos)
+ # def _update_get_tickets_button_pos(self) -> None:
+ # assert bui.app.classic is not None
+ # uiscale = bui.app.ui_v1.uiscale
+ # pos = (
+ # self._width
+ # - 252
+ # - (
+ # self._x_inset
+ # + (
+ # 47
+ # if uiscale is bui.UIScale.SMALL
+ # and bui.is_party_icon_visible()
+ # else 0
+ # )
+ # ),
+ # self._height - 70,
+ # )
+ # if self._get_tickets_button:
+ # bui.buttonwidget(edit=self._get_tickets_button, position=pos)
+ # if self._ticket_count_text:
+ # bui.textwidget(edit=self._ticket_count_text, position=pos)
def _restore_purchases(self) -> None:
from bauiv1lib import account
@@ -385,24 +411,24 @@ class StoreBrowserWindow(bui.Window):
bui.textwidget(edit=tab_data['text'], text='')
bui.imagewidget(edit=tab_data['img'], opacity=0.0)
- def _update_tickets_text(self) -> None:
- from bauiv1 import SpecialChar
+ # def _update_tickets_text(self) -> None:
+ # from bauiv1 import SpecialChar
- if not self._root_widget:
- return
- plus = bui.app.plus
- assert plus is not None
- sval: str | bui.Lstr
- if plus.get_v1_account_state() == 'signed_in':
- sval = bui.charstr(SpecialChar.TICKET) + str(
- plus.get_v1_account_ticket_count()
- )
- else:
- sval = bui.Lstr(resource='getTicketsWindow.titleText')
- if self._get_tickets_button:
- bui.buttonwidget(edit=self._get_tickets_button, label=sval)
- if self._ticket_count_text:
- bui.textwidget(edit=self._ticket_count_text, text=sval)
+ # if not self._root_widget:
+ # return
+ # plus = bui.app.plus
+ # assert plus is not None
+ # sval: str | bui.Lstr
+ # if plus.get_v1_account_state() == 'signed_in':
+ # sval = bui.charstr(SpecialChar.TICKET) + str(
+ # plus.get_v1_account_ticket_count()
+ # )
+ # else:
+ # sval = bui.Lstr(resource='getTicketsWindow.titleText')
+ # if self._get_tickets_button:
+ # bui.buttonwidget(edit=self._get_tickets_button, label=sval)
+ # if self._ticket_count_text:
+ # bui.textwidget(edit=self._ticket_count_text, text=sval)
def _set_tab(self, tab_id: TabID) -> None:
if self._current_tab is tab_id:
@@ -574,7 +600,8 @@ class StoreBrowserWindow(bui.Window):
"""Attempt to purchase the provided item."""
from bauiv1lib import account
from bauiv1lib.confirm import ConfirmWindow
- from bauiv1lib import gettickets
+
+ # from bauiv1lib import gettickets
assert bui.app.classic is not None
store = bui.app.classic.store
@@ -620,7 +647,8 @@ class StoreBrowserWindow(bui.Window):
our_tickets = plus.get_v1_account_ticket_count()
if price is not None and our_tickets < price:
bui.getsound('error').play()
- gettickets.show_get_tickets_prompt()
+ print('FIXME - show not-enough-tickets info.')
+ # gettickets.show_get_tickets_prompt()
else:
def do_it() -> None:
@@ -1242,6 +1270,20 @@ class StoreBrowserWindow(bui.Window):
maxwidth=self._scroll_width * 0.9,
)
+ @override
+ def get_main_window_state(self) -> bui.MainWindowState:
+ # Support recreating our window for back/refresh purposes.
+ cls = type(self)
+ return bui.BasicMainWindowState(
+ create_call=lambda transition, origin_widget: cls(
+ transition=transition, origin_widget=origin_widget
+ )
+ )
+
+ @override
+ def on_main_window_close(self) -> None:
+ self._save_state()
+
def _save_state(self) -> None:
try:
sel = self._root_widget.get_selected_child()
@@ -1250,9 +1292,9 @@ class StoreBrowserWindow(bui.Window):
for tab_id, tab in self._tab_row.tabs.items()
if sel == tab.button
]
- if sel == self._get_tickets_button:
- sel_name = 'GetTickets'
- elif sel == self._scrollwidget:
+ # if sel == self._get_tickets_button:
+ # sel_name = 'GetTickets'
+ if sel == self._scrollwidget:
sel_name = 'Scroll'
elif sel == self._back_button:
sel_name = 'Back'
@@ -1269,7 +1311,6 @@ class StoreBrowserWindow(bui.Window):
logging.exception('Error saving state for %s.', self)
def _restore_state(self) -> None:
- from efro.util import enum_by_value
try:
sel: bui.Widget | None
@@ -1280,25 +1321,21 @@ class StoreBrowserWindow(bui.Window):
assert isinstance(sel_name, (str, type(None)))
try:
- current_tab = enum_by_value(
- self.TabID, bui.app.config.get('Store Tab')
- )
+ current_tab = self.TabID(bui.app.config.get('Store Tab'))
except ValueError:
current_tab = self.TabID.CHARACTERS
if self._show_tab is not None:
current_tab = self._show_tab
- if sel_name == 'GetTickets' and self._get_tickets_button:
- sel = self._get_tickets_button
- elif sel_name == 'Back':
+ # if sel_name == 'GetTickets' and self._get_tickets_button:
+ # sel = self._get_tickets_button
+ if sel_name == 'Back':
sel = self._back_button
elif sel_name == 'Scroll':
sel = self._scrollwidget
elif isinstance(sel_name, str) and sel_name.startswith('Tab:'):
try:
- sel_tab_id = enum_by_value(
- self.TabID, sel_name.split(':')[-1]
- )
+ sel_tab_id = self.TabID(sel_name.split(':')[-1])
except ValueError:
sel_tab_id = self.TabID.CHARACTERS
sel = self._tab_row.tabs[sel_tab_id].button
@@ -1317,58 +1354,63 @@ class StoreBrowserWindow(bui.Window):
except Exception:
logging.exception('Error restoring state for %s.', self)
- def _on_get_more_tickets_press(self) -> None:
- # pylint: disable=cyclic-import
- from bauiv1lib.account import show_sign_in_prompt
- from bauiv1lib.gettickets import GetTicketsWindow
+ # def _on_get_more_tickets_press(self) -> None:
+ # # pylint: disable=cyclic-import
+ # from bauiv1lib.account import show_sign_in_prompt
+ # from bauiv1lib.gettickets import GetTicketsWindow
- # 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
+ # # 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
- assert plus is not None
+ # plus = bui.app.plus
+ # assert plus is not None
- if plus.get_v1_account_state() != 'signed_in':
- show_sign_in_prompt()
- return
- self._save_state()
- bui.containerwidget(edit=self._root_widget, transition='out_left')
- window = GetTicketsWindow(
- from_modal_store=self._modal,
- store_back_location=self._back_location,
- ).get_root_widget()
- if not self._modal:
- assert bui.app.classic is not None
- bui.app.ui_v1.set_main_menu_window(
- window, from_window=self._root_widget
- )
+ # if plus.get_v1_account_state() != 'signed_in':
+ # show_sign_in_prompt()
+ # return
+ # self._save_state()
+ # bui.containerwidget(edit=self._root_widget, transition='out_left')
+ # window = GetTicketsWindow(
+ # from_modal_store=self._modal,
+ # store_back_location=self._back_location,
+ # )
+ # if not self._modal:
+ # assert bui.app.classic is not None
+ # bui.app.ui_v1.set_main_window(window, from_window=self)
def _back(self) -> None:
# pylint: disable=cyclic-import
- from bauiv1lib.coop.browser import CoopBrowserWindow
- from bauiv1lib.mainmenu import MainMenuWindow
+ # from bauiv1lib.coop.browser import CoopBrowserWindow
+ # 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()
- bui.containerwidget(
- edit=self._root_widget, transition=self._transition_out
- )
- if not self._modal:
- assert bui.app.classic is not None
- if self._back_location == 'CoopBrowserWindow':
- bui.app.ui_v1.set_main_menu_window(
- CoopBrowserWindow(transition='in_left').get_root_widget(),
- from_window=self._root_widget,
- )
- else:
- bui.app.ui_v1.set_main_menu_window(
- MainMenuWindow(transition='in_left').get_root_widget(),
- from_window=self._root_widget,
- )
+
+ if self._modal:
+ bui.containerwidget(
+ edit=self._root_widget, transition=self._transition_out
+ )
+ else:
+ self.main_window_back()
+ # if not self._modal:
+ # assert bui.app.classic is not None
+ # if self._back_location == 'CoopBrowserWindow':
+ # bui.app.ui_v1.set_main_window(
+ # CoopBrowserWindow(transition='in_left'),
+ # from_window=self,
+ # is_back=True,
+ # )
+ # else:
+ # bui.app.ui_v1.set_main_window(
+ # MainMenuWindow(transition='in_left'),
+ # from_window=self,
+ # is_back=True,
+ # is_top_level=True,
+ # )
if self._on_close_call is not None:
self._on_close_call()
diff --git a/src/assets/ba_data/python/bauiv1lib/tournamententry.py b/src/assets/ba_data/python/bauiv1lib/tournamententry.py
index 972fddcf..025ae53b 100644
--- a/src/assets/ba_data/python/bauiv1lib/tournamententry.py
+++ b/src/assets/ba_data/python/bauiv1lib/tournamententry.py
@@ -100,7 +100,7 @@ class TournamentEntryWindow(PopupWindow):
scale=scale,
bg_color=bg_color,
offset=offset,
- toolbar_visibility='menu_currency',
+ toolbar_visibility='menu_store_no_back',
)
self._last_ad_press_time = -9999.0
@@ -273,28 +273,6 @@ class TournamentEntryWindow(PopupWindow):
self._get_tickets_button: bui.Widget | None = None
self._ticket_count_text: bui.Widget | None = None
- if not bui.app.ui_v1.use_toolbars:
- if bui.app.classic.allow_ticket_purchases:
- self._get_tickets_button = bui.buttonwidget(
- parent=self.root_widget,
- position=(self._width - 190 + 105, self._height - 34),
- autoselect=True,
- scale=0.5,
- size=(120, 60),
- textcolor=(0.2, 1, 0.2),
- label=bui.charstr(bui.SpecialChar.TICKET),
- color=(0.65, 0.5, 0.8),
- on_activate_call=self._on_get_tickets_press,
- )
- else:
- self._ticket_count_text = bui.textwidget(
- parent=self.root_widget,
- scale=0.5,
- position=(self._width - 190 + 125, self._height - 34),
- color=(0.2, 1, 0.2),
- h_align='center',
- v_align='center',
- )
self._seconds_remaining = None
@@ -632,7 +610,7 @@ class TournamentEntryWindow(PopupWindow):
bui.apptimer(0 if practice else 1.25, self._transition_out)
def _on_pay_with_tickets_press(self) -> None:
- from bauiv1lib import gettickets
+ # from bauiv1lib import gettickets
plus = bui.app.plus
assert plus is not None
@@ -675,7 +653,8 @@ class TournamentEntryWindow(PopupWindow):
ticket_count = None
ticket_cost = self._purchase_price
if ticket_count is not None and ticket_count < ticket_cost:
- gettickets.show_get_tickets_prompt()
+ # gettickets.show_get_tickets_prompt()
+ print('FIXME - show not-enough-tickets msg.')
bui.getsound('error').play()
self._transition_out()
return
@@ -780,19 +759,19 @@ class TournamentEntryWindow(PopupWindow):
plus.run_v1_account_transactions()
self._launch()
- def _on_get_tickets_press(self) -> None:
- from bauiv1lib import gettickets
+ # def _on_get_tickets_press(self) -> None:
+ # from bauiv1lib import gettickets
- # If we're already entering, ignore presses.
- if self._entering:
- return
+ # # If we're already entering, ignore presses.
+ # if self._entering:
+ # return
- # Bring up get-tickets window and then kill ourself (we're on the
- # overlay layer so we'd show up above it).
- gettickets.GetTicketsWindow(
- modal=True, origin_widget=self._get_tickets_button
- )
- self._transition_out()
+ # # Bring up get-tickets window and then kill ourself (we're on the
+ # # overlay layer so we'd show up above it).
+ # gettickets.GetTicketsWindow(
+ # modal=True, origin_widget=self._get_tickets_button
+ # )
+ # self._transition_out()
def _on_cancel(self) -> None:
plus = bui.app.plus
diff --git a/src/assets/ba_data/python/bauiv1lib/v2upgrade.py b/src/assets/ba_data/python/bauiv1lib/v2upgrade.py
index 1ce5b3be..381500e0 100644
--- a/src/assets/ba_data/python/bauiv1lib/v2upgrade.py
+++ b/src/assets/ba_data/python/bauiv1lib/v2upgrade.py
@@ -11,7 +11,6 @@ class V2UpgradeWindow(bui.Window):
"""A window presenting a URL to the user visually."""
def __init__(self, login_name: str, code: str):
- from bauiv1lib.account.settings import show_what_is_v2_page
app = bui.app
assert app.classic is not None
@@ -116,3 +115,12 @@ class V2UpgradeWindow(bui.Window):
def _done(self) -> None:
bui.containerwidget(edit=self._root_widget, transition='out_left')
+
+
+def show_what_is_v2_page() -> None:
+ """Show the webpage describing V2 accounts."""
+ plus = bui.app.plus
+ assert plus is not None
+
+ bamasteraddr = plus.get_master_server_address(version=2)
+ bui.open_url(f'{bamasteraddr}/whatisv2')
diff --git a/src/assets/ba_data/python/bauiv1lib/watch.py b/src/assets/ba_data/python/bauiv1lib/watch.py
index 84ef3c87..7d6ced1a 100644
--- a/src/assets/ba_data/python/bauiv1lib/watch.py
+++ b/src/assets/ba_data/python/bauiv1lib/watch.py
@@ -7,7 +7,7 @@ from __future__ import annotations
import os
import logging
from enum import Enum
-from typing import TYPE_CHECKING, cast
+from typing import TYPE_CHECKING, cast, override
import bascenev1 as bs
import bauiv1 as bui
@@ -16,7 +16,7 @@ if TYPE_CHECKING:
from typing import Any
-class WatchWindow(bui.Window):
+class WatchWindow(bui.MainWindow):
"""Window for watching replays."""
class TabID(Enum):
@@ -31,20 +31,9 @@ class WatchWindow(bui.Window):
origin_widget: bui.Widget | None = None,
):
# pylint: disable=too-many-locals
- # pylint: disable=too-many-statements
from bauiv1lib.tabs import TabRow
bui.set_analytics_screen('Watch Window')
- scale_origin: tuple[float, float] | None
- if origin_widget is not None:
- self._transition_out = 'out_scale'
- scale_origin = origin_widget.get_screen_space_center()
- transition = 'in_scale'
- else:
- self._transition_out = 'out_right'
- scale_origin = None
- assert bui.app.classic is not None
- bui.app.ui_v1.set_main_menu_location('Watch')
self._tab_data: dict[str, Any] = {}
self._my_replays_scroll_width: float | None = None
self._my_replays_watch_replay_button: bui.Widget | None = None
@@ -58,7 +47,7 @@ class WatchWindow(bui.Window):
self._width = 1440 if uiscale is bui.UIScale.SMALL else 1040
x_inset = 200 if uiscale is bui.UIScale.SMALL else 0
self._height = (
- 578
+ 570
if uiscale is bui.UIScale.SMALL
else 670 if uiscale is bui.UIScale.MEDIUM else 800
)
@@ -68,25 +57,29 @@ class WatchWindow(bui.Window):
super().__init__(
root_widget=bui.containerwidget(
size=(self._width, self._height + extra_top),
- transition=transition,
- toolbar_visibility='menu_minimal',
- scale_origin_stack_offset=scale_origin,
- scale=(
- 1.3
+ toolbar_visibility=(
+ 'menu_minimal'
if uiscale is bui.UIScale.SMALL
- else 0.97 if uiscale is bui.UIScale.MEDIUM else 0.8
+ else 'menu_full'
+ ),
+ scale=(
+ 1.32
+ if uiscale is bui.UIScale.SMALL
+ else 0.85 if uiscale is bui.UIScale.MEDIUM else 0.65
),
stack_offset=(
- (0, -10)
+ (0, 30)
if uiscale is bui.UIScale.SMALL
- else (0, 15) if uiscale is bui.UIScale.MEDIUM else (0, 0)
+ else (0, 0) if uiscale is bui.UIScale.MEDIUM else (0, 0)
),
- )
+ ),
+ transition=transition,
+ origin_widget=origin_widget,
)
- if uiscale is bui.UIScale.SMALL and bui.app.ui_v1.use_toolbars:
+ if uiscale is bui.UIScale.SMALL:
bui.containerwidget(
- edit=self._root_widget, on_cancel_call=self._back
+ edit=self._root_widget, on_cancel_call=self.main_window_back
)
self._back_button = None
else:
@@ -98,7 +91,7 @@ class WatchWindow(bui.Window):
scale=1.1,
label=bui.Lstr(resource='backText'),
button_type='back',
- on_activate_call=self._back,
+ on_activate_call=self.main_window_back,
)
bui.containerwidget(edit=self._root_widget, cancel_button=btn)
bui.buttonwidget(
@@ -110,13 +103,20 @@ class WatchWindow(bui.Window):
bui.textwidget(
parent=self._root_widget,
- position=(self._width * 0.5, self._height - 38),
+ position=(
+ self._width * 0.5,
+ self._height - (65 if uiscale is bui.UIScale.SMALL else 38),
+ ),
size=(0, 0),
color=bui.app.ui_v1.title_color,
- scale=1.5,
+ scale=0.7 if uiscale is bui.UIScale.SMALL else 1.5,
h_align='center',
v_align='center',
- text=bui.Lstr(resource=f'{self._r}.titleText'),
+ text=(
+ ''
+ if uiscale is bui.UIScale.SMALL
+ else bui.Lstr(resource=f'{self._r}.titleText')
+ ),
maxwidth=400,
)
@@ -139,18 +139,15 @@ class WatchWindow(bui.Window):
on_select_call=self._set_tab,
)
- if bui.app.ui_v1.use_toolbars:
- first_tab = self._tab_row.tabs[tabdefs[0][0]]
- last_tab = self._tab_row.tabs[tabdefs[-1][0]]
- bui.widget(
- edit=last_tab.button,
- right_widget=bui.get_special_widget('party_button'),
- )
- if uiscale is bui.UIScale.SMALL:
- bbtn = bui.get_special_widget('back_button')
- bui.widget(
- edit=first_tab.button, up_widget=bbtn, left_widget=bbtn
- )
+ first_tab = self._tab_row.tabs[tabdefs[0][0]]
+ last_tab = self._tab_row.tabs[tabdefs[-1][0]]
+ bui.widget(
+ edit=last_tab.button,
+ right_widget=bui.get_special_widget('squad_button'),
+ )
+ if uiscale is bui.UIScale.SMALL:
+ bbtn = bui.get_special_widget('back_button')
+ bui.widget(edit=first_tab.button, up_widget=bbtn, left_widget=bbtn)
self._scroll_width = self._width - scroll_buffer_h
self._scroll_height = self._height - 180
@@ -174,6 +171,20 @@ class WatchWindow(bui.Window):
self._restore_state()
+ @override
+ def get_main_window_state(self) -> bui.MainWindowState:
+ # Support recreating our window for back/refresh purposes.
+ cls = type(self)
+ return bui.BasicMainWindowState(
+ create_call=lambda transition, origin_widget: cls(
+ transition=transition, origin_widget=origin_widget
+ )
+ )
+
+ @override
+ def on_main_window_close(self) -> None:
+ self._save_state()
+
def _set_tab(self, tab_id: TabID) -> None:
# pylint: disable=too-many-locals
@@ -281,7 +292,7 @@ class WatchWindow(bui.Window):
)
bui.widget(edit=btn1, up_widget=self._tab_row.tabs[tab_id].button)
assert bui.app.classic is not None
- if uiscale is bui.UIScale.SMALL and bui.app.ui_v1.use_toolbars:
+ if uiscale is bui.UIScale.SMALL:
bui.widget(
edit=btn1,
left_widget=bui.get_special_widget('back_button'),
@@ -611,8 +622,6 @@ class WatchWindow(bui.Window):
logging.exception('Error saving state for %s.', self)
def _restore_state(self) -> None:
- from efro.util import enum_by_value
-
try:
sel: bui.Widget | None
assert bui.app.classic is not None
@@ -621,9 +630,7 @@ class WatchWindow(bui.Window):
)
assert isinstance(sel_name, (str, type(None)))
try:
- current_tab = enum_by_value(
- self.TabID, bui.app.config.get('Watch Tab')
- )
+ current_tab = self.TabID(bui.app.config.get('Watch Tab'))
except ValueError:
current_tab = self.TabID.MY_REPLAYS
self._set_tab(current_tab)
@@ -634,9 +641,7 @@ class WatchWindow(bui.Window):
sel = self._tab_container
elif isinstance(sel_name, str) and sel_name.startswith('Tab:'):
try:
- sel_tab_id = enum_by_value(
- self.TabID, sel_name.split(':')[-1]
- )
+ sel_tab_id = self.TabID(sel_name.split(':')[-1])
except ValueError:
sel_tab_id = self.TabID.MY_REPLAYS
sel = self._tab_row.tabs[sel_tab_id].button
@@ -648,20 +653,3 @@ class WatchWindow(bui.Window):
bui.containerwidget(edit=self._root_widget, selected_child=sel)
except Exception:
logging.exception('Error restoring state for %s.', self)
-
- def _back(self) -> None:
- 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()
- bui.containerwidget(
- edit=self._root_widget, transition=self._transition_out
- )
- assert bui.app.classic is not None
- bui.app.ui_v1.set_main_menu_window(
- MainMenuWindow(transition='in_left').get_root_widget(),
- from_window=self._root_widget,
- )
diff --git a/src/ballistica/base/app_adapter/app_adapter_sdl.cc b/src/ballistica/base/app_adapter/app_adapter_sdl.cc
index 0a3e6a92..d574f665 100644
--- a/src/ballistica/base/app_adapter/app_adapter_sdl.cc
+++ b/src/ballistica/base/app_adapter/app_adapter_sdl.cc
@@ -647,8 +647,14 @@ void AppAdapterSDL::ReloadRenderer_(const GraphicsSettings_* settings) {
fullscreen_ = settings->fullscreen;
// A reasonable default window size.
- auto width = static_cast(kBaseVirtualResX * 0.8f);
- auto height = static_cast(kBaseVirtualResY * 0.8f);
+ int width, height;
+ if (g_base->ui->scale() == UIScale::kSmall) {
+ width = static_cast(kBaseVirtualResSmallX * 0.8f);
+ height = static_cast(kBaseVirtualResSmallY * 0.8f);
+ } else {
+ width = static_cast(kBaseVirtualResX * 0.8f);
+ height = static_cast(kBaseVirtualResY * 0.8f);
+ }
uint32_t flags = SDL_WINDOW_OPENGL | SDL_WINDOW_SHOWN
| SDL_WINDOW_ALLOW_HIGHDPI | SDL_WINDOW_RESIZABLE;
diff --git a/src/ballistica/base/app_mode/app_mode_empty.cc b/src/ballistica/base/app_mode/empty_app_mode.cc
similarity index 65%
rename from src/ballistica/base/app_mode/app_mode_empty.cc
rename to src/ballistica/base/app_mode/empty_app_mode.cc
index 6f409f28..2cd147a1 100644
--- a/src/ballistica/base/app_mode/app_mode_empty.cc
+++ b/src/ballistica/base/app_mode/empty_app_mode.cc
@@ -1,35 +1,50 @@
// Released under the MIT License. See LICENSE for details.
-#include "ballistica/base/app_mode/app_mode_empty.h"
+#include "ballistica/base/app_mode/empty_app_mode.h"
#include "ballistica/base/graphics/component/simple_component.h"
namespace ballistica::base {
-static AppModeEmpty* g_app_mode_empty{};
+static EmptyAppMode* g_empty_app_mode{};
-AppModeEmpty::AppModeEmpty() = default;
+EmptyAppMode::EmptyAppMode() = default;
-auto AppModeEmpty::GetSingleton() -> AppModeEmpty* {
+auto EmptyAppMode::GetSingleton() -> EmptyAppMode* {
assert(g_base == nullptr || g_base->InLogicThread());
- if (g_app_mode_empty == nullptr) {
- g_app_mode_empty = new AppModeEmpty();
+ if (g_empty_app_mode == nullptr) {
+ g_empty_app_mode = new EmptyAppMode();
}
- return g_app_mode_empty;
+ return g_empty_app_mode;
}
-void AppModeEmpty::Reset() {
+void EmptyAppMode::OnActivate() {
+ assert(g_base->InLogicThread());
+
+ Reset_();
+}
+
+void EmptyAppMode::Reset_() {
+ reset_count_++;
+
// When we are first created (for use as a placeholder before any
// app-modes are set) we just draw nothing. However once we actually get
// reset for use as a an explicit app mode, we do our hello thing.
- hello_mode_ = true;
+ hello_mode_ = (reset_count_ > 1);
- // Fade in if we currently aren't.
- g_base->graphics->FadeScreen(true, 250, nullptr);
+ // Reset the engine to a default state.
+ g_base->Reset();
+
+ // When we're a 'real' app-mode, fade in if we currently aren't. Otherwise
+ // let's stay faded out and let the first actual app-mode do the fading
+ // in.
+ if (hello_mode_) {
+ g_base->graphics->FadeScreen(true, 250, nullptr);
+ }
}
-void AppModeEmpty::DrawWorld(base::FrameDef* frame_def) {
+void EmptyAppMode::DrawWorld(base::FrameDef* frame_def) {
if (!hello_mode_) {
return;
}
diff --git a/src/ballistica/base/app_mode/app_mode_empty.h b/src/ballistica/base/app_mode/empty_app_mode.h
similarity index 62%
rename from src/ballistica/base/app_mode/app_mode_empty.h
rename to src/ballistica/base/app_mode/empty_app_mode.h
index 17c97b53..256763d9 100644
--- a/src/ballistica/base/app_mode/app_mode_empty.h
+++ b/src/ballistica/base/app_mode/empty_app_mode.h
@@ -1,7 +1,7 @@
// Released under the MIT License. See LICENSE for details.
-#ifndef BALLISTICA_BASE_APP_MODE_APP_MODE_EMPTY_H_
-#define BALLISTICA_BASE_APP_MODE_APP_MODE_EMPTY_H_
+#ifndef BALLISTICA_BASE_APP_MODE_EMPTY_APP_MODE_H_
+#define BALLISTICA_BASE_APP_MODE_EMPTY_APP_MODE_H_
#include
@@ -13,19 +13,21 @@ namespace ballistica::base {
/// An app-mode that doesn't do much of anything in particular. It is set as
/// a default when starting the app, but can also be used for 'hello world'
/// type stuff.
-class AppModeEmpty : public AppMode {
+class EmptyAppMode : public AppMode {
public:
- AppModeEmpty();
+ EmptyAppMode();
- static auto GetSingleton() -> AppModeEmpty*;
- void Reset();
+ static auto GetSingleton() -> EmptyAppMode*;
+ void OnActivate() override;
void DrawWorld(base::FrameDef* frame_def) override;
private:
+ void Reset_();
Object::Ref hello_text_group_;
+ int reset_count_{};
bool hello_mode_{};
};
} // namespace ballistica::base
-#endif // BALLISTICA_BASE_APP_MODE_APP_MODE_EMPTY_H_
+#endif // BALLISTICA_BASE_APP_MODE_EMPTY_APP_MODE_H_
diff --git a/src/ballistica/base/assets/assets.cc b/src/ballistica/base/assets/assets.cc
index 2673d025..2f135ecd 100644
--- a/src/ballistica/base/assets/assets.cc
+++ b/src/ballistica/base/assets/assets.cc
@@ -1352,7 +1352,9 @@ void Assets::SetLanguageKeys(
language_state_++;
// Let some subsystems know that language has changed.
- g_base->app_mode()->LanguageChanged();
+ if (auto* app_mode = g_base->app_mode()) {
+ app_mode->LanguageChanged();
+ }
g_base->ui->LanguageChanged();
g_base->graphics->LanguageChanged();
}
diff --git a/src/ballistica/base/base.cc b/src/ballistica/base/base.cc
index c8919dbd..6266558e 100644
--- a/src/ballistica/base/base.cc
+++ b/src/ballistica/base/base.cc
@@ -3,7 +3,7 @@
#include "ballistica/base/base.h"
#include "ballistica/base/app_adapter/app_adapter.h"
-#include "ballistica/base/app_mode/app_mode_empty.h"
+#include "ballistica/base/app_mode/empty_app_mode.h"
#include "ballistica/base/assets/assets_server.h"
#include "ballistica/base/audio/audio.h"
#include "ballistica/base/audio/audio_server.h"
@@ -40,7 +40,7 @@ BaseFeatureSet* g_base{};
BaseFeatureSet::BaseFeatureSet()
: app_adapter{BaseBuildSwitches::CreateAppAdapter()},
app_config{new AppConfig()},
- app_mode_{AppModeEmpty::GetSingleton()},
+ app_mode_{EmptyAppMode::GetSingleton()},
assets{new Assets()},
assets_server{new AssetsServer()},
audio{new Audio()},
@@ -440,7 +440,7 @@ void BaseFeatureSet::set_app_mode(AppMode* mode) {
// Redundant sets should not happen (make an exception here for empty mode
// since that's in place before any app mode is officially set).
- if (mode == app_mode_ && mode != AppModeEmpty::GetSingleton()) {
+ if (mode == app_mode_ && mode != EmptyAppMode::GetSingleton()) {
Log(LogLevel::kWarning,
"set_app_mode called with already-current app-mode; unexpected.");
}
@@ -979,4 +979,12 @@ void BaseFeatureSet::SetAppActive(bool active) {
[] { g_base->logic->OnAppActiveChanged(); });
}
+void BaseFeatureSet::Reset() {
+ ui->Reset();
+ input->Reset();
+ graphics->Reset();
+ python->Reset();
+ audio->Reset();
+}
+
} // namespace ballistica::base
diff --git a/src/ballistica/base/base.h b/src/ballistica/base/base.h
index 93acf16d..8c0c76c4 100644
--- a/src/ballistica/base/base.h
+++ b/src/ballistica/base/base.h
@@ -713,6 +713,11 @@ class BaseFeatureSet : public FeatureSetNativeComponent,
/// High level screen-message call usable from any thread.
void ScreenMessage(const std::string& s, const Vector3f& color) override;
+ /// Has the app bootstrapping phase completed? The bootstrapping phase
+ /// involves initial screen/graphics setup. Asset loading is not allowed
+ /// until it is complete.
+ auto IsAppBootstrapped() const -> bool override;
+
/// Has StartApp been called (and completely finished its work)? Code that
/// sends calls/messages to other threads or otherwise uses app
/// functionality may want to check this to avoid crashes. Note that some
@@ -720,11 +725,6 @@ class BaseFeatureSet : public FeatureSetNativeComponent,
/// IsAppBootstrapped returns true. This call is thread safe.
auto IsAppStarted() const -> bool override;
- /// Has the app bootstrapping phase completed? The bootstrapping phase
- /// involves initial screen/graphics setup. Asset loading is not allowed
- /// until it is complete.
- auto IsAppBootstrapped() const -> bool override;
-
void PlusDirectSendV1CloudLogs(const std::string& prefix,
const std::string& suffix, bool instant,
int* result) override;
@@ -773,14 +773,14 @@ class BaseFeatureSet : public FeatureSetNativeComponent,
/// 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;
+ /// Set current clipboard text. Raises an Exception if clipboard is
+ /// unsupported.
+ void ClipboardSetText(const std::string& text);
+
// Const subsystems.
AppAdapter* const app_adapter;
AppConfig* const app_config;
@@ -819,6 +819,8 @@ class BaseFeatureSet : public FeatureSetNativeComponent,
auto app_active() -> bool const { return app_active_; }
+ void Reset();
+
private:
BaseFeatureSet();
void LogVersionInfo_();
diff --git a/src/ballistica/base/graphics/graphics.cc b/src/ballistica/base/graphics/graphics.cc
index 41adaba9..772a0497 100644
--- a/src/ballistica/base/graphics/graphics.cc
+++ b/src/ballistica/base/graphics/graphics.cc
@@ -19,6 +19,7 @@
#include "ballistica/base/python/support/python_context_call.h"
#include "ballistica/base/support/app_config.h"
#include "ballistica/base/ui/ui.h"
+#include "ballistica/shared/ballistica.h"
#include "ballistica/shared/foundation/event_loop.h"
namespace ballistica::base {
@@ -728,6 +729,9 @@ void Graphics::DrawUI(FrameDef* frame_def) {
// Just do generic thing in our default implementation.
// Special variants like GraphicsVR may do fancier stuff here.
g_base->ui->Draw(frame_def);
+
+ // We may want to see the bounds of our virtual screen.
+ DrawUIBounds(frame_def->overlay_pass());
}
void Graphics::DrawDevUI(FrameDef* frame_def) {
@@ -1500,14 +1504,25 @@ void Graphics::DrawRadialMeter(MeshIndexedSimpleFull* m, float amt) {
void Graphics::OnScreenSizeChange() {}
void Graphics::CalcVirtualRes_(float* x, float* y) {
+ assert(g_base);
+ float base_virtual_res_x;
+ float base_virtual_res_y;
+ if (g_base->ui->scale() == UIScale::kSmall) {
+ base_virtual_res_x = kBaseVirtualResSmallX;
+ base_virtual_res_y = kBaseVirtualResSmallY;
+ } else {
+ base_virtual_res_x = kBaseVirtualResX;
+ base_virtual_res_y = kBaseVirtualResY;
+ }
+
float x_in = *x;
float y_in = *y;
- if (*x / *y > static_cast(kBaseVirtualResX)
- / static_cast(kBaseVirtualResY)) {
- *y = kBaseVirtualResY;
+ if (*x / *y > static_cast(base_virtual_res_x)
+ / static_cast(base_virtual_res_y)) {
+ *y = base_virtual_res_y;
*x = *y * (x_in / y_in);
} else {
- *x = kBaseVirtualResX;
+ *x = base_virtual_res_x;
*y = *x * (y_in / x_in);
}
}
@@ -1701,4 +1716,35 @@ void Graphics::UpdatePlaceholderSettings() {
settings()->texture_quality, client_context()->auto_texture_quality);
}
+void Graphics::DrawUIBounds(RenderPass* pass) {
+ // We can optionally draw a guide to show the edges of the overlay pass
+ if (draw_ui_bounds_) {
+ SimpleComponent c(pass);
+ c.SetColor(1, 0, 0);
+ {
+ auto xf = c.ScopedTransform();
+ // float width = pass->virtual_width();
+ // float height = pass->virtual_height();
+
+ // printf("DIMS %.2f %.2f\n", width, height);
+
+ float width, height;
+ if (g_base->ui->scale() == UIScale::kSmall) {
+ width = kBaseVirtualResSmallX;
+ height = kBaseVirtualResSmallY;
+ } else {
+ width = kBaseVirtualResX;
+ height = kBaseVirtualResY;
+ }
+
+ // Slight offset in z to reduce z fighting.
+ c.Translate(0.5f * pass->virtual_width(), 0.5f * pass->virtual_height(),
+ 0.0f);
+ c.Scale(width, height, 0.01f);
+ c.DrawMeshAsset(g_base->assets->SysMesh(SysMeshID::kOverlayGuide));
+ }
+ c.Submit();
+ }
+}
+
} // namespace ballistica::base
diff --git a/src/ballistica/base/graphics/graphics.h b/src/ballistica/base/graphics/graphics.h
index 687f07ea..5fc0c02c 100644
--- a/src/ballistica/base/graphics/graphics.h
+++ b/src/ballistica/base/graphics/graphics.h
@@ -150,6 +150,8 @@ class Graphics {
r, g, b, a);
}
+ void DrawUIBounds(RenderPass* pass);
+
// Enable progress bar drawing locally.
void EnableProgressBar(bool fade_in);
@@ -356,6 +358,8 @@ class Graphics {
assert(client_context_snapshot_.Exists());
return client_context_snapshot_.Get()->Get();
}
+ auto draw_ui_bounds() const { return draw_ui_bounds_; }
+ void set_draw_ui_bounds(bool val) { draw_ui_bounds_ = val; }
ScreenMessages* const screenmessages;
@@ -415,6 +419,7 @@ class Graphics {
bool applied_app_config_{};
bool sent_initial_graphics_settings_{};
bool got_screen_resolution_{};
+ bool draw_ui_bounds_{};
Vector3f shadow_offset_{0.0f, 0.0f, 0.0f};
Vector2f shadow_scale_{1.0f, 1.0f};
Vector3f tint_{1.0f, 1.0f, 1.0f};
diff --git a/src/ballistica/base/graphics/graphics_vr.cc b/src/ballistica/base/graphics/graphics_vr.cc
index 3325555b..ab60fcdd 100644
--- a/src/ballistica/base/graphics/graphics_vr.cc
+++ b/src/ballistica/base/graphics/graphics_vr.cc
@@ -87,8 +87,6 @@ auto GraphicsVR::ValueTest(const std::string& arg, double* absval,
*outval = ValueTestFloat(&vr_overlay_scale_, absval, deltaval);
} else if (arg == "lockVROverlay") {
*outval = ValueTestBool(&lock_vr_overlay_, absval, deltaval);
- } else if (arg == "showOverlayBounds") {
- *outval = ValueTestBool(&draw_overlay_bounds_, absval, deltaval);
} else if (arg == "headScale") {
*outval = ValueTestFloat(&vr_test_head_scale_, absval, deltaval);
} else if (arg == "vrCamOffsetY") {
@@ -143,9 +141,6 @@ void GraphicsVR::DrawUI(FrameDef* frame_def) {
// In VR mode we have to draw our overlay-flat texture into space as
// part of the regular overlay pass.
DrawVROverlay(frame_def);
-
- // We may want to see the bounds of our overlay.
- DrawOverlayBounds(frame_def->overlay_pass());
}
void GraphicsVR::CalcVROverlayMatrices(FrameDef* frame_def) {
@@ -293,25 +288,6 @@ void GraphicsVR::DrawVROverlay(FrameDef* frame_def) {
}
}
-void GraphicsVR::DrawOverlayBounds(RenderPass* pass) {
- // We can optionally draw a guide to show the edges of the overlay pass
- if (draw_overlay_bounds_) {
- SimpleComponent c(pass);
- c.SetColor(1, 0, 0);
- {
- auto xf = c.ScopedTransform();
- float width = screen_virtual_width();
- float height = screen_virtual_height();
-
- // Slight offset in z to reduce z fighting.
- c.Translate(0.5f * width, 0.5f * height, 1.0f);
- c.Scale(width, height, 100.0f);
- c.DrawMeshAsset(g_base->assets->SysMesh(SysMeshID::kOverlayGuide));
- }
- c.Submit();
- }
-}
-
void GraphicsVR::DrawVRControllers(FrameDef* frame_def) {
if (!g_core->vr_mode()) {
return;
diff --git a/src/ballistica/base/graphics/graphics_vr.h b/src/ballistica/base/graphics/graphics_vr.h
index a365c743..a1d176dd 100644
--- a/src/ballistica/base/graphics/graphics_vr.h
+++ b/src/ballistica/base/graphics/graphics_vr.h
@@ -71,7 +71,6 @@ class GraphicsVR : public Graphics {
auto CalcVROverlayMatrix(const Vector3f& cam_pt,
const Vector3f& cam_target_pt) const -> Matrix44f;
void DrawVROverlay(FrameDef* frame_def);
- void DrawOverlayBounds(RenderPass* pass);
void DrawVRControllers(FrameDef* frame_def);
float vr_overlay_scale_{1.0f};
@@ -84,7 +83,6 @@ class GraphicsVR : public Graphics {
Vector3f vr_overlay_center_{0.0f, 0.0f, 0.0f};
bool vr_overlay_center_enabled_{};
bool lock_vr_overlay_{};
- bool draw_overlay_bounds_{};
float vr_test_head_scale_{kDefaultVRHeadScale};
VRHandsState vr_hands_state_;
};
diff --git a/src/ballistica/base/python/base_python.cc b/src/ballistica/base/python/base_python.cc
index 7d78f684..251544eb 100644
--- a/src/ballistica/base/python/base_python.cc
+++ b/src/ballistica/base/python/base_python.cc
@@ -463,18 +463,10 @@ auto BasePython::GetPyEnum_SpecialChar(PyObject* obj) -> SpecialChar {
return GetPyEnum(BasePython::ObjID::kSpecialCharClass, obj);
}
-// auto BasePython::GetPyEnum_TimeType(PyObject* obj) -> TimeType {
-// return GetPyEnum(BasePython::ObjID::kTimeTypeClass, obj);
-// }
-
auto BasePython::GetPyEnum_QuitType(PyObject* obj) -> QuitType {
return GetPyEnum(BasePython::ObjID::kQuitTypeClass, obj);
}
-// auto BasePython::GetPyEnum_TimeFormat(PyObject* obj) -> TimeFormat {
-// return GetPyEnum(BasePython::ObjID::kTimeFormatClass, obj);
-// }
-
auto BasePython::IsPyEnum_InputType(PyObject* obj) -> bool {
return IsPyEnum(BasePython::ObjID::kInputTypeClass, obj);
}
diff --git a/src/ballistica/base/python/base_python.h b/src/ballistica/base/python/base_python.h
index 61237013..ed3dc3ed 100644
--- a/src/ballistica/base/python/base_python.h
+++ b/src/ballistica/base/python/base_python.h
@@ -154,8 +154,6 @@ class BasePython {
static auto GetPyEnum_Permission(PyObject* obj) -> Permission;
static auto GetPyEnum_SpecialChar(PyObject* obj) -> SpecialChar;
- // static auto GetPyEnum_TimeType(PyObject* obj) -> TimeType;
- // static auto GetPyEnum_TimeFormat(PyObject* obj) -> TimeFormat;
static auto IsPyEnum_InputType(PyObject* obj) -> bool;
static auto GetPyEnum_InputType(PyObject* obj) -> InputType;
static auto GetPyEnum_QuitType(PyObject* obj) -> QuitType;
diff --git a/src/ballistica/base/python/methods/python_methods_base_1.cc b/src/ballistica/base/python/methods/python_methods_base_1.cc
index 97d285bb..4ca96254 100644
--- a/src/ballistica/base/python/methods/python_methods_base_1.cc
+++ b/src/ballistica/base/python/methods/python_methods_base_1.cc
@@ -3,7 +3,7 @@
#include "ballistica/base/python/methods/python_methods_base_1.h"
#include "ballistica/base/app_adapter/app_adapter.h"
-#include "ballistica/base/app_mode/app_mode_empty.h"
+#include "ballistica/base/app_mode/empty_app_mode.h"
#include "ballistica/base/audio/audio_server.h"
#include "ballistica/base/graphics/graphics_server.h"
#include "ballistica/base/logic/logic.h"
@@ -1321,29 +1321,27 @@ static PyMethodDef PyUserAgentStringDef = {
"(internal)\n",
};
-// --------------------- on_empty_app_mode_activate ----------------------------
+// --------------------- empty_app_mode_activate ----------------------------
static auto PyOnEmptyAppModeActivate(PyObject* self) -> PyObject* {
BA_PYTHON_TRY;
BA_PRECONDITION(g_base->InLogicThread());
- g_base->set_app_mode(AppModeEmpty::GetSingleton());
- AppModeEmpty::GetSingleton()->Reset();
-
+ g_base->set_app_mode(EmptyAppMode::GetSingleton());
Py_RETURN_NONE;
BA_PYTHON_CATCH;
}
static PyMethodDef PyOnEmptyAppModeActivateDef = {
- "on_empty_app_mode_activate", // name
+ "empty_app_mode_activate", // name
(PyCFunction)PyOnEmptyAppModeActivate, // method
METH_NOARGS, // flags
- "on_empty_app_mode_activate() -> None\n"
+ "empty_app_mode_activate() -> None\n"
"\n"
"(internal)\n",
};
-// --------------------- on_empty_app_mode_deactivate --------------------------
+// --------------------- empty_app_mode_deactivate --------------------------
static auto PyOnEmptyAppModeDeactivate(PyObject* self) -> PyObject* {
BA_PYTHON_TRY;
@@ -1354,38 +1352,38 @@ static auto PyOnEmptyAppModeDeactivate(PyObject* self) -> PyObject* {
}
static PyMethodDef PyOnEmptyAppModeDeactivateDef = {
- "on_empty_app_mode_deactivate", // name
+ "empty_app_mode_deactivate", // name
(PyCFunction)PyOnEmptyAppModeDeactivate, // method
METH_NOARGS, // flags
- "on_empty_app_mode_deactivate() -> None\n"
+ "empty_app_mode_deactivate() -> None\n"
"\n"
"(internal)\n",
};
-// ----------------- empty_app_mode_handle_intent_default ----------------------
+// --------------- empty_app_mode_handle_app_intent_default --------------------
-static auto PyEmptyAppModeHandleIntentDefault(PyObject* self) -> PyObject* {
+static auto PyEmptyAppModeHandleAppIntentDefault(PyObject* self) -> PyObject* {
BA_PYTHON_TRY;
BA_PRECONDITION(g_base->InLogicThread());
Py_RETURN_NONE;
BA_PYTHON_CATCH;
}
-static PyMethodDef PyEmptyAppModeHandleIntentDefaultDef = {
- "empty_app_mode_handle_intent_default", // name
- (PyCFunction)PyEmptyAppModeHandleIntentDefault, // method
- METH_NOARGS, // flags
+static PyMethodDef PyEmptyAppModeHandleAppIntentDefaultDef = {
+ "empty_app_mode_handle_app_intent_default", // name
+ (PyCFunction)PyEmptyAppModeHandleAppIntentDefault, // method
+ METH_NOARGS, // flags
- "empty_app_mode_handle_intent_default() -> None\n"
+ "empty_app_mode_handle_app_intent_default() -> None\n"
"\n"
"(internal)\n",
};
-// ------------------ empty_app_mode_handle_intent_exec ------------------------
+// ---------------- empty_app_mode_handle_app_intent_exec ----------------------
-static auto PyEmptyAppModeHandleIntentExec(PyObject* self, PyObject* args,
- PyObject* keywds) -> PyObject* {
+static auto PyEmptyAppModeHandleAppIntentExec(PyObject* self, PyObject* args,
+ PyObject* keywds) -> PyObject* {
BA_PYTHON_TRY;
const char* command;
static const char* kwlist[] = {"command", nullptr};
@@ -1408,12 +1406,12 @@ static auto PyEmptyAppModeHandleIntentExec(PyObject* self, PyObject* args,
BA_PYTHON_CATCH;
}
-static PyMethodDef PyEmptyAppModeHandleIntentExecDef = {
- "empty_app_mode_handle_intent_exec", // name
- (PyCFunction)PyEmptyAppModeHandleIntentExec, // method
- METH_VARARGS | METH_KEYWORDS, // flags
+static PyMethodDef PyEmptyAppModeHandleAppIntentExecDef = {
+ "empty_app_mode_handle_app_intent_exec", // name
+ (PyCFunction)PyEmptyAppModeHandleAppIntentExec, // method
+ METH_VARARGS | METH_KEYWORDS, // flags
- "empty_app_mode_handle_intent_exec(command: str) -> None\n"
+ "empty_app_mode_handle_app_intent_exec(command: str) -> None\n"
"\n"
"(internal)",
};
@@ -1739,8 +1737,8 @@ auto PythonMethodsBase1::GetMethods() -> std::vector {
PyUserAgentStringDef,
PyOnEmptyAppModeActivateDef,
PyOnEmptyAppModeDeactivateDef,
- PyEmptyAppModeHandleIntentDefaultDef,
- PyEmptyAppModeHandleIntentExecDef,
+ PyEmptyAppModeHandleAppIntentDefaultDef,
+ PyEmptyAppModeHandleAppIntentExecDef,
PyGetImmediateReturnCodeDef,
PyCompleteShutdownDef,
PyShutdownSuppressBeginDef,
diff --git a/src/ballistica/base/python/methods/python_methods_base_2.cc b/src/ballistica/base/python/methods/python_methods_base_2.cc
index f0606522..64ea4617 100644
--- a/src/ballistica/base/python/methods/python_methods_base_2.cc
+++ b/src/ballistica/base/python/methods/python_methods_base_2.cc
@@ -182,32 +182,6 @@ static auto PyScreenMessage(PyObject* self, PyObject* args,
return nullptr;
}
- // TEMP - we used to have a single ba.screenmessage() call that would
- // broadcast messages to all clients when called by a server in a game context
- // and simply print them locally in other cases. In 1.7.20 the broadcast form
- // has been moved to bascenev1.broadcastmessage(). But there's probably lots
- // of code out there using screenmessage() not realizing it won't do what they
- // intended anymore. So for now let's issue a warning when it *would* have
- // done the broadcast thing. (just assuming that's the case any time there's a
- // non-empty context)
- static bool did_warning{};
- if (!did_warning && !g_base->CurrentContext().IsEmpty()) {
- did_warning = true;
- auto* envval = getenv("BA_SUPPRESS_SCREEN_MESSAGE_WARNING");
- bool suppress = (envval && strcmp(envval, "1") == 0);
- if (!suppress) {
- Log(LogLevel::kWarning,
- "WARNING! screenmessage() is being called in a gameplay situation.\n"
- "Previously this would send a message to all connected clients,"
- " but as of 1.7.20 it only shows a message on the local device.\n"
- "To get the old behavior, change your code to use"
- " bascenev1.broadcastmessage() instead.\n"
- "You can set env var BA_SUPPRESS_SCREEN_MESSAGE_WARNING=1 to"
- " suppress this warning.");
- g_base->PrintPythonStackTrace();
- }
- }
-
std::string message_str = g_base->python->GetPyLString(message_obj);
Vector3f color{1, 1, 1};
if (color_obj != Py_None) {
@@ -862,6 +836,29 @@ static PyMethodDef PyFullscreenControlSetDef = {
"(internal)\n",
};
+// -------------------------- allows_ticket_sales ------------------------------
+
+static auto PyAllowsTicketSales(PyObject* self, PyObject* args,
+ PyObject* keywds) -> PyObject* {
+ BA_PYTHON_TRY;
+
+ BA_PRECONDITION(g_base->InLogicThread());
+
+ Py_RETURN_TRUE;
+
+ BA_PYTHON_CATCH;
+}
+
+static PyMethodDef PyAllowsTicketSalesDef = {
+ "allows_ticket_sales", // name
+ (PyCFunction)PyAllowsTicketSales, // method
+ METH_NOARGS, // flags
+
+ "allows_ticket_sales() -> bool\n"
+ "\n"
+ "(internal)\n",
+};
+
// ----------------------------- supports_vsync --------------------------------
static auto PySupportsVSync(PyObject* self) -> PyObject* {
@@ -955,6 +952,7 @@ auto PythonMethodsBase2::GetMethods() -> std::vector {
PySafeColorDef,
PyCharStrDef,
PyFullscreenControlAvailableDef,
+ PyAllowsTicketSalesDef,
PySupportsVSyncDef,
PySupportsMaxFPSDef,
PyShowProgressBarDef,
diff --git a/src/ballistica/base/python/methods/python_methods_base_3.cc b/src/ballistica/base/python/methods/python_methods_base_3.cc
index 6a6aa4d1..831031dd 100644
--- a/src/ballistica/base/python/methods/python_methods_base_3.cc
+++ b/src/ballistica/base/python/methods/python_methods_base_3.cc
@@ -14,6 +14,7 @@
#include "ballistica/base/support/app_config.h"
#include "ballistica/base/ui/dev_console.h"
#include "ballistica/base/ui/ui.h"
+#include "ballistica/shared/foundation/macros.h"
#include "ballistica/shared/generic/native_stack_trace.h"
#include "ballistica/shared/generic/utils.h"
@@ -1826,6 +1827,85 @@ static PyMethodDef PyGetInputIdleTimeDef = {
"\n"
"Return seconds since any local input occurred (touch, keypress, etc.).",
};
+
+// --------------------------- get_draw_ui_bounds -----------------------------
+
+static auto PyGetDrawUIBounds(PyObject* self) -> PyObject* {
+ BA_PYTHON_TRY;
+
+ BA_PRECONDITION(g_base->InLogicThread());
+ if (g_base->graphics->draw_ui_bounds()) {
+ Py_RETURN_TRUE;
+ }
+ Py_RETURN_FALSE;
+
+ BA_PYTHON_CATCH;
+}
+
+static PyMethodDef PyGetDrawUIBoundsDef = {
+ "get_draw_ui_bounds", // name
+ (PyCFunction)PyGetDrawUIBounds, // method
+ METH_NOARGS, // flags
+
+ "get_draw_ui_bounds() -> bool\n"
+ "\n"
+ "(internal)",
+};
+
+// --------------------------- set_draw_ui_bounds -----------------------------
+
+static auto PySetDrawUIBounds(PyObject* self, PyObject* args,
+ PyObject* keywds) -> PyObject* {
+ BA_PYTHON_TRY;
+
+ BA_PRECONDITION(g_base->InLogicThread());
+
+ int value;
+ static const char* kwlist[] = {"value", nullptr};
+ if (!PyArg_ParseTupleAndKeywords(args, keywds, "p",
+ const_cast(kwlist), &value)) {
+ return nullptr;
+ }
+
+ // if (g_base->graphics->draw_ui_bounds()) {
+ // Py_RETURN_TRUE;
+ // }
+ // Py_RETURN_FALSE;
+ g_base->graphics->set_draw_ui_bounds(value);
+ Py_RETURN_NONE;
+
+ BA_PYTHON_CATCH;
+}
+
+static PyMethodDef PySetDrawUIBoundsDef = {
+ "set_draw_ui_bounds", // name
+ (PyCFunction)PySetDrawUIBounds, // method
+ METH_VARARGS | METH_KEYWORDS, // flags
+
+ "set_draw_ui_bounds(value: bool) -> None\n"
+ "\n"
+ "(internal)",
+};
+
+// ----------------------------- push_back_press -------------------------------
+
+static auto PyPushBackPress(PyObject* self) -> PyObject* {
+ BA_PYTHON_TRY;
+ g_base->ui->PushBackButtonCall(nullptr);
+ Py_RETURN_NONE;
+ BA_PYTHON_CATCH;
+}
+
+static PyMethodDef PyPushBackPressDef = {
+ "push_back_press", // name
+ (PyCFunction)PyPushBackPress, // method
+ METH_NOARGS, // flags
+
+ "push_back_press() -> None\n"
+ "\n"
+ "(internal)",
+};
+
// -----------------------------------------------------------------------------
auto PythonMoethodsBase3::GetMethods() -> std::vector {
@@ -1895,6 +1975,9 @@ auto PythonMoethodsBase3::GetMethods() -> std::vector {
PyTempTestingDef,
PyOpenFileExternallyDef,
PyGetInputIdleTimeDef,
+ PyPushBackPressDef,
+ PyGetDrawUIBoundsDef,
+ PySetDrawUIBoundsDef,
};
}
diff --git a/src/ballistica/base/ui/dev_console.cc b/src/ballistica/base/ui/dev_console.cc
index 739c93fc..00c938c4 100644
--- a/src/ballistica/base/ui/dev_console.cc
+++ b/src/ballistica/base/ui/dev_console.cc
@@ -85,7 +85,7 @@ static auto XOffs(DevConsoleHAnchor_ attach) -> float {
}
static auto IsValidHungryChar_(uint32_t this_char) -> bool {
- // Include letters, numbers, and underscore.
+ // Include letters, numbers, and underscore.
return ((this_char >= 65 && this_char <= 90)
|| (this_char >= 97 && this_char <= 122)
|| (this_char >= 48 && this_char <= 57) || this_char == '_');
@@ -1204,15 +1204,21 @@ void DevConsole::Draw(FrameDef* frame_def) {
border_height * bs);
{
SimpleComponent c(pass);
+
+ // Backing.
c.SetTransparent(true);
- c.SetColor(0, 0, 0.1f, 0.9f);
+ c.SetColor(0.04f, 0, 0.15f, 0.86f);
c.DrawMesh(&bg_mesh_);
c.Submit();
+
+ // Stripe.
if (python_terminal_visible_) {
c.SetColor(1.0f, 1.0f, 1.0f, 0.1f);
c.DrawMesh(&stripe_mesh_);
c.Submit();
}
+
+ // Border.
c.SetColor(0.25f, 0.2f, 0.3f, 1.0f);
c.DrawMesh(&border_mesh_);
}
@@ -1336,7 +1342,7 @@ void DevConsole::Draw(FrameDef* frame_def) {
c.SetTransparent(true);
c.SetColor(1, 1, 1, 1);
c.SetFlatness(1.0f);
- float draw_scale = 0.6f;
+ float draw_scale = 0.64f;
float v_inc = 18.0f;
float h = 0.5f
* (g_base->graphics->screen_virtual_width()
diff --git a/src/ballistica/base/ui/ui.cc b/src/ballistica/base/ui/ui.cc
index d57d3118..af6ee26c 100644
--- a/src/ballistica/base/ui/ui.cc
+++ b/src/ballistica/base/ui/ui.cc
@@ -235,11 +235,11 @@ auto UI::HandleMouseDown(int button, float x, float y,
handled = dev_console_->HandleMouseDown(button, x, y);
}
- if (!handled) {
- if (auto* ui_delegate = g_base->ui->delegate()) {
- handled = ui_delegate->HandleLegacyRootUIMouseDown(x, y);
- }
- }
+ // if (!handled) {
+ // if (auto* ui_delegate = g_base->ui->delegate()) {
+ // handled = ui_delegate->HandleLegacyRootUIMouseDown(x, y);
+ // }
+ // }
if (!handled) {
handled = SendWidgetMessage(WidgetMessage(
@@ -268,9 +268,9 @@ void UI::HandleMouseUp(int button, float x, float y) {
}
}
- if (auto* ui_delegate = g_base->ui->delegate()) {
- ui_delegate->HandleLegacyRootUIMouseUp(x, y);
- }
+ // if (auto* ui_delegate = g_base->ui->delegate()) {
+ // ui_delegate->HandleLegacyRootUIMouseUp(x, y);
+ // }
}
auto UI::UIHasDirectKeyboardInput() const -> bool {
@@ -296,9 +296,9 @@ void UI::HandleMouseMotion(float x, float y) {
SendWidgetMessage(
WidgetMessage(WidgetMessage::Type::kMouseMove, nullptr, x, y));
- if (auto* ui_delegate = g_base->ui->delegate()) {
- ui_delegate->HandleLegacyRootUIMouseMotion(x, y);
- }
+ // if (auto* ui_delegate = g_base->ui->delegate()) {
+ // ui_delegate->HandleLegacyRootUIMouseMotion(x, y);
+ // }
}
void UI::PushBackButtonCall(InputDevice* input_device) {
@@ -338,8 +338,11 @@ void UI::SetUIInputDevice(InputDevice* input_device) {
}
void UI::Reset() {
+ assert(g_base->InLogicThread());
+ // Reset and then deactivate any current delegate.
if (auto* ui_delegate = g_base->ui->delegate()) {
ui_delegate->Reset();
+ g_base->ui->set_ui_delegate(nullptr);
}
}
@@ -645,19 +648,25 @@ void UI::PushUIOperationRunnable(Runnable* runnable) {
assert(g_base->InLogicThread());
if (operation_context_ != nullptr) {
+ // Once we're finishing the context, nothing else should be adding calls
+ // to it. UPDATE - this is actually ok. Things like widget-select
+ // commands can happen as part of user callbacks which themselves add
+ // additional callbacks to the current ui-operation.
+ //
+ // if (operation_context_->ran_finish()) {
+ // auto trace = g_core->platform->GetNativeStackTrace();
+ // BA_LOG_ERROR_NATIVE_TRACE(
+ // "UI::PushUIOperationRunnable() called during UI operation
+ // finish.");
+ // return;
+ // }
+
operation_context_->AddRunnable(runnable);
return;
+ } else {
+ BA_LOG_ERROR_NATIVE_TRACE(
+ "UI::PushUIOperationRunnable() called outside of UI operation.");
}
-
- // For now, gracefully fall back to pushing an event if there's no current
- // operation. Once we've got any bugs cleared out, can leave this as just
- // an error log.
-
- auto trace = g_core->platform->GetNativeStackTrace();
- BA_LOG_ERROR_NATIVE_TRACE_ONCE(
- "UI::PushUIOperationRunnable() called outside of UI operation.");
-
- g_base->logic->event_loop()->PushRunnable(runnable);
}
auto UI::InUIOperation() -> bool {
diff --git a/src/ballistica/base/ui/ui.h b/src/ballistica/base/ui/ui.h
index fa903322..bda94f57 100644
--- a/src/ballistica/base/ui/ui.h
+++ b/src/ballistica/base/ui/ui.h
@@ -135,6 +135,7 @@ class UI {
/// call.
void Finish();
void AddRunnable(Runnable* runnable);
+ auto ran_finish() const { return ran_finish_; }
private:
bool ran_finish_{};
diff --git a/src/ballistica/base/ui/ui_delegate.h b/src/ballistica/base/ui/ui_delegate.h
index 5bcebc81..a668f78f 100644
--- a/src/ballistica/base/ui/ui_delegate.h
+++ b/src/ballistica/base/ui/ui_delegate.h
@@ -34,9 +34,9 @@ class UIDelegateInterface {
virtual auto MainMenuVisible() -> bool = 0;
virtual auto PartyIconVisible() -> bool = 0;
virtual void ActivatePartyIcon() = 0;
- virtual void HandleLegacyRootUIMouseMotion(float x, float y) = 0;
- virtual auto HandleLegacyRootUIMouseDown(float x, float y) -> bool = 0;
- virtual void HandleLegacyRootUIMouseUp(float x, float y) = 0;
+ // virtual void HandleLegacyRootUIMouseMotion(float x, float y) = 0;
+ // virtual auto HandleLegacyRootUIMouseDown(float x, float y) -> bool = 0;
+ // virtual void HandleLegacyRootUIMouseUp(float x, float y) = 0;
virtual void Draw(FrameDef* frame_def) = 0;
virtual auto PartyWindowOpen() -> bool = 0;
virtual void Reset() = 0;
diff --git a/src/ballistica/classic/classic.h b/src/ballistica/classic/classic.h
index efba8d9d..0d01d7a5 100644
--- a/src/ballistica/classic/classic.h
+++ b/src/ballistica/classic/classic.h
@@ -26,6 +26,7 @@ class SceneV1FeatureSet;
namespace ballistica::classic {
// Predeclared types our feature-set provides.
+class ClassicAppMode;
class ClassicFeatureSet;
class ClassicPython;
class StressTest;
diff --git a/src/ballistica/classic/python/classic_python.cc b/src/ballistica/classic/python/classic_python.cc
index 70769f9c..41d52348 100644
--- a/src/ballistica/classic/python/classic_python.cc
+++ b/src/ballistica/classic/python/classic_python.cc
@@ -3,7 +3,7 @@
#include "ballistica/classic/python/classic_python.h"
#include "ballistica/classic/python/methods/python_methods_classic.h"
-#include "ballistica/scene_v1/support/scene_v1_app_mode.h"
+#include "ballistica/classic/support/classic_app_mode.h"
#include "ballistica/shared/python/python_command.h"
#include "ballistica/shared/python/python_module_builder.h"
@@ -91,7 +91,7 @@ auto ClassicPython::GetControllerFloatValue(
}
auto ClassicPython::BuildPublicPartyStateVal() -> PyObject* {
- auto* appmode = scene_v1::SceneV1AppMode::GetActiveOrThrow();
+ auto* appmode = ClassicAppMode::GetActiveOrThrow();
auto&& public_ipv4 = appmode->public_party_public_address_ipv4();
PyObject* ipv4obj;
diff --git a/src/ballistica/classic/python/methods/python_methods_classic.cc b/src/ballistica/classic/python/methods/python_methods_classic.cc
index 482d2565..eab05e73 100644
--- a/src/ballistica/classic/python/methods/python_methods_classic.cc
+++ b/src/ballistica/classic/python/methods/python_methods_classic.cc
@@ -6,10 +6,11 @@
#include "ballistica/base/graphics/support/camera.h"
#include "ballistica/base/input/input.h"
#include "ballistica/base/logic/logic.h"
+#include "ballistica/classic/support/classic_app_mode.h"
#include "ballistica/classic/support/stress_test.h"
-#include "ballistica/scene_v1/support/scene_v1_app_mode.h"
#include "ballistica/shared/foundation/event_loop.h"
#include "ballistica/shared/python/python.h"
+#include "ballistica/shared/python/python_command.h"
#include "ballistica/shared/python/python_sys.h"
namespace ballistica::classic {
@@ -50,7 +51,7 @@ static auto PyValueTest(PyObject* self, PyObject* args,
}
double return_val = 0.0f;
if (!strcmp(arg, "bufferTime")) {
- auto* appmode = scene_v1::SceneV1AppMode::GetSingleton();
+ auto* appmode = ClassicAppMode::GetSingleton();
if (have_change) {
appmode->set_buffer_time(appmode->buffer_time()
@@ -62,7 +63,7 @@ static auto PyValueTest(PyObject* self, PyObject* args,
appmode->set_buffer_time(std::max(0, appmode->buffer_time()));
return_val = appmode->buffer_time();
} else if (!strcmp(arg, "delaySampling")) {
- auto* appmode = scene_v1::SceneV1AppMode::GetSingleton();
+ auto* appmode = ClassicAppMode::GetSingleton();
if (have_change) {
appmode->set_delay_bucket_samples(appmode->delay_bucket_samples()
+ static_cast(change));
@@ -74,7 +75,7 @@ static auto PyValueTest(PyObject* self, PyObject* args,
std::max(1, appmode->delay_bucket_samples()));
return_val = appmode->delay_bucket_samples();
} else if (!strcmp(arg, "dynamicsSyncTime")) {
- auto* appmode = scene_v1::SceneV1AppMode::GetSingleton();
+ auto* appmode = ClassicAppMode::GetSingleton();
if (have_change) {
appmode->set_dynamics_sync_time(appmode->dynamics_sync_time()
+ static_cast(change));
@@ -179,12 +180,121 @@ static PyMethodDef PySetStressTestingDef = {
"(internal)",
};
+// --------------- classic_app_mode_handle_app_intent_exec ---------------------
+
+static auto PyClassicAppModeHandleAppIntentExec(PyObject* self, PyObject* args,
+ PyObject* keywds) -> PyObject* {
+ BA_PYTHON_TRY;
+ const char* command;
+ static const char* kwlist[] = {"command", nullptr};
+ if (!PyArg_ParseTupleAndKeywords(args, keywds, "s",
+ const_cast(kwlist), &command)) {
+ return nullptr;
+ }
+ auto* appmode = ClassicAppMode::GetActiveOrThrow();
+
+ // Run the command.
+ if (g_core->core_config().exec_command.has_value()) {
+ bool success = PythonCommand(*g_core->core_config().exec_command,
+ BA_BUILD_COMMAND_FILENAME)
+ .Exec(true, nullptr, nullptr);
+ if (!success) {
+ // TODO(ericf): what should we do in this case?
+ // Obviously if we add return/success values for intents we should set
+ // that here.
+ }
+ }
+ // If the stuff we just ran didn't result in a session, create a default
+ // one.
+ if (!appmode->GetForegroundSession()) {
+ appmode->RunMainMenu();
+ }
+ Py_RETURN_NONE;
+ BA_PYTHON_CATCH;
+}
+
+static PyMethodDef PyClassicAppModeHandleAppIntentExecDef = {
+ "classic_app_mode_handle_app_intent_exec", // name
+ (PyCFunction)PyClassicAppModeHandleAppIntentExec, // method
+ METH_VARARGS | METH_KEYWORDS, // flags
+
+ "classic_app_mode_handle_app_intent_exec(command: str) -> None\n"
+ "\n"
+ "(internal)",
+};
+
+// -------------- classic_app_mode_handle_app_intent_default ------------------
+
+static auto PyClassicAppModeHandleAppIntentDefault(PyObject* self)
+ -> PyObject* {
+ BA_PYTHON_TRY;
+ BA_PRECONDITION(g_base->InLogicThread());
+ auto* appmode = ClassicAppMode::GetActiveOrThrow();
+ appmode->RunMainMenu();
+ Py_RETURN_NONE;
+ BA_PYTHON_CATCH;
+}
+
+static PyMethodDef PyClassicAppModeHandleAppIntentDefaultDef = {
+ "classic_app_mode_handle_app_intent_default", // name
+ (PyCFunction)PyClassicAppModeHandleAppIntentDefault, // method
+ METH_NOARGS, // flags
+
+ "classic_app_mode_handle_app_intent_default() -> None\n"
+ "\n"
+ "(internal)\n",
+};
+
+// ------------------------ classic_app_mode_activate --------------------------
+
+static auto PyClassicAppModeActivate(PyObject* self) -> PyObject* {
+ BA_PYTHON_TRY;
+ BA_PRECONDITION(g_base->InLogicThread());
+ g_base->set_app_mode(ClassicAppMode::GetSingleton());
+ Py_RETURN_NONE;
+ BA_PYTHON_CATCH;
+}
+
+static PyMethodDef PyClassicAppModeActivateDef = {
+ "classic_app_mode_activate", // name
+ (PyCFunction)PyClassicAppModeActivate, // method
+ METH_NOARGS, // flags
+
+ "classic_app_mode_activate() -> None\n"
+ "\n"
+ "(internal)\n",
+};
+
+// ---------------------- classic_app_mode_deactivate --------------------------
+
+static auto PyClassicAppModeDeactivate(PyObject* self) -> PyObject* {
+ BA_PYTHON_TRY;
+ BA_PRECONDITION(g_base->InLogicThread());
+ // Currently doing nothing.
+ Py_RETURN_NONE;
+ BA_PYTHON_CATCH;
+}
+
+static PyMethodDef PyClassicAppModeDeactivateDef = {
+ "classic_app_mode_deactivate", // name
+ (PyCFunction)PyClassicAppModeDeactivate, // method
+ METH_NOARGS, // flags
+
+ "classic_app_mode_deactivate() -> None\n"
+ "\n"
+ "(internal)\n",
+};
+
// -----------------------------------------------------------------------------
auto PythonMethodsClassic::GetMethods() -> std::vector {
return {
PyValueTestDef,
PySetStressTestingDef,
+ PyClassicAppModeHandleAppIntentExecDef,
+ PyClassicAppModeHandleAppIntentDefaultDef,
+ PyClassicAppModeActivateDef,
+ PyClassicAppModeDeactivateDef,
};
}
diff --git a/src/ballistica/scene_v1/support/scene_v1_app_mode.cc b/src/ballistica/classic/support/classic_app_mode.cc
similarity index 84%
rename from src/ballistica/scene_v1/support/scene_v1_app_mode.cc
rename to src/ballistica/classic/support/classic_app_mode.cc
index c22cff02..364c03eb 100644
--- a/src/ballistica/scene_v1/support/scene_v1_app_mode.cc
+++ b/src/ballistica/classic/support/classic_app_mode.cc
@@ -1,6 +1,6 @@
// Released under the MIT License. See LICENSE for details.
-#include "ballistica/scene_v1/support/scene_v1_app_mode.h"
+#include "ballistica/classic/support/classic_app_mode.h"
#include "ballistica/base/audio/audio.h"
#include "ballistica/base/audio/audio_source.h"
@@ -25,7 +25,7 @@
#include "ballistica/shared/generic/utils.h"
#include "ballistica/ui_v1/ui_v1.h"
-namespace ballistica::scene_v1 {
+namespace ballistica::classic {
const int kMaxChatMessages = 40;
@@ -44,82 +44,106 @@ const int kKickVoteFailRetryDelayInitiatorExtra = 120000;
// to kick).
const int kKickVoteMinimumClients = (g_buildconfig.headless_build() ? 3 : 4);
-struct SceneV1AppMode::ScanResultsEntryPriv_ {
+struct ClassicAppMode::ScanResultsEntryPriv_ {
scene_v1::PlayerSpec player_spec;
std::string address;
uint32_t last_query_id{};
millisecs_t last_contact_time{};
};
-base::InputDeviceDelegate* SceneV1AppMode::CreateInputDeviceDelegate(
+base::InputDeviceDelegate* ClassicAppMode::CreateInputDeviceDelegate(
base::InputDevice* device) {
// We create a special delegate for our special ClientInputDevice types;
// everything else gets our regular delegate.
- if (auto* client_device = dynamic_cast(device)) {
- auto* obj = Object::NewDeferred();
+ if (auto* client_device =
+ dynamic_cast(device)) {
+ auto* obj = Object::NewDeferred();
obj->StoreClientDeviceInfo(client_device);
return obj;
}
- return Object::NewDeferred();
+ return Object::NewDeferred();
}
// Go with 5 minute ban.
const int kKickBanSeconds = 5 * 60;
-bool SceneV1AppMode::InClassicMainMenuSession() const {
- HostSession* hostsession =
- ContextRefSceneV1::FromAppForegroundContext().GetHostSession();
+bool ClassicAppMode::InClassicMainMenuSession() const {
+ scene_v1::HostSession* hostsession =
+ scene_v1::ContextRefSceneV1::FromAppForegroundContext().GetHostSession();
return (hostsession && hostsession->is_main_menu());
}
-static SceneV1AppMode* g_scene_v1_app_mode{};
+static ClassicAppMode* g_scene_v1_app_mode{};
-void SceneV1AppMode::OnActivate() {
+void ClassicAppMode::OnActivate() {
assert(g_base->InLogicThread());
// Make sure we pull this only once when we are first active.
if (host_protocol_version_ == -1) {
- host_protocol_version_ =
- std::clamp(g_base->app_config->Resolve(
- base::AppConfig::IntID::kSceneV1HostProtocol),
- kProtocolVersionHostMin, kProtocolVersionMax);
+ host_protocol_version_ = std::clamp(
+ g_base->app_config->Resolve(
+ base::AppConfig::IntID::kSceneV1HostProtocol),
+ scene_v1::kProtocolVersionHostMin, scene_v1::kProtocolVersionMax);
}
Reset_();
- // We use UIV1.
- if (!g_core->HeadlessMode()) {
- g_base->ui->set_ui_delegate(ui_v1::UIV1FeatureSet::Import());
- }
-
// To set initial states, explicitly fire some of our 'On-Foo-Changed'
// callbacks.
DoApplyAppConfig();
LanguageChanged();
}
-void SceneV1AppMode::OnAppStart() { assert(g_base->InLogicThread()); }
+void ClassicAppMode::OnAppStart() { assert(g_base->InLogicThread()); }
-void SceneV1AppMode::OnAppShutdown() {
+void ClassicAppMode::OnAppShutdown() {
assert(g_base->InLogicThread());
connections_->Shutdown();
}
-void SceneV1AppMode::OnAppSuspend() {
+void ClassicAppMode::OnAppSuspend() {
assert(g_base->InLogicThread());
// App is going into background or whatnot. Kill any sockets/etc.
EndHostScanning();
}
-void SceneV1AppMode::OnAppUnsuspend() { assert(g_base->InLogicThread()); }
+void ClassicAppMode::OnAppUnsuspend() { assert(g_base->InLogicThread()); }
+
+// Reset everything to a blank slate.
+void ClassicAppMode::Reset_() {
+ assert(g_base);
+ assert(g_base->InLogicThread());
+
+ // Tear down any existing session.
+ foreground_session_.Clear();
+ PruneSessions_();
+
+ // If all is well our sessions should all be dead at this point.
+ if (g_scene_v1->session_count != 0) {
+ Log(LogLevel::kError, "SceneV1 session count is non-zero ("
+ + std::to_string(g_scene_v1->session_count)
+ + ") on ClassicAppMode::Reset_().");
+ }
+
+ // Reset the engine itself to a default state.
+ g_base->Reset();
+
+ // Import UIV1 and wire it up for UI duty.
+ if (!g_core->HeadlessMode()) {
+ g_base->ui->set_ui_delegate(ui_v1::UIV1FeatureSet::Import());
+ }
+
+ // Fade in if we currently aren't.
+ g_base->graphics->FadeScreen(true, 250, nullptr);
+}
// Note: for now we're making our host-scan network calls directly from the
// logic thread. This is generally not a good idea since it appears that even
// in non-blocking mode they're still blocking for 3-4ms sometimes. But for
// now since this is only used minimally and only while in the UI I guess it's
// ok.
-void SceneV1AppMode::HostScanCycle() {
+void ClassicAppMode::HostScanCycle() {
assert(g_base->InLogicThread());
// We need to create a scanner socket - an ipv4 socket we can send out
@@ -291,14 +315,14 @@ void SceneV1AppMode::HostScanCycle() {
}
}
-void SceneV1AppMode::EndHostScanning() {
+void ClassicAppMode::EndHostScanning() {
if (scan_socket_ != -1) {
g_core->platform->CloseSocket(scan_socket_);
scan_socket_ = -1;
}
}
-void SceneV1AppMode::PruneScanResults_() {
+void ClassicAppMode::PruneScanResults_() {
millisecs_t t = g_core->GetAppTimeMillisecs();
auto i = scan_results_.begin();
while (i != scan_results_.end()) {
@@ -311,8 +335,8 @@ void SceneV1AppMode::PruneScanResults_() {
}
}
-auto SceneV1AppMode::GetScanResults()
- -> std::vector {
+auto ClassicAppMode::GetScanResults()
+ -> std::vector {
std::vector results;
results.resize(scan_results_.size());
{
@@ -330,7 +354,7 @@ auto SceneV1AppMode::GetScanResults()
return results;
}
-auto SceneV1AppMode::GetActive() -> SceneV1AppMode* {
+auto ClassicAppMode::GetActive() -> ClassicAppMode* {
// Note: this gets called by non-logic threads, and not
// doing any locking here so bg thread callers should
// keep in mind that app-mode may change under them.
@@ -342,63 +366,64 @@ auto SceneV1AppMode::GetActive() -> SceneV1AppMode* {
return nullptr;
}
-bool SceneV1AppMode::HasConnectionToHost() const {
+bool ClassicAppMode::HasConnectionToHost() const {
return connections()->has_connection_to_host();
}
-millisecs_t SceneV1AppMode::LastClientJoinTime() const {
+millisecs_t ClassicAppMode::LastClientJoinTime() const {
return last_connection_to_client_join_time();
}
-bool SceneV1AppMode::HasConnectionToClients() const {
+bool ClassicAppMode::HasConnectionToClients() const {
return connections()->HasConnectionToClients();
}
-auto SceneV1AppMode::GetActiveOrWarn() -> SceneV1AppMode* {
+auto ClassicAppMode::GetActiveOrWarn() -> ClassicAppMode* {
auto* val{GetActive()};
if (val == nullptr) {
Log(LogLevel::kWarning,
- "Attempting to access SceneAppMode while it is inactive.");
+ "Attempting to access ClassicAppMode while it is inactive.");
}
return val;
}
-auto SceneV1AppMode::GetActiveOrThrow() -> SceneV1AppMode* {
+auto ClassicAppMode::GetActiveOrThrow() -> ClassicAppMode* {
auto* val{GetActive()};
if (val == nullptr) {
- throw Exception("Attempting to access SceneAppMode while it is inactive.");
+ throw Exception(
+ "Attempting to access ClassicAppMode while it is inactive.");
}
return val;
}
-auto SceneV1AppMode::GetActiveOrFatal() -> SceneV1AppMode* {
+auto ClassicAppMode::GetActiveOrFatal() -> ClassicAppMode* {
auto* val{GetActive()};
if (val == nullptr) {
- FatalError("Attempting to access SceneAppMode while it is inactive.");
+ FatalError("Attempting to access ClassicAppMode while it is inactive.");
}
return val;
}
-auto SceneV1AppMode::GetSingleton() -> SceneV1AppMode* {
+auto ClassicAppMode::GetSingleton() -> ClassicAppMode* {
assert(g_base->InLogicThread());
if (g_scene_v1_app_mode == nullptr) {
- g_scene_v1_app_mode = new SceneV1AppMode();
+ g_scene_v1_app_mode = new ClassicAppMode();
}
return g_scene_v1_app_mode;
}
-SceneV1AppMode::SceneV1AppMode()
+ClassicAppMode::ClassicAppMode()
: game_roster_(cJSON_CreateArray()),
- connections_(std::make_unique()) {}
+ connections_(std::make_unique()) {}
-void SceneV1AppMode::HandleIncomingUDPPacket(const std::vector& data,
+void ClassicAppMode::HandleIncomingUDPPacket(const std::vector& data,
const SockAddr& addr) {
// Just forward it along to our connection-set to handle.
connections()->HandleIncomingUDPPacket(data, addr);
}
-auto SceneV1AppMode::HandleJSONPing(const std::string& data_str)
+auto ClassicAppMode::HandleJSONPing(const std::string& data_str)
-> std::string {
// Note to self - this is called in a non-logic thread.
cJSON* data = cJSON_Parse(data_str.c_str());
@@ -415,20 +440,20 @@ auto SceneV1AppMode::HandleJSONPing(const std::string& data_str)
return buffer;
}
-void SceneV1AppMode::SetGameRoster(cJSON* r) {
+void ClassicAppMode::SetGameRoster(cJSON* r) {
if (game_roster_ != nullptr) {
cJSON_Delete(game_roster_);
}
game_roster_ = r;
}
-auto SceneV1AppMode::GetPartySize() const -> int {
+auto ClassicAppMode::GetPartySize() const -> int {
assert(g_base->InLogicThread());
assert(game_roster_ != nullptr);
return cJSON_GetArraySize(game_roster_);
}
-auto SceneV1AppMode::GetHeadlessNextDisplayTimeStep() -> microsecs_t {
+auto ClassicAppMode::GetHeadlessNextDisplayTimeStep() -> microsecs_t {
std::optional min_time_to_next;
for (auto&& i : sessions_) {
if (!i.Exists()) {
@@ -447,7 +472,7 @@ auto SceneV1AppMode::GetHeadlessNextDisplayTimeStep() -> microsecs_t {
: base::kHeadlessMaxDisplayTimeStep;
}
-void SceneV1AppMode::StepDisplayTime() {
+void ClassicAppMode::StepDisplayTime() {
assert(g_base->InLogicThread());
auto startms{core::CorePlatform::GetCurrentMillisecs()};
@@ -529,7 +554,7 @@ void SceneV1AppMode::StepDisplayTime() {
}
}
-auto SceneV1AppMode::GetGameRosterMessage_() -> std::vector {
+auto ClassicAppMode::GetGameRosterMessage_() -> std::vector {
// This message is simply a flattened json string of our roster (including
// terminating char).
char* s = cJSON_PrintUnformatted(game_roster_);
@@ -542,8 +567,8 @@ auto SceneV1AppMode::GetGameRosterMessage_() -> std::vector {
return msg;
}
-base::ContextRef SceneV1AppMode::GetForegroundContext() {
- Session* s = GetForegroundSession();
+base::ContextRef ClassicAppMode::GetForegroundContext() {
+ scene_v1::Session* s = GetForegroundSession();
if (s) {
return s->GetForegroundContext();
} else {
@@ -551,7 +576,7 @@ base::ContextRef SceneV1AppMode::GetForegroundContext() {
}
}
-void SceneV1AppMode::UpdateGameRoster() {
+void ClassicAppMode::UpdateGameRoster() {
assert(g_base->InLogicThread());
assert(game_roster_ != nullptr);
@@ -570,14 +595,15 @@ void SceneV1AppMode::UpdateGameRoster() {
bool include_self = (connections()->GetConnectedClientCount() > 0);
- if (auto* hs = dynamic_cast(GetForegroundSession())) {
+ if (auto* hs = dynamic_cast(GetForegroundSession())) {
// Add our host-y self.
if (include_self) {
cJSON* client_dict = cJSON_CreateObject();
cJSON_AddItemToObject(
client_dict, "spec",
- cJSON_CreateString(
- PlayerSpec::GetAccountPlayerSpec().GetSpecString().c_str()));
+ cJSON_CreateString(scene_v1::PlayerSpec::GetAccountPlayerSpec()
+ .GetSpecString()
+ .c_str()));
// Add our list of local players.
cJSON* player_array = cJSON_CreateArray();
@@ -632,10 +658,11 @@ void SceneV1AppMode::UpdateGameRoster() {
if (p->accepted() && p->name_is_real()
&& delegate->IsRemoteClient()) {
auto* client_delegate =
- static_cast(delegate);
- assert(dynamic_cast(delegate)
+ static_cast(delegate);
+ assert(dynamic_cast(delegate)
== client_delegate);
- ConnectionToClient* ctc = client_delegate->connection_to_client();
+ scene_v1::ConnectionToClient* ctc =
+ client_delegate->connection_to_client();
// Add some basic info for each remote player.
if (ctc != nullptr && ctc == i.second.Get()) {
@@ -669,12 +696,12 @@ void SceneV1AppMode::UpdateGameRoster() {
game_roster_dirty_ = true;
}
-void SceneV1AppMode::UpdateKickVote_() {
+void ClassicAppMode::UpdateKickVote_() {
if (!kick_vote_in_progress_) {
return;
}
- ConnectionToClient* kick_vote_starter = kick_vote_starter_.Get();
- ConnectionToClient* kick_vote_target = kick_vote_target_.Get();
+ scene_v1::ConnectionToClient* kick_vote_starter = kick_vote_starter_.Get();
+ scene_v1::ConnectionToClient* kick_vote_target = kick_vote_target_.Get();
// If the target is no longer with us, silently end.
if (kick_vote_target == nullptr) {
@@ -688,7 +715,8 @@ void SceneV1AppMode::UpdateKickVote_() {
// Tally current votes for connected clients; if anything has changed, print
// the update and possibly perform the kick.
- for (ConnectionToClient* client : connections()->GetConnectionsToClients()) {
+ for (scene_v1::ConnectionToClient* client :
+ connections()->GetConnectionsToClients()) {
++total_client_count;
if (client->kick_voted()) {
if (client->kick_vote_choice()) {
@@ -716,7 +744,7 @@ void SceneV1AppMode::UpdateKickVote_() {
// Disallow kicking for a while for everyone.. but ESPECIALLY so for the
// guy who launched the failed vote.
- for (ConnectionToClient* client :
+ for (scene_v1::ConnectionToClient* client :
connections()->GetConnectionsToClients()) {
millisecs_t delay = kKickVoteFailRetryDelay;
if (client == kick_vote_starter) {
@@ -774,8 +802,8 @@ void SceneV1AppMode::UpdateKickVote_() {
}
}
-void SceneV1AppMode::StartKickVote(ConnectionToClient* starter,
- ConnectionToClient* target) {
+void ClassicAppMode::StartKickVote(scene_v1::ConnectionToClient* starter,
+ scene_v1::ConnectionToClient* target) {
// Restrict votes per client.
millisecs_t current_time = g_core->GetAppTimeMillisecs();
@@ -825,7 +853,7 @@ void SceneV1AppMode::StartKickVote(ConnectionToClient* starter,
+ "\"]]}",
1, 0, 0);
} else {
- std::vector connected_clients =
+ std::vector connected_clients =
connections()->GetConnectionsToClients();
// Ok, kick off a vote.. (send the question and instructions to everyone
@@ -861,7 +889,7 @@ void SceneV1AppMode::StartKickVote(ConnectionToClient* starter,
kick_vote_target_ = target;
// Reset votes for all connected clients.
- for (ConnectionToClient* client :
+ for (scene_v1::ConnectionToClient* client :
connections()->GetConnectionsToClients()) {
if (client == starter) {
client->set_kick_voted(true);
@@ -873,19 +901,19 @@ void SceneV1AppMode::StartKickVote(ConnectionToClient* starter,
}
}
-void SceneV1AppMode::SetForegroundScene(Scene* sg) {
+void ClassicAppMode::SetForegroundScene(scene_v1::Scene* sg) {
assert(g_base->InLogicThread());
if (foreground_scene_.Get() != sg) {
foreground_scene_ = sg;
// If this scene has a globals-node, put it in charge of stuff.
- if (GlobalsNode* g = sg->globals_node()) {
+ if (scene_v1::GlobalsNode* g = sg->globals_node()) {
g->SetAsForeground();
}
}
}
-auto SceneV1AppMode::GetNetworkDebugString() -> std::string {
+auto ClassicAppMode::GetNetworkDebugString() -> std::string {
char net_info_str[128];
int64_t in_count = 0;
int64_t in_size = 0;
@@ -898,7 +926,7 @@ auto SceneV1AppMode::GetNetworkDebugString() -> std::string {
bool show = false;
// Add in/out data for any host connection.
- if (ConnectionToHost* connection_to_host =
+ if (scene_v1::ConnectionToHost* connection_to_host =
connections()->connection_to_host()) {
if (connection_to_host->can_communicate()) show = true;
in_size += connection_to_host->GetBytesInPerSecond();
@@ -912,7 +940,7 @@ auto SceneV1AppMode::GetNetworkDebugString() -> std::string {
} else {
int connected_count = 0;
for (auto&& i : connections()->connections_to_clients()) {
- ConnectionToClient* client = i.second.Get();
+ scene_v1::ConnectionToClient* client = i.second.Get();
if (client->can_communicate()) {
show = true;
connected_count += 1;
@@ -942,8 +970,8 @@ auto SceneV1AppMode::GetNetworkDebugString() -> std::string {
static_cast_check_fit(resends));
return net_info_str;
}
-auto SceneV1AppMode::GetDisplayPing() -> std::optional {
- if (ConnectionToHost* connection_to_host =
+auto ClassicAppMode::GetDisplayPing() -> std::optional {
+ if (scene_v1::ConnectionToHost* connection_to_host =
connections()->connection_to_host()) {
if (connection_to_host->can_communicate()) {
return connection_to_host->current_ping();
@@ -952,7 +980,7 @@ auto SceneV1AppMode::GetDisplayPing() -> std::optional {
return {};
}
-void SceneV1AppMode::CleanUpBeforeConnectingToHost() {
+void ClassicAppMode::CleanUpBeforeConnectingToHost() {
// We can't have connected clients and a host-connection at the same time.
// Make a minimal attempt to disconnect any client connections we have, but
// get them off the list immediately.
@@ -965,7 +993,7 @@ void SceneV1AppMode::CleanUpBeforeConnectingToHost() {
SetPublicPartyEnabled(false);
}
-void SceneV1AppMode::LaunchHostSession(PyObject* session_type_obj,
+void ClassicAppMode::LaunchHostSession(PyObject* session_type_obj,
base::BenchmarkType benchmark_type) {
if (in_update_) {
throw Exception(
@@ -983,10 +1011,11 @@ void SceneV1AppMode::LaunchHostSession(PyObject* session_type_obj,
// This should kill any current session and get us back to a blank slate.
Reset_();
- Object::WeakRef old_foreground_session(foreground_session_);
+ Object::WeakRef old_foreground_session(
+ foreground_session_);
try {
// Create the new session.
- auto s(Object::New(session_type_obj));
+ auto s(Object::New(session_type_obj));
s->set_benchmark_type(benchmark_type);
sessions_.emplace_back(s);
@@ -1000,7 +1029,7 @@ void SceneV1AppMode::LaunchHostSession(PyObject* session_type_obj,
}
}
-void SceneV1AppMode::LaunchReplaySession(const std::string& file_name) {
+void ClassicAppMode::LaunchReplaySession(const std::string& file_name) {
if (in_update_)
throw Exception(
"can't launch a session from within a session update; use "
@@ -1015,9 +1044,11 @@ void SceneV1AppMode::LaunchReplaySession(const std::string& file_name) {
Reset_();
// Create the new session.
- Object::WeakRef old_foreground_session(foreground_session_);
+ Object::WeakRef old_foreground_session(
+ foreground_session_);
try {
- auto s(Object::New(file_name));
+ auto s(Object::New(
+ file_name));
sessions_.push_back(s);
// It should have set itself as FG.
@@ -1030,7 +1061,7 @@ void SceneV1AppMode::LaunchReplaySession(const std::string& file_name) {
}
}
-void SceneV1AppMode::LaunchClientSession() {
+void ClassicAppMode::LaunchClientSession() {
if (in_update_) {
throw Exception(
"can't launch a session from within a session update; use "
@@ -1045,9 +1076,10 @@ void SceneV1AppMode::LaunchClientSession() {
Reset_();
// Create the new session.
- Object::WeakRef old_foreground_session(foreground_session_);
+ Object::WeakRef old_foreground_session(
+ foreground_session_);
try {
- auto s(Object::New());
+ auto s(Object::New());
sessions_.push_back(s);
// It should have set itself as FG.
@@ -1059,35 +1091,11 @@ void SceneV1AppMode::LaunchClientSession() {
}
}
-// Reset to a blank slate.
-void SceneV1AppMode::Reset_() {
- assert(g_base);
- assert(g_base->InLogicThread());
-
- // Tear down our existing session.
- foreground_session_.Clear();
- PruneSessions_();
-
- // If all is well our sessions should all be dead.
- if (g_scene_v1->session_count != 0) {
- Log(LogLevel::kError, "Session-count is non-zero ("
- + std::to_string(g_scene_v1->session_count)
- + ") on Logic::Reset.");
- }
-
- g_scene_v1->Reset();
- g_base->ui->Reset();
- g_base->input->Reset();
- g_base->graphics->Reset();
- g_base->python->Reset();
- g_base->audio->Reset();
-}
-
-void SceneV1AppMode::ChangeGameSpeed(int offs) {
+void ClassicAppMode::ChangeGameSpeed(int offs) {
assert(g_base->InLogicThread());
// If we're in a replay session, adjust playback speed there.
- if (dynamic_cast(GetForegroundSession())) {
+ if (dynamic_cast(GetForegroundSession())) {
int old_speed = replay_speed_exponent();
SetReplaySpeedExponent(replay_speed_exponent() + offs);
if (old_speed != replay_speed_exponent()) {
@@ -1103,27 +1111,27 @@ void SceneV1AppMode::ChangeGameSpeed(int offs) {
debug_speed_exponent_ += offs;
debug_speed_mult_ = powf(2.0f, static_cast(debug_speed_exponent_));
ScreenMessage("DEBUG GAME SPEED TO " + std::to_string(debug_speed_mult_));
- Session* s = GetForegroundSession();
+ scene_v1::Session* s = GetForegroundSession();
if (s) {
s->DebugSpeedMultChanged();
}
}
}
-void SceneV1AppMode::OnScreenSizeChange() {
- if (Session* session = GetForegroundSession()) {
+void ClassicAppMode::OnScreenSizeChange() {
+ if (scene_v1::Session* session = GetForegroundSession()) {
session->OnScreenSizeChange();
}
}
// Called by a newly made Session instance to set itself as the current
// session.
-void SceneV1AppMode::SetForegroundSession(Session* s) {
+void ClassicAppMode::SetForegroundSession(scene_v1::Session* s) {
assert(g_base->InLogicThread());
foreground_session_ = s;
}
-void SceneV1AppMode::LocalDisplayChatMessage(
+void ClassicAppMode::LocalDisplayChatMessage(
const std::vector& buffer) {
// 1 type byte, 1 spec-len byte, 1 or more spec chars, 0 or more msg chars.
if (buffer.size() > 3) {
@@ -1140,7 +1148,7 @@ void SceneV1AppMode::LocalDisplayChatMessage(
b2[msg_len] = 0;
std::string final_message =
- PlayerSpec(b1.data()).GetDisplayString() + ": " + b2.data();
+ scene_v1::PlayerSpec(b1.data()).GetDisplayString() + ": " + b2.data();
// Store it locally.
chat_messages_.push_back(final_message);
@@ -1165,9 +1173,10 @@ void SceneV1AppMode::LocalDisplayChatMessage(
}
}
-void SceneV1AppMode::DoApplyAppConfig() {
+void ClassicAppMode::DoApplyAppConfig() {
// Kick-idle-players setting (hmm is this still relevant?).
- auto* host_session = dynamic_cast(foreground_session_.Get());
+ auto* host_session =
+ dynamic_cast(foreground_session_.Get());
kick_idle_players_ =
g_base->app_config->Resolve(base::AppConfig::BoolID::kKickIdlePlayers);
if (host_session) {
@@ -1181,7 +1190,7 @@ void SceneV1AppMode::DoApplyAppConfig() {
base::AppConfig::OptionalFloatID::kIdleExitMinutes);
}
-void SceneV1AppMode::PruneSessions_() {
+void ClassicAppMode::PruneSessions_() {
bool have_dead_session = false;
for (auto&& i : sessions_) {
if (i.Exists()) {
@@ -1200,7 +1209,7 @@ void SceneV1AppMode::PruneSessions_() {
}
}
if (have_dead_session) {
- std::vector > live_list;
+ std::vector > live_list;
for (auto&& i : sessions_) {
if (i.Exists()) {
live_list.push_back(i);
@@ -1210,31 +1219,31 @@ void SceneV1AppMode::PruneSessions_() {
}
}
-void SceneV1AppMode::LanguageChanged() {
+void ClassicAppMode::LanguageChanged() {
assert(g_base && g_base->InLogicThread());
- if (Session* session = GetForegroundSession()) {
+ if (scene_v1::Session* session = GetForegroundSession()) {
session->LanguageChanged();
}
}
-void SceneV1AppMode::SetReplaySpeedExponent(int val) {
+void ClassicAppMode::SetReplaySpeedExponent(int val) {
replay_speed_exponent_ = std::min(3, std::max(-3, val));
replay_speed_mult_ = powf(2.0f, static_cast(replay_speed_exponent_));
}
-void SceneV1AppMode::PauseReplay() { replay_paused_ = true; }
+void ClassicAppMode::PauseReplay() { replay_paused_ = true; }
-void SceneV1AppMode::ResumeReplay() { replay_paused_ = false; }
+void ClassicAppMode::ResumeReplay() { replay_paused_ = false; }
-void SceneV1AppMode::SetDebugSpeedExponent(int val) {
+void ClassicAppMode::SetDebugSpeedExponent(int val) {
debug_speed_exponent_ = val;
debug_speed_mult_ = powf(2.0f, static_cast(debug_speed_exponent_));
- Session* s = GetForegroundSession();
+ scene_v1::Session* s = GetForegroundSession();
if (s) s->DebugSpeedMultChanged();
}
-void SceneV1AppMode::SetPublicPartyEnabled(bool val) {
+void ClassicAppMode::SetPublicPartyEnabled(bool val) {
assert(g_base->InLogicThread());
if (val == public_party_enabled_) {
return;
@@ -1243,7 +1252,7 @@ void SceneV1AppMode::SetPublicPartyEnabled(bool val) {
g_base->plus()->PushPublicPartyState();
}
-void SceneV1AppMode::SetPublicPartySize(int count) {
+void ClassicAppMode::SetPublicPartySize(int count) {
assert(g_base->InLogicThread());
if (count == public_party_size_) {
return;
@@ -1257,7 +1266,7 @@ void SceneV1AppMode::SetPublicPartySize(int count) {
}
}
-void SceneV1AppMode::SetPublicPartyQueueEnabled(bool enabled) {
+void ClassicAppMode::SetPublicPartyQueueEnabled(bool enabled) {
assert(g_base->InLogicThread());
if (enabled == public_party_queue_enabled_) {
return;
@@ -1271,7 +1280,7 @@ void SceneV1AppMode::SetPublicPartyQueueEnabled(bool enabled) {
}
}
-void SceneV1AppMode::SetPublicPartyMaxSize(int count) {
+void ClassicAppMode::SetPublicPartyMaxSize(int count) {
assert(g_base->InLogicThread());
if (count == public_party_max_size_) {
return;
@@ -1285,7 +1294,7 @@ void SceneV1AppMode::SetPublicPartyMaxSize(int count) {
}
}
-void SceneV1AppMode::SetPublicPartyName(const std::string& name) {
+void ClassicAppMode::SetPublicPartyName(const std::string& name) {
assert(g_base->InLogicThread());
if (name == public_party_name_) {
return;
@@ -1299,7 +1308,7 @@ void SceneV1AppMode::SetPublicPartyName(const std::string& name) {
}
}
-void SceneV1AppMode::SetPublicPartyStatsURL(const std::string& url) {
+void ClassicAppMode::SetPublicPartyStatsURL(const std::string& url) {
assert(g_base->InLogicThread());
if (url == public_party_stats_url_) {
return;
@@ -1313,7 +1322,7 @@ void SceneV1AppMode::SetPublicPartyStatsURL(const std::string& url) {
}
}
-void SceneV1AppMode::SetPublicPartyPlayerCount(int count) {
+void ClassicAppMode::SetPublicPartyPlayerCount(int count) {
assert(g_base->InLogicThread());
if (count == public_party_player_count_) {
return;
@@ -1327,21 +1336,21 @@ void SceneV1AppMode::SetPublicPartyPlayerCount(int count) {
}
}
-auto SceneV1AppMode::DoesWorldFillScreen() -> bool {
+auto ClassicAppMode::DoesWorldFillScreen() -> bool {
if (auto* session = GetForegroundSession()) {
return session->DoesFillScreen();
}
return false;
}
-void SceneV1AppMode::DrawWorld(base::FrameDef* frame_def) {
+void ClassicAppMode::DrawWorld(base::FrameDef* frame_def) {
if (auto* session = GetForegroundSession()) {
session->Draw(frame_def);
frame_def->set_benchmark_type(session->benchmark_type());
}
}
-auto SceneV1AppMode::ShouldAnnouncePartyJoinsAndLeaves() -> bool {
+auto ClassicAppMode::ShouldAnnouncePartyJoinsAndLeaves() -> bool {
assert(g_base->InLogicThread());
// At the moment we don't announce these for public internet parties.. (too
@@ -1349,7 +1358,7 @@ auto SceneV1AppMode::ShouldAnnouncePartyJoinsAndLeaves() -> bool {
return !public_party_enabled();
}
-auto SceneV1AppMode::IsPlayerBanned(const PlayerSpec& spec) -> bool {
+auto ClassicAppMode::IsPlayerBanned(const scene_v1::PlayerSpec& spec) -> bool {
millisecs_t current_time = g_core->GetAppTimeMillisecs();
// Now is a good time to prune no-longer-banned specs.
@@ -1366,11 +1375,12 @@ auto SceneV1AppMode::IsPlayerBanned(const PlayerSpec& spec) -> bool {
return false;
}
-void SceneV1AppMode::BanPlayer(const PlayerSpec& spec, millisecs_t duration) {
+void ClassicAppMode::BanPlayer(const scene_v1::PlayerSpec& spec,
+ millisecs_t duration) {
banned_players_.emplace_back(g_core->GetAppTimeMillisecs() + duration, spec);
}
-void SceneV1AppMode::HandleQuitOnIdle_() {
+void ClassicAppMode::HandleQuitOnIdle_() {
if (idle_exit_minutes_) {
auto idle_seconds{static_cast(g_base->input->input_idle_time())
* 0.001f};
@@ -1383,7 +1393,7 @@ void SceneV1AppMode::HandleQuitOnIdle_() {
}
}
-void SceneV1AppMode::SetInternalMusic(base::SoundAsset* music, float volume,
+void ClassicAppMode::SetInternalMusic(base::SoundAsset* music, float volume,
bool loop) {
// Stop any playing music.
if (internal_music_play_id_) {
@@ -1405,7 +1415,7 @@ void SceneV1AppMode::SetInternalMusic(base::SoundAsset* music, float volume,
}
}
-void SceneV1AppMode::HandleGameQuery(const char* buffer, size_t size,
+void ClassicAppMode::HandleGameQuery(const char* buffer, size_t size,
sockaddr_storage* from) {
if (size == 5) {
// If we're already in a party, don't advertise since they
@@ -1467,18 +1477,19 @@ void SceneV1AppMode::HandleGameQuery(const char* buffer, size_t size,
}
}
-void SceneV1AppMode::RunMainMenu() {
+void ClassicAppMode::RunMainMenu() {
assert(g_base->InLogicThread());
if (g_base->logic->shutting_down()) {
return;
}
assert(g_base->InLogicThread());
- PythonRef result = g_scene_v1->python->objs()
- .Get(SceneV1Python::ObjID::kLaunchMainMenuSessionCall)
- .Call();
+ PythonRef result =
+ g_scene_v1->python->objs()
+ .Get(scene_v1::SceneV1Python::ObjID::kLaunchMainMenuSessionCall)
+ .Call();
if (!result.Exists()) {
throw Exception("Error running scene_v1 main menu.");
}
}
-} // namespace ballistica::scene_v1
+} // namespace ballistica::classic
diff --git a/src/ballistica/scene_v1/support/scene_v1_app_mode.h b/src/ballistica/classic/support/classic_app_mode.h
similarity index 85%
rename from src/ballistica/scene_v1/support/scene_v1_app_mode.h
rename to src/ballistica/classic/support/classic_app_mode.h
index 45bcd8b7..0fa75159 100644
--- a/src/ballistica/scene_v1/support/scene_v1_app_mode.h
+++ b/src/ballistica/classic/support/classic_app_mode.h
@@ -1,7 +1,7 @@
// Released under the MIT License. See LICENSE for details.
-#ifndef BALLISTICA_SCENE_V1_SUPPORT_SCENE_V1_APP_MODE_H_
-#define BALLISTICA_SCENE_V1_SUPPORT_SCENE_V1_APP_MODE_H_
+#ifndef BALLISTICA_CLASSIC_SUPPORT_CLASSIC_APP_MODE_H_
+#define BALLISTICA_CLASSIC_SUPPORT_CLASSIC_APP_MODE_H_
#include
#include