Merge pull request #2 from efroemling/main

big toolbar-mode UI revamp work in progress
This commit is contained in:
Vishal 2024-08-30 00:44:32 +05:30 committed by GitHub
commit cf02059b60
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
178 changed files with 7565 additions and 7385 deletions

126
.efrocachemap generated
View File

@ -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"
}

View File

@ -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

View File

@ -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

View File

@ -201,8 +201,8 @@
<ClInclude Include="..\..\src\ballistica\base\app_adapter\app_adapter_vr.h" />
<ClCompile Include="..\..\src\ballistica\base\app_mode\app_mode.cc" />
<ClInclude Include="..\..\src\ballistica\base\app_mode\app_mode.h" />
<ClCompile Include="..\..\src\ballistica\base\app_mode\app_mode_empty.cc" />
<ClInclude Include="..\..\src\ballistica\base\app_mode\app_mode_empty.h" />
<ClCompile Include="..\..\src\ballistica\base\app_mode\empty_app_mode.cc" />
<ClInclude Include="..\..\src\ballistica\base\app_mode\empty_app_mode.h" />
<ClCompile Include="..\..\src\ballistica\base\assets\asset.cc" />
<ClInclude Include="..\..\src\ballistica\base\assets\asset.h" />
<ClCompile Include="..\..\src\ballistica\base\assets\assets.cc" />
@ -455,6 +455,8 @@
<ClInclude Include="..\..\src\ballistica\classic\python\classic_python.h" />
<ClCompile Include="..\..\src\ballistica\classic\python\methods\python_methods_classic.cc" />
<ClInclude Include="..\..\src\ballistica\classic\python\methods\python_methods_classic.h" />
<ClCompile Include="..\..\src\ballistica\classic\support\classic_app_mode.cc" />
<ClInclude Include="..\..\src\ballistica\classic\support\classic_app_mode.h" />
<ClCompile Include="..\..\src\ballistica\classic\support\stress_test.cc" />
<ClInclude Include="..\..\src\ballistica\classic\support\stress_test.h" />
<ClCompile Include="..\..\src\ballistica\classic\support\v1_account.cc" />
@ -649,8 +651,6 @@
<ClInclude Include="..\..\src\ballistica\scene_v1\support\player_spec.h" />
<ClCompile Include="..\..\src\ballistica\scene_v1\support\scene.cc" />
<ClInclude Include="..\..\src\ballistica\scene_v1\support\scene.h" />
<ClCompile Include="..\..\src\ballistica\scene_v1\support\scene_v1_app_mode.cc" />
<ClInclude Include="..\..\src\ballistica\scene_v1\support\scene_v1_app_mode.h" />
<ClCompile Include="..\..\src\ballistica\scene_v1\support\scene_v1_context.cc" />
<ClInclude Include="..\..\src\ballistica\scene_v1\support\scene_v1_context.h" />
<ClCompile Include="..\..\src\ballistica\scene_v1\support\scene_v1_input_device_delegate.cc" />
@ -744,8 +744,6 @@
<ClInclude Include="..\..\src\ballistica\ui_v1\python\methods\python_methods_ui_v1.h" />
<ClCompile Include="..\..\src\ballistica\ui_v1\python\ui_v1_python.cc" />
<ClInclude Include="..\..\src\ballistica\ui_v1\python\ui_v1_python.h" />
<ClCompile Include="..\..\src\ballistica\ui_v1\support\root_ui.cc" />
<ClInclude Include="..\..\src\ballistica\ui_v1\support\root_ui.h" />
<ClCompile Include="..\..\src\ballistica\ui_v1\ui_v1.cc" />
<ClInclude Include="..\..\src\ballistica\ui_v1\ui_v1.h" />
<ClCompile Include="..\..\src\ballistica\ui_v1\widget\button_widget.cc" />

View File

@ -37,10 +37,10 @@
<ClInclude Include="..\..\src\ballistica\base\app_mode\app_mode.h">
<Filter>ballistica\base\app_mode</Filter>
</ClInclude>
<ClCompile Include="..\..\src\ballistica\base\app_mode\app_mode_empty.cc">
<ClCompile Include="..\..\src\ballistica\base\app_mode\empty_app_mode.cc">
<Filter>ballistica\base\app_mode</Filter>
</ClCompile>
<ClInclude Include="..\..\src\ballistica\base\app_mode\app_mode_empty.h">
<ClInclude Include="..\..\src\ballistica\base\app_mode\empty_app_mode.h">
<Filter>ballistica\base\app_mode</Filter>
</ClInclude>
<ClCompile Include="..\..\src\ballistica\base\assets\asset.cc">
@ -799,6 +799,12 @@
<ClInclude Include="..\..\src\ballistica\classic\python\methods\python_methods_classic.h">
<Filter>ballistica\classic\python\methods</Filter>
</ClInclude>
<ClCompile Include="..\..\src\ballistica\classic\support\classic_app_mode.cc">
<Filter>ballistica\classic\support</Filter>
</ClCompile>
<ClInclude Include="..\..\src\ballistica\classic\support\classic_app_mode.h">
<Filter>ballistica\classic\support</Filter>
</ClInclude>
<ClCompile Include="..\..\src\ballistica\classic\support\stress_test.cc">
<Filter>ballistica\classic\support</Filter>
</ClCompile>
@ -1381,12 +1387,6 @@
<ClInclude Include="..\..\src\ballistica\scene_v1\support\scene.h">
<Filter>ballistica\scene_v1\support</Filter>
</ClInclude>
<ClCompile Include="..\..\src\ballistica\scene_v1\support\scene_v1_app_mode.cc">
<Filter>ballistica\scene_v1\support</Filter>
</ClCompile>
<ClInclude Include="..\..\src\ballistica\scene_v1\support\scene_v1_app_mode.h">
<Filter>ballistica\scene_v1\support</Filter>
</ClInclude>
<ClCompile Include="..\..\src\ballistica\scene_v1\support\scene_v1_context.cc">
<Filter>ballistica\scene_v1\support</Filter>
</ClCompile>
@ -1666,12 +1666,6 @@
<ClInclude Include="..\..\src\ballistica\ui_v1\python\ui_v1_python.h">
<Filter>ballistica\ui_v1\python</Filter>
</ClInclude>
<ClCompile Include="..\..\src\ballistica\ui_v1\support\root_ui.cc">
<Filter>ballistica\ui_v1\support</Filter>
</ClCompile>
<ClInclude Include="..\..\src\ballistica\ui_v1\support\root_ui.h">
<Filter>ballistica\ui_v1\support</Filter>
</ClInclude>
<ClCompile Include="..\..\src\ballistica\ui_v1\ui_v1.cc">
<Filter>ballistica\ui_v1</Filter>
</ClCompile>
@ -2054,7 +2048,6 @@
<Filter Include="ballistica\ui_v1\python" />
<Filter Include="ballistica\ui_v1\python\class" />
<Filter Include="ballistica\ui_v1\python\methods" />
<Filter Include="ballistica\ui_v1\support" />
<Filter Include="ballistica\ui_v1\widget" />
<Filter Include="external" />
<Filter Include="external\open_dynamics_engine-ef" />

View File

@ -196,8 +196,8 @@
<ClInclude Include="..\..\src\ballistica\base\app_adapter\app_adapter_vr.h" />
<ClCompile Include="..\..\src\ballistica\base\app_mode\app_mode.cc" />
<ClInclude Include="..\..\src\ballistica\base\app_mode\app_mode.h" />
<ClCompile Include="..\..\src\ballistica\base\app_mode\app_mode_empty.cc" />
<ClInclude Include="..\..\src\ballistica\base\app_mode\app_mode_empty.h" />
<ClCompile Include="..\..\src\ballistica\base\app_mode\empty_app_mode.cc" />
<ClInclude Include="..\..\src\ballistica\base\app_mode\empty_app_mode.h" />
<ClCompile Include="..\..\src\ballistica\base\assets\asset.cc" />
<ClInclude Include="..\..\src\ballistica\base\assets\asset.h" />
<ClCompile Include="..\..\src\ballistica\base\assets\assets.cc" />
@ -450,6 +450,8 @@
<ClInclude Include="..\..\src\ballistica\classic\python\classic_python.h" />
<ClCompile Include="..\..\src\ballistica\classic\python\methods\python_methods_classic.cc" />
<ClInclude Include="..\..\src\ballistica\classic\python\methods\python_methods_classic.h" />
<ClCompile Include="..\..\src\ballistica\classic\support\classic_app_mode.cc" />
<ClInclude Include="..\..\src\ballistica\classic\support\classic_app_mode.h" />
<ClCompile Include="..\..\src\ballistica\classic\support\stress_test.cc" />
<ClInclude Include="..\..\src\ballistica\classic\support\stress_test.h" />
<ClCompile Include="..\..\src\ballistica\classic\support\v1_account.cc" />
@ -644,8 +646,6 @@
<ClInclude Include="..\..\src\ballistica\scene_v1\support\player_spec.h" />
<ClCompile Include="..\..\src\ballistica\scene_v1\support\scene.cc" />
<ClInclude Include="..\..\src\ballistica\scene_v1\support\scene.h" />
<ClCompile Include="..\..\src\ballistica\scene_v1\support\scene_v1_app_mode.cc" />
<ClInclude Include="..\..\src\ballistica\scene_v1\support\scene_v1_app_mode.h" />
<ClCompile Include="..\..\src\ballistica\scene_v1\support\scene_v1_context.cc" />
<ClInclude Include="..\..\src\ballistica\scene_v1\support\scene_v1_context.h" />
<ClCompile Include="..\..\src\ballistica\scene_v1\support\scene_v1_input_device_delegate.cc" />
@ -739,8 +739,6 @@
<ClInclude Include="..\..\src\ballistica\ui_v1\python\methods\python_methods_ui_v1.h" />
<ClCompile Include="..\..\src\ballistica\ui_v1\python\ui_v1_python.cc" />
<ClInclude Include="..\..\src\ballistica\ui_v1\python\ui_v1_python.h" />
<ClCompile Include="..\..\src\ballistica\ui_v1\support\root_ui.cc" />
<ClInclude Include="..\..\src\ballistica\ui_v1\support\root_ui.h" />
<ClCompile Include="..\..\src\ballistica\ui_v1\ui_v1.cc" />
<ClInclude Include="..\..\src\ballistica\ui_v1\ui_v1.h" />
<ClCompile Include="..\..\src\ballistica\ui_v1\widget\button_widget.cc" />

View File

@ -37,10 +37,10 @@
<ClInclude Include="..\..\src\ballistica\base\app_mode\app_mode.h">
<Filter>ballistica\base\app_mode</Filter>
</ClInclude>
<ClCompile Include="..\..\src\ballistica\base\app_mode\app_mode_empty.cc">
<ClCompile Include="..\..\src\ballistica\base\app_mode\empty_app_mode.cc">
<Filter>ballistica\base\app_mode</Filter>
</ClCompile>
<ClInclude Include="..\..\src\ballistica\base\app_mode\app_mode_empty.h">
<ClInclude Include="..\..\src\ballistica\base\app_mode\empty_app_mode.h">
<Filter>ballistica\base\app_mode</Filter>
</ClInclude>
<ClCompile Include="..\..\src\ballistica\base\assets\asset.cc">
@ -799,6 +799,12 @@
<ClInclude Include="..\..\src\ballistica\classic\python\methods\python_methods_classic.h">
<Filter>ballistica\classic\python\methods</Filter>
</ClInclude>
<ClCompile Include="..\..\src\ballistica\classic\support\classic_app_mode.cc">
<Filter>ballistica\classic\support</Filter>
</ClCompile>
<ClInclude Include="..\..\src\ballistica\classic\support\classic_app_mode.h">
<Filter>ballistica\classic\support</Filter>
</ClInclude>
<ClCompile Include="..\..\src\ballistica\classic\support\stress_test.cc">
<Filter>ballistica\classic\support</Filter>
</ClCompile>
@ -1381,12 +1387,6 @@
<ClInclude Include="..\..\src\ballistica\scene_v1\support\scene.h">
<Filter>ballistica\scene_v1\support</Filter>
</ClInclude>
<ClCompile Include="..\..\src\ballistica\scene_v1\support\scene_v1_app_mode.cc">
<Filter>ballistica\scene_v1\support</Filter>
</ClCompile>
<ClInclude Include="..\..\src\ballistica\scene_v1\support\scene_v1_app_mode.h">
<Filter>ballistica\scene_v1\support</Filter>
</ClInclude>
<ClCompile Include="..\..\src\ballistica\scene_v1\support\scene_v1_context.cc">
<Filter>ballistica\scene_v1\support</Filter>
</ClCompile>
@ -1666,12 +1666,6 @@
<ClInclude Include="..\..\src\ballistica\ui_v1\python\ui_v1_python.h">
<Filter>ballistica\ui_v1\python</Filter>
</ClInclude>
<ClCompile Include="..\..\src\ballistica\ui_v1\support\root_ui.cc">
<Filter>ballistica\ui_v1\support</Filter>
</ClCompile>
<ClInclude Include="..\..\src\ballistica\ui_v1\support\root_ui.h">
<Filter>ballistica\ui_v1\support</Filter>
</ClInclude>
<ClCompile Include="..\..\src\ballistica\ui_v1\ui_v1.cc">
<Filter>ballistica\ui_v1</Filter>
</ClCompile>
@ -2054,7 +2048,6 @@
<Filter Include="ballistica\ui_v1\python" />
<Filter Include="ballistica\ui_v1\python\class" />
<Filter Include="ballistica\ui_v1\python\methods" />
<Filter Include="ballistica\ui_v1\support" />
<Filter Include="ballistica\ui_v1\widget" />
<Filter Include="external" />
<Filter Include="external\open_dynamics_engine-ef" />

View File

@ -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",

View File

@ -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

View File

@ -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"

View File

@ -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",

View File

@ -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 \

View File

@ -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',

View File

@ -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()

View File

@ -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

View File

@ -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()

View File

@ -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.
"""

View File

@ -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))

View File

@ -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()

View File

@ -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

View File

@ -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.
)

View File

@ -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.')

View File

@ -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)

View File

@ -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:

View File

@ -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

View File

@ -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,

View File

@ -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'

View File

@ -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

View File

@ -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

View File

@ -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',

View File

@ -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()

View File

@ -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():

View File

@ -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."""

View File

@ -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('<br>\n') if s != ''
]

View File

@ -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',
]

View File

@ -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.
"""

View File

@ -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',

View File

@ -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

View File

@ -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:

View File

@ -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

View File

@ -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)

View File

@ -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,
)

View File

@ -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

View File

@ -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(

View File

@ -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
),

View File

@ -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,
)

View File

@ -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
),

View File

@ -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()

View File

@ -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)

View File

@ -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
)
)

View File

@ -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
)

View File

@ -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)

View File

@ -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,
)

View File

@ -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:

View File

@ -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,

View File

@ -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)

View File

@ -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

View File

@ -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,
)

View File

@ -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:

View File

@ -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
)
)

View File

@ -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()

View File

@ -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()

View File

@ -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
)
)

View File

@ -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)

View File

@ -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()

File diff suppressed because it is too large Load Diff

View File

@ -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)
)
),
)

View File

@ -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()

View File

@ -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(

View File

@ -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()

View File

@ -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:

View File

@ -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:

View File

@ -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:

View File

@ -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,
)

View File

@ -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(

View File

@ -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,
# )

View File

@ -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')

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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)

View File

@ -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

View File

@ -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()

View File

@ -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)

View File

@ -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,
# )

View File

@ -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:

View File

@ -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:

View File

@ -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,
# )

View File

@ -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,
)

View File

@ -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,
)

View File

@ -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()

View File

@ -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

View File

@ -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):

View File

@ -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'),
)

View File

@ -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,
)

View File

@ -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,
)

View File

@ -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,
)

View File

@ -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
)

View File

@ -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)

View File

@ -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:

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