diff --git a/.efrocachemap b/.efrocachemap
index 0740010e..a62b1c94 100644
--- a/.efrocachemap
+++ b/.efrocachemap
@@ -1282,10 +1282,10 @@
"build/assets/ba_data/textures/buttonPunch.ktx": "9de2058679dd2e55a9c180fafa810559",
"build/assets/ba_data/textures/buttonPunch.pvr": "7b7ced951fcee4e572988f232d729ad6",
"build/assets/ba_data/textures/buttonPunch_preview.png": "e78d05abb78fef5fe18119c4f3fd1dbe",
- "build/assets/ba_data/textures/buttonSquare.dds": "2475ef6978210f993d7c61fc08a15c18",
- "build/assets/ba_data/textures/buttonSquare.ktx": "15794e5bb14980f49ee17a3bcfe7e9b3",
- "build/assets/ba_data/textures/buttonSquare.pvr": "ef1c0a321975aa3a7cad6e4ec29c1196",
- "build/assets/ba_data/textures/buttonSquare_preview.png": "d903b64a5f41bce2a64be077bcdfd4ba",
+ "build/assets/ba_data/textures/buttonSquare.dds": "8b4091b6fc25d964059673062cc9faa6",
+ "build/assets/ba_data/textures/buttonSquare.ktx": "21fe9898e50c6d44151e059e0a4b94ea",
+ "build/assets/ba_data/textures/buttonSquare.pvr": "c454666fa706aed388e4ce366248b34d",
+ "build/assets/ba_data/textures/buttonSquare_preview.png": "e5dc8e92b0306b218be7b6c927aa4e06",
"build/assets/ba_data/textures/chTitleChar1.dds": "d8c615a51d900da15b8aba5ce35296bf",
"build/assets/ba_data/textures/chTitleChar1.ktx": "44384ea28c9fe01440deb1fc80c7224a",
"build/assets/ba_data/textures/chTitleChar1.pvr": "304d7551786a2eca008bd2057996d05e",
@@ -1517,10 +1517,10 @@
"build/assets/ba_data/textures/fontExtras.dds": "d2d20fdde7c6114925ba626ade35151f",
"build/assets/ba_data/textures/fontExtras.ktx": "2dde1a343493a9329792d7042116d301",
"build/assets/ba_data/textures/fontExtras.pvr": "80ab1f61fafba22ce0259177944beabf",
- "build/assets/ba_data/textures/fontExtras2.dds": "18063a12912dadc9528afd90d1cf2369",
- "build/assets/ba_data/textures/fontExtras2.ktx": "36da7f6cfbfb8d32fb14371de0a8f660",
- "build/assets/ba_data/textures/fontExtras2.pvr": "7a4e8e64ac05313b1782fb5b958150d0",
- "build/assets/ba_data/textures/fontExtras2_preview.png": "f5bc05c8c34cb3ff7284e9edab7b8bcd",
+ "build/assets/ba_data/textures/fontExtras2.dds": "56e365473f2babd5404a4ea63924c4ca",
+ "build/assets/ba_data/textures/fontExtras2.ktx": "1de6da8081788bea25cfb0e8df0d16ca",
+ "build/assets/ba_data/textures/fontExtras2.pvr": "eb327acb727a626c5f46e2e281a0b789",
+ "build/assets/ba_data/textures/fontExtras2_preview.png": "aa3ca66b5368da12eb70c21d73dc74b8",
"build/assets/ba_data/textures/fontExtras3.dds": "4fce73998583207fd7692a816e3d86b0",
"build/assets/ba_data/textures/fontExtras3.ktx": "13d61e663088bc97c1fa906a46ed5955",
"build/assets/ba_data/textures/fontExtras3.pvr": "56bac0d5f8672f36e1408a1e57cdd3d3",
@@ -1626,6 +1626,10 @@
"build/assets/ba_data/textures/glow.ktx": "fb924f8b6e5dc268f469fb5791cac131",
"build/assets/ba_data/textures/glow.pvr": "7ef85fd8127acfb9dafdc75cdcb61a72",
"build/assets/ba_data/textures/glow_preview.png": "6f32a4134d5e2ee70d7f804c8fdae3b0",
+ "build/assets/ba_data/textures/goldPass.dds": "e39d1181ace28b2f3aa2f68b29cff597",
+ "build/assets/ba_data/textures/goldPass.ktx": "cb0ff1dc86be777325ba0d1b54d1f3ac",
+ "build/assets/ba_data/textures/goldPass.pvr": "d5e9e6dd71d571fad5898af6626cf343",
+ "build/assets/ba_data/textures/goldPass_preview.png": "81614ca81d55029721b7ceb3e21831ab",
"build/assets/ba_data/textures/googlePlayAchievementsIcon.dds": "f43d34da87ae80344c5f47e39dc53455",
"build/assets/ba_data/textures/googlePlayAchievementsIcon.ktx": "fb15945ffc91b8b513c41d122c371925",
"build/assets/ba_data/textures/googlePlayAchievementsIcon.pvr": "662e91f51ed4f6dac5695c099a0cecbc",
@@ -2410,6 +2414,22 @@
"build/assets/ba_data/textures/tnt.ktx": "b65ce2a5713a00e37c50aa213b299d8e",
"build/assets/ba_data/textures/tnt.pvr": "9688020124094656a932ef5d446e575a",
"build/assets/ba_data/textures/tnt_preview.png": "37f907b09acbdc72d62e48311e28f439",
+ "build/assets/ba_data/textures/tokens1.dds": "ac5dfc96143d1e8896b83ad5c2b5b707",
+ "build/assets/ba_data/textures/tokens1.ktx": "7eb2e53c71462087fbca6b1e4f428981",
+ "build/assets/ba_data/textures/tokens1.pvr": "2c5475ffef4c43ca3136056c0dabbb10",
+ "build/assets/ba_data/textures/tokens1_preview.png": "de5142cc0be755f2c61cbcd49c6b6c5b",
+ "build/assets/ba_data/textures/tokens2.dds": "5ecb1fa56e9f0cb2665cac9ed195541e",
+ "build/assets/ba_data/textures/tokens2.ktx": "6bbda19cf5c1ea432a5b58f7d5d04013",
+ "build/assets/ba_data/textures/tokens2.pvr": "011a96e01e39e84857e47a3da4a9ea62",
+ "build/assets/ba_data/textures/tokens2_preview.png": "213649d48adeded11c0125f209f43b3e",
+ "build/assets/ba_data/textures/tokens3.dds": "659d16c00046721e65040fd858b0036c",
+ "build/assets/ba_data/textures/tokens3.ktx": "8edee22873a572abcbb57d4a2c804164",
+ "build/assets/ba_data/textures/tokens3.pvr": "bf0463650ce22789b7196117c5a1114a",
+ "build/assets/ba_data/textures/tokens3_preview.png": "9d3e38908db44848152ce85cdaf63656",
+ "build/assets/ba_data/textures/tokens4.dds": "5b3c5f56f86e10b1e99b53b50d0d04f7",
+ "build/assets/ba_data/textures/tokens4.ktx": "f5288b1e86b2c5f8bdf7c185fda49dfd",
+ "build/assets/ba_data/textures/tokens4.pvr": "b2fc14fb36b6c67d64134a1d41104037",
+ "build/assets/ba_data/textures/tokens4_preview.png": "75dc96161bf8182f42124af24f418cf5",
"build/assets/ba_data/textures/touchArrows.dds": "08e1b07035b236cd9c94a33396624030",
"build/assets/ba_data/textures/touchArrows.ktx": "404024fec655041449ed6dca9ba8a35b",
"build/assets/ba_data/textures/touchArrows.pvr": "bce89715f94e530bc243a32e112f3391",
@@ -2478,14 +2498,18 @@
"build/assets/ba_data/textures/white.ktx": "a948a0fcadd2c7ff5ade346dc0856b92",
"build/assets/ba_data/textures/white.pvr": "4281d8f181ccb325d77fabb5c8c87816",
"build/assets/ba_data/textures/white_preview.png": "39e65093bf4792cb696927a08909bb2c",
- "build/assets/ba_data/textures/windowHSmallVMed.dds": "ea2f027e5b201e53b30000dce19985d9",
- "build/assets/ba_data/textures/windowHSmallVMed.ktx": "e0d2b9519570fd4bfc6792bebac4253c",
- "build/assets/ba_data/textures/windowHSmallVMed.pvr": "352934e17d70bd1d2376c02f9d97b923",
- "build/assets/ba_data/textures/windowHSmallVMed_preview.png": "8f10851830b7e640d07bb26cac2e564b",
- "build/assets/ba_data/textures/windowHSmallVSmall.dds": "1fdab760550124d83455a3b32a488abe",
- "build/assets/ba_data/textures/windowHSmallVSmall.ktx": "c064b5bfab58e3526114c712d522b4e2",
- "build/assets/ba_data/textures/windowHSmallVSmall.pvr": "b9eee1de93b19477409cdc04e91d76da",
- "build/assets/ba_data/textures/windowHSmallVSmall_preview.png": "0a61e76caa8bc93e9deaa3369b712a18",
+ "build/assets/ba_data/textures/windowBottomCap.dds": "d0daf8ff9d6b6d99ef24518e43cd4b0a",
+ "build/assets/ba_data/textures/windowBottomCap.ktx": "a948f0953ccdf9a483d25c359a432804",
+ "build/assets/ba_data/textures/windowBottomCap.pvr": "54a390728cca11339445de329b4f4bf5",
+ "build/assets/ba_data/textures/windowBottomCap_preview.png": "fdeb2221b9cfc1bc92805826033b6cee",
+ "build/assets/ba_data/textures/windowHSmallVMed.dds": "c5720b09bef0d51afb4cb294ba516651",
+ "build/assets/ba_data/textures/windowHSmallVMed.ktx": "1d0977585e0ee8c9064a415b35c4b5e9",
+ "build/assets/ba_data/textures/windowHSmallVMed.pvr": "e6844e1d4d0b4161e1f4acb020cf4321",
+ "build/assets/ba_data/textures/windowHSmallVMed_preview.png": "b9d66c1f932c2091b097e4007d1db82a",
+ "build/assets/ba_data/textures/windowHSmallVSmall.dds": "1d3c13f37c4b6f39a45adf20ebec4514",
+ "build/assets/ba_data/textures/windowHSmallVSmall.ktx": "120593e4ecb90079353d02cc8eecd41f",
+ "build/assets/ba_data/textures/windowHSmallVSmall.pvr": "d40c4924e93017e384e32bbe610fb4fc",
+ "build/assets/ba_data/textures/windowHSmallVSmall_preview.png": "b3db4b605a7937736a4f2e9c233a03fb",
"build/assets/ba_data/textures/wings.dds": "ed0aee36ae96b2c5c3e9ee03ef23c998",
"build/assets/ba_data/textures/wings.ktx": "127b7381b882448b80428c96c7483dbb",
"build/assets/ba_data/textures/wings.pvr": "3e07ec0f582d897c2b21395ae6c5aa3f",
@@ -2572,13 +2596,13 @@
"build/assets/pylib-android/_osx_support.py": "c0871f8d2b36955d67f0446bb9fa3827",
"build/assets/pylib-android/_py_abc.py": "180d5cf138b011bd6a280c2f433bed47",
"build/assets/pylib-android/_pydatetime.py": "af813d3d6cb8ccf3e90b76b91f5fa7f2",
- "build/assets/pylib-android/_pydecimal.py": "4572eb8c67bcfbbcda7ed47055c6e6b5",
+ "build/assets/pylib-android/_pydecimal.py": "e58d83934dbffe094d2976a9e1132190",
"build/assets/pylib-android/_pyio.py": "618a6fa97c93e8c63c8e5cf3c283d5fe",
- "build/assets/pylib-android/_pylong.py": "e057ab3c9eea264704dff2af204884f8",
+ "build/assets/pylib-android/_pylong.py": "0ae926c324155186a115b54eb88d0526",
"build/assets/pylib-android/_sitebuiltins.py": "8b5e3f6e73917962fa014ad2c4a55e61",
"build/assets/pylib-android/_strptime.py": "8c65b46a50f13ca2389b19b32b7f2996",
- "build/assets/pylib-android/_sysconfigdata__linux_.py": "e112c946a141bcf18e0656a14a52ca71",
- "build/assets/pylib-android/_sysconfigdata_d_linux_.py": "063295bed5c2c6f30d323abb2cf82fd9",
+ "build/assets/pylib-android/_sysconfigdata__linux_.py": "6c03d32177713f6abdf9e692aaa53ed6",
+ "build/assets/pylib-android/_sysconfigdata_d_linux_.py": "c37336d1880d691f3108e1cedf5e7355",
"build/assets/pylib-android/_threading_local.py": "4a9688e3987d7d692db46feb9214945e",
"build/assets/pylib-android/_weakrefset.py": "e4fa8532ace46dfbc35149c41ea497f7",
"build/assets/pylib-android/abc.py": "a0daa1ed187eee8690c1e8438b97da90",
@@ -2587,7 +2611,7 @@
"build/assets/pylib-android/argparse.py": "479fa19b01a256e9162cfed4b3866708",
"build/assets/pylib-android/ast.py": "e59b9f09a7d1f8979b94f900a72f74c6",
"build/assets/pylib-android/asyncio/__init__.py": "4a732a7b4c77634cab1909d8be43cb4c",
- "build/assets/pylib-android/asyncio/__main__.py": "8e391b47f448ad922dc2614dbd93011e",
+ "build/assets/pylib-android/asyncio/__main__.py": "18122a137578030c2a458f7af9d93edd",
"build/assets/pylib-android/asyncio/base_events.py": "071d87caa982ce12333bf31b691dd0b0",
"build/assets/pylib-android/asyncio/base_futures.py": "5b4cefd0a571e9e8d913a052f2e0b15e",
"build/assets/pylib-android/asyncio/base_subprocess.py": "8a805e04a2911b2d8e297f0029def4b1",
@@ -2601,7 +2625,7 @@
"build/assets/pylib-android/asyncio/locks.py": "1630852effc563876002c88455a2f7f4",
"build/assets/pylib-android/asyncio/log.py": "1e101049c5cd7ad159b63ef97fe8fb0b",
"build/assets/pylib-android/asyncio/mixins.py": "9be94c61811a65522320e29e3dec16b0",
- "build/assets/pylib-android/asyncio/proactor_events.py": "ff72970f982322a1141d710557847d4d",
+ "build/assets/pylib-android/asyncio/proactor_events.py": "97d197fadbae03691260f805ac1049e2",
"build/assets/pylib-android/asyncio/protocols.py": "b8aa105b79d24f88c7a2c2cdbc8e7814",
"build/assets/pylib-android/asyncio/queues.py": "f63be54780730992e2377c51ac373126",
"build/assets/pylib-android/asyncio/runners.py": "e523f1abdd9ab2cf2bc355acea052bd8",
@@ -2619,8 +2643,8 @@
"build/assets/pylib-android/asyncio/unix_events.py": "e5490096c3c8ce4d2537317c608862d9",
"build/assets/pylib-android/asyncio/windows_events.py": "e70d58554906a59b6d823850aa6b8395",
"build/assets/pylib-android/asyncio/windows_utils.py": "4efbef16e6692c9424804d9bdc496761",
- "build/assets/pylib-android/base64.py": "00f5d31b06dd0f489d8b14b7bcf43db7",
- "build/assets/pylib-android/bdb.py": "1e26c626405bfc06b1e7231183eb928a",
+ "build/assets/pylib-android/base64.py": "b89a7dfe64760a2402b5bf255174969c",
+ "build/assets/pylib-android/bdb.py": "e7f362c5918a54efc0085efc7bae3a8d",
"build/assets/pylib-android/bisect.py": "69d3166bd72a28217f1bffa40dc9c33b",
"build/assets/pylib-android/bz2.py": "cd6a5f2491bc52afd8fc180097371473",
"build/assets/pylib-android/cProfile.py": "bec17d6b102c0123c4b743ac685de752",
@@ -2648,7 +2672,7 @@
"build/assets/pylib-android/copyreg.py": "de5fb1333a0e388e69749e78ed5e55ae",
"build/assets/pylib-android/crypt.py": "e12ad225ff7be254f543a48ed68b9465",
"build/assets/pylib-android/csv.py": "a896b3c30246ae11b1633b402675b6b9",
- "build/assets/pylib-android/ctypes/__init__.py": "235310a16d0c17607d86807724dfc51f",
+ "build/assets/pylib-android/ctypes/__init__.py": "53033669e712496a979fc62d320ff76b",
"build/assets/pylib-android/ctypes/_aix.py": "5fd9184c6794ee90ac1441f2c5fe7335",
"build/assets/pylib-android/ctypes/_endian.py": "75edb1136e6c70ff5797b639d74d3a76",
"build/assets/pylib-android/ctypes/macholib/__init__.py": "b2ee4220c9357720236e4da2f849f7da",
@@ -2662,17 +2686,17 @@
"build/assets/pylib-android/curses/has_key.py": "c74b8d6db329fbbd872b7b91bfa94624",
"build/assets/pylib-android/curses/panel.py": "8f36fdade9588f8a4362d2cc057a6eff",
"build/assets/pylib-android/curses/textpad.py": "c53f9edcb5abba15eb755fdebef0eb19",
- "build/assets/pylib-android/dataclasses.py": "52ef11127750fed81643fe829bc4c737",
+ "build/assets/pylib-android/dataclasses.py": "a57847f8c5f4dc7681d8a39a96bf399b",
"build/assets/pylib-android/datetime.py": "b4a98cc076882de0f4c5787b888b2eb6",
- "build/assets/pylib-android/decimal.py": "f57d255d45b5d1d7d8e13c41a283c3e4",
+ "build/assets/pylib-android/decimal.py": "bc71d12e6e91b709990e51c1e5c22a04",
"build/assets/pylib-android/difflib.py": "6b3c8fd541b2b8d0320727025cd25275",
"build/assets/pylib-android/dis.py": "a13ae44a4be77ce18f74d1e56659ea03",
- "build/assets/pylib-android/doctest.py": "25cfaf6115c8d69f5e84c66ce4d51dae",
+ "build/assets/pylib-android/doctest.py": "d5799d14d763cb9788073a7d9fcb3cac",
"build/assets/pylib-android/email/__init__.py": "4ff603eeeb4ce0302c8bd1b220fc5e13",
"build/assets/pylib-android/email/_encoded_words.py": "d7b77501689dd1ce32da789b46264a6a",
- "build/assets/pylib-android/email/_header_value_parser.py": "f472e3136172faada943c60181e886eb",
+ "build/assets/pylib-android/email/_header_value_parser.py": "ca9f4ed18429b711d22e25dc6117da3d",
"build/assets/pylib-android/email/_parseaddr.py": "a6d2999aeed17f060be8797e761a01aa",
- "build/assets/pylib-android/email/_policybase.py": "09b0c21693ac080c52c52f5daab616aa",
+ "build/assets/pylib-android/email/_policybase.py": "b632710aafb0885a10b6ed8c7576e9cb",
"build/assets/pylib-android/email/base64mime.py": "d3e4fc07d04833487677dd2a888c3826",
"build/assets/pylib-android/email/charset.py": "8bcb2315a8340755057e66398e975542",
"build/assets/pylib-android/email/contentmanager.py": "e88780ef1d37a11ff216060f740f2572",
@@ -2683,7 +2707,7 @@
"build/assets/pylib-android/email/header.py": "09923b7b77bf91bed41a71b6e3c6e4d9",
"build/assets/pylib-android/email/headerregistry.py": "dfd48f9c41454d5bc2355cb1762fb869",
"build/assets/pylib-android/email/iterators.py": "752ece28a18545e70fa67a7cf2fe3ef3",
- "build/assets/pylib-android/email/message.py": "d35660dfc0ff8e0ed0125f0e465771b6",
+ "build/assets/pylib-android/email/message.py": "68764155da8820f7d4810b08b0fe7af0",
"build/assets/pylib-android/email/mime/__init__.py": "340c83beff7dcff8f5c7b87cd43cedaf",
"build/assets/pylib-android/email/mime/application.py": "4ce678512f30cac9fd95993186d2eef7",
"build/assets/pylib-android/email/mime/audio.py": "4073e45bda9524e3cbe29374951fdc16",
@@ -2694,7 +2718,7 @@
"build/assets/pylib-android/email/mime/nonmultipart.py": "a96d8d31156781a7511cec04e46a95f6",
"build/assets/pylib-android/email/mime/text.py": "634e0b909f94788cf97fc8a0b914d12c",
"build/assets/pylib-android/email/parser.py": "d78f74ba45a3618608cdc9ece2aa411a",
- "build/assets/pylib-android/email/policy.py": "34946fe746ce5d39634d54f18716c8f3",
+ "build/assets/pylib-android/email/policy.py": "96ff779e28c2fa000dd84acced05650c",
"build/assets/pylib-android/email/quoprimime.py": "c5e54c3f0e70d55145517382d4455765",
"build/assets/pylib-android/email/utils.py": "05398f8a134b22fac6ab3a1d4b84fcb1",
"build/assets/pylib-android/encodings/__init__.py": "6a342ed3b218da63b7f7937beafcc20c",
@@ -2825,7 +2849,7 @@
"build/assets/pylib-android/fnmatch.py": "a1bc67633695d4defd4c0886428c5363",
"build/assets/pylib-android/fractions.py": "02689771b334f161f5bd4b052aaa3e33",
"build/assets/pylib-android/ftplib.py": "84c6c5d111b887aa3986a02d164e4cdd",
- "build/assets/pylib-android/functools.py": "a60c3a01c02be0f20a3e91de2b9e188f",
+ "build/assets/pylib-android/functools.py": "25c531165121ae37d026331f7364f379",
"build/assets/pylib-android/genericpath.py": "700f98a87ac51709fc817a23e48b52f3",
"build/assets/pylib-android/getopt.py": "2c02d59b410128b2ebff26e3030568a6",
"build/assets/pylib-android/getpass.py": "c19e383e949c147a30ecc554b8598e91",
@@ -2869,9 +2893,9 @@
"build/assets/pylib-android/importlib/resources/simple.py": "16b97df12d2762e00bbb7ee6189750a2",
"build/assets/pylib-android/importlib/simple.py": "f34f28cd359ecaf9fd6cf18a4dca2c33",
"build/assets/pylib-android/importlib/util.py": "8b414591277a59478a9acab9ef9c56e2",
- "build/assets/pylib-android/inspect.py": "1f9f508f836c6f55658d722e9383ada9",
+ "build/assets/pylib-android/inspect.py": "c3ef07d9f7ac90aad135a198faf2029b",
"build/assets/pylib-android/io.py": "4f501a9e8a4ff4c2ca8152b8f5634cb9",
- "build/assets/pylib-android/ipaddress.py": "f8aab9a66231645bc9bd92301f1c6ac0",
+ "build/assets/pylib-android/ipaddress.py": "523062c7debdcd4c554b3e7c7b3714b1",
"build/assets/pylib-android/json/__init__.py": "e8b000d2bf8c53b55a72bc3053800596",
"build/assets/pylib-android/json/decoder.py": "82a8faab8ae9599b2b5f58322b8055ee",
"build/assets/pylib-android/json/encoder.py": "c85377b74511c6f463715e02118ca566",
@@ -2879,9 +2903,9 @@
"build/assets/pylib-android/json/tool.py": "a83b2c5dafa3adfd772a058cddbc0afc",
"build/assets/pylib-android/keyword.py": "4132d92e0b8a50d8f7119ed5fabf1674",
"build/assets/pylib-android/linecache.py": "965977aa395c6db802aa732f30660097",
- "build/assets/pylib-android/locale.py": "7c90ef0f6f75d9a5ee9a709ca266a2e2",
+ "build/assets/pylib-android/locale.py": "b337ca2d5d86061fd6e0592125a87f50",
"build/assets/pylib-android/logging/__init__.py": "88f53a4eec4313c2e2ab9130250d3ab5",
- "build/assets/pylib-android/logging/config.py": "cc6d515fd6b7b16d8ab66fe934249f9b",
+ "build/assets/pylib-android/logging/config.py": "63b9328b44c899e6858af68fc65b7646",
"build/assets/pylib-android/logging/handlers.py": "0d0ca5db1f8e3588301a4cfbc4d6d365",
"build/assets/pylib-android/lzma.py": "d7388640aa6af4c64fdc821471930f57",
"build/assets/pylib-android/mailbox.py": "7de36be1f3365d2202d1e6548c538360",
@@ -2890,23 +2914,23 @@
"build/assets/pylib-android/modulefinder.py": "3fc074c018de1dd15ab2e309be199dea",
"build/assets/pylib-android/netrc.py": "9dd6b80891863e23b5e0a57bb80c7346",
"build/assets/pylib-android/nntplib.py": "4926aa991bd4b96b9318d6242135abf9",
- "build/assets/pylib-android/ntpath.py": "1e3997be9aec964b4a3caa2ff7172c78",
+ "build/assets/pylib-android/ntpath.py": "9790c31a274924d4168d8fa1f396f9c0",
"build/assets/pylib-android/nturl2path.py": "937624c4b6f213b652addea66296ccc4",
"build/assets/pylib-android/numbers.py": "bb9d88751c4f892d66e43961c521de7f",
"build/assets/pylib-android/opcode.py": "27b5ed52b503d4cf1adb7d65116655c3",
"build/assets/pylib-android/operator.py": "61e197bc43df97ec39ae3e5e59b11c19",
"build/assets/pylib-android/optparse.py": "5f65f891612b68c71a2846da86254285",
- "build/assets/pylib-android/os.py": "1d20abe92b62ba9fc876be6714e29337",
- "build/assets/pylib-android/pathlib.py": "1bf23fd9f937bb58eec2a62a60d73280",
+ "build/assets/pylib-android/os.py": "e9627e3183be83287f785cfa0878a820",
+ "build/assets/pylib-android/pathlib.py": "b6a1d7be62bb73cf45e43617f5270957",
"build/assets/pylib-android/pdb.py": "c62b3dff47478089a415ed83671a636c",
"build/assets/pylib-android/pickle.py": "ec61f6c0d4bd9f21c664a3673d6ce158",
"build/assets/pylib-android/pickletools.py": "e2cb81715625efdc409f8d34e36a9569",
"build/assets/pylib-android/pipes.py": "2dd796bdbb87982034234fec50d4526c",
"build/assets/pylib-android/pkgutil.py": "417ff74f276b7659b93ac5e8ee425f0f",
- "build/assets/pylib-android/platform.py": "d15e44b3dcd3271c78bcd4b0cd37dbbb",
+ "build/assets/pylib-android/platform.py": "263c9442b51433c6956a9e7a561d9b7e",
"build/assets/pylib-android/plistlib.py": "89a4be15fa63e930d5b5fc3f9c99c4f7",
"build/assets/pylib-android/poplib.py": "8150e0a07082ec4dbb017c4bc7163d7d",
- "build/assets/pylib-android/posixpath.py": "f7403faf74c3968010c45e204aad6415",
+ "build/assets/pylib-android/posixpath.py": "41b7b16fe21131e0012177f090aacc47",
"build/assets/pylib-android/pprint.py": "8a1792d9fe0cad9605a9deb9af479341",
"build/assets/pylib-android/profile.py": "8f26436d147cfd6d00d19592cf0d8c91",
"build/assets/pylib-android/pstats.py": "1eb59db1b83dd577c863d26ca04b3105",
@@ -2930,17 +2954,17 @@
"build/assets/pylib-android/selectors.py": "3c94b3b678c473543cdc7f1d2b20a6f6",
"build/assets/pylib-android/shelve.py": "3e569c07c863ecbd7f35a6c382d1785a",
"build/assets/pylib-android/shlex.py": "adb6b917c5d3adadbdcb77a9257595fd",
- "build/assets/pylib-android/shutil.py": "b24c18a65894e6860b16fb8462f9c1c5",
+ "build/assets/pylib-android/shutil.py": "b23c4712f744f3030ba1f87d1a2bf6a4",
"build/assets/pylib-android/signal.py": "6c286caa92d351510f74cb4e1b3661da",
- "build/assets/pylib-android/site.py": "ddbf5304e85bedcc06f9a49b2e544e74",
+ "build/assets/pylib-android/site.py": "e1523c36412e4ec333acbd5b0d2d6ea2",
"build/assets/pylib-android/smtplib.py": "a8121c0c6888489f89cdb2d5b06638f7",
"build/assets/pylib-android/sndhdr.py": "6b62a34738529e39528c0cb498a23eee",
- "build/assets/pylib-android/socket.py": "93b3814a3cd3753bfbb8caa1e37ea8dd",
+ "build/assets/pylib-android/socket.py": "e0463a87103f5d2e08f772b9a53ea816",
"build/assets/pylib-android/socketserver.py": "08d185dbe1e568e299133cc8dcb35940",
"build/assets/pylib-android/sqlite3/__init__.py": "e64822b75a1c8f1be3e23ed6923e12eb",
"build/assets/pylib-android/sqlite3/__main__.py": "52b4c89dbcbaab2bcbd71da0317fdf46",
"build/assets/pylib-android/sqlite3/dbapi2.py": "b3b44b48fcb3999ca0269023c2fb7268",
- "build/assets/pylib-android/sqlite3/dump.py": "8d2085ec40031d544694759608e53178",
+ "build/assets/pylib-android/sqlite3/dump.py": "ac821eee63ad27947460c6aca84d4e8d",
"build/assets/pylib-android/sre_compile.py": "a1784e9ccbea7d9963cab75b536b40c8",
"build/assets/pylib-android/sre_constants.py": "5c5be32a5334d9b0a848dad520746a63",
"build/assets/pylib-android/sre_parse.py": "cca15b9ab31509e6642f9d2fd4fb9d91",
@@ -2955,8 +2979,8 @@
"build/assets/pylib-android/symtable.py": "7808839ac346e577ef1ab623c324d9e1",
"build/assets/pylib-android/sysconfig.py": "6c0114db24638ab125b9e933d44c79ef",
"build/assets/pylib-android/tabnanny.py": "017f45469d744cc511654fe887bda204",
- "build/assets/pylib-android/tarfile.py": "43041075df31a5c8e7d8ea626391f185",
- "build/assets/pylib-android/telnetlib.py": "1a746e1fc988b9f5c12b9fd5a97c00ea",
+ "build/assets/pylib-android/tarfile.py": "c895804e267dc809bf1fbd5e990078ba",
+ "build/assets/pylib-android/telnetlib.py": "0700ac21e04abde09aac1eb1e52ebbde",
"build/assets/pylib-android/tempfile.py": "916365f4533239ffe6d66440b0ead6ae",
"build/assets/pylib-android/textwrap.py": "3eb16a40553205dc96be5cb9039f3c8c",
"build/assets/pylib-android/this.py": "8b0a9a1fa0a45a37e6c656eca1922277",
@@ -2968,21 +2992,21 @@
"build/assets/pylib-android/tomllib/_parser.py": "f9a4dc92c44403f970e94c2e494a7358",
"build/assets/pylib-android/tomllib/_re.py": "0e509117e16c41c491615e06bb98861d",
"build/assets/pylib-android/tomllib/_types.py": "07be9616d6f5e401fd31fbeea619fc97",
- "build/assets/pylib-android/trace.py": "e0015a322cf5fbb35310705e81953ab1",
+ "build/assets/pylib-android/trace.py": "3e0ba305fd653f127f2ddcc278abbbc4",
"build/assets/pylib-android/traceback.py": "84cbe40fcb14833ce4a1c0be22c4c9bc",
"build/assets/pylib-android/tracemalloc.py": "e4d10d2bee7773566e46797a939e5cbf",
"build/assets/pylib-android/tty.py": "06d3dd054c50fbff2712f3a38cca880f",
"build/assets/pylib-android/types.py": "134f86ee0d4879d294f95ea39c396027",
- "build/assets/pylib-android/typing.py": "5780108232e902b6c03c9dede3203e15",
+ "build/assets/pylib-android/typing.py": "56bcd9a7595ef9b7f9332894b7ebc1a7",
"build/assets/pylib-android/urllib/__init__.py": "340c83beff7dcff8f5c7b87cd43cedaf",
"build/assets/pylib-android/urllib/error.py": "b7dde0483ff647eb87162d6e19c04fa0",
- "build/assets/pylib-android/urllib/parse.py": "9533d0e59e5ec79810be417dfc8b351a",
+ "build/assets/pylib-android/urllib/parse.py": "2b10efb06b24573929dafb6167b26099",
"build/assets/pylib-android/urllib/request.py": "bf59985a796494de7374fb909f32f933",
"build/assets/pylib-android/urllib/response.py": "c8537707a4b1e493c0ec4489ab523c93",
"build/assets/pylib-android/urllib/robotparser.py": "5a7616bdf398c166f953ad48c25506eb",
"build/assets/pylib-android/uu.py": "7cb795ca0c9e914550e6245dfe53c53e",
"build/assets/pylib-android/uuid.py": "fc298b89a3f1327be7808f0125c57866",
- "build/assets/pylib-android/warnings.py": "0786f92f520b209fc88b9352fd7fc474",
+ "build/assets/pylib-android/warnings.py": "4deb449b93c3a0d8737555589f6f111c",
"build/assets/pylib-android/wave.py": "91c8be6651123c0d264fada07b63fd8e",
"build/assets/pylib-android/weakref.py": "dd14612f02ca8acd723f633b6fff0adf",
"build/assets/pylib-android/webbrowser.py": "45185d3143cc2513d3141e7cbfdafb1b",
@@ -2996,7 +3020,7 @@
"build/assets/pylib-android/xml/dom/minidom.py": "3f4037810f081b078cc3fcab75bae3a0",
"build/assets/pylib-android/xml/dom/pulldom.py": "e133090081e446d69e5d1884f64fe1b6",
"build/assets/pylib-android/xml/dom/xmlbuilder.py": "610ebe0afcf5c7a72c6f708ae34e4f7e",
- "build/assets/pylib-android/xml/etree/ElementInclude.py": "164c77dfb9abdf9c70def8812a00843a",
+ "build/assets/pylib-android/xml/etree/ElementInclude.py": "cdb7b9c55c44fbeb94bf96500b7bb5aa",
"build/assets/pylib-android/xml/etree/ElementPath.py": "fca9220534a8160762dee5e7da5f4144",
"build/assets/pylib-android/xml/etree/ElementTree.py": "dade97ab01bfa32bafca09daf7e7bbb2",
"build/assets/pylib-android/xml/etree/__init__.py": "82a62c7b190620aa1d3a40cc9ba829a5",
@@ -4038,52 +4062,52 @@
"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": "984af200e5e35417de6ace1da79c1351",
- "build/prefab/full/linux_arm64_gui/release/ballisticakit": "0902b446e625e001396b70ab1e567423",
- "build/prefab/full/linux_arm64_server/debug/dist/ballisticakit_headless": "7e940b9dabe493f106c2e9d41fad32d2",
- "build/prefab/full/linux_arm64_server/release/dist/ballisticakit_headless": "3492bf499cbf195e311ea7587d4247c4",
- "build/prefab/full/linux_x86_64_gui/debug/ballisticakit": "80ce3502f6ff6ae99d5a61cb746f476a",
- "build/prefab/full/linux_x86_64_gui/release/ballisticakit": "08f433223f802b142a64a4d7e8ff129e",
- "build/prefab/full/linux_x86_64_server/debug/dist/ballisticakit_headless": "ee89411f673161492bf8762656c8308a",
- "build/prefab/full/linux_x86_64_server/release/dist/ballisticakit_headless": "bcb36f5a15e3fe28cbc5606b2196736d",
- "build/prefab/full/mac_arm64_gui/debug/ballisticakit": "fb21bde8f575ef1aaabfee5bd2651571",
- "build/prefab/full/mac_arm64_gui/release/ballisticakit": "3c3ad695c496f35ca06b9b95d4ff1db7",
- "build/prefab/full/mac_arm64_server/debug/dist/ballisticakit_headless": "f3ee8ad1b441c7788f2a76106a740f14",
- "build/prefab/full/mac_arm64_server/release/dist/ballisticakit_headless": "b29045a9462537f4cc4a487a0d75abc3",
- "build/prefab/full/mac_x86_64_gui/debug/ballisticakit": "46c370f0616494816ea1ff73aa2b0d2f",
- "build/prefab/full/mac_x86_64_gui/release/ballisticakit": "ffb4d1ab15033abbc9a7ccdf64493b25",
- "build/prefab/full/mac_x86_64_server/debug/dist/ballisticakit_headless": "0a542a05b30e4f1d443e3e63be3406aa",
- "build/prefab/full/mac_x86_64_server/release/dist/ballisticakit_headless": "8711c3e818a759b050cab7dcff2e1147",
- "build/prefab/full/windows_x86_gui/debug/BallisticaKit.exe": "edb7277a16fb2847388b3c55447e5fbd",
- "build/prefab/full/windows_x86_gui/release/BallisticaKit.exe": "8980b84f0c39fb6f8bead208e4b794de",
- "build/prefab/full/windows_x86_server/debug/dist/BallisticaKitHeadless.exe": "109c5e9d5a415a0c5bb81ad076680174",
- "build/prefab/full/windows_x86_server/release/dist/BallisticaKitHeadless.exe": "49aa40bff81a8342ff16a41c143d7917",
- "build/prefab/lib/linux_arm64_gui/debug/libballisticaplus.a": "f231b10895bdcb542de87b887ca181fd",
- "build/prefab/lib/linux_arm64_gui/release/libballisticaplus.a": "ae936a119668ede7b36f38c8672f4bf8",
- "build/prefab/lib/linux_arm64_server/debug/libballisticaplus.a": "f231b10895bdcb542de87b887ca181fd",
- "build/prefab/lib/linux_arm64_server/release/libballisticaplus.a": "ae936a119668ede7b36f38c8672f4bf8",
- "build/prefab/lib/linux_x86_64_gui/debug/libballisticaplus.a": "419b7edff05748a74370f29945bca90c",
- "build/prefab/lib/linux_x86_64_gui/release/libballisticaplus.a": "1e75c6c1f0b0aae130bacfb8b65507cc",
- "build/prefab/lib/linux_x86_64_server/debug/libballisticaplus.a": "419b7edff05748a74370f29945bca90c",
- "build/prefab/lib/linux_x86_64_server/release/libballisticaplus.a": "1e75c6c1f0b0aae130bacfb8b65507cc",
- "build/prefab/lib/mac_arm64_gui/debug/libballisticaplus.a": "d965aa38a18627966633308f5a4e61b9",
- "build/prefab/lib/mac_arm64_gui/release/libballisticaplus.a": "5d12b05022f064c2aaf096c466da687c",
- "build/prefab/lib/mac_arm64_server/debug/libballisticaplus.a": "d965aa38a18627966633308f5a4e61b9",
- "build/prefab/lib/mac_arm64_server/release/libballisticaplus.a": "5d12b05022f064c2aaf096c466da687c",
- "build/prefab/lib/mac_x86_64_gui/debug/libballisticaplus.a": "ff5b18144bf249558517bff7c97dc84f",
- "build/prefab/lib/mac_x86_64_gui/release/libballisticaplus.a": "efffc4f330e77530accd9a9f82840a6c",
- "build/prefab/lib/mac_x86_64_server/debug/libballisticaplus.a": "c20363fe2af3d54e666b1c8ee67f6b76",
- "build/prefab/lib/mac_x86_64_server/release/libballisticaplus.a": "efffc4f330e77530accd9a9f82840a6c",
- "build/prefab/lib/windows/Debug_Win32/BallisticaKitGenericPlus.lib": "54b1fb0ff0a189a59da87f3578a1119a",
- "build/prefab/lib/windows/Debug_Win32/BallisticaKitGenericPlus.pdb": "c0e6444e540a2046b858fc4b4629f6b4",
- "build/prefab/lib/windows/Debug_Win32/BallisticaKitHeadlessPlus.lib": "b44c5bb764eb9a30c49d3dc36c54d784",
- "build/prefab/lib/windows/Debug_Win32/BallisticaKitHeadlessPlus.pdb": "23afa2f159052928a16cc3e5bde1f864",
- "build/prefab/lib/windows/Release_Win32/BallisticaKitGenericPlus.lib": "40437dd15a65d97481d88e245de923bf",
- "build/prefab/lib/windows/Release_Win32/BallisticaKitGenericPlus.pdb": "4e7a439617c2ad9d6100a7ec6e593713",
- "build/prefab/lib/windows/Release_Win32/BallisticaKitHeadlessPlus.lib": "a19afc6c611054e2c5df4dd3c07afed9",
- "build/prefab/lib/windows/Release_Win32/BallisticaKitHeadlessPlus.pdb": "5156925d106a00a4602ef7204f70ab5a",
+ "build/prefab/full/linux_arm64_gui/debug/ballisticakit": "de1a31ddd37764c6a31c6c619a0069fe",
+ "build/prefab/full/linux_arm64_gui/release/ballisticakit": "1a4894c9eb324e6d2a442f72e5651c2c",
+ "build/prefab/full/linux_arm64_server/debug/dist/ballisticakit_headless": "1999d90958ff312203a290aaff97a24f",
+ "build/prefab/full/linux_arm64_server/release/dist/ballisticakit_headless": "9a33cde90e447754e0b669f1e08948b2",
+ "build/prefab/full/linux_x86_64_gui/debug/ballisticakit": "c52c4b14e87cada2d2de8060caf64ec3",
+ "build/prefab/full/linux_x86_64_gui/release/ballisticakit": "4894e58ac2fa3cbff0c0542e33afd94e",
+ "build/prefab/full/linux_x86_64_server/debug/dist/ballisticakit_headless": "eee4b5407e69075d925fad94de12c0b8",
+ "build/prefab/full/linux_x86_64_server/release/dist/ballisticakit_headless": "5d041c76ab815765179308a01b6a0028",
+ "build/prefab/full/mac_arm64_gui/debug/ballisticakit": "8ae3a85f68553255fa47a86e35cd41b0",
+ "build/prefab/full/mac_arm64_gui/release/ballisticakit": "cd9dac595579b8e19f15eade2cf8155f",
+ "build/prefab/full/mac_arm64_server/debug/dist/ballisticakit_headless": "1ab4f141dd2541ae40e886b5d448d86f",
+ "build/prefab/full/mac_arm64_server/release/dist/ballisticakit_headless": "fb869f599357cb079851542411b2a852",
+ "build/prefab/full/mac_x86_64_gui/debug/ballisticakit": "d06777b1f953c4f102d3ddb5b9d79337",
+ "build/prefab/full/mac_x86_64_gui/release/ballisticakit": "ac0582306bab0108da45dc53e0c544a7",
+ "build/prefab/full/mac_x86_64_server/debug/dist/ballisticakit_headless": "f847c697c1d541bbd1207355065fa988",
+ "build/prefab/full/mac_x86_64_server/release/dist/ballisticakit_headless": "6edc275e5c427a04df8b02b115d2d832",
+ "build/prefab/full/windows_x86_gui/debug/BallisticaKit.exe": "473ef626512034b43a8fa54ad66af1ce",
+ "build/prefab/full/windows_x86_gui/release/BallisticaKit.exe": "45f6a37d5e21b45542260f86b7c4784b",
+ "build/prefab/full/windows_x86_server/debug/dist/BallisticaKitHeadless.exe": "4d9922c7ba8e15e2059fcef7f87d195a",
+ "build/prefab/full/windows_x86_server/release/dist/BallisticaKitHeadless.exe": "fd57a5b64d0fc65bd04bbfdd4b63965d",
+ "build/prefab/lib/linux_arm64_gui/debug/libballisticaplus.a": "fbfd42ab5d27e3ff921fa9cda02b2b45",
+ "build/prefab/lib/linux_arm64_gui/release/libballisticaplus.a": "abb5870a4b01cda2c9a958149f759325",
+ "build/prefab/lib/linux_arm64_server/debug/libballisticaplus.a": "fbfd42ab5d27e3ff921fa9cda02b2b45",
+ "build/prefab/lib/linux_arm64_server/release/libballisticaplus.a": "abb5870a4b01cda2c9a958149f759325",
+ "build/prefab/lib/linux_x86_64_gui/debug/libballisticaplus.a": "1b51a49163d412cdd0b1441e3459e6c4",
+ "build/prefab/lib/linux_x86_64_gui/release/libballisticaplus.a": "4a39d8397fb76df3bafa366d8085d1cd",
+ "build/prefab/lib/linux_x86_64_server/debug/libballisticaplus.a": "1b51a49163d412cdd0b1441e3459e6c4",
+ "build/prefab/lib/linux_x86_64_server/release/libballisticaplus.a": "4a39d8397fb76df3bafa366d8085d1cd",
+ "build/prefab/lib/mac_arm64_gui/debug/libballisticaplus.a": "c4ed230b63e7109de342e045733963e5",
+ "build/prefab/lib/mac_arm64_gui/release/libballisticaplus.a": "adb7ef762cb92b4c3bbe74a36e8ac6df",
+ "build/prefab/lib/mac_arm64_server/debug/libballisticaplus.a": "c4ed230b63e7109de342e045733963e5",
+ "build/prefab/lib/mac_arm64_server/release/libballisticaplus.a": "adb7ef762cb92b4c3bbe74a36e8ac6df",
+ "build/prefab/lib/mac_x86_64_gui/debug/libballisticaplus.a": "0863bb24d2a42598dd20e7b55beb1c02",
+ "build/prefab/lib/mac_x86_64_gui/release/libballisticaplus.a": "1805fd5900917411f0e8032bf3c4734c",
+ "build/prefab/lib/mac_x86_64_server/debug/libballisticaplus.a": "3277c2f61c315e2ca7ed6776ba2363e2",
+ "build/prefab/lib/mac_x86_64_server/release/libballisticaplus.a": "1805fd5900917411f0e8032bf3c4734c",
+ "build/prefab/lib/windows/Debug_Win32/BallisticaKitGenericPlus.lib": "b639992e280ee3a73b764a3e3e58baec",
+ "build/prefab/lib/windows/Debug_Win32/BallisticaKitGenericPlus.pdb": "5ebaf4315d286080e5789939edfe43ce",
+ "build/prefab/lib/windows/Debug_Win32/BallisticaKitHeadlessPlus.lib": "be8f76ded92473fbab8b28018097456c",
+ "build/prefab/lib/windows/Debug_Win32/BallisticaKitHeadlessPlus.pdb": "dcab88e3c14c6f4c4a03dd8617328eaf",
+ "build/prefab/lib/windows/Release_Win32/BallisticaKitGenericPlus.lib": "bab18c50134744a7206ea1ded2d90ab2",
+ "build/prefab/lib/windows/Release_Win32/BallisticaKitGenericPlus.pdb": "c6788ba7cd99fc29a0b6ca8351f412b4",
+ "build/prefab/lib/windows/Release_Win32/BallisticaKitHeadlessPlus.lib": "e61726c963e53ee7746ffbd7ed35c919",
+ "build/prefab/lib/windows/Release_Win32/BallisticaKitHeadlessPlus.pdb": "841fbbd6b87ac5d50ccdda647d8ff909",
"src/assets/ba_data/python/babase/_mgen/__init__.py": "f885fed7f2ed98ff2ba271f9dbe3391c",
- "src/assets/ba_data/python/babase/_mgen/enums.py": "5548f407d97e380069f6c596c4e36cd7",
+ "src/assets/ba_data/python/babase/_mgen/enums.py": "cb299985623bbcc86015cb103a424ae6",
"src/ballistica/base/mgen/pyembed/binding_base.inc": "efa61468cf098f77cc6a234461d8b86d",
"src/ballistica/base/mgen/pyembed/binding_base_app.inc": "97efb93f4bfd8e8b09f2db24398e29fc",
"src/ballistica/classic/mgen/pyembed/binding_classic.inc": "3ceb412513963f0818ab39c58bf292e3",
diff --git a/.idea/dictionaries/ericf.xml b/.idea/dictionaries/ericf.xml
index 74cd2518..d7b16700 100644
--- a/.idea/dictionaries/ericf.xml
+++ b/.idea/dictionaries/ericf.xml
@@ -1232,7 +1232,7 @@
getcollisionmesh
getconf
getconfig
- getcurrency
+ gettickets
getcwd
getdata
getenv
@@ -3082,7 +3082,6 @@
tournamentbutton
tournamententry
tournamentscores
- tpartial
tpath
tpathsegs
tpexl
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 27fbab44..5c71e354 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,4 +1,4 @@
-### 1.7.36 (build 21911, api 8, 2024-07-05)
+### 1.7.36 (build 21919, api 8, 2024-07-21)
- Wired up initial support for using asset-packages for bundled assets.
- bacloud workspace commands are now a bit smarter; you can now do things like
`bacloud workspace put .` or even just `bacloud workspace put` and it will
@@ -6,6 +6,29 @@
as a second argument. Both `workspace get` and `workspace put` now also have
an optional `--workspace` arg if you want to sync with a workspace different
than the local directory name.
+- Cleaned up look and feel on horizontal scrollbars, especially when and how
+ they fade in and out.
+- Fixed an issue where ConfigNumberEdit objects would draw incorrectly with
+ textscale set to non-1.0 values.
+- Fixed a nasty bug with the new stdin handling from 1.7.35 which could cause
+ the stdin thread to spin at 100% cpu usage in some cases (such as when
+ launching the Mac build from the Finder and not a terminal).
+- Added a `draw_controller_mult` arg to `bauiv1.imagewidget()` to control how
+ brightly the image pulses when its controller widget is selected (can prevent
+ brightly colored images from blowing out too much).
+- The Mac version is now correctly rendering to a sRGB colorspace instead of P3.
+ This was causing some bright colors to render extra-eye-destroying bright.
+- Fixed an issue with the Repeater() class which could cause key presses in UIs
+ to get lost if many were happening in short succession. An easy way to observe
+ this (at least on my machine) was to press left and right repeatedly in the
+ main menu - some presses would be lost and the selection would 'drift' one
+ direction.
+- Replaced all `efro.call.tpartial` calls with Python's built in
+ `functools.partial`. Mypy's 1.11 update added full type checking for
+ `functools.partial` so there's no benefit to maintaining our own special
+ version anymore. This also applies to `ba*.Call` which is redundant in the
+ same way. Both `efro.call.tpartial` and `ba*.Call` will probably be marked
+ deprecated and go away at some point.
### 1.7.35 (build 21889, api 8, 2024-06-20)
- Fixed an issue where the engine would block at exit on some version of Linux
diff --git a/ballisticakit-cmake/.idea/dictionaries/ericf.xml b/ballisticakit-cmake/.idea/dictionaries/ericf.xml
index 32e97f84..0a5e5fb3 100644
--- a/ballisticakit-cmake/.idea/dictionaries/ericf.xml
+++ b/ballisticakit-cmake/.idea/dictionaries/ericf.xml
@@ -1807,7 +1807,6 @@
toucs
tournamentbutton
toutf
- tpartial
tpath
tpathsegs
tpexl
diff --git a/config/requirements.txt b/config/requirements.txt
index 6ebf4c08..bcf7c972 100644
--- a/config/requirements.txt
+++ b/config/requirements.txt
@@ -1,21 +1,21 @@
cpplint==1.6.1
dmgbuild==1.6.1
filelock==3.15.4
-furo==2024.5.6
-mypy==1.10.1
+furo==2024.7.18
+mypy==1.11.0
pbxproj==4.2.0
pdoc==14.5.1
pur==7.3.2
-pylint==3.2.5
+pylint==3.2.6
pylsp-mypy==0.6.8
-pytest==8.2.2
+pytest==8.3.1
python-daemon==3.0.1
python-lsp-black==2.0.0
python-lsp-server==1.11.0
requests==2.32.3
-Sphinx==7.3.7
-tomlkit==0.12.5
+Sphinx==7.4.7
+tomlkit==0.13.0
types-certifi==2021.10.8.3
types-filelock==3.2.7
-types-requests==2.32.0.20240622
+types-requests==2.32.0.20240712
typing_extensions==4.12.2
diff --git a/src/assets/.asset_manifest_private.json b/src/assets/.asset_manifest_private.json
index 2b777d83..e9ecb042 100644
--- a/src/assets/.asset_manifest_private.json
+++ b/src/assets/.asset_manifest_private.json
@@ -1629,6 +1629,10 @@
"ba_data/textures/glow.ktx",
"ba_data/textures/glow.pvr",
"ba_data/textures/glow_preview.png",
+ "ba_data/textures/goldPass.dds",
+ "ba_data/textures/goldPass.ktx",
+ "ba_data/textures/goldPass.pvr",
+ "ba_data/textures/goldPass_preview.png",
"ba_data/textures/googlePlayAchievementsIcon.dds",
"ba_data/textures/googlePlayAchievementsIcon.ktx",
"ba_data/textures/googlePlayAchievementsIcon.pvr",
@@ -2413,6 +2417,22 @@
"ba_data/textures/tnt.ktx",
"ba_data/textures/tnt.pvr",
"ba_data/textures/tnt_preview.png",
+ "ba_data/textures/tokens1.dds",
+ "ba_data/textures/tokens1.ktx",
+ "ba_data/textures/tokens1.pvr",
+ "ba_data/textures/tokens1_preview.png",
+ "ba_data/textures/tokens2.dds",
+ "ba_data/textures/tokens2.ktx",
+ "ba_data/textures/tokens2.pvr",
+ "ba_data/textures/tokens2_preview.png",
+ "ba_data/textures/tokens3.dds",
+ "ba_data/textures/tokens3.ktx",
+ "ba_data/textures/tokens3.pvr",
+ "ba_data/textures/tokens3_preview.png",
+ "ba_data/textures/tokens4.dds",
+ "ba_data/textures/tokens4.ktx",
+ "ba_data/textures/tokens4.pvr",
+ "ba_data/textures/tokens4_preview.png",
"ba_data/textures/touchArrows.dds",
"ba_data/textures/touchArrows.ktx",
"ba_data/textures/touchArrows.pvr",
@@ -2481,6 +2501,10 @@
"ba_data/textures/white.ktx",
"ba_data/textures/white.pvr",
"ba_data/textures/white_preview.png",
+ "ba_data/textures/windowBottomCap.dds",
+ "ba_data/textures/windowBottomCap.ktx",
+ "ba_data/textures/windowBottomCap.pvr",
+ "ba_data/textures/windowBottomCap_preview.png",
"ba_data/textures/windowHSmallVMed.dds",
"ba_data/textures/windowHSmallVMed.ktx",
"ba_data/textures/windowHSmallVMed.pvr",
diff --git a/src/assets/.asset_manifest_public.json b/src/assets/.asset_manifest_public.json
index e7346f00..34a6bcb0 100644
--- a/src/assets/.asset_manifest_public.json
+++ b/src/assets/.asset_manifest_public.json
@@ -380,8 +380,9 @@
"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__/getcurrency.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__/kiosk.cpython-312.opt-1.pyc",
@@ -454,8 +455,9 @@
"ba_data/python/bauiv1lib/gather/nearbytab.py",
"ba_data/python/bauiv1lib/gather/privatetab.py",
"ba_data/python/bauiv1lib/gather/publictab.py",
- "ba_data/python/bauiv1lib/getcurrency.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/keyboard/__init__.py",
diff --git a/src/assets/Makefile b/src/assets/Makefile
index 70028be4..a4a3872f 100644
--- a/src/assets/Makefile
+++ b/src/assets/Makefile
@@ -364,8 +364,9 @@ SCRIPT_TARGETS_PY_PUBLIC = \
$(BUILD_DIR)/ba_data/python/bauiv1lib/gather/nearbytab.py \
$(BUILD_DIR)/ba_data/python/bauiv1lib/gather/privatetab.py \
$(BUILD_DIR)/ba_data/python/bauiv1lib/gather/publictab.py \
- $(BUILD_DIR)/ba_data/python/bauiv1lib/getcurrency.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/keyboard/__init__.py \
@@ -641,8 +642,9 @@ SCRIPT_TARGETS_PYC_PUBLIC = \
$(BUILD_DIR)/ba_data/python/bauiv1lib/gather/__pycache__/nearbytab.cpython-312.opt-1.pyc \
$(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__/getcurrency.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/keyboard/__pycache__/__init__.cpython-312.opt-1.pyc \
@@ -5711,6 +5713,7 @@ TEX2D_DDS_TARGETS = \
$(BUILD_DIR)/ba_data/textures/gladiatorIcon.dds \
$(BUILD_DIR)/ba_data/textures/gladiatorIconColorMask.dds \
$(BUILD_DIR)/ba_data/textures/glow.dds \
+ $(BUILD_DIR)/ba_data/textures/goldPass.dds \
$(BUILD_DIR)/ba_data/textures/googlePlayAchievementsIcon.dds \
$(BUILD_DIR)/ba_data/textures/googlePlayGamesIcon.dds \
$(BUILD_DIR)/ba_data/textures/googlePlayLeaderboardsIcon.dds \
@@ -5907,6 +5910,10 @@ TEX2D_DDS_TARGETS = \
$(BUILD_DIR)/ba_data/textures/tipTopLevelColor.dds \
$(BUILD_DIR)/ba_data/textures/tipTopPreview.dds \
$(BUILD_DIR)/ba_data/textures/tnt.dds \
+ $(BUILD_DIR)/ba_data/textures/tokens1.dds \
+ $(BUILD_DIR)/ba_data/textures/tokens2.dds \
+ $(BUILD_DIR)/ba_data/textures/tokens3.dds \
+ $(BUILD_DIR)/ba_data/textures/tokens4.dds \
$(BUILD_DIR)/ba_data/textures/touchArrows.dds \
$(BUILD_DIR)/ba_data/textures/touchArrowsActions.dds \
$(BUILD_DIR)/ba_data/textures/towerDLevelColor.dds \
@@ -5924,6 +5931,7 @@ TEX2D_DDS_TARGETS = \
$(BUILD_DIR)/ba_data/textures/warriorIcon.dds \
$(BUILD_DIR)/ba_data/textures/warriorIconColorMask.dds \
$(BUILD_DIR)/ba_data/textures/white.dds \
+ $(BUILD_DIR)/ba_data/textures/windowBottomCap.dds \
$(BUILD_DIR)/ba_data/textures/windowHSmallVMed.dds \
$(BUILD_DIR)/ba_data/textures/windowHSmallVSmall.dds \
$(BUILD_DIR)/ba_data/textures/wings.dds \
@@ -6116,6 +6124,7 @@ TEX2D_PVR_TARGETS = \
$(BUILD_DIR)/ba_data/textures/gladiatorIcon.pvr \
$(BUILD_DIR)/ba_data/textures/gladiatorIconColorMask.pvr \
$(BUILD_DIR)/ba_data/textures/glow.pvr \
+ $(BUILD_DIR)/ba_data/textures/goldPass.pvr \
$(BUILD_DIR)/ba_data/textures/googlePlayAchievementsIcon.pvr \
$(BUILD_DIR)/ba_data/textures/googlePlayGamesIcon.pvr \
$(BUILD_DIR)/ba_data/textures/googlePlayLeaderboardsIcon.pvr \
@@ -6312,6 +6321,10 @@ TEX2D_PVR_TARGETS = \
$(BUILD_DIR)/ba_data/textures/tipTopLevelColor.pvr \
$(BUILD_DIR)/ba_data/textures/tipTopPreview.pvr \
$(BUILD_DIR)/ba_data/textures/tnt.pvr \
+ $(BUILD_DIR)/ba_data/textures/tokens1.pvr \
+ $(BUILD_DIR)/ba_data/textures/tokens2.pvr \
+ $(BUILD_DIR)/ba_data/textures/tokens3.pvr \
+ $(BUILD_DIR)/ba_data/textures/tokens4.pvr \
$(BUILD_DIR)/ba_data/textures/touchArrows.pvr \
$(BUILD_DIR)/ba_data/textures/touchArrowsActions.pvr \
$(BUILD_DIR)/ba_data/textures/towerDLevelColor.pvr \
@@ -6329,6 +6342,7 @@ TEX2D_PVR_TARGETS = \
$(BUILD_DIR)/ba_data/textures/warriorIcon.pvr \
$(BUILD_DIR)/ba_data/textures/warriorIconColorMask.pvr \
$(BUILD_DIR)/ba_data/textures/white.pvr \
+ $(BUILD_DIR)/ba_data/textures/windowBottomCap.pvr \
$(BUILD_DIR)/ba_data/textures/windowHSmallVMed.pvr \
$(BUILD_DIR)/ba_data/textures/windowHSmallVSmall.pvr \
$(BUILD_DIR)/ba_data/textures/wings.pvr \
@@ -6521,6 +6535,7 @@ TEX2D_KTX_TARGETS = \
$(BUILD_DIR)/ba_data/textures/gladiatorIcon.ktx \
$(BUILD_DIR)/ba_data/textures/gladiatorIconColorMask.ktx \
$(BUILD_DIR)/ba_data/textures/glow.ktx \
+ $(BUILD_DIR)/ba_data/textures/goldPass.ktx \
$(BUILD_DIR)/ba_data/textures/googlePlayAchievementsIcon.ktx \
$(BUILD_DIR)/ba_data/textures/googlePlayGamesIcon.ktx \
$(BUILD_DIR)/ba_data/textures/googlePlayLeaderboardsIcon.ktx \
@@ -6717,6 +6732,10 @@ TEX2D_KTX_TARGETS = \
$(BUILD_DIR)/ba_data/textures/tipTopLevelColor.ktx \
$(BUILD_DIR)/ba_data/textures/tipTopPreview.ktx \
$(BUILD_DIR)/ba_data/textures/tnt.ktx \
+ $(BUILD_DIR)/ba_data/textures/tokens1.ktx \
+ $(BUILD_DIR)/ba_data/textures/tokens2.ktx \
+ $(BUILD_DIR)/ba_data/textures/tokens3.ktx \
+ $(BUILD_DIR)/ba_data/textures/tokens4.ktx \
$(BUILD_DIR)/ba_data/textures/touchArrows.ktx \
$(BUILD_DIR)/ba_data/textures/touchArrowsActions.ktx \
$(BUILD_DIR)/ba_data/textures/towerDLevelColor.ktx \
@@ -6734,6 +6753,7 @@ TEX2D_KTX_TARGETS = \
$(BUILD_DIR)/ba_data/textures/warriorIcon.ktx \
$(BUILD_DIR)/ba_data/textures/warriorIconColorMask.ktx \
$(BUILD_DIR)/ba_data/textures/white.ktx \
+ $(BUILD_DIR)/ba_data/textures/windowBottomCap.ktx \
$(BUILD_DIR)/ba_data/textures/windowHSmallVMed.ktx \
$(BUILD_DIR)/ba_data/textures/windowHSmallVSmall.ktx \
$(BUILD_DIR)/ba_data/textures/wings.ktx \
@@ -6926,6 +6946,7 @@ TEX2D_PREVIEW_PNG_TARGETS = \
$(BUILD_DIR)/ba_data/textures/gladiatorIconColorMask_preview.png \
$(BUILD_DIR)/ba_data/textures/gladiatorIcon_preview.png \
$(BUILD_DIR)/ba_data/textures/glow_preview.png \
+ $(BUILD_DIR)/ba_data/textures/goldPass_preview.png \
$(BUILD_DIR)/ba_data/textures/googlePlayAchievementsIcon_preview.png \
$(BUILD_DIR)/ba_data/textures/googlePlayGamesIcon_preview.png \
$(BUILD_DIR)/ba_data/textures/googlePlayLeaderboardsIcon_preview.png \
@@ -7122,6 +7143,10 @@ TEX2D_PREVIEW_PNG_TARGETS = \
$(BUILD_DIR)/ba_data/textures/tipTopLevelColor_preview.png \
$(BUILD_DIR)/ba_data/textures/tipTopPreview_preview.png \
$(BUILD_DIR)/ba_data/textures/tnt_preview.png \
+ $(BUILD_DIR)/ba_data/textures/tokens1_preview.png \
+ $(BUILD_DIR)/ba_data/textures/tokens2_preview.png \
+ $(BUILD_DIR)/ba_data/textures/tokens3_preview.png \
+ $(BUILD_DIR)/ba_data/textures/tokens4_preview.png \
$(BUILD_DIR)/ba_data/textures/touchArrowsActions_preview.png \
$(BUILD_DIR)/ba_data/textures/touchArrows_preview.png \
$(BUILD_DIR)/ba_data/textures/towerDLevelColor_preview.png \
@@ -7139,6 +7164,7 @@ TEX2D_PREVIEW_PNG_TARGETS = \
$(BUILD_DIR)/ba_data/textures/warriorIconColorMask_preview.png \
$(BUILD_DIR)/ba_data/textures/warriorIcon_preview.png \
$(BUILD_DIR)/ba_data/textures/white_preview.png \
+ $(BUILD_DIR)/ba_data/textures/windowBottomCap_preview.png \
$(BUILD_DIR)/ba_data/textures/windowHSmallVMed_preview.png \
$(BUILD_DIR)/ba_data/textures/windowHSmallVSmall_preview.png \
$(BUILD_DIR)/ba_data/textures/wings_preview.png \
diff --git a/src/assets/ba_data/python/babase/_accountv2.py b/src/assets/ba_data/python/babase/_accountv2.py
index 37914bce..f6544a50 100644
--- a/src/assets/ba_data/python/babase/_accountv2.py
+++ b/src/assets/ba_data/python/babase/_accountv2.py
@@ -6,9 +6,9 @@ from __future__ import annotations
import hashlib
import logging
+from functools import partial
from typing import TYPE_CHECKING, assert_never
-from efro.call import tpartial
from efro.error import CommunicationError
from bacommon.login import LoginType
import _babase
@@ -223,7 +223,7 @@ class AccountV2Subsystem:
if service_str is not None:
_babase.apptimer(
2.0,
- tpartial(
+ partial(
_babase.screenmessage,
Lstr(
resource='notUsingAccountText',
diff --git a/src/assets/ba_data/python/babase/_app.py b/src/assets/ba_data/python/babase/_app.py
index bc2e5de6..58ba2fce 100644
--- a/src/assets/ba_data/python/babase/_app.py
+++ b/src/assets/ba_data/python/babase/_app.py
@@ -7,13 +7,11 @@ from __future__ import annotations
import os
import logging
from enum import Enum
+from functools import partial
from typing import TYPE_CHECKING, TypeVar, override
from concurrent.futures import ThreadPoolExecutor
from threading import RLock
-
-from efro.call import tpartial
-
import _babase
from babase._language import LanguageSubsystem
from babase._plugin import PluginSubsystem
@@ -512,7 +510,7 @@ class App:
# Do the actual work of calcing our app-mode/etc. in a bg thread
# since it may block for a moment to load modules/etc.
- self.threadpool_submit_no_wait(tpartial(self._set_intent, intent))
+ self.threadpool_submit_no_wait(partial(self._set_intent, intent))
def push_apply_app_config(self) -> None:
"""Internal. Use app.config.apply() to apply app config changes."""
@@ -644,13 +642,13 @@ class App:
# kick back to the logic thread to apply.
mode = modetype()
_babase.pushcall(
- tpartial(self._apply_intent, intent, mode),
+ partial(self._apply_intent, intent, mode),
from_other_thread=True,
)
except Exception:
logging.exception('Error setting app intent to %s.', intent)
_babase.pushcall(
- tpartial(self._display_set_intent_error, intent),
+ partial(self._display_set_intent_error, intent),
from_other_thread=True,
)
diff --git a/src/assets/ba_data/python/babase/_apputils.py b/src/assets/ba_data/python/babase/_apputils.py
index 55507553..05d2b3d5 100644
--- a/src/assets/ba_data/python/babase/_apputils.py
+++ b/src/assets/ba_data/python/babase/_apputils.py
@@ -7,10 +7,10 @@ import gc
import os
import logging
from threading import Thread
+from functools import partial
from dataclasses import dataclass
from typing import TYPE_CHECKING, override
-from efro.call import tpartial
from efro.log import LogLevel
from efro.dataclassio import ioprepped, dataclass_to_json, dataclass_from_json
@@ -18,7 +18,7 @@ import _babase
from babase._appsubsystem import AppSubsystem
if TYPE_CHECKING:
- from typing import Any, TextIO
+ from typing import Any, TextIO, Callable
import babase
@@ -320,7 +320,7 @@ def dump_app_state(
# We want this to work from any thread, so need to kick this part
# over to the logic thread so timer works.
_babase.pushcall(
- tpartial(_babase.apptimer, delay + 1.0, log_dumped_app_state),
+ partial(_babase.apptimer, delay + 1.0, log_dumped_app_state),
from_other_thread=True,
suppress_other_thread_warning=True,
)
diff --git a/src/assets/ba_data/python/babase/_general.py b/src/assets/ba_data/python/babase/_general.py
index d1914d57..8c6b0d16 100644
--- a/src/assets/ba_data/python/babase/_general.py
+++ b/src/assets/ba_data/python/babase/_general.py
@@ -15,8 +15,8 @@ from efro.terminal import Clr
import _babase
if TYPE_CHECKING:
+ import functools
from typing import Any
- from efro.call import Call as Call # 'as Call' so we re-export.
# Declare distinct types for different time measurements we use so the
@@ -166,7 +166,7 @@ class _WeakCall:
if not self._did_invalid_call_warning:
logging.warning(
'Warning: callable passed to babase.WeakCall() is not'
- ' weak-referencable (%s); use babase.Call() instead'
+ ' weak-referencable (%s); use functools.partial instead'
' to avoid this warning.',
stack_info=True,
)
@@ -199,9 +199,13 @@ class _Call:
The callable is strong-referenced so it won't die until this
object does.
+ WARNING: This is exactly the same as Python's built in functools.partial().
+ Use functools.partial instead of this for new code, as this will probably
+ be deprecated at some point.
+
Note that a bound method (ex: ``myobj.dosomething``) contains a reference
to ``self`` (``myobj`` in that case), so you will be keeping that object
- alive too. Use babase.WeakCall if you want to pass a method to callback
+ alive too. Use babase.WeakCall if you want to pass a method to a callback
without keeping its object alive.
"""
@@ -239,12 +243,16 @@ class _Call:
if TYPE_CHECKING:
- # Some interaction between our ballistica pylint plugin
- # and this code is crashing starting on pylint 2.15.0.
- # This seems to fix things for now.
+ # For type-checking, point at functools.partial which gives us full
+ # type checking on both positional and keyword arguments (as of mypy
+ # 1.11).
+
+ # Note: Something here is wonky with pylint, possibly related to our
+ # custom pylint plugin. Disabling all checks seems to fix it.
# pylint: disable=all
- WeakCall = Call
- Call = Call
+
+ WeakCall = functools.partial
+ Call = functools.partial
else:
WeakCall = _WeakCall
WeakCall.__name__ = 'WeakCall'
diff --git a/src/assets/ba_data/python/babase/_login.py b/src/assets/ba_data/python/babase/_login.py
index 81c07917..0f3d1c4c 100644
--- a/src/assets/ba_data/python/babase/_login.py
+++ b/src/assets/ba_data/python/babase/_login.py
@@ -6,6 +6,7 @@ from __future__ import annotations
import time
import logging
+from functools import partial
from dataclasses import dataclass
from typing import TYPE_CHECKING, final, override
@@ -162,8 +163,8 @@ class LoginAdapter:
the adapter will attempt to sign in if possible. An exception will
be returned if the sign-in attempt fails.
"""
+
assert _babase.in_logic_thread()
- from babase._general import Call
# Have been seeing multiple sign-in attempts come through
# nearly simultaneously which can be problematic server-side.
@@ -185,7 +186,7 @@ class LoginAdapter:
appnow,
)
_babase.pushcall(
- Call(
+ partial(
result_cb,
self,
RuntimeError('sign_in called too soon after last.'),
@@ -215,7 +216,7 @@ class LoginAdapter:
self.login_type.name,
)
_babase.pushcall(
- Call(
+ partial(
result_cb,
self,
RuntimeError('fetch-sign-in-token failed.'),
@@ -245,7 +246,7 @@ class LoginAdapter:
self.login_type.name,
response,
)
- _babase.pushcall(Call(result_cb, self, response))
+ _babase.pushcall(partial(result_cb, self, response))
else:
# This means our credentials were explicitly rejected.
if response.credentials is None:
@@ -262,7 +263,7 @@ class LoginAdapter:
result2 = self.SignInResult(
credentials=response.credentials
)
- _babase.pushcall(Call(result_cb, self, result2))
+ _babase.pushcall(partial(result_cb, self, result2))
assert _babase.app.plus is not None
_babase.app.plus.cloud.send_message_cb(
@@ -293,10 +294,9 @@ class LoginAdapter:
as needed. The provided completion_cb should then be called with
either a token or None if sign in failed or was cancelled.
"""
- from babase._general import Call
# Default implementation simply fails immediately.
- _babase.pushcall(Call(completion_cb, None))
+ _babase.pushcall(partial(completion_cb, None))
def _update_implicit_login_state(self) -> None:
# If we've received an implicit login state, schedule it to be
@@ -304,7 +304,6 @@ class LoginAdapter:
# called so that account-client-v2 has had a chance to load
# any existing state so it can properly respond to this.
if self._implicit_login_state_dirty and self._on_app_loading_called:
- from babase._general import Call
if DEBUG_LOG:
logging.debug(
@@ -315,7 +314,7 @@ class LoginAdapter:
assert _babase.app.plus is not None
_babase.pushcall(
- Call(
+ partial(
_babase.app.plus.accounts.on_implicit_login_state_changed,
self.login_type,
self._implicit_login_state,
diff --git a/src/assets/ba_data/python/babase/_meta.py b/src/assets/ba_data/python/babase/_meta.py
index 6e1224d8..9a66d77b 100644
--- a/src/assets/ba_data/python/babase/_meta.py
+++ b/src/assets/ba_data/python/babase/_meta.py
@@ -7,12 +7,12 @@ from __future__ import annotations
import os
import time
import logging
-from threading import Thread
from pathlib import Path
+from threading import Thread
+from functools import partial
from typing import TYPE_CHECKING, TypeVar
from dataclasses import dataclass, field
-from efro.call import tpartial
import _babase
if TYPE_CHECKING:
@@ -116,7 +116,7 @@ class MetadataSubsystem:
loading work happens, pass completion_cb_in_bg_thread=True.
"""
Thread(
- target=tpartial(
+ target=partial(
self._load_exported_classes,
cls,
completion_cb,
@@ -145,7 +145,7 @@ class MetadataSubsystem:
except Exception:
logging.exception('Error loading exported classes.')
- completion_call = tpartial(completion_cb, classes)
+ completion_call = partial(completion_cb, classes)
if completion_cb_in_bg_thread:
completion_call()
else:
diff --git a/src/assets/ba_data/python/babase/_workspace.py b/src/assets/ba_data/python/babase/_workspace.py
index 07e5009b..554392a6 100644
--- a/src/assets/ba_data/python/babase/_workspace.py
+++ b/src/assets/ba_data/python/babase/_workspace.py
@@ -9,9 +9,9 @@ import sys
import logging
from pathlib import Path
from threading import Thread
+from functools import partial
from typing import TYPE_CHECKING
-from efro.call import tpartial
from efro.error import CleanError
import _babase
import bacommon.cloud
@@ -118,7 +118,7 @@ class WorkspaceSubsystem:
state.iteration += 1
_babase.pushcall(
- tpartial(
+ partial(
self._successmsg,
Lstr(
resource='activatedText',
@@ -130,7 +130,7 @@ class WorkspaceSubsystem:
except _SkipSyncError:
_babase.pushcall(
- tpartial(
+ partial(
self._errmsg,
Lstr(
resource='workspaceSyncReuseText',
@@ -145,7 +145,7 @@ class WorkspaceSubsystem:
# be in wonky state.
set_path = False
_babase.pushcall(
- tpartial(self._errmsg, Lstr(value=str(exc))),
+ partial(self._errmsg, Lstr(value=str(exc))),
from_other_thread=True,
)
except Exception:
@@ -153,7 +153,7 @@ class WorkspaceSubsystem:
set_path = False
logging.exception("Error syncing workspace '%s'.", workspacename)
_babase.pushcall(
- tpartial(
+ partial(
self._errmsg,
Lstr(
resource='workspaceSyncErrorText',
diff --git a/src/assets/ba_data/python/baenv.py b/src/assets/ba_data/python/baenv.py
index 6d2ea593..09825511 100644
--- a/src/assets/ba_data/python/baenv.py
+++ b/src/assets/ba_data/python/baenv.py
@@ -52,7 +52,7 @@ if TYPE_CHECKING:
# Build number and version of the ballistica binary we expect to be
# using.
-TARGET_BALLISTICA_BUILD = 21911
+TARGET_BALLISTICA_BUILD = 21919
TARGET_BALLISTICA_VERSION = '1.7.36'
diff --git a/src/assets/ba_data/python/baplus/_cloud.py b/src/assets/ba_data/python/baplus/_cloud.py
index 0a7b4aff..bc8593a2 100644
--- a/src/assets/ba_data/python/baplus/_cloud.py
+++ b/src/assets/ba_data/python/baplus/_cloud.py
@@ -100,6 +100,15 @@ class CloudSubsystem(babase.AppSubsystem):
],
) -> None: ...
+ @overload
+ def send_message_cb(
+ self,
+ msg: bacommon.cloud.StoreQueryMessage,
+ on_response: Callable[
+ [bacommon.cloud.StoreQueryResponse | Exception], None
+ ],
+ ) -> None: ...
+
def send_message_cb(
self,
msg: Message,
@@ -194,6 +203,10 @@ def cloud_console_exec(code: str) -> None:
except Exception:
import traceback
+ # Note to self: Seems like we should just use
+ # logging.exception() here. Except currently that winds up
+ # triggering our cloud logging stuff so we'd probably want a
+ # specific logger or whatnot to avoid that.
apptime = babase.apptime()
print(f'Exec error at time {apptime:.2f}.', file=sys.stderr)
traceback.print_exc()
diff --git a/src/assets/ba_data/python/baplus/_subsystem.py b/src/assets/ba_data/python/baplus/_subsystem.py
index 7268abc3..c5ef5ff6 100644
--- a/src/assets/ba_data/python/baplus/_subsystem.py
+++ b/src/assets/ba_data/python/baplus/_subsystem.py
@@ -254,6 +254,11 @@ class PlusSubsystem(AppSubsystem):
"""(internal)"""
return _baplus.tournament_query(callback, args)
+ @staticmethod
+ def supports_purchases() -> bool:
+ """Does this platform support in-app-purchases?"""
+ return _baplus.supports_purchases()
+
@staticmethod
def have_incentivized_ad() -> bool:
"""Is an incentivized ad available?"""
diff --git a/src/assets/ba_data/python/bauiv1lib/config.py b/src/assets/ba_data/python/bauiv1lib/config.py
index 82e6976f..337a25f9 100644
--- a/src/assets/ba_data/python/bauiv1lib/config.py
+++ b/src/assets/ba_data/python/bauiv1lib/config.py
@@ -119,10 +119,10 @@ class ConfigNumberEdit:
self.nametext = bui.textwidget(
parent=parent,
- position=position,
- size=(100, 30),
+ position=(position[0], position[1] + 12.0),
+ size=(0, 0),
text=displayname,
- # maxwidth=160 + xoffset,
+ maxwidth=150 + xoffset,
color=(0.8, 0.8, 0.8, 1.0),
h_align='left',
v_align='center',
@@ -130,8 +130,8 @@ class ConfigNumberEdit:
)
self.valuetext = bui.textwidget(
parent=parent,
- position=(position[0] + 146 + xoffset, position[1]),
- size=(60, 28),
+ position=(position[0] + 216 + xoffset, position[1] + 12.0),
+ size=(0, 0),
editable=False,
color=(0.3, 1.0, 0.3, 1.0),
h_align='right',
diff --git a/src/assets/ba_data/python/bauiv1lib/connectivity.py b/src/assets/ba_data/python/bauiv1lib/connectivity.py
index a0707d59..41062f84 100644
--- a/src/assets/ba_data/python/bauiv1lib/connectivity.py
+++ b/src/assets/ba_data/python/bauiv1lib/connectivity.py
@@ -28,8 +28,10 @@ def wait_for_connectivity(
assert plus is not None
# Quick-out: if we're already connected, don't bother with the UI.
+ # We do, however, push this call instead of calling it immediately
+ # so as to be consistent with the waiting path.
if plus.cloud.connected:
- on_connected()
+ bui.pushcall(on_connected)
return
WaitForConnectivityWindow(on_connected=on_connected, on_cancel=on_cancel)
diff --git a/src/assets/ba_data/python/bauiv1lib/continues.py b/src/assets/ba_data/python/bauiv1lib/continues.py
index a3637d75..366fcbb4 100644
--- a/src/assets/ba_data/python/bauiv1lib/continues.py
+++ b/src/assets/ba_data/python/bauiv1lib/continues.py
@@ -214,7 +214,7 @@ class ContinuesWindow(bui.Window):
self._on_cancel()
def _on_continue_press(self) -> None:
- from bauiv1lib import getcurrency
+ from bauiv1lib import gettickets
plus = bui.app.plus
assert plus is not None
@@ -238,7 +238,7 @@ class ContinuesWindow(bui.Window):
self._counting_down = False
bui.textwidget(edit=self._counter_text, text='')
bui.getsound('error').play()
- getcurrency.show_get_tickets_prompt()
+ gettickets.show_get_tickets_prompt()
return
if not self._transitioning_out:
bui.getsound('swish').play()
diff --git a/src/assets/ba_data/python/bauiv1lib/coop/tournamentbutton.py b/src/assets/ba_data/python/bauiv1lib/coop/tournamentbutton.py
index 0ee8a597..e4adb499 100644
--- a/src/assets/ba_data/python/bauiv1lib/coop/tournamentbutton.py
+++ b/src/assets/ba_data/python/bauiv1lib/coop/tournamentbutton.py
@@ -290,6 +290,7 @@ class TournamentButton:
text='-',
v_align='center',
maxwidth=170,
+ glow_type='uniform',
scale=1.4,
color=value_color,
flatness=1.0,
diff --git a/src/assets/ba_data/python/bauiv1lib/gather/privatetab.py b/src/assets/ba_data/python/bauiv1lib/gather/privatetab.py
index 00bb4a2c..ac10cfb5 100644
--- a/src/assets/ba_data/python/bauiv1lib/gather/privatetab.py
+++ b/src/assets/ba_data/python/bauiv1lib/gather/privatetab.py
@@ -20,7 +20,7 @@ from bacommon.net import (
PrivatePartyConnectResult,
)
from bauiv1lib.gather import GatherTab
-from bauiv1lib.getcurrency import GetCurrencyWindow, show_get_tickets_prompt
+from bauiv1lib.gettickets import GetTicketsWindow, show_get_tickets_prompt
import bascenev1 as bs
import bauiv1 as bui
@@ -487,7 +487,7 @@ class PrivateGatherTab(GatherTab):
# Bring up get-tickets window and then kill ourself (we're on the
# overlay layer so we'd show up above it).
- GetCurrencyWindow(modal=True, origin_widget=self._get_tickets_button)
+ GetTicketsWindow(modal=True, origin_widget=self._get_tickets_button)
def _build_host_tab(self) -> None:
# pylint: disable=too-many-branches
diff --git a/src/assets/ba_data/python/bauiv1lib/getcurrency.py b/src/assets/ba_data/python/bauiv1lib/gettickets.py
similarity index 89%
rename from src/assets/ba_data/python/bauiv1lib/getcurrency.py
rename to src/assets/ba_data/python/bauiv1lib/gettickets.py
index 7883f613..266b3b1a 100644
--- a/src/assets/ba_data/python/bauiv1lib/getcurrency.py
+++ b/src/assets/ba_data/python/bauiv1lib/gettickets.py
@@ -14,8 +14,8 @@ if TYPE_CHECKING:
from typing import Any
-class GetCurrencyWindow(bui.Window):
- """Window for purchasing/acquiring currency."""
+class GetTicketsWindow(bui.Window):
+ """Window for purchasing/acquiring classic tickets."""
def __init__(
self,
@@ -97,14 +97,42 @@ class GetCurrencyWindow(bui.Window):
bui.textwidget(
parent=self._root_widget,
- position=(self._width * 0.5, self._height - 55),
+ position=(self._width * 0.5 - 15, self._height - 47),
size=(0, 0),
color=bui.app.ui_v1.title_color,
scale=1.2,
- h_align='center',
+ h_align='right',
v_align='center',
text=bui.Lstr(resource=self._r + '.titleText'),
- maxwidth=290,
+ # 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='Get Tokens',
+ on_activate_call=self._get_tokens_press,
+ )
+
+ # 'New!' by tokens button
+ bui.textwidget(
+ parent=self._root_widget,
+ text='New!',
+ 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:
@@ -437,7 +465,7 @@ class GetCurrencyWindow(bui.Window):
tc_y_offs = 0
h = self._width - (185 + x_inset)
- v = self._height - 95 + tc_y_offs
+ v = self._height - 105 + tc_y_offs
txt1 = (
bui.Lstr(resource=self._r + '.youHaveText')
@@ -733,6 +761,41 @@ class GetCurrencyWindow(bui.Window):
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
@@ -760,6 +823,27 @@ class GetCurrencyWindow(bui.Window):
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.
@@ -769,6 +853,7 @@ def show_get_tickets_prompt() -> None:
from bauiv1lib.confirm import ConfirmWindow
assert bui.app.classic is not None
+
if bui.app.classic.allow_ticket_purchases:
ConfirmWindow(
bui.Lstr(
@@ -777,7 +862,7 @@ def show_get_tickets_prompt() -> None:
'You don\'t have enough tickets for this!',
)
),
- lambda: GetCurrencyWindow(modal=True),
+ lambda: GetTicketsWindow(modal=True),
ok_text=bui.Lstr(resource='getTicketsWindow.titleText'),
width=460,
height=130,
diff --git a/src/assets/ba_data/python/bauiv1lib/gettokens.py b/src/assets/ba_data/python/bauiv1lib/gettokens.py
new file mode 100644
index 00000000..e0d04854
--- /dev/null
+++ b/src/assets/ba_data/python/bauiv1lib/gettokens.py
@@ -0,0 +1,618 @@
+# Released under the MIT License. See LICENSE for details.
+#
+"""UI functionality for purchasing/acquiring currency."""
+
+from __future__ import annotations
+
+import logging
+from enum import Enum
+from functools import partial
+from dataclasses import dataclass
+from typing import TYPE_CHECKING, assert_never
+
+import bacommon.cloud
+
+import bauiv1 as bui
+from bauiv1lib.connectivity import wait_for_connectivity
+
+if TYPE_CHECKING:
+ from typing import Any, Callable
+
+
+@dataclass
+class _ButtonDef:
+ itemid: str
+ width: float
+ color: tuple[float, float, float]
+ imgdefs: list[_ImgDef]
+ txtdefs: list[_TxtDef]
+ prepad: float = 0.0
+
+
+@dataclass
+class _ImgDef:
+ tex: str
+ pos: tuple[float, float]
+ size: tuple[float, float]
+ color: tuple[float, float, float] = (1, 1, 1)
+ opacity: float = 1.0
+ draw_controller_mult: float | None = None
+
+
+class TextContents(Enum):
+ """Some type of text to show."""
+
+ PRICE = 'price'
+
+
+@dataclass
+class _TxtDef:
+ text: str | TextContents
+ pos: tuple[float, float]
+ maxwidth: float | None
+ scale: float = 1.0
+ color: tuple[float, float, float] = (1, 1, 1)
+ rotate: float | None = None
+
+
+class GetTokensWindow(bui.Window):
+ """Window for purchasing/acquiring classic tickets."""
+
+ def __del__(self) -> None:
+ print('~GetTokensWindow()')
+
+ def __init__(
+ self,
+ transition: str = 'in_right',
+ origin_widget: bui.Widget | None = None,
+ restore_previous_call: Callable[[bui.Widget], None] | None = None,
+ ):
+ # pylint: disable=too-many-locals
+
+ bwidthstd = 170
+ bwidthwide = 300
+ ycolor = (0, 0, 0.3)
+ pcolor = (0, 0, 0.3)
+ pos1 = 65
+ pos2 = 34
+ titlescale = 0.9
+ pricescale = 0.65
+ bcapcol1 = (0.25, 0.13, 0.02)
+ self._buttondefs: list[_ButtonDef] = [
+ _ButtonDef(
+ itemid='tokens1',
+ width=bwidthstd,
+ color=ycolor,
+ imgdefs=[
+ _ImgDef(
+ 'tokens1',
+ pos=(-3, 85),
+ size=(172, 172),
+ opacity=1.0,
+ draw_controller_mult=0.5,
+ ),
+ _ImgDef(
+ 'windowBottomCap',
+ pos=(1.5, 4),
+ size=(bwidthstd * 0.960, 100),
+ color=bcapcol1,
+ opacity=1.0,
+ ),
+ ],
+ txtdefs=[
+ _TxtDef(
+ '50 Tokens',
+ pos=(bwidthstd * 0.5, pos1),
+ color=(1.1, 1.05, 1.0),
+ scale=titlescale,
+ maxwidth=bwidthstd * 0.9,
+ ),
+ _TxtDef(
+ TextContents.PRICE,
+ pos=(bwidthstd * 0.5, pos2),
+ color=(1.1, 1.05, 1.0),
+ scale=pricescale,
+ maxwidth=bwidthstd * 0.9,
+ ),
+ ],
+ ),
+ _ButtonDef(
+ itemid='tokens2',
+ width=bwidthstd,
+ color=ycolor,
+ imgdefs=[
+ _ImgDef(
+ 'tokens2',
+ pos=(-3, 85),
+ size=(172, 172),
+ opacity=1.0,
+ draw_controller_mult=0.5,
+ ),
+ _ImgDef(
+ 'windowBottomCap',
+ pos=(1.5, 4),
+ size=(bwidthstd * 0.960, 100),
+ color=bcapcol1,
+ opacity=1.0,
+ ),
+ ],
+ txtdefs=[
+ _TxtDef(
+ '500 Tokens',
+ pos=(bwidthstd * 0.5, pos1),
+ color=(1.1, 1.05, 1.0),
+ scale=titlescale,
+ maxwidth=bwidthstd * 0.9,
+ ),
+ _TxtDef(
+ TextContents.PRICE,
+ pos=(bwidthstd * 0.5, pos2),
+ color=(1.1, 1.05, 1.0),
+ scale=pricescale,
+ maxwidth=bwidthstd * 0.9,
+ ),
+ ],
+ ),
+ _ButtonDef(
+ itemid='tokens3',
+ width=bwidthstd,
+ color=ycolor,
+ imgdefs=[
+ _ImgDef(
+ 'tokens3',
+ pos=(-3, 85),
+ size=(172, 172),
+ opacity=1.0,
+ draw_controller_mult=0.5,
+ ),
+ _ImgDef(
+ 'windowBottomCap',
+ pos=(1.5, 4),
+ size=(bwidthstd * 0.960, 100),
+ color=bcapcol1,
+ opacity=1.0,
+ ),
+ ],
+ txtdefs=[
+ _TxtDef(
+ '1200 Tokens',
+ pos=(bwidthstd * 0.5, pos1),
+ color=(1.1, 1.05, 1.0),
+ scale=titlescale,
+ maxwidth=bwidthstd * 0.9,
+ ),
+ _TxtDef(
+ TextContents.PRICE,
+ pos=(bwidthstd * 0.5, pos2),
+ color=(1.1, 1.05, 1.0),
+ scale=pricescale,
+ maxwidth=bwidthstd * 0.9,
+ ),
+ ],
+ ),
+ _ButtonDef(
+ itemid='tokens4',
+ width=bwidthstd,
+ color=ycolor,
+ imgdefs=[
+ _ImgDef(
+ 'tokens4',
+ pos=(-3, 85),
+ size=(172, 172),
+ opacity=1.0,
+ draw_controller_mult=0.5,
+ ),
+ _ImgDef(
+ 'windowBottomCap',
+ pos=(1.5, 4),
+ size=(bwidthstd * 0.960, 100),
+ color=bcapcol1,
+ opacity=1.0,
+ ),
+ ],
+ txtdefs=[
+ _TxtDef(
+ '2600 Tokens',
+ pos=(bwidthstd * 0.5, pos1),
+ color=(1.1, 1.05, 1.0),
+ scale=titlescale,
+ maxwidth=bwidthstd * 0.9,
+ ),
+ _TxtDef(
+ TextContents.PRICE,
+ pos=(bwidthstd * 0.5, pos2),
+ color=(1.1, 1.05, 1.0),
+ scale=pricescale,
+ maxwidth=bwidthstd * 0.9,
+ ),
+ ],
+ ),
+ _ButtonDef(
+ itemid='gold_pass',
+ width=bwidthwide,
+ color=pcolor,
+ imgdefs=[
+ _ImgDef(
+ 'goldPass',
+ pos=(-7, 102),
+ size=(312, 156),
+ draw_controller_mult=0.3,
+ ),
+ _ImgDef(
+ 'windowBottomCap',
+ pos=(8, 4),
+ size=(bwidthwide * 0.923, 116),
+ color=(0.25, 0.12, 0.15),
+ opacity=1.0,
+ ),
+ ],
+ txtdefs=[
+ _TxtDef(
+ 'Gold Pass',
+ pos=(bwidthwide * 0.5, pos1 + 27),
+ color=(1.1, 1.05, 1.0),
+ scale=titlescale,
+ maxwidth=bwidthwide * 0.8,
+ ),
+ _TxtDef(
+ 'Infinite tokens.',
+ pos=(bwidthwide * 0.5, pos1 + 6),
+ color=(1.1, 1.05, 1.0),
+ scale=0.4,
+ maxwidth=bwidthwide * 0.8,
+ ),
+ _TxtDef(
+ 'No ads.',
+ pos=(bwidthwide * 0.5, pos1 + 6 - 13 * 1),
+ color=(1.1, 1.05, 1.0),
+ scale=0.4,
+ maxwidth=bwidthwide * 0.8,
+ ),
+ _TxtDef(
+ 'Forever.',
+ pos=(bwidthwide * 0.5, pos1 + 6 - 13 * 2),
+ color=(1.1, 1.05, 1.0),
+ scale=0.4,
+ maxwidth=bwidthwide * 0.8,
+ ),
+ _TxtDef(
+ TextContents.PRICE,
+ pos=(bwidthwide * 0.5, pos2 - 9),
+ color=(1.1, 1.05, 1.0),
+ scale=pricescale,
+ maxwidth=bwidthwide * 0.8,
+ ),
+ ],
+ prepad=-8,
+ ),
+ ]
+
+ self._transitioning_out = False
+ self._restore_previous_call = restore_previous_call
+ self._textcolor = (0.92, 0.92, 2.0)
+
+ # 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 = 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._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),
+ transition=transition,
+ scale_origin_stack_offset=scale_origin,
+ color=(0.3, 0.23, 0.36),
+ 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)
+ ),
+ )
+ )
+
+ 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)
+
+ bui.textwidget(
+ parent=self._root_widget,
+ position=(self._width * 0.5, self._height - 47),
+ size=(0, 0),
+ color=self._textcolor,
+ flatness=0.0,
+ shadow=1.0,
+ scale=1.2,
+ h_align='center',
+ v_align='center',
+ text='Get Tokens',
+ maxwidth=260,
+ )
+
+ self._status_text = bui.textwidget(
+ parent=self._root_widget,
+ position=(self._width * 0.5, self._height * 0.5),
+ h_align='center',
+ v_align='center',
+ color=(0.6, 0.6, 0.6),
+ scale=0.75,
+ text='Loading...',
+ )
+
+ # Get all textures used by our buttons preloading so hopefully
+ # they'll be in place by the time we show them.
+ for bdef in self._buttondefs:
+ for bimg in bdef.imgdefs:
+ bui.gettexture(bimg.tex)
+
+ # Wait for a master-server connection if need be. Otherwise we
+ # could error if called at the wrong time even with an internet
+ # connection, which is unintuitive.
+ wait_for_connectivity(
+ on_connected=bui.WeakCall(self._on_have_connectivity),
+ on_cancel=bui.WeakCall(self._back),
+ )
+
+ def _on_have_connectivity(self) -> None:
+ plus = bui.app.plus
+ assert plus is not None
+
+ # Sanity check; we need to be signed in. (we should not be
+ # allowed to get here if we aren't, but it could happen for
+ # fluke-ish reasons.)
+ if plus.accounts.primary is None:
+ bui.screenmessage(
+ bui.Lstr(resource='notSignedInErrorText'), color=(1, 0, 0)
+ )
+ bui.getsound('error').play()
+ self._back()
+ return
+
+ with plus.accounts.primary:
+ plus.cloud.send_message_cb(
+ bacommon.cloud.StoreQueryMessage(),
+ on_response=bui.WeakCall(self._on_store_query_response),
+ )
+
+ def _on_load_error(self) -> None:
+ bui.textwidget(
+ edit=self._status_text,
+ text=bui.Lstr(resource='internal.unavailableNoConnectionText'),
+ color=(1, 0, 0),
+ )
+
+ def _on_store_query_response(
+ self, response: bacommon.cloud.StoreQueryResponse | Exception
+ ) -> None:
+ # pylint: disable=too-many-locals
+
+ plus = bui.app.plus
+
+ # If our message failed, just error and back out.
+ if isinstance(response, Exception):
+ logging.info('Store query failed.', exc_info=response)
+
+ bui.screenmessage(bui.Lstr(resource='errorText'), color=(1, 0, 0))
+ bui.getsound('error').play()
+ self._back()
+ return
+
+ bui.textwidget(edit=self._status_text, text='')
+
+ xinset = 40
+
+ scrollwidth = self._width - 2 * (self._x_inset + xinset)
+ scrollheight = 280
+ buttonpadding = -5
+
+ yoffs = 5
+
+ # 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)
+
+ h_scroll = bui.hscrollwidget(
+ parent=self._root_widget,
+ size=(scrollwidth, scrollheight),
+ position=(self._x_inset + xinset, 45),
+ claims_left_right=True,
+ highlight=False,
+ border_opacity=0.25,
+ )
+ subcontainer = bui.containerwidget(
+ parent=h_scroll,
+ background=False,
+ size=(max(total_button_width, scrollwidth), scrollheight),
+ )
+ tinfobtn = bui.buttonwidget(
+ parent=self._root_widget,
+ autoselect=True,
+ label='Learn More',
+ position=(self._width * 0.5 - 75, self._height * 0.703),
+ size=(180, 43),
+ scale=0.8,
+ color=(0.4, 0.25, 0.5),
+ textcolor=self._textcolor,
+ on_activate_call=partial(
+ self._on_learn_more_press, response.token_info_url
+ ),
+ )
+
+ x = 0.0
+ bwidgets: list[bui.Widget] = []
+ for i, buttondef in enumerate(self._buttondefs):
+
+ price = None if plus is None else plus.get_price(buttondef.itemid)
+
+ x += buttondef.prepad
+ tdelay = 0.3 - i / len(self._buttondefs) * 0.25
+ btn = bui.buttonwidget(
+ autoselect=True,
+ label='',
+ color=buttondef.color,
+ transition_delay=tdelay,
+ up_widget=tinfobtn,
+ parent=subcontainer,
+ size=(buttondef.width, 275),
+ position=(x, -10 + yoffs),
+ button_type='square',
+ on_activate_call=partial(
+ self._purchase_press, buttondef.itemid
+ ),
+ )
+ bwidgets.append(btn)
+ for imgdef in buttondef.imgdefs:
+ _img = bui.imagewidget(
+ parent=subcontainer,
+ size=imgdef.size,
+ position=(x + imgdef.pos[0], imgdef.pos[1] + yoffs),
+ draw_controller=btn,
+ draw_controller_mult=imgdef.draw_controller_mult,
+ color=imgdef.color,
+ texture=bui.gettexture(imgdef.tex),
+ transition_delay=tdelay,
+ opacity=imgdef.opacity,
+ )
+ for txtdef in buttondef.txtdefs:
+ txt: bui.Lstr | str
+ if isinstance(txtdef.text, TextContents):
+ if txtdef.text is TextContents.PRICE:
+ tcolor = (
+ (1, 1, 1, 0.5) if price is None else txtdef.color
+ )
+ txt = (
+ bui.Lstr(resource='unavailableText')
+ if price is None
+ else price
+ )
+ else:
+ # Make sure we cover all cases.
+ assert_never(txtdef.text)
+ else:
+ tcolor = txtdef.color
+ txt = txtdef.text
+ _txt = bui.textwidget(
+ parent=subcontainer,
+ text=txt,
+ position=(x + txtdef.pos[0], txtdef.pos[1] + yoffs),
+ size=(0, 0),
+ scale=txtdef.scale,
+ h_align='center',
+ v_align='center',
+ draw_controller=btn,
+ color=tcolor,
+ transition_delay=tdelay,
+ flatness=0.0,
+ shadow=1.0,
+ rotate=txtdef.rotate,
+ maxwidth=txtdef.maxwidth,
+ )
+ x += buttondef.width + buttonpadding
+ bui.containerwidget(edit=subcontainer, visible_child=bwidgets[0])
+
+ _tinfotxt = bui.textwidget(
+ parent=self._root_widget,
+ position=(self._width * 0.5, self._height * 0.812),
+ color=self._textcolor,
+ shadow=1.0,
+ scale=0.7,
+ size=(0, 0),
+ h_align='center',
+ v_align='center',
+ text='BombSquad\'s shiny new currency.',
+ )
+ _tnumtxt = 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=str(response.tokens),
+ )
+ _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),
+ )
+
+ def _purchase_press(self, itemid: str) -> None:
+ plus = bui.app.plus
+
+ price = None if plus is None else plus.get_price(itemid)
+
+ if price is None:
+ if plus is not None and plus.supports_purchases():
+ # Looks like internet is down or something temporary.
+ errmsg = 'This purchase is not available.'
+ else:
+ # Looks like purchases will never work here.
+ errmsg = (
+ 'Sorry, purchases are not available on this build.\n'
+ 'As a fallback, sign in to this account on another'
+ ' platform and make purchases from there.'
+ )
+
+ bui.screenmessage(errmsg, color=(1, 0.5, 0))
+ bui.getsound('error').play()
+ return
+
+ print(f'WOULD PURCHASE {itemid}')
+
+ def _back(self) -> None:
+
+ # No-op if our underlying widget is dead or on its way out.
+ if not self._root_widget or self._root_widget.transitioning_out:
+ return
+
+ bui.containerwidget(
+ edit=self._root_widget, transition=self._transition_out
+ )
+ if self._restore_previous_call is not None:
+ self._restore_previous_call(self._root_widget)
+
+ def _on_learn_more_press(self, url: str) -> None:
+ bui.open_url(url)
diff --git a/src/assets/ba_data/python/bauiv1lib/mainmenu.py b/src/assets/ba_data/python/bauiv1lib/mainmenu.py
index 48242d9c..0f10f1cc 100644
--- a/src/assets/ba_data/python/bauiv1lib/mainmenu.py
+++ b/src/assets/ba_data/python/bauiv1lib/mainmenu.py
@@ -39,7 +39,8 @@ class MainMenuWindow(bui.Window):
bui.set_analytics_screen('Main Menu')
self._show_remote_app_info_on_first_launch()
- # Make a vanilla container; we'll modify it to our needs in refresh.
+ # Make a vanilla container; we'll modify it to our needs in
+ # refresh.
super().__init__(
root_widget=bui.containerwidget(
transition=transition,
@@ -91,7 +92,6 @@ class MainMenuWindow(bui.Window):
0.27, bui.WeakCall(self._check_refresh), repeat=True
)
- # noinspection PyUnresolvedReferences
@staticmethod
def _preload_modules() -> None:
"""Preload modules we use; avoids hitches (called in bg thread)."""
@@ -162,8 +162,9 @@ class MainMenuWindow(bui.Window):
if now < self._next_refresh_allow_time:
return
- # Don't refresh for the first few seconds the game is up so we don't
- # interrupt the transition in.
+ # Don't refresh for the first few seconds the game is up so we
+ # don't interrupt the transition in.
+
# bui.app.main_menu_window_refresh_check_count += 1
# if bui.app.main_menu_window_refresh_check_count < 4:
# return
@@ -413,8 +414,8 @@ class MainMenuWindow(bui.Window):
else:
self._quit_button = None
- # If we're not in-game, have no quit button, and this is android,
- # we want back presses to quit our activity.
+ # If we're not in-game, have no quit button, and this is
+ # android, we want back presses to quit our activity.
if (
not self._in_game
and not self._have_quit_button
@@ -428,9 +429,9 @@ class MainMenuWindow(bui.Window):
edit=self._root_widget, on_cancel_call=_do_quit
)
- # Add speed-up/slow-down buttons for replays.
- # (ideally this should be part of a fading-out playback bar like most
- # media players but this works for now).
+ # 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
@@ -605,13 +606,13 @@ class MainMenuWindow(bui.Window):
def _set_allow_time() -> None:
self._next_refresh_allow_time = bui.apptime() + 2.5
- # Slight hack: widget transitions currently only progress when
- # frames are being drawn, but this tends to get called before
- # frame drawing even starts, meaning we don't know exactly how
- # long we should wait before refreshing to avoid interrupting
- # the transition. To make things a bit better, let's do a
- # redundant set of the time in a deferred call which hopefully
- # happens closer to actual frame draw times.
+ # Slight hack: widget transitions currently only progress
+ # when frames are being drawn, but this tends to get called
+ # before frame drawing even starts, meaning we don't know
+ # exactly how long we should wait before refreshing to avoid
+ # interrupting the transition. To make things a bit better,
+ # let's do a redundant set of the time in a deferred call
+ # which hopefully happens closer to actual frame draw times.
_set_allow_time()
bui.pushcall(_set_allow_time)
diff --git a/src/assets/ba_data/python/bauiv1lib/partyqueue.py b/src/assets/ba_data/python/bauiv1lib/partyqueue.py
index 147fabfc..ef514291 100644
--- a/src/assets/ba_data/python/bauiv1lib/partyqueue.py
+++ b/src/assets/ba_data/python/bauiv1lib/partyqueue.py
@@ -564,7 +564,7 @@ class PartyQueueWindow(bui.Window):
def on_boost_press(self) -> None:
"""Boost was pressed."""
from bauiv1lib import account
- from bauiv1lib import getcurrency
+ from bauiv1lib import gettickets
plus = bui.app.plus
assert plus is not None
@@ -575,7 +575,7 @@ class PartyQueueWindow(bui.Window):
if plus.get_v1_account_ticket_count() < self._boost_tickets:
bui.getsound('error').play()
- getcurrency.show_get_tickets_prompt()
+ gettickets.show_get_tickets_prompt()
return
bui.getsound('laserReverse').play()
diff --git a/src/assets/ba_data/python/bauiv1lib/profile/edit.py b/src/assets/ba_data/python/bauiv1lib/profile/edit.py
index 7099e040..6c6f2a26 100644
--- a/src/assets/ba_data/python/bauiv1lib/profile/edit.py
+++ b/src/assets/ba_data/python/bauiv1lib/profile/edit.py
@@ -545,8 +545,6 @@ class EditProfileWindow(bui.Window):
for n in [
bui.SpecialChar.GOOGLE_PLAY_GAMES_LOGO,
bui.SpecialChar.GAME_CENTER_LOGO,
- bui.SpecialChar.GAME_CIRCLE_LOGO,
- bui.SpecialChar.OUYA_LOGO,
bui.SpecialChar.LOCAL_ACCOUNT,
bui.SpecialChar.OCULUS_LOGO,
bui.SpecialChar.NVIDIA_LOGO,
diff --git a/src/assets/ba_data/python/bauiv1lib/profile/upgrade.py b/src/assets/ba_data/python/bauiv1lib/profile/upgrade.py
index a3968d96..23ed962c 100644
--- a/src/assets/ba_data/python/bauiv1lib/profile/upgrade.py
+++ b/src/assets/ba_data/python/bauiv1lib/profile/upgrade.py
@@ -210,7 +210,7 @@ class ProfileUpgradeWindow(bui.Window):
)
def _on_upgrade_press(self) -> None:
- from bauiv1lib import getcurrency
+ from bauiv1lib import gettickets
if self._status is None:
plus = bui.app.plus
@@ -220,7 +220,7 @@ class ProfileUpgradeWindow(bui.Window):
tickets = plus.get_v1_account_ticket_count()
if tickets < self._cost:
bui.getsound('error').play()
- getcurrency.show_get_tickets_prompt()
+ gettickets.show_get_tickets_prompt()
return
bui.screenmessage(
bui.Lstr(resource='purchasingText'), color=(0, 1, 0)
diff --git a/src/assets/ba_data/python/bauiv1lib/purchase.py b/src/assets/ba_data/python/bauiv1lib/purchase.py
index 56e03cec..60f97b0e 100644
--- a/src/assets/ba_data/python/bauiv1lib/purchase.py
+++ b/src/assets/ba_data/python/bauiv1lib/purchase.py
@@ -162,7 +162,7 @@ class PurchaseWindow(bui.Window):
bui.containerwidget(edit=self._root_widget, transition='out_left')
def _purchase(self) -> None:
- from bauiv1lib import getcurrency
+ from bauiv1lib import gettickets
plus = bui.app.plus
assert plus is not None
@@ -176,7 +176,7 @@ class PurchaseWindow(bui.Window):
except Exception:
ticket_count = None
if ticket_count is not None and ticket_count < self._price:
- getcurrency.show_get_tickets_prompt()
+ gettickets.show_get_tickets_prompt()
bui.getsound('error').play()
return
diff --git a/src/assets/ba_data/python/bauiv1lib/specialoffer.py b/src/assets/ba_data/python/bauiv1lib/specialoffer.py
index 24ae04db..33853420 100644
--- a/src/assets/ba_data/python/bauiv1lib/specialoffer.py
+++ b/src/assets/ba_data/python/bauiv1lib/specialoffer.py
@@ -440,7 +440,7 @@ class SpecialOfferWindow(bui.Window):
def _on_get_more_tickets_press(self) -> None:
from bauiv1lib import account
- from bauiv1lib import getcurrency
+ from bauiv1lib import gettickets
plus = bui.app.plus
assert plus is not None
@@ -448,10 +448,10 @@ class SpecialOfferWindow(bui.Window):
if plus.get_v1_account_state() != 'signed_in':
account.show_sign_in_prompt()
return
- getcurrency.GetCurrencyWindow(modal=True).get_root_widget()
+ gettickets.GetTicketsWindow(modal=True).get_root_widget()
def _purchase(self) -> None:
- from bauiv1lib import getcurrency
+ from bauiv1lib import gettickets
from bauiv1lib import confirm
plus = bui.app.plus
@@ -474,7 +474,7 @@ class SpecialOfferWindow(bui.Window):
except Exception:
ticket_count = None
if ticket_count is not None and ticket_count < self._offer['price']:
- getcurrency.show_get_tickets_prompt()
+ gettickets.show_get_tickets_prompt()
bui.getsound('error').play()
return
diff --git a/src/assets/ba_data/python/bauiv1lib/store/browser.py b/src/assets/ba_data/python/bauiv1lib/store/browser.py
index 901fc9f3..129a7a37 100644
--- a/src/assets/ba_data/python/bauiv1lib/store/browser.py
+++ b/src/assets/ba_data/python/bauiv1lib/store/browser.py
@@ -574,7 +574,7 @@ class StoreBrowserWindow(bui.Window):
"""Attempt to purchase the provided item."""
from bauiv1lib import account
from bauiv1lib.confirm import ConfirmWindow
- from bauiv1lib import getcurrency
+ from bauiv1lib import gettickets
assert bui.app.classic is not None
store = bui.app.classic.store
@@ -620,7 +620,7 @@ class StoreBrowserWindow(bui.Window):
our_tickets = plus.get_v1_account_ticket_count()
if price is not None and our_tickets < price:
bui.getsound('error').play()
- getcurrency.show_get_tickets_prompt()
+ gettickets.show_get_tickets_prompt()
else:
def do_it() -> None:
@@ -1320,7 +1320,7 @@ class StoreBrowserWindow(bui.Window):
def _on_get_more_tickets_press(self) -> None:
# pylint: disable=cyclic-import
from bauiv1lib.account import show_sign_in_prompt
- from bauiv1lib.getcurrency import GetCurrencyWindow
+ from bauiv1lib.gettickets import GetTicketsWindow
# no-op if our underlying widget is dead or on its way out.
if not self._root_widget or self._root_widget.transitioning_out:
@@ -1334,7 +1334,7 @@ class StoreBrowserWindow(bui.Window):
return
self._save_state()
bui.containerwidget(edit=self._root_widget, transition='out_left')
- window = GetCurrencyWindow(
+ window = GetTicketsWindow(
from_modal_store=self._modal,
store_back_location=self._back_location,
).get_root_widget()
@@ -1391,7 +1391,7 @@ def _check_merch_availability_in_bg_thread() -> None:
def _store_in_logic_thread() -> None:
cfg = bui.app.config
- current: str | None = cfg.get(MERCH_LINK_KEY)
+ current = cfg.get(MERCH_LINK_KEY)
if not isinstance(current, str | None):
current = None
if current != response.url:
@@ -1414,6 +1414,9 @@ def _check_merch_availability_in_bg_thread() -> None:
# Slight hack; start checking merch availability in the bg (but only if
# it looks like we've been imported for use in a running app; don't want
# to do this during docs generation/etc.)
+
+# TODO: Should wire this up explicitly to app bootstrapping; not good to
+# be kicking off work at module import time.
if (
os.environ.get('BA_RUNNING_WITH_DUMMY_MODULES') != '1'
and bui.app.state is not bui.app.State.NOT_STARTED
diff --git a/src/assets/ba_data/python/bauiv1lib/tournamententry.py b/src/assets/ba_data/python/bauiv1lib/tournamententry.py
index 4595d34c..972fddcf 100644
--- a/src/assets/ba_data/python/bauiv1lib/tournamententry.py
+++ b/src/assets/ba_data/python/bauiv1lib/tournamententry.py
@@ -632,7 +632,7 @@ class TournamentEntryWindow(PopupWindow):
bui.apptimer(0 if practice else 1.25, self._transition_out)
def _on_pay_with_tickets_press(self) -> None:
- from bauiv1lib import getcurrency
+ from bauiv1lib import gettickets
plus = bui.app.plus
assert plus is not None
@@ -675,7 +675,7 @@ class TournamentEntryWindow(PopupWindow):
ticket_count = None
ticket_cost = self._purchase_price
if ticket_count is not None and ticket_count < ticket_cost:
- getcurrency.show_get_tickets_prompt()
+ gettickets.show_get_tickets_prompt()
bui.getsound('error').play()
self._transition_out()
return
@@ -781,7 +781,7 @@ class TournamentEntryWindow(PopupWindow):
self._launch()
def _on_get_tickets_press(self) -> None:
- from bauiv1lib import getcurrency
+ from bauiv1lib import gettickets
# If we're already entering, ignore presses.
if self._entering:
@@ -789,7 +789,7 @@ class TournamentEntryWindow(PopupWindow):
# Bring up get-tickets window and then kill ourself (we're on the
# overlay layer so we'd show up above it).
- getcurrency.GetCurrencyWindow(
+ gettickets.GetTicketsWindow(
modal=True, origin_widget=self._get_tickets_button
)
self._transition_out()
diff --git a/src/ballistica/base/app_adapter/app_adapter.cc b/src/ballistica/base/app_adapter/app_adapter.cc
index ae03b81b..48417181 100644
--- a/src/ballistica/base/app_adapter/app_adapter.cc
+++ b/src/ballistica/base/app_adapter/app_adapter.cc
@@ -148,5 +148,6 @@ void AppAdapter::NativeReviewRequest() {
void AppAdapter::DoNativeReviewRequest() { FatalError("Fixme unimplemented."); }
auto AppAdapter::ShouldSilenceAudioForInactive() -> bool const { return false; }
+auto AppAdapter::SupportsPurchases() -> bool { return false; }
} // namespace ballistica::base
diff --git a/src/ballistica/base/app_adapter/app_adapter.h b/src/ballistica/base/app_adapter/app_adapter.h
index 8db58c7e..f51fdde1 100644
--- a/src/ballistica/base/app_adapter/app_adapter.h
+++ b/src/ballistica/base/app_adapter/app_adapter.h
@@ -222,6 +222,8 @@ class AppAdapter {
virtual void DoClipboardSetText(const std::string& text);
virtual auto DoClipboardGetText() -> std::string;
+ virtual auto SupportsPurchases() -> bool;
+
protected:
virtual ~AppAdapter();
diff --git a/src/ballistica/base/app_adapter/app_adapter_sdl.cc b/src/ballistica/base/app_adapter/app_adapter_sdl.cc
index 5bc2ed08..0a3e6a92 100644
--- a/src/ballistica/base/app_adapter/app_adapter_sdl.cc
+++ b/src/ballistica/base/app_adapter/app_adapter_sdl.cc
@@ -408,6 +408,7 @@ void AppAdapterSDL::HandleSDLEvent_(const SDL_Event& event) {
}
case SDL_KEYDOWN: {
+ // printf("KEYDOWN %d\n", static_cast(event.key.keysym.sym));
if (!event.key.repeat) {
g_base->input->PushKeyPressEvent(event.key.keysym);
}
@@ -415,6 +416,7 @@ void AppAdapterSDL::HandleSDLEvent_(const SDL_Event& event) {
}
case SDL_KEYUP: {
+ // printf("KEYUP %d\n", static_cast(event.key.keysym.sym));
g_base->input->PushKeyReleaseEvent(event.key.keysym);
break;
}
diff --git a/src/ballistica/base/assets/assets.cc b/src/ballistica/base/assets/assets.cc
index 3b0a4db3..2673d025 100644
--- a/src/ballistica/base/assets/assets.cc
+++ b/src/ballistica/base/assets/assets.cc
@@ -1259,7 +1259,7 @@ void Assets::InitSpecialChars() {
special_char_strings_[SpecialChar::kOuyaButtonU] = "\xee\x80\x9A";
special_char_strings_[SpecialChar::kOuyaButtonY] = "\xee\x80\x9B";
special_char_strings_[SpecialChar::kOuyaButtonA] = "\xee\x80\x9C";
- special_char_strings_[SpecialChar::kOuyaLogo] = "\xee\x80\x9D";
+ special_char_strings_[SpecialChar::kToken] = "\xee\x80\x9D";
special_char_strings_[SpecialChar::kLogo] = "\xee\x80\x9E";
special_char_strings_[SpecialChar::kTicket] = "\xee\x80\x9F";
special_char_strings_[SpecialChar::kGooglePlayGamesLogo] = "\xee\x80\xA0";
diff --git a/src/ballistica/base/graphics/text/text_graphics.cc b/src/ballistica/base/graphics/text/text_graphics.cc
index dd13a924..a4d9aa90 100644
--- a/src/ballistica/base/graphics/text/text_graphics.cc
+++ b/src/ballistica/base/graphics/text/text_graphics.cc
@@ -54,8 +54,8 @@ TextGraphics::TextGraphics() {
}
// Shrink account logos and move them up a bit.
- if (index == 29 || index == 32 || index == 33 || index == 38
- || index == 40 || index == 48 || index == 49) {
+ if (index == 32 || index == 33 || index == 38 || index == 40
+ || index == 48 || index == 49) {
g.pen_offset_y += 0.4f;
extra_advance += 0.08f;
g.x_size *= 0.55f;
diff --git a/src/ballistica/base/platform/base_platform.cc b/src/ballistica/base/platform/base_platform.cc
index c0fe45a4..7c226efb 100644
--- a/src/ballistica/base/platform/base_platform.cc
+++ b/src/ballistica/base/platform/base_platform.cc
@@ -347,6 +347,14 @@ int BasePlatform::SmartGetC_(FILE* stream) {
return EOF;
}
+ // Need to catch these error cases and bail, oherwise we'll spin
+ // forever getting the same thing. (Noticed this happening on Mac build
+ // where we get an immediate POLLNVAL if there's no terminal attached
+ // to stdin.
+ if (fds[0].revents & (POLLERR | POLLNVAL | POLLHUP)) {
+ return EOF;
+ }
+
if (fds[0].revents & POLLIN) {
// stdin is ready for reading.
char buffer[256];
diff --git a/src/ballistica/base/support/repeater.cc b/src/ballistica/base/support/repeater.cc
index 899a9cce..8e306593 100644
--- a/src/ballistica/base/support/repeater.cc
+++ b/src/ballistica/base/support/repeater.cc
@@ -6,6 +6,7 @@
#include "ballistica/shared/foundation/event_loop.h"
namespace ballistica::base {
+
Repeater::Repeater(seconds_t initial_delay, seconds_t repeat_delay,
Runnable* runnable)
: initial_delay_(initial_delay),
@@ -14,39 +15,40 @@ Repeater::Repeater(seconds_t initial_delay, seconds_t repeat_delay,
assert(g_base->InLogicThread());
assert(initial_delay >= 0.0);
assert(repeat_delay >= 0.0);
+}
+
+void Repeater::PostInit_() {
+ assert(g_base->InLogicThread());
// Let's go ahead and run our initial time in a deferred call;
// this is generally safer than running in the middle of whatever UI
- // code set this up.
+ // code called us. Note that we use a strong ref here - if we use a
+ // weak ref then it is possible for our initial key press to get lost
+ // if the repeater gets canceled due to other keypresses/etc before
+ // the initial call runs.
+
+ auto strong_this = Object::Ref(this);
+ g_base->logic->event_loop()->PushCall(
+ [strong_this] { strong_this->runnable_->RunAndLogErrors(); });
+
auto weak_this = Object::WeakRef(this);
- g_base->logic->event_loop()->PushCall([weak_this] {
- if (weak_this.Exists()) {
- weak_this->runnable_->RunAndLogErrors();
- if (!weak_this.Exists()) {
- // Runnable might have killed us.
- return;
- }
- // Kick off our initial delay timer (generally the longer one).
- weak_this->timer_ =
- DisplayTimer::New(weak_this->initial_delay_, false, [weak_this] {
- // Timer should not have fired if we died.
- assert(weak_this.Exists());
- weak_this->runnable_->RunAndLogErrors();
- if (!weak_this.Exists()) {
- // Runnable might have killed us.
- return;
- }
- // Kick off our repeat timer (generally the short one).
- weak_this->timer_ =
- DisplayTimer::New(weak_this->repeat_delay_, true, [weak_this] {
- // Timer should not have fired if we died.
- assert(weak_this.Exists());
- weak_this->runnable_->RunAndLogErrors();
- // Doesn't matter if Runnable killed us since we don't
- // touch anything for the remainder of this function.
- });
- });
+ timer_ = DisplayTimer::New(weak_this->initial_delay_, false, [weak_this] {
+ // Timer should not have fired if we died.
+ assert(weak_this.Exists());
+ weak_this->runnable_->RunAndLogErrors();
+ if (!weak_this.Exists()) {
+ // Runnable we just ran might have killed us.
+ return;
}
+ // Kick off our repeat timer (generally the short one).
+ weak_this->timer_ =
+ DisplayTimer::New(weak_this->repeat_delay_, true, [weak_this] {
+ // Timer should not have fired if we died.
+ assert(weak_this.Exists());
+ weak_this->runnable_->RunAndLogErrors();
+ // Doesn't matter if Runnable killed us since we don't
+ // touch anything for the remainder of this function.
+ });
});
}
diff --git a/src/ballistica/base/support/repeater.h b/src/ballistica/base/support/repeater.h
index 2568ac24..df48e66b 100644
--- a/src/ballistica/base/support/repeater.h
+++ b/src/ballistica/base/support/repeater.h
@@ -14,17 +14,22 @@ namespace ballistica::base {
/// Uses display-time so emphasizes visual smoothness over accuracy.
class Repeater : public Object {
public:
- Repeater(seconds_t initial_delay, seconds_t repeat_delay, Runnable* runnable);
- ~Repeater();
-
template
static auto New(seconds_t initial_delay, seconds_t repeat_delay,
const F& lambda) {
- return Object::New(initial_delay, repeat_delay,
- NewLambdaRunnable(lambda).Get());
+ auto&& rep = Object::New(initial_delay, repeat_delay,
+ NewLambdaRunnable(lambda).Get());
+ // We need to run this bit *after* constructing our obj since it creates
+ // a strong ref.
+ rep->PostInit_();
+ return Object::Ref(rep);
}
private:
+ friend class Object; // Allows our constructor to be private.
+ Repeater(seconds_t initial_delay, seconds_t repeat_delay, Runnable* runnable);
+ ~Repeater();
+ void PostInit_();
seconds_t initial_delay_;
seconds_t repeat_delay_;
Object::Ref timer_;
diff --git a/src/ballistica/shared/ballistica.cc b/src/ballistica/shared/ballistica.cc
index 22b3fd7b..1e8ad083 100644
--- a/src/ballistica/shared/ballistica.cc
+++ b/src/ballistica/shared/ballistica.cc
@@ -39,7 +39,7 @@ auto main(int argc, char** argv) -> int {
namespace ballistica {
// These are set automatically via script; don't modify them here.
-const int kEngineBuildNumber = 21911;
+const int kEngineBuildNumber = 21919;
const char* kEngineVersion = "1.7.36";
const int kEngineApiVersion = 8;
diff --git a/src/ballistica/shared/foundation/types.h b/src/ballistica/shared/foundation/types.h
index 8a8f45dd..805fd579 100644
--- a/src/ballistica/shared/foundation/types.h
+++ b/src/ballistica/shared/foundation/types.h
@@ -188,7 +188,7 @@ enum class SpecialChar : uint8_t {
kOuyaButtonU,
kOuyaButtonY,
kOuyaButtonA,
- kOuyaLogo,
+ kToken,
kLogo,
kTicket,
kGooglePlayGamesLogo,
diff --git a/src/ballistica/ui_v1/python/methods/python_methods_ui_v1.cc b/src/ballistica/ui_v1/python/methods/python_methods_ui_v1.cc
index e6a7b2be..a04a1f00 100644
--- a/src/ballistica/ui_v1/python/methods/python_methods_ui_v1.cc
+++ b/src/ballistica/ui_v1/python/methods/python_methods_ui_v1.cc
@@ -704,6 +704,7 @@ static auto PyImageWidget(PyObject* self, PyObject* args,
PyObject* tilt_scale_obj = Py_None;
PyObject* mask_texture_obj = Py_None;
PyObject* radial_amount_obj = Py_None;
+ PyObject* draw_controller_mult_obj = Py_None;
static const char* kwlist[] = {"edit",
"parent",
@@ -723,14 +724,16 @@ static auto PyImageWidget(PyObject* self, PyObject* args,
"tilt_scale",
"mask_texture",
"radial_amount",
+ "draw_controller_mult",
nullptr};
if (!PyArg_ParseTupleAndKeywords(
- args, keywds, "|OOOOOOOOOOOOOOOOOO", const_cast(kwlist),
+ args, keywds, "|OOOOOOOOOOOOOOOOOOO", const_cast(kwlist),
&edit_obj, &parent_obj, &size_obj, &pos_obj, &color_obj, &texture_obj,
&opacity_obj, &mesh_transparent_obj, &mesh_opaque_obj,
&has_alpha_channel_obj, &tint_texture_obj, &tint_color_obj,
&transition_delay_obj, &draw_controller_obj, &tint2_color_obj,
- &tilt_scale_obj, &mask_texture_obj, &radial_amount_obj))
+ &tilt_scale_obj, &mask_texture_obj, &radial_amount_obj,
+ &draw_controller_mult_obj))
return nullptr;
if (!g_base->CurrentContext().IsEmpty()) {
@@ -831,6 +834,9 @@ static auto PyImageWidget(PyObject* self, PyObject* args,
if (tilt_scale_obj != Py_None) {
b->set_tilt_scale(Python::GetPyFloat(tilt_scale_obj));
}
+ if (draw_controller_mult_obj != Py_None) {
+ b->set_draw_controller_mult(Python::GetPyFloat(draw_controller_mult_obj));
+ }
// if making a new widget add it at the end
if (edit_obj == Py_None) {
@@ -866,7 +872,8 @@ static PyMethodDef PyImageWidgetDef = {
" tint2_color: Sequence[float] | None = None,\n"
" tilt_scale: float | None = None,\n"
" mask_texture: bauiv1.Texture | None = None,\n"
- " radial_amount: float | None = None)\n"
+ " radial_amount: float | None = None,\n"
+ " draw_controller_mult: float | None = None)\n"
" -> bauiv1.Widget\n"
"\n"
"Create or edit an image widget.\n"
@@ -1816,7 +1823,7 @@ static auto PyHScrollWidget(PyObject* self, PyObject* args,
if (c.size() != 3) {
throw Exception("Expected 3 floats for color.", PyExcType::kValue);
}
- widget->setColor(c[0], c[1], c[2]);
+ widget->SetColor(c[0], c[1], c[2]);
}
if (capture_arrows_obj != Py_None) {
widget->set_capture_arrows(Python::GetPyBool(capture_arrows_obj));
diff --git a/src/ballistica/ui_v1/widget/h_scroll_widget.cc b/src/ballistica/ui_v1/widget/h_scroll_widget.cc
index 7f0984d1..db2cdd01 100644
--- a/src/ballistica/ui_v1/widget/h_scroll_widget.cc
+++ b/src/ballistica/ui_v1/widget/h_scroll_widget.cc
@@ -5,6 +5,7 @@
#include "ballistica/base/graphics/component/empty_component.h"
#include "ballistica/base/graphics/component/simple_component.h"
#include "ballistica/base/support/app_timer.h"
+#include "ballistica/shared/foundation/inline.h"
namespace ballistica::ui_v1 {
@@ -41,7 +42,7 @@ void HScrollWidget::OnTouchDelayTimerExpired() {
touch_delay_timer_.Clear();
}
-void HScrollWidget::ClampThumb(bool velocity_clamp, bool position_clamp) {
+void HScrollWidget::ClampThumb_(bool velocity_clamp, bool position_clamp) {
BA_DEBUG_UI_READ_LOCK;
bool is_scrolling = (touch_held_ || !has_momentum_);
float strong_force;
@@ -59,7 +60,7 @@ void HScrollWidget::ClampThumb(bool velocity_clamp, bool position_clamp) {
if (velocity_clamp) {
if (child_offset_h_ < 0) {
- // even in velocity case do some sane clamping
+ // Even in velocity case do some sane clamping.
float diff = child_offset_h_;
inertia_scroll_rate_ +=
diff * (is_scrolling ? strong_force : weak_force);
@@ -77,7 +78,7 @@ void HScrollWidget::ClampThumb(bool velocity_clamp, bool position_clamp) {
}
}
- // hard clipping if we're dragging the scrollbar
+ // Hard clipping if we're dragging the scrollbar.
if (position_clamp) {
if (child_offset_h_smoothed_
> child_w - (width() - 2 * (border_width_ + kHMargin))) {
@@ -111,17 +112,18 @@ auto HScrollWidget::HandleMessage(const base::WidgetMessage& m) -> bool {
if (i == widgets().end()) break;
float child_w = (**i).GetWidth();
- // see where we'd have to scroll to get selection at left and right
+ // See where we'd have to scroll to get selection at left and right.
float child_offset_left =
child_w - m.fval1 - (width() - 2 * (border_width_ + kHMargin));
float child_offset_right = child_w - m.fval1 - m.fval3;
- // if we're in the middle, dont do anything
+ // If we're in the middle, dont do anything.
if (child_offset_h_ > child_offset_left
&& child_offset_h_ < child_offset_right) {
} else {
float prev_child_offset = child_offset_h_;
- // do whatever offset is less of a move
+
+ // Do whatever offset is less of a move.
if (std::abs(child_offset_left - child_offset_h_)
< std::abs(child_offset_right - child_offset_h_)) {
child_offset_h_ = child_offset_left;
@@ -129,12 +131,13 @@ auto HScrollWidget::HandleMessage(const base::WidgetMessage& m) -> bool {
child_offset_h_ = child_offset_right;
}
- // if we're moving left, stop at the end
+ // If we're moving left, stop at the end.
{
float max_val = child_w - (width() - 2 * (border_width_ + kHMargin));
if (child_offset_h_ > max_val) child_offset_h_ = max_val;
}
- // if we're moving right, stop at the top
+
+ // If we're moving right, stop at the top.
{
if (child_offset_h_ < prev_child_offset) {
if (child_offset_h_ < 0) child_offset_h_ = 0;
@@ -154,6 +157,7 @@ auto HScrollWidget::HandleMessage(const base::WidgetMessage& m) -> bool {
break;
}
case base::WidgetMessage::Type::kMouseMove: {
+ last_mouse_move_time_ = g_base->logic->display_time();
float x = m.fval1;
float y = m.fval2;
bool claimed2 = (m.fval3 > 0.0f);
@@ -177,17 +181,17 @@ auto HScrollWidget::HandleMessage(const base::WidgetMessage& m) -> bool {
touch_x_ = x;
touch_y_ = y;
- // if this is a new scroll-touch, see which direction the drag is
- // happening; if it's primarily vertical lets disown it so it can
- // get handled by the scroll widget above us (presumably a vertical
- // scroll widget)
+ // If this is a new scroll-touch, see which direction the drag
+ // is happening; if it's primarily vertical lets disown it so it
+ // can get handled by the scroll widget above us (presumably a
+ // vertical scroll widget).
if (new_scroll_touch_) {
float x_diff = std::abs(touch_x_ - touch_start_x_);
float y_diff = std::abs(touch_y_ - touch_start_y_);
float dist = x_diff * x_diff + y_diff * y_diff;
- // if they're somehow equal, wait and look at the next one..
+ // If they're somehow equal, wait and look at the next one.
if (x_diff != y_diff && dist > 30.0f) {
new_scroll_touch_ = false;
@@ -200,8 +204,8 @@ auto HScrollWidget::HandleMessage(const base::WidgetMessage& m) -> bool {
// Handle generating delayed press/releases.
if (static_cast(m.type)) { // <- FIXME WHAT IS THIS FOR??
- // If we move more than a slight amount it means our touch isn't a
- // click.
+ // If we move more than a slight amount it means our touch
+ // isn't a click.
if (!touch_is_scrolling_
&& ((std::abs(touch_x_ - touch_start_x_) > 10.0f)
|| (std::abs(touch_y_ - touch_start_y_) > 10.0f))) {
@@ -253,7 +257,7 @@ auto HScrollWidget::HandleMessage(const base::WidgetMessage& m) -> bool {
child_offset_h_ = thumb_click_start_child_offset_h_
- rate * (x - thumb_click_start_h_);
- ClampThumb(false, true);
+ ClampThumb_(false, true);
MarkForUpdate();
}
@@ -272,12 +276,12 @@ auto HScrollWidget::HandleMessage(const base::WidgetMessage& m) -> bool {
touch_held_ = false;
- // If we moved at all, we mark it as claimed to keep
- // sub-widgets from acting on it (since we used it for scrolling).
+ // If we moved at all, we mark it as claimed to keep sub-widgets
+ // from acting on it (since we used it for scrolling).
bool claimed2 = touch_is_scrolling_ || m_claimed;
- // If we're not claiming it and we haven't sent a mouse_down yet due
- // to our delay, send that first.
+ // If we're not claiming it and we haven't sent a mouse_down yet
+ // due to our delay, send that first.
if (!claimed2 && !touch_down_sent_) {
ContainerWidget::HandleMessage(base::WidgetMessage(
base::WidgetMessage::Type::kMouseDown, nullptr, m.fval1,
@@ -294,9 +298,9 @@ auto HScrollWidget::HandleMessage(const base::WidgetMessage& m) -> bool {
}
}
- // If coords are outside of our bounds, pass a mouse-up along for anyone
- // tracking a drag, but mark it as claimed so it doesn't actually get
- // acted on.
+ // If coords are outside of our bounds, pass a mouse-up along for
+ // anyone tracking a drag, but mark it as claimed so it doesn't
+ // actually get acted on.
float x = m.fval1;
float y = m.fval2;
if (!((y >= 0.0f) && (y < height()) && (x >= 0.0f) && (x < width()))) {
@@ -399,8 +403,8 @@ auto HScrollWidget::HandleMessage(const base::WidgetMessage& m) -> bool {
touch_down_x_ = x - child_offset_h_;
touch_is_scrolling_ = false;
- // If there's significant scrolling happening we never pass touches.
- // they're only used to scroll more/less.
+ // If there's significant scrolling happening we never pass
+ // touches. they're only used to scroll more/less.
if (std::abs(inertia_scroll_rate_) > 0.05f) {
touch_is_scrolling_ = true;
}
@@ -441,7 +445,7 @@ auto HScrollWidget::HandleMessage(const base::WidgetMessage& m) -> bool {
smoothing_amount_ = 1.0f; // So we can see the transition.
child_offset_h_ -= (width() - 2 * (border_width_ + kHMargin));
MarkForUpdate();
- ClampThumb(false, true);
+ ClampThumb_(false, true);
} else if (x >= sb_thumb_right - sb_thumb_width) {
// On thumb.
mouse_held_thumb_ = true;
@@ -452,7 +456,7 @@ auto HScrollWidget::HandleMessage(const base::WidgetMessage& m) -> bool {
smoothing_amount_ = 1.0f; // So we can see the transition.
child_offset_h_ += (width() - 2 * (border_width_ + kHMargin));
MarkForUpdate();
- ClampThumb(false, true);
+ ClampThumb_(false, true);
}
}
}
@@ -519,18 +523,20 @@ void HScrollWidget::UpdateLayout() {
void HScrollWidget::Draw(base::RenderPass* pass, bool draw_transparent) {
have_drawn_ = true;
- millisecs_t current_time = pass->frame_def()->display_time_millisecs();
+ auto* frame_def{pass->frame_def()};
+ millisecs_t current_time_ms = frame_def->display_time_millisecs();
float prev_child_offset_h_smoothed = child_offset_h_smoothed_;
- // Ok, lets update our inertial scrolling during the opaque pass.
- // (we really should have some sort of update() function for this but widgets
+ // Ok, lets update our inertial scrolling during the opaque pass. (we
+ // really should have some sort of update() function for this but widgets
// don't have that currently)
if (!draw_transparent) {
// (skip huge differences)
- if (current_time - inertia_scroll_update_time_ > 1000)
- inertia_scroll_update_time_ = current_time - 1000;
- while (current_time - inertia_scroll_update_time_ > 5) {
- inertia_scroll_update_time_ += 5;
+ if (current_time_ms - inertia_scroll_update_time_ms_ > 1000) {
+ inertia_scroll_update_time_ms_ = current_time_ms - 1000;
+ }
+ while (current_time_ms - inertia_scroll_update_time_ms_ > 5) {
+ inertia_scroll_update_time_ms_ += 5;
if (touch_mode_) {
if (touch_held_) {
@@ -545,18 +551,20 @@ void HScrollWidget::Draw(base::RenderPass* pass, bool draw_transparent) {
inertia_scroll_rate_ *= 0.98f;
}
- ClampThumb(true, mouse_held_thumb_);
+ ClampThumb_(true, mouse_held_thumb_);
child_offset_h_ += inertia_scroll_rate_;
if (!has_momentum_
- && (current_time - last_velocity_event_time_millisecs_ > 1000 / 30)) {
+ && (current_time_ms - last_velocity_event_time_millisecs_
+ > 1000 / 30)) {
inertia_scroll_rate_ = 0;
}
- // Lastly we apply smoothing so that if we're snapping to a specific place
- // we don't go instantly there we blend between smoothed and non-smoothed
- // depending on whats driving us (we dont want to add smoothing on top of
- // inertial scrolling for example or it'll feel muddy)
+ // Lastly we apply smoothing so that if we're snapping to a specific
+ // place we don't go instantly there we blend between smoothed and
+ // non-smoothed depending on whats driving us (we dont want to add
+ // smoothing on top of inertial scrolling for example or it'll feel
+ // muddy)
float diff = child_offset_h_ - child_offset_h_smoothed_;
if (std::abs(diff) < 1.0f)
child_offset_h_smoothed_ = child_offset_h_;
@@ -601,58 +609,60 @@ void HScrollWidget::Draw(base::RenderPass* pass, bool draw_transparent) {
}
// scroll trough (depth 0.7f to 0.8f)
- if (draw_transparent && border_opacity_ > 0.0f) {
- if (trough_dirty_) {
- float b2 = b + 4;
- float t2 = b2 + scroll_bar_height_;
- float l2;
- float r2;
- l2 = l + (border_width_);
- r2 = r - (border_width_);
- float b_border, t_border, l_border, r_border;
- b_border = 3;
- t_border = 0;
- l_border = width() * 0.006f;
- r_border = width() * 0.002f;
- trough_height_ = t2 - b2 + b_border + t_border;
- trough_width_ = r2 - l2 + l_border + r_border;
- trough_center_y_ = b2 - b_border + trough_height_ * 0.5f;
- trough_center_x_ = l2 - l_border + trough_width_ * 0.5f;
- trough_dirty_ = false;
- }
+ if (explicit_bool(false)) {
+ if (draw_transparent && border_opacity_ > 0.0f) {
+ if (trough_dirty_) {
+ float b2 = b + 4;
+ float t2 = b2 + scroll_bar_height_;
+ float l2;
+ float r2;
+ l2 = l + (border_width_);
+ r2 = r - (border_width_);
+ float b_border, t_border, l_border, r_border;
+ b_border = 3;
+ t_border = 0;
+ l_border = width() * 0.006f;
+ r_border = width() * 0.002f;
+ trough_height_ = t2 - b2 + b_border + t_border;
+ trough_width_ = r2 - l2 + l_border + r_border;
+ trough_center_y_ = b2 - b_border + trough_height_ * 0.5f;
+ trough_center_x_ = l2 - l_border + trough_width_ * 0.5f;
+ trough_dirty_ = false;
+ }
- base::SimpleComponent c(pass);
- c.SetTransparent(true);
- c.SetColor(1, 1, 1, border_opacity_);
- c.SetTexture(g_base->assets->SysTexture(base::SysTextureID::kUIAtlas));
- {
- auto xf = c.ScopedTransform();
- c.Translate(trough_center_x_, trough_center_y_, 0.7f);
- c.Scale(trough_width_, trough_height_, 0.1f);
- c.Rotate(-90, 0, 0, 1);
- c.DrawMeshAsset(g_base->assets->SysMesh(
- base::SysMeshID::kScrollBarTroughTransparent));
+ base::SimpleComponent c(pass);
+ c.SetTransparent(true);
+ c.SetColor(1, 1, 1, border_opacity_);
+ c.SetTexture(g_base->assets->SysTexture(base::SysTextureID::kUIAtlas));
+ {
+ auto xf = c.ScopedTransform();
+ c.Translate(trough_center_x_, trough_center_y_, 0.7f);
+ c.Scale(trough_width_, trough_height_, 0.1f);
+ c.Rotate(-90, 0, 0, 1);
+ c.DrawMeshAsset(g_base->assets->SysMesh(
+ base::SysMeshID::kScrollBarTroughTransparent));
+ }
+ c.Submit();
}
- c.Submit();
}
- // scroll bars
- if (amount_visible_ > 0 && amount_visible_ < 1) {
- // scroll thumb at depth 0.8f-0.9
+ // Scroll bars.
+ if (amount_visible_ > 0.0f && amount_visible_ < 1.0f) {
+ // Scroll thumb at depth 0.8 - 0.9.
{
- float sb_thumb_width = amount_visible_ * (width() - 2 * border_width_);
+ float sb_thumb_width = amount_visible_ * (width() - 2.0f * border_width_);
if (thumb_dirty_) {
float sb_thumb_right =
r - border_width_
- - ((width() - (border_width_ * 2) - sb_thumb_width)
+ - ((width() - (border_width_ * 2.0f) - sb_thumb_width)
* child_offset_h_smoothed_ / child_max_offset_);
- float b2 = 4;
+ float b2 = 4.0f;
float t2 = b2 + scroll_bar_height_;
float r2 = sb_thumb_right;
float l2 = r2 - sb_thumb_width;
float b_border, t_border, l_border, r_border;
- b_border = 6;
- t_border = 3;
+ b_border = 6.0f;
+ t_border = 3.0f;
if (sb_thumb_width > 100) {
auto wd = r2 - l2;
l_border = wd * 0.04f;
@@ -679,25 +689,32 @@ void HScrollWidget::Draw(base::RenderPass* pass, bool draw_transparent) {
// c_scale = 1.25f;
// }
+ float frame_duration = frame_def->display_time_elapsed();
+
bool smooth_diff =
(std::abs(child_offset_h_smoothed_ - child_offset_h_) > 0.01f);
if (touch_mode_) {
if (smooth_diff || (touch_held_ && touch_is_scrolling_)
|| std::abs(inertia_scroll_rate_) > 1.0f) {
- touch_fade_ = std::min(1.5f, touch_fade_ + 0.02f);
- } else {
- // FIXME: Shouldn't be frame based.
- touch_fade_ = std::max(0.0f, touch_fade_ - 0.015f);
+ last_scroll_bar_show_time_ = frame_def->display_time();
}
} else {
if (smooth_diff || (touch_held_ && touch_is_scrolling_)
- || std::abs(inertia_scroll_rate_) > 1.0f || mouse_over_) {
- touch_fade_ = std::min(1.5f, touch_fade_ + 0.02f);
- } else {
- // FIXME: Shouldn't be frame based.
- touch_fade_ = std::max(0.0f, touch_fade_ - 0.015f);
+ || std::abs(inertia_scroll_rate_) > 1.0f
+ || (mouse_over_
+ && frame_def->display_time() - last_mouse_move_time_ < 0.1)) {
+ last_scroll_bar_show_time_ = frame_def->display_time();
}
}
+
+ // Fade in if we want to see the scrollbar. Start fading out a moment
+ // after we stop wanting to see it.
+ if (frame_def->display_time() - last_scroll_bar_show_time_ < 1.0) {
+ touch_fade_ = std::min(1.5f, touch_fade_ + 2.0f * frame_duration);
+ } else {
+ touch_fade_ = std::max(0.0f, touch_fade_ - frame_duration);
+ }
+
c.SetColor(0, 0, 0, std::min(1.0f, 0.3f * touch_fade_));
{
@@ -722,7 +739,7 @@ void HScrollWidget::Draw(base::RenderPass* pass, bool draw_transparent) {
}
}
- // outline shadow (depth 0.9 to 1.0)
+ // Outline shadow (depth 0.9 to 1.0).
if (draw_transparent && border_opacity_ > 0.0f) {
if (shadow_dirty_) {
float r2 = l + width();
@@ -754,12 +771,12 @@ void HScrollWidget::Draw(base::RenderPass* pass, bool draw_transparent) {
c.Submit();
}
- // if selected, do glow at depth 0.9-1.0
+ // If selected, do glow at depth 0.9 - 1.0.
if (draw_transparent && IsHierarchySelected()
&& g_base->ui->ShouldHighlightWidgets() && highlight_
&& border_opacity_ > 0.0f) {
float m = 0.8f
- + std::abs(sinf(static_cast(current_time) * 0.006467f))
+ + std::abs(sinf(static_cast(current_time_ms) * 0.006467f))
* 0.2f * border_opacity_;
if (glow_dirty_) {
diff --git a/src/ballistica/ui_v1/widget/h_scroll_widget.h b/src/ballistica/ui_v1/widget/h_scroll_widget.h
index 5655fab4..c5317b2c 100644
--- a/src/ballistica/ui_v1/widget/h_scroll_widget.h
+++ b/src/ballistica/ui_v1/widget/h_scroll_widget.h
@@ -33,7 +33,7 @@ class HScrollWidget : public ContainerWidget {
MarkForUpdate();
}
void OnTouchDelayTimerExpired();
- void setColor(float r, float g, float b) {
+ void SetColor(float r, float g, float b) {
color_red_ = r;
color_green_ = g;
color_blue_ = b;
@@ -47,39 +47,30 @@ class HScrollWidget : public ContainerWidget {
void UpdateLayout() override;
private:
- void ClampThumb(bool velocity_clamp, bool position_clamp);
+ void ClampThumb_(bool velocity_clamp, bool position_clamp);
- bool touch_mode_{};
+ Object::Ref touch_delay_timer_;
+ seconds_t last_scroll_bar_show_time_{};
+ seconds_t last_mouse_move_time_{};
float color_red_{0.55f};
float color_green_{0.47f};
float color_blue_{0.67f};
- bool has_momentum_{true};
- bool trough_dirty_{true};
- bool shadow_dirty_{true};
- bool glow_dirty_{true};
- bool thumb_dirty_{true};
- millisecs_t last_velocity_event_time_millisecs_{};
float touch_fade_{};
- bool center_small_content_{};
float center_offset_x_{};
- bool touch_held_{};
- int touch_held_click_count_{};
float touch_down_x_{};
float touch_x_{};
float touch_y_{};
float touch_start_x_{};
float touch_start_y_{};
- bool touch_is_scrolling_{};
- bool touch_down_sent_{};
- bool touch_up_sent_{};
- bool new_scroll_touch_{};
float trough_width_{};
float trough_height_{};
float trough_center_x_{};
float trough_center_y_{};
- float thumb_width_{}, thumb_height_{}, thumb_center_x_{}, thumb_center_y_{};
+ float thumb_width_{};
+ float thumb_height_{};
+ float thumb_center_x_{};
+ float thumb_center_y_{};
float smoothing_amount_{1.0f};
- bool highlight_{true};
float glow_width_{};
float glow_height_{};
float glow_center_x_{};
@@ -89,16 +80,8 @@ class HScrollWidget : public ContainerWidget {
float outline_center_x_{};
float outline_center_y_{};
float border_opacity_{1.0f};
- bool capture_arrows_{};
- bool mouse_held_scroll_down_{};
- bool mouse_held_scroll_up_{};
- bool mouse_held_thumb_{};
float thumb_click_start_h_{};
float thumb_click_start_child_offset_h_{};
- bool mouse_held_page_down_{};
- bool mouse_held_page_up_{};
- bool mouse_over_thumb_{};
- bool mouse_over_{};
float scroll_bar_height_{10.0f};
float border_width_{2.0f};
float border_height_{2.0f};
@@ -106,10 +89,32 @@ class HScrollWidget : public ContainerWidget {
float child_offset_h_smoothed_{};
float child_max_offset_{};
float amount_visible_{};
- bool have_drawn_{};
- millisecs_t inertia_scroll_update_time_{};
float inertia_scroll_rate_{};
- Object::Ref touch_delay_timer_;
+ millisecs_t inertia_scroll_update_time_ms_{};
+ millisecs_t last_velocity_event_time_millisecs_{};
+ int touch_held_click_count_{};
+ bool touch_is_scrolling_{};
+ bool touch_down_sent_{};
+ bool touch_up_sent_{};
+ bool new_scroll_touch_{};
+ bool touch_held_{};
+ bool touch_mode_{};
+ bool has_momentum_{true};
+ bool trough_dirty_{true};
+ bool shadow_dirty_{true};
+ bool glow_dirty_{true};
+ bool thumb_dirty_{true};
+ bool center_small_content_{};
+ bool highlight_{true};
+ bool capture_arrows_{};
+ bool mouse_held_scroll_down_{};
+ bool mouse_held_scroll_up_{};
+ bool mouse_held_thumb_{};
+ bool mouse_held_page_down_{};
+ bool mouse_held_page_up_{};
+ bool mouse_over_thumb_{};
+ bool mouse_over_{};
+ bool have_drawn_{};
};
} // namespace ballistica::ui_v1
diff --git a/src/ballistica/ui_v1/widget/image_widget.cc b/src/ballistica/ui_v1/widget/image_widget.cc
index 926992f6..c33446f8 100644
--- a/src/ballistica/ui_v1/widget/image_widget.cc
+++ b/src/ballistica/ui_v1/widget/image_widget.cc
@@ -87,7 +87,9 @@ void ImageWidget::Draw(base::RenderPass* pass, bool draw_transparent) {
// Draw brightness.
float db = 1.0f;
if (Widget* draw_controller = draw_control_parent()) {
- db *= draw_controller->GetDrawBrightness(current_time);
+ db *= (draw_controller_mult_
+ * draw_controller->GetDrawBrightness(current_time))
+ + (1.0f - draw_controller_mult_) * 1.0f;
}
// Opaque portion may get drawn transparent or opaque depending on our
diff --git a/src/ballistica/ui_v1/widget/image_widget.h b/src/ballistica/ui_v1/widget/image_widget.h
index 51376de7..8e2149df 100644
--- a/src/ballistica/ui_v1/widget/image_widget.h
+++ b/src/ballistica/ui_v1/widget/image_widget.h
@@ -43,6 +43,9 @@ class ImageWidget : public Widget {
tint2_color_green_ = g;
tint2_color_blue_ = b;
}
+ void set_draw_controller_mult(float val) {
+ draw_controller_mult_ = std::max(0.0f, std::min(1.0f, val));
+ }
void set_opacity(float o) { opacity_ = o; }
void SetTexture(base::TextureAsset* val) { texture_ = val; }
void SetTintTexture(base::TextureAsset* val) { tint_texture_ = val; }
@@ -89,6 +92,7 @@ class ImageWidget : public Widget {
float tint2_color_green_{1.0f};
float tint2_color_blue_{1.0f};
float opacity_{1.0f};
+ float draw_controller_mult_{1.0f};
};
} // namespace ballistica::ui_v1
diff --git a/src/ballistica/ui_v1/widget/scroll_widget.cc b/src/ballistica/ui_v1/widget/scroll_widget.cc
index 82d04bce..e4b83766 100644
--- a/src/ballistica/ui_v1/widget/scroll_widget.cc
+++ b/src/ballistica/ui_v1/widget/scroll_widget.cc
@@ -24,8 +24,8 @@ void ScrollWidget::OnTouchDelayTimerExpired() {
if (touch_held_) {
// Pass a mouse-down event if we haven't moved.
if (!touch_is_scrolling_ && !touch_down_sent_) {
- // Gather up any user code triggered by this stuff and run it at the end
- // before we return.
+ // Gather up any user code triggered by this stuff and run it at the
+ // end before we return.
base::UI::OperationContext ui_op_context;
ContainerWidget::HandleMessage(base::WidgetMessage(
@@ -42,7 +42,7 @@ void ScrollWidget::OnTouchDelayTimerExpired() {
touch_delay_timer_.Clear();
}
-void ScrollWidget::ClampThumb(bool velocity_clamp, bool position_clamp) {
+void ScrollWidget::ClampThumb_(bool velocity_clamp, bool position_clamp) {
BA_DEBUG_UI_READ_LOCK;
bool is_scrolling;
@@ -118,7 +118,7 @@ auto ScrollWidget::HandleMessage(const base::WidgetMessage& m) -> bool {
smoothing_amount_ = 1.0f; // So we can see the transition.
child_offset_v_ -= (60);
MarkForUpdate();
- ClampThumb(false, true);
+ ClampThumb_(false, true);
}
break;
@@ -127,7 +127,7 @@ auto ScrollWidget::HandleMessage(const base::WidgetMessage& m) -> bool {
smoothing_amount_ = 1.0f; // So we can see the transition.
child_offset_v_ += (60);
MarkForUpdate();
- ClampThumb(false, true);
+ ClampThumb_(false, true);
}
break;
@@ -187,8 +187,8 @@ auto ScrollWidget::HandleMessage(const base::WidgetMessage& m) -> bool {
if (ContainerWidget::HandleMessage(m)) {
claimed = true;
- // Keep track of the average scrolling going on. (only update when we
- // get non-momentum events)
+ // Keep track of the average scrolling going on. (only update when
+ // we get non-momentum events)
if (std::abs(m.fval3) > 0.001f && !has_momentum_) {
float smoothing = 0.8f;
avg_scroll_speed_h_ =
@@ -207,8 +207,8 @@ auto ScrollWidget::HandleMessage(const base::WidgetMessage& m) -> bool {
float x = m.fval1;
float y = m.fval2;
- // Keep track of the average scrolling going on. (only update when we get
- // non-momentum events).
+ // Keep track of the average scrolling going on. (only update when we
+ // get non-momentum events).
if (std::abs(m.fval3) > 0.001f && !has_momentum_) {
float smoothing = 0.8f;
avg_scroll_speed_v_ =
@@ -219,9 +219,9 @@ auto ScrollWidget::HandleMessage(const base::WidgetMessage& m) -> bool {
smoothing * avg_scroll_speed_h_ + (1.0f - smoothing) * 0.0f;
}
- // If a child appears to be looking at horizontal scroll events and we're
- // scrolling more horizontally than vertically in general, ignore vertical
- // scrolling (should probably make this less fuzzy).
+ // If a child appears to be looking at horizontal scroll events and
+ // we're scrolling more horizontally than vertically in general,
+ // ignore vertical scrolling (should probably make this less fuzzy).
bool ignore_regular_scrolling = false;
bool child_claimed_h_scroll_recently =
(g_core->GetAppTimeMillisecs() - last_sub_widget_h_scroll_claim_time_
@@ -301,8 +301,8 @@ auto ScrollWidget::HandleMessage(const base::WidgetMessage& m) -> bool {
float y = m.fval2;
if ((x >= 0.0f) && (x < width() + right_overlap) && (y >= 0.0f)
&& (y < height())) {
- // On touch devices, touches begin scrolling, (and eventually can count
- // as clicks if they don't move).
+ // On touch devices, touches begin scrolling, (and eventually can
+ // count as clicks if they don't move).
if (touch_mode_) {
touch_held_ = true;
auto click_count = static_cast(m.fval3);
@@ -318,8 +318,8 @@ auto ScrollWidget::HandleMessage(const base::WidgetMessage& m) -> bool {
child_is_scrolling_ = false;
child_disowned_scroll_ = false;
- // If there's already significant scrolling happening, we handle all
- // these ourself as scroll events.
+ // If there's already significant scrolling happening, we handle
+ // all these ourself as scroll events.
if (std::abs(inertia_scroll_rate_) > 0.05f) {
touch_is_scrolling_ = true;
}
@@ -361,19 +361,18 @@ auto ScrollWidget::HandleMessage(const base::WidgetMessage& m) -> bool {
smoothing_amount_ = 1.0f;
child_offset_v_ -= (height() - 2 * (border_height_ + V_MARGIN));
MarkForUpdate();
- ClampThumb(false, true);
+ ClampThumb_(false, true);
} else if (y >= sb_thumb_top - sb_thumb_height) {
// On thumb.
mouse_held_thumb_ = true;
thumb_click_start_v_ = y;
thumb_click_start_child_offset_v_ = child_offset_v_;
} else if (y >= s_bottom) {
- // Below thumb (page down).
- // So we can see the transition.
+ // Below thumb (page down). So we can see the transition.
smoothing_amount_ = 1.0f;
child_offset_v_ += (height() - 2 * (border_height_ + V_MARGIN));
MarkForUpdate();
- ClampThumb(false, true);
+ ClampThumb_(false, true);
}
}
}
@@ -388,12 +387,13 @@ auto ScrollWidget::HandleMessage(const base::WidgetMessage& m) -> bool {
float y = m.fval2;
bool was_claimed = (m.fval3 > 0.0f);
- // If coords are outside of our bounds we don't want to pass mouse-moved
- // events through the standard container logic. (otherwise, if we mouse
- // down over a button that doesn't overlap the scroll area but overlaps
- // some widget in the scroll area, the widget would claim the move and the
- // button would lose its mouse-over-highlight; ew.) There may be some
- // case where we *would* want to pass this though.
+ // If coords are outside of our bounds we don't want to pass
+ // mouse-moved events through the standard container logic.
+ // (otherwise, if we mouse down over a button that doesn't overlap the
+ // scroll area but overlaps some widget in the scroll area, the widget
+ // would claim the move and the button would lose its
+ // mouse-over-highlight; ew.) There may be some case where we *would*
+ // want to pass this though.
if (!((x >= 0.0f) && (x < width() + right_overlap) && (y >= 0.0f)
&& (y < height()))) {
pass = false;
@@ -404,14 +404,15 @@ auto ScrollWidget::HandleMessage(const base::WidgetMessage& m) -> bool {
} else {
if (touch_mode_) {
if (touch_held_) {
- // If we have a child claiming this scrolling action for themselves,
- // just keep passing them the events as long as they get claimed.
+ // If we have a child claiming this scrolling action for
+ // themselves, just keep passing them the events as long as they
+ // get claimed.
if (child_is_scrolling_ && !child_disowned_scroll_) {
bool move_claimed = ContainerWidget::HandleMessage(
base::WidgetMessage(base::WidgetMessage::Type::kMouseMove,
nullptr, m.fval1, m.fval2, m.fval3));
- // If they stopped claiming them, send a scroll-mouse-up to tie
- // things up.
+ // If they stopped claiming them, send a scroll-mouse-up to
+ // tie things up.
if (!move_claimed) {
ContainerWidget::HandleMessage(
base::WidgetMessage(base::WidgetMessage::Type::kMouseUp,
@@ -419,12 +420,13 @@ auto ScrollWidget::HandleMessage(const base::WidgetMessage& m) -> bool {
child_disowned_scroll_ = true;
}
} else {
- // If no child is scrolling; this touch motion is ours to handle.
+ // If no child is scrolling; this touch motion is ours to
+ // handle.
touch_x_ = x;
touch_y_ = y;
- // If we move more than a slight amount it means our touch isn't a
- // click.
+ // If we move more than a slight amount it means our touch
+ // isn't a click.
if (!touch_is_scrolling_
&& ((std::abs(touch_x_ - touch_start_x_) > 10.0f)
|| (std::abs(touch_y_ - touch_start_y_) > 10.0f))) {
@@ -476,7 +478,7 @@ auto ScrollWidget::HandleMessage(const base::WidgetMessage& m) -> bool {
/ ((1.0f - ((s_top - s_bottom) / child_h)) * (s_top - s_bottom));
child_offset_v_ = thumb_click_start_child_offset_v_
- rate * (y - thumb_click_start_v_);
- ClampThumb(false, true);
+ ClampThumb_(false, true);
MarkForUpdate();
}
break;
@@ -491,8 +493,8 @@ auto ScrollWidget::HandleMessage(const base::WidgetMessage& m) -> bool {
if (touch_held_) {
touch_held_ = false;
- // If we moved at all, we mark it as claimed to keep
- // sub-widgets from acting on it (since we used it for scrolling)
+ // If we moved at all, we mark it as claimed to keep sub-widgets
+ // from acting on it (since we used it for scrolling)
bool claimed2 = touch_is_scrolling_ || child_is_scrolling_;
// if a child is still scrolling, send them a scroll-mouse-up
@@ -502,8 +504,8 @@ auto ScrollWidget::HandleMessage(const base::WidgetMessage& m) -> bool {
nullptr, m.fval1, m.fval2, false));
}
- // If we're not claiming it and we haven't sent a mouse_down yet due
- // to our delay, send that first..
+ // If we're not claiming it and we haven't sent a mouse_down yet
+ // due to our delay, send that first..
if (!claimed2 && !touch_down_sent_) {
ContainerWidget::HandleMessage(base::WidgetMessage(
base::WidgetMessage::Type::kMouseDown, nullptr, m.fval1,
@@ -521,9 +523,9 @@ auto ScrollWidget::HandleMessage(const base::WidgetMessage& m) -> bool {
}
}
- // If coords are outside of our bounds, pass a mouse-up along for anyone
- // tracking a drag, but mark it as claimed so it doesn't actually get
- // acted on.
+ // If coords are outside of our bounds, pass a mouse-up along for
+ // anyone tracking a drag, but mark it as claimed so it doesn't
+ // actually get acted on.
float x = m.fval1;
float y = m.fval2;
if (!((x >= 0.0f) && (x < width() + right_overlap) && (y >= 0.0f)
@@ -558,7 +560,7 @@ auto ScrollWidget::HandleMessage(const base::WidgetMessage& m) -> bool {
void ScrollWidget::UpdateLayout() {
BA_DEBUG_UI_READ_LOCK;
- // move everything based on our offset
+ // Move everything based on our offset.
auto i = widgets().begin();
if (i == widgets().end()) {
amount_visible_ = 0;
@@ -600,11 +602,11 @@ void ScrollWidget::Draw(base::RenderPass* pass, bool draw_transparent) {
millisecs_t current_time = pass->frame_def()->display_time_millisecs();
float prev_child_offset_v_smoothed = child_offset_v_smoothed_;
- // ok lets update our inertial scrolling during the opaque pass
- // (we really should have some sort of update() function for this but widgets
- // don't have that)
+ // Ok, lets update our inertial scrolling during the opaque pass (we
+ // really should have some sort of update() function for this but widgets
+ // don't have that).
if (!draw_transparent) {
- // (skip huge differences)
+ // Skip huge differences.
if (current_time - inertia_scroll_update_time_ > 1000) {
inertia_scroll_update_time_ = current_time - 1000;
}
@@ -623,16 +625,17 @@ void ScrollWidget::Draw(base::RenderPass* pass, bool draw_transparent) {
} else {
inertia_scroll_rate_ *= 0.98f;
}
- ClampThumb(true, mouse_held_thumb_);
+ ClampThumb_(true, mouse_held_thumb_);
child_offset_v_ += inertia_scroll_rate_;
if (!has_momentum_
&& (current_time - last_velocity_event_time_millisecs_ > 1000 / 30))
inertia_scroll_rate_ = 0;
- // lastly we apply smoothing so that if we're snapping to a specific place
- // we don't go instantly there we blend between smoothed and non-smoothed
- // depending on whats driving us (we dont want to add smoothing on top of
- // inertial scrolling for example or it'll feel muddy)
+ // Lastly we apply smoothing so that if we're snapping to a specific
+ // place we don't go instantly there we blend between smoothed and
+ // non-smoothed depending on whats driving us (we dont want to add
+ // smoothing on top of inertial scrolling for example or it'll feel
+ // muddy).
float diff = child_offset_v_ - child_offset_v_smoothed_;
if (std::abs(diff) < 1.0f) {
child_offset_v_smoothed_ = child_offset_v_;
@@ -641,7 +644,8 @@ void ScrollWidget::Draw(base::RenderPass* pass, bool draw_transparent) {
}
smoothing_amount_ = std::max(0.0f, smoothing_amount_ - 0.005f);
}
- // only re-layout our widgets if we've moved a significant amount
+
+ // Only re-layout our widgets if we've moved a significant amount.
if (std::abs(prev_child_offset_v_smoothed - child_offset_v_smoothed_)
> 0.01f) {
MarkForUpdate();
@@ -675,7 +679,7 @@ void ScrollWidget::Draw(base::RenderPass* pass, bool draw_transparent) {
1.0f);
}
- // scroll trough (depth 0.7f to 0.8f)
+ // Scroll trough (depth 0.7 to 0.8).
if (draw_transparent) {
if (trough_dirty_) {
float r2 = l + width();
@@ -709,9 +713,9 @@ void ScrollWidget::Draw(base::RenderPass* pass, bool draw_transparent) {
c.Submit();
}
- // scroll bars
+ // Scroll bars.
if (amount_visible_ > 0 && amount_visible_ < 1) {
- // scroll thumb at depth 0.8f-0.9
+ // Scroll thumb at depth 0.8 - 0.9.
{
float sb_thumb_height = amount_visible_ * (height() - 2 * border_height_);
if (thumb_dirty_) {
@@ -777,7 +781,7 @@ void ScrollWidget::Draw(base::RenderPass* pass, bool draw_transparent) {
}
}
- // outline shadow (depth 0.9f to 1.0f)
+ // Outline shadow (depth 0.9 to 1.0).
if (draw_transparent) {
if (shadow_dirty_) {
float r2 = l + width();
@@ -811,7 +815,7 @@ void ScrollWidget::Draw(base::RenderPass* pass, bool draw_transparent) {
}
}
- // if selected, do glow at depth 0.9f-1.0
+ // If selected, do glow at depth 0.9 - 1.0.
if (draw_transparent && IsHierarchySelected()
&& g_base->ui->ShouldHighlightWidgets() && highlight_) {
float m = 0.8f
diff --git a/src/ballistica/ui_v1/widget/scroll_widget.h b/src/ballistica/ui_v1/widget/scroll_widget.h
index af21b0cd..cfee14dd 100644
--- a/src/ballistica/ui_v1/widget/scroll_widget.h
+++ b/src/ballistica/ui_v1/widget/scroll_widget.h
@@ -47,37 +47,33 @@ class ScrollWidget : public ContainerWidget {
void UpdateLayout() override;
private:
- void ClampThumb(bool velocity_clamp, bool position_clamp);
- bool touch_mode_{};
+ void ClampThumb_(bool velocity_clamp, bool position_clamp);
+
+ Object::Ref touch_delay_timer_;
+ millisecs_t last_sub_widget_h_scroll_claim_time_{};
+ millisecs_t last_velocity_event_time_millisecs_{};
+ millisecs_t inertia_scroll_update_time_{};
+ int touch_held_click_count_{};
float color_red_{0.55f};
float color_green_{0.47f};
float color_blue_{0.67f};
- bool has_momentum_{true};
- bool trough_dirty_{true};
- bool shadow_dirty_{true};
- bool glow_dirty_{true};
- bool thumb_dirty_{true};
- millisecs_t last_sub_widget_h_scroll_claim_time_{};
- millisecs_t last_velocity_event_time_millisecs_{};
float avg_scroll_speed_h_{};
float avg_scroll_speed_v_{};
- bool center_small_content_{};
float center_offset_y_{};
- bool touch_held_{};
- int touch_held_click_count_{};
float touch_down_y_{};
float touch_x_{};
float touch_y_{};
float touch_start_x_{};
float touch_start_y_{};
- bool touch_is_scrolling_{};
- bool touch_down_sent_{};
- bool touch_up_sent_{};
- float trough_width_{}, trough_height_{}, trough_center_x_{},
- trough_center_y_{};
- float thumb_width_{}, thumb_height_{}, thumb_center_x_{}, thumb_center_y_{};
+ float trough_width_{};
+ float trough_height_{};
+ float trough_center_x_{};
+ float trough_center_y_{};
+ float thumb_width_{};
+ float thumb_height_{};
+ float thumb_center_x_{};
+ float thumb_center_y_{};
float smoothing_amount_{1.0f};
- bool highlight_{true};
float glow_width_{};
float glow_height_{};
float glow_center_x_{};
@@ -87,15 +83,8 @@ class ScrollWidget : public ContainerWidget {
float outline_center_x_{};
float outline_center_y_{};
float border_opacity_{1.0f};
- bool capture_arrows_{false};
- bool mouse_held_scroll_down_{};
- bool mouse_held_scroll_up_{};
- bool mouse_held_thumb_{};
float thumb_click_start_v_{};
float thumb_click_start_child_offset_v_{};
- bool mouse_held_page_down_{};
- bool mouse_held_page_up_{};
- bool mouse_over_thumb_{};
float scroll_bar_width_{10.0f};
float border_width_{2.0f};
float border_height_{2.0f};
@@ -103,13 +92,30 @@ class ScrollWidget : public ContainerWidget {
float child_offset_v_smoothed_{};
float child_max_offset_{};
float amount_visible_{};
+ float inertia_scroll_rate_{};
+ bool mouse_held_page_down_{};
+ bool mouse_held_page_up_{};
+ bool mouse_over_thumb_{};
+ bool touch_is_scrolling_{};
+ bool touch_down_sent_{};
+ bool touch_up_sent_{};
+ bool touch_mode_{};
+ bool has_momentum_{true};
+ bool trough_dirty_{true};
+ bool shadow_dirty_{true};
+ bool glow_dirty_{true};
+ bool thumb_dirty_{true};
+ bool center_small_content_{};
+ bool touch_held_{};
+ bool highlight_{true};
+ bool capture_arrows_{false};
+ bool mouse_held_scroll_down_{};
+ bool mouse_held_scroll_up_{};
+ bool mouse_held_thumb_{};
bool have_drawn_{};
bool touch_down_passed_{};
bool child_is_scrolling_{};
bool child_disowned_scroll_{};
- millisecs_t inertia_scroll_update_time_{};
- float inertia_scroll_rate_{};
- Object::Ref touch_delay_timer_;
};
} // namespace ballistica::ui_v1
diff --git a/tools/bacommon/cloud.py b/tools/bacommon/cloud.py
index 8376ef81..40fee022 100644
--- a/tools/bacommon/cloud.py
+++ b/tools/bacommon/cloud.py
@@ -250,3 +250,39 @@ class ManageAccountResponse(Response):
"""Here's that sign-in result you asked for, boss."""
url: Annotated[str | None, IOAttrs('u')]
+
+
+@ioprepped
+@dataclass
+class StoreQueryMessage(Message):
+ """Message asking about purchasable stuff and store related state."""
+
+ @override
+ @classmethod
+ def get_response_types(cls) -> list[type[Response] | None]:
+ return [StoreQueryResponse]
+
+
+@ioprepped
+@dataclass
+class StoreQueryResponse(Response):
+ """Here's that store info you asked for, boss."""
+
+ class Result(Enum):
+ """Our overall result."""
+
+ SUCCESS = 's'
+ ERROR = 'e'
+
+ @dataclass
+ class Purchase:
+ """Info about a purchasable thing."""
+
+ purchaseid: Annotated[str, IOAttrs('id')]
+
+ # Overall result; all data is undefined if not SUCCESS.
+ result: Annotated[Result, IOAttrs('r')]
+
+ tokens: Annotated[int, IOAttrs('t')]
+ available_purchases: Annotated[list[Purchase], IOAttrs('p')]
+ token_info_url: Annotated[str, IOAttrs('tiu')]
diff --git a/tools/bacommon/workspace/assetsv1.py b/tools/bacommon/workspace/assetsv1.py
index bafeda37..7047c31b 100644
--- a/tools/bacommon/workspace/assetsv1.py
+++ b/tools/bacommon/workspace/assetsv1.py
@@ -30,6 +30,10 @@ class AssetsV1GlobalVals:
str | None, IOAttrs('base_assets', store_default=False)
] = None
+ base_assets_filter: Annotated[
+ str, IOAttrs('base_assets_filter', store_default=False)
+ ] = ''
+
class AssetsV1PathValsTypeID(Enum):
"""Types of vals we can store for paths."""
diff --git a/tools/batools/build.py b/tools/batools/build.py
index 52ec13ee..ae67af27 100644
--- a/tools/batools/build.py
+++ b/tools/batools/build.py
@@ -615,7 +615,7 @@ def cmake_prep_dir(dirname: str, verbose: bool = False) -> None:
# ...or if homebrew SDL.h resolved path changes (happens for updates)
sdl_h_path = Path('/opt/homebrew/include/SDL2/SDL.h')
homebrew_sdl_h_resolved: str = (
- str(sdl_h_path.resolve()) if sdl_h_path.is_symlink() else ''
+ str(sdl_h_path.resolve()) if sdl_h_path.exists() else ''
)
entries.append(Entry('homebrew_sdl_h_resolved', homebrew_sdl_h_resolved))
diff --git a/tools/efro/call.py b/tools/efro/call.py
index 933f3f0b..66b0f014 100644
--- a/tools/efro/call.py
+++ b/tools/efro/call.py
@@ -4,328 +4,14 @@
from __future__ import annotations
-from typing import TYPE_CHECKING, TypeVar, Generic, Callable, cast
import functools
+from typing import TYPE_CHECKING
if TYPE_CHECKING:
- from typing import Any, overload
+ pass
-CT = TypeVar('CT', bound=Callable)
-
-
-class _CallbackCall(Generic[CT]):
- """Descriptor for exposing a call with a type defined by a TypeVar."""
-
- def __get__(self, obj: Any, type_in: Any = None) -> CT:
- return cast(CT, None)
-
-
-class CallbackSet(Generic[CT]):
- """Wrangles callbacks for a particular event in a type-safe manner."""
-
- # In the type-checker's eyes, our 'run' attr is a CallbackCall which
- # returns a callable with the type we were created with. This lets us
- # type-check our run calls. (Is there another way to expose a function
- # with a signature defined by a generic?..)
- # At runtime, run() simply passes its args verbatim to its registered
- # callbacks; no types are checked.
- if TYPE_CHECKING:
- run: _CallbackCall[CT] = _CallbackCall()
- else:
-
- def run(self, *args, **keywds):
- """Run all callbacks."""
- print('HELLO FROM RUN', *args, **keywds)
-
- def __init__(self) -> None:
- print('CallbackSet()')
-
- def __del__(self) -> None:
- print('~CallbackSet()')
-
- def add(self, call: CT) -> None:
- """Add a callback to be run."""
- print('Would add call', call)
-
-
-# Define Call() which can be used in type-checking call-wrappers that behave
-# similarly to functools.partial (in that they take a callable and some
-# positional arguments to be passed to it).
-
-# In type-checking land, We define several different _CallXArg classes
-# corresponding to different argument counts and define Call() as an
-# overloaded function which returns one of them based on how many args are
-# passed.
-
-# To use this, simply assign your call type to this Call for type checking:
-# Example:
-# class _MyCallWrapper:
-#
-# if TYPE_CHECKING:
-# MyCallWrapper = efro.call.Call
-# else:
-# MyCallWrapper = _MyCallWrapper
-
-# Note that this setup currently only works with positional arguments; if you
-# would like to pass args via keyword you can wrap a lambda or local function
-# which takes keyword args and converts to a call containing keywords.
-
-if TYPE_CHECKING:
- In1T = TypeVar('In1T')
- In2T = TypeVar('In2T')
- In3T = TypeVar('In3T')
- In4T = TypeVar('In4T')
- In5T = TypeVar('In5T')
- In6T = TypeVar('In6T')
- In7T = TypeVar('In7T')
- OutT = TypeVar('OutT')
-
- class _CallNoArgs(Generic[OutT]):
- """Single argument variant of call wrapper."""
-
- def __init__(self, _call: Callable[[], OutT]): ...
-
- def __call__(self) -> OutT: ...
-
- class _Call1Arg(Generic[In1T, OutT]):
- """Single argument variant of call wrapper."""
-
- def __init__(self, _call: Callable[[In1T], OutT]): ...
-
- def __call__(self, _arg1: In1T) -> OutT: ...
-
- class _Call2Args(Generic[In1T, In2T, OutT]):
- """Two argument variant of call wrapper"""
-
- def __init__(self, _call: Callable[[In1T, In2T], OutT]): ...
-
- def __call__(self, _arg1: In1T, _arg2: In2T) -> OutT: ...
-
- class _Call3Args(Generic[In1T, In2T, In3T, OutT]):
- """Three argument variant of call wrapper"""
-
- def __init__(self, _call: Callable[[In1T, In2T, In3T], OutT]): ...
-
- def __call__(self, _arg1: In1T, _arg2: In2T, _arg3: In3T) -> OutT: ...
-
- class _Call4Args(Generic[In1T, In2T, In3T, In4T, OutT]):
- """Four argument variant of call wrapper"""
-
- def __init__(self, _call: Callable[[In1T, In2T, In3T, In4T], OutT]): ...
-
- def __call__(
- self, _arg1: In1T, _arg2: In2T, _arg3: In3T, _arg4: In4T
- ) -> OutT: ...
-
- class _Call5Args(Generic[In1T, In2T, In3T, In4T, In5T, OutT]):
- """Five argument variant of call wrapper"""
-
- def __init__(
- self, _call: Callable[[In1T, In2T, In3T, In4T, In5T], OutT]
- ): ...
-
- def __call__(
- self,
- _arg1: In1T,
- _arg2: In2T,
- _arg3: In3T,
- _arg4: In4T,
- _arg5: In5T,
- ) -> OutT: ...
-
- class _Call6Args(Generic[In1T, In2T, In3T, In4T, In5T, In6T, OutT]):
- """Six argument variant of call wrapper"""
-
- def __init__(
- self, _call: Callable[[In1T, In2T, In3T, In4T, In5T, In6T], OutT]
- ): ...
-
- def __call__(
- self,
- _arg1: In1T,
- _arg2: In2T,
- _arg3: In3T,
- _arg4: In4T,
- _arg5: In5T,
- _arg6: In6T,
- ) -> OutT: ...
-
- class _Call7Args(Generic[In1T, In2T, In3T, In4T, In5T, In6T, In7T, OutT]):
- """Seven argument variant of call wrapper"""
-
- def __init__(
- self,
- _call: Callable[[In1T, In2T, In3T, In4T, In5T, In6T, In7T], OutT],
- ): ...
-
- def __call__(
- self,
- _arg1: In1T,
- _arg2: In2T,
- _arg3: In3T,
- _arg4: In4T,
- _arg5: In5T,
- _arg6: In6T,
- _arg7: In7T,
- ) -> OutT: ...
-
- # No arg call; no args bundled.
- # noinspection PyPep8Naming
- @overload
- def Call(call: Callable[[], OutT]) -> _CallNoArgs[OutT]: ...
-
- # 1 arg call; 1 arg bundled.
- # noinspection PyPep8Naming
- @overload
- def Call(call: Callable[[In1T], OutT], arg1: In1T) -> _CallNoArgs[OutT]: ...
-
- # 1 arg call; no args bundled.
- # noinspection PyPep8Naming
- @overload
- def Call(call: Callable[[In1T], OutT]) -> _Call1Arg[In1T, OutT]: ...
-
- # 2 arg call; 2 args bundled.
- # noinspection PyPep8Naming
- @overload
- def Call(
- call: Callable[[In1T, In2T], OutT], arg1: In1T, arg2: In2T
- ) -> _CallNoArgs[OutT]: ...
-
- # 2 arg call; 1 arg bundled.
- # noinspection PyPep8Naming
- @overload
- def Call(
- call: Callable[[In1T, In2T], OutT], arg1: In1T
- ) -> _Call1Arg[In2T, OutT]: ...
-
- # 2 arg call; no args bundled.
- # noinspection PyPep8Naming
- @overload
- def Call(
- call: Callable[[In1T, In2T], OutT]
- ) -> _Call2Args[In1T, In2T, OutT]: ...
-
- # 3 arg call; 3 args bundled.
- # noinspection PyPep8Naming
- @overload
- def Call(
- call: Callable[[In1T, In2T, In3T], OutT],
- arg1: In1T,
- arg2: In2T,
- arg3: In3T,
- ) -> _CallNoArgs[OutT]: ...
-
- # 3 arg call; 2 args bundled.
- # noinspection PyPep8Naming
- @overload
- def Call(
- call: Callable[[In1T, In2T, In3T], OutT], arg1: In1T, arg2: In2T
- ) -> _Call1Arg[In3T, OutT]: ...
-
- # 3 arg call; 1 arg bundled.
- # noinspection PyPep8Naming
- @overload
- def Call(
- call: Callable[[In1T, In2T, In3T], OutT], arg1: In1T
- ) -> _Call2Args[In2T, In3T, OutT]: ...
-
- # 3 arg call; no args bundled.
- # noinspection PyPep8Naming
- @overload
- def Call(
- call: Callable[[In1T, In2T, In3T], OutT]
- ) -> _Call3Args[In1T, In2T, In3T, OutT]: ...
-
- # 4 arg call; 4 args bundled.
- # noinspection PyPep8Naming
- @overload
- def Call(
- call: Callable[[In1T, In2T, In3T, In4T], OutT],
- arg1: In1T,
- arg2: In2T,
- arg3: In3T,
- arg4: In4T,
- ) -> _CallNoArgs[OutT]: ...
-
- # 4 arg call; 3 args bundled.
- # noinspection PyPep8Naming
- @overload
- def Call(
- call: Callable[[In1T, In2T, In3T, In4T], OutT],
- arg1: In1T,
- arg2: In2T,
- arg3: In3T,
- ) -> _Call1Arg[In4T, OutT]: ...
-
- # 4 arg call; 2 args bundled.
- # noinspection PyPep8Naming
- @overload
- def Call(
- call: Callable[[In1T, In2T, In3T, In4T], OutT],
- arg1: In1T,
- arg2: In2T,
- ) -> _Call2Args[In3T, In4T, OutT]: ...
-
- # 4 arg call; 1 arg bundled.
- # noinspection PyPep8Naming
- @overload
- def Call(
- call: Callable[[In1T, In2T, In3T, In4T], OutT],
- arg1: In1T,
- ) -> _Call3Args[In2T, In3T, In4T, OutT]: ...
-
- # 4 arg call; no args bundled.
- # noinspection PyPep8Naming
- @overload
- def Call(
- call: Callable[[In1T, In2T, In3T, In4T], OutT],
- ) -> _Call4Args[In1T, In2T, In3T, In4T, OutT]: ...
-
- # 5 arg call; 5 args bundled.
- # noinspection PyPep8Naming
- @overload
- def Call(
- call: Callable[[In1T, In2T, In3T, In4T, In5T], OutT],
- arg1: In1T,
- arg2: In2T,
- arg3: In3T,
- arg4: In4T,
- arg5: In5T,
- ) -> _CallNoArgs[OutT]: ...
-
- # 6 arg call; 6 args bundled.
- # noinspection PyPep8Naming
- @overload
- def Call(
- call: Callable[[In1T, In2T, In3T, In4T, In5T, In6T], OutT],
- arg1: In1T,
- arg2: In2T,
- arg3: In3T,
- arg4: In4T,
- arg5: In5T,
- arg6: In6T,
- ) -> _CallNoArgs[OutT]: ...
-
- # 7 arg call; 7 args bundled.
- # noinspection PyPep8Naming
- @overload
- def Call(
- call: Callable[[In1T, In2T, In3T, In4T, In5T, In6T, In7T], OutT],
- arg1: In1T,
- arg2: In2T,
- arg3: In3T,
- arg4: In4T,
- arg5: In5T,
- arg6: In6T,
- arg7: In7T,
- ) -> _CallNoArgs[OutT]: ...
-
- # noinspection PyPep8Naming
- def Call(*_args: Any, **_keywds: Any) -> Any: ...
-
- # (Type-safe Partial)
- # A convenient wrapper around functools.partial which adds type-safety
- # (though it does not support keyword arguments).
- tpartial = Call
-else:
- tpartial = functools.partial
+# TODO: should deprecate tpartial since it nowadays simply wraps
+# functools.partial (mypy added support for functools.partial in 1.11 so
+# there's no benefit to rolling our own type-safe version anymore).
+# Perhaps we can use Python 13's @warnings.deprecated() stuff for this.
+tpartial = functools.partial
diff --git a/tools/efro/dataclassio/_outputter.py b/tools/efro/dataclassio/_outputter.py
index d9f9acec..1ca31186 100644
--- a/tools/efro/dataclassio/_outputter.py
+++ b/tools/efro/dataclassio/_outputter.py
@@ -54,15 +54,19 @@ class _Outputter:
def run(self) -> Any:
"""Do the thing."""
+ obj = self._obj
+
+ # mypy workaround - if we check 'obj' here it assumes the
+ # isinstance call below fails.
assert dataclasses.is_dataclass(self._obj)
# For special extended data types, call their 'will_output' callback.
# FIXME - should probably move this into _process_dataclass so it
# can work on nested values.
- if isinstance(self._obj, IOExtendedData):
- self._obj.will_output()
+ if isinstance(obj, IOExtendedData):
+ obj.will_output()
- return self._process_dataclass(type(self._obj), self._obj, '')
+ return self._process_dataclass(type(obj), obj, '')
def soft_default_check(
self, value: Any, anntype: Any, fieldpath: str
diff --git a/tools/efro/log.py b/tools/efro/log.py
index e1a3db31..3e1326ca 100644
--- a/tools/efro/log.py
+++ b/tools/efro/log.py
@@ -10,13 +10,13 @@ import logging
import datetime
import itertools
from enum import Enum
+from functools import partial
from collections import deque
from dataclasses import dataclass, field
from typing import TYPE_CHECKING, Annotated, override
from threading import Thread, current_thread, Lock
from efro.util import utc_now
-from efro.call import tpartial
from efro.terminal import Clr
from efro.dataclassio import ioprepped, IOAttrs, dataclass_to_json
@@ -178,7 +178,7 @@ class LogHandler(logging.Handler):
# process cached entries at the same time to ensure there are no
# race conditions that could cause entries to be skipped/etc.
self._event_loop.call_soon_threadsafe(
- tpartial(self._add_callback_in_thread, call, feed_existing_logs)
+ partial(self._add_callback_in_thread, call, feed_existing_logs)
)
def _add_callback_in_thread(
@@ -342,7 +342,7 @@ class LogHandler(logging.Handler):
if __debug__:
formattime = echotime = time.monotonic()
self._event_loop.call_soon_threadsafe(
- tpartial(
+ partial(
self._emit_in_thread,
record.name,
record.levelno,
@@ -395,7 +395,7 @@ class LogHandler(logging.Handler):
echotime = time.monotonic()
self._event_loop.call_soon_threadsafe(
- tpartial(
+ partial(
self._emit_in_thread,
record.name,
record.levelno,
@@ -427,7 +427,7 @@ class LogHandler(logging.Handler):
# the bg event loop thread we've already got.
self._last_slow_emit_warning_time = now
self._event_loop.call_soon_threadsafe(
- tpartial(
+ partial(
logging.warning,
'efro.log.LogHandler emit took too long'
' (%.2fs total; %.2fs format, %.2fs echo,'
@@ -477,7 +477,7 @@ class LogHandler(logging.Handler):
# another thread for each character. Perhaps should do some sort
# of basic accumulation here?
self._event_loop.call_soon_threadsafe(
- tpartial(self._file_write_in_thread, name, output)
+ partial(self._file_write_in_thread, name, output)
)
def _file_write_in_thread(self, name: str, output: str) -> None:
@@ -537,7 +537,7 @@ class LogHandler(logging.Handler):
"""Send raw stdout/stderr flush to the logger to be collated."""
self._event_loop.call_soon_threadsafe(
- tpartial(self._file_flush_in_thread, name)
+ partial(self._file_flush_in_thread, name)
)
def _file_flush_in_thread(self, name: str) -> None:
@@ -739,11 +739,7 @@ def setup_logging(
# Optionally intercept Python's stdout/stderr output and generate
# log entries from it.
if log_stdout_stderr:
- sys.stdout = FileLogEcho( # type: ignore
- sys.stdout, 'stdout', loghandler
- )
- sys.stderr = FileLogEcho( # type: ignore
- sys.stderr, 'stderr', loghandler
- )
+ sys.stdout = FileLogEcho(sys.stdout, 'stdout', loghandler)
+ sys.stderr = FileLogEcho(sys.stderr, 'stderr', loghandler)
return loghandler
diff --git a/tools/efro/message/_sender.py b/tools/efro/message/_sender.py
index 17f2925f..93cd38f6 100644
--- a/tools/efro/message/_sender.py
+++ b/tools/efro/message/_sender.py
@@ -52,6 +52,9 @@ class MessageSender:
def __init__(self, protocol: MessageProtocol) -> None:
self.protocol = protocol
self._send_raw_message_call: Callable[[Any, str], str] | None = None
+ self._send_raw_message_ex_call: (
+ Callable[[Any, str, Message], str] | None
+ ) = None
self._send_async_raw_message_call: (
Callable[[Any, str], Awaitable[str]] | None
) = None
@@ -80,6 +83,19 @@ class MessageSender:
self._send_raw_message_call = call
return call
+ def send_ex_method(
+ self, call: Callable[[Any, str, Message], str]
+ ) -> Callable[[Any, str, Message], str]:
+ """Function decorator for extended send method.
+
+ Version of send_method which is also is passed the original
+ unencoded message; can be useful for cases where metadata is sent
+ along with messages referring to their payloads/etc.
+ """
+ assert self._send_raw_message_ex_call is None
+ self._send_raw_message_ex_call = call
+ return call
+
def send_async_method(
self, call: Callable[[Any, str], Awaitable[str]]
) -> Callable[[Any, str], Awaitable[str]]:
@@ -200,14 +216,23 @@ class MessageSender:
for when message sending and response handling need to happen
in different contexts/threads.
"""
- if self._send_raw_message_call is None:
+ if (
+ self._send_raw_message_call is None
+ and self._send_raw_message_ex_call is None
+ ):
raise RuntimeError('send() is unimplemented for this type.')
msg_encoded = self._encode_message(bound_obj, message)
try:
- response_encoded = self._send_raw_message_call(
- bound_obj, msg_encoded
- )
+ if self._send_raw_message_ex_call is not None:
+ response_encoded = self._send_raw_message_ex_call(
+ bound_obj, msg_encoded, message
+ )
+ else:
+ assert self._send_raw_message_call is not None
+ response_encoded = self._send_raw_message_call(
+ bound_obj, msg_encoded
+ )
except Exception as exc:
response = ErrorSysResponse(
error_message='Error in MessageSender @send_method.',
diff --git a/tools/efro/rpc.py b/tools/efro/rpc.py
index 11ac1535..9acfab04 100644
--- a/tools/efro/rpc.py
+++ b/tools/efro/rpc.py
@@ -9,6 +9,7 @@ import asyncio
import logging
import weakref
from enum import Enum
+from functools import partial
from collections import deque
from dataclasses import dataclass
from threading import current_thread
@@ -84,7 +85,6 @@ def ssl_stream_writer_underlying_transport_info(
def ssl_stream_writer_force_close_check(writer: asyncio.StreamWriter) -> None:
"""Ensure a writer is closed; hacky workaround for odd hang."""
- from efro.call import tpartial
from threading import Thread
# Disabling for now..
@@ -100,9 +100,8 @@ def ssl_stream_writer_force_close_check(writer: asyncio.StreamWriter) -> None:
raw_transport = getattr(sslproto, '_transport', None)
if raw_transport is not None:
Thread(
- target=tpartial(
- _do_writer_force_close_check,
- weakref.ref(raw_transport),
+ target=partial(
+ _do_writer_force_close_check, weakref.ref(raw_transport)
),
daemon=True,
).start()
diff --git a/tools/efro/terminal.py b/tools/efro/terminal.py
index 6d8cd745..569eebb1 100644
--- a/tools/efro/terminal.py
+++ b/tools/efro/terminal.py
@@ -72,6 +72,7 @@ def _default_color_enabled() -> bool:
import platform
# If our stdout is not attached to a terminal, go with no-color.
+ assert sys.__stdout__ is not None
if not sys.__stdout__.isatty():
return False
diff --git a/tools/efro/util.py b/tools/efro/util.py
index d16c1a8c..a771cb4e 100644
--- a/tools/efro/util.py
+++ b/tools/efro/util.py
@@ -8,7 +8,6 @@ import os
import time
import weakref
import datetime
-import functools
from enum import Enum
from typing import TYPE_CHECKING, cast, TypeVar, Generic, overload
@@ -16,8 +15,6 @@ if TYPE_CHECKING:
import asyncio
from typing import Any, Callable, Literal
- from efro.call import Call as Call # 'as Call' so we re-export.
-
T = TypeVar('T')
ValT = TypeVar('ValT')
ArgT = TypeVar('ArgT')
@@ -36,13 +33,6 @@ _g_empty_weak_ref = weakref.ref(_EmptyObj())
assert _g_empty_weak_ref() is None
-# TODO: kill this and just use efro.call.tpartial
-if TYPE_CHECKING:
- Call = Call
-else:
- Call = functools.partial
-
-
def explicit_bool(val: bool) -> bool:
"""Return a non-inferable boolean value.
diff --git a/tools/efrotools/pybuild.py b/tools/efrotools/pybuild.py
index f6e1ca45..70dfbcfe 100644
--- a/tools/efrotools/pybuild.py
+++ b/tools/efrotools/pybuild.py
@@ -14,7 +14,7 @@ from efrotools.util import readfile, writefile, replace_exact
# Python version we build here (not necessarily same as we use in repo).
PY_VER_ANDROID = '3.12'
-PY_VER_EXACT_ANDROID = '3.12.3'
+PY_VER_EXACT_ANDROID = '3.12.4'
PY_VER_APPLE = '3.12'
PY_VER_EXACT_APPLE = '3.12.0'
@@ -38,7 +38,7 @@ VERSION_MIN_TVOS = '9.0'
#
# For now will try to ride out this 3.0 LTS version as long as possible.
OPENSSL_VER_APPLE = '3.0.12-1'
-OPENSSL_VER_ANDROID = '3.0.13'
+OPENSSL_VER_ANDROID = '3.0.14'
LIBFFI_VER_APPLE = '3.4.4-1'
BZIP2_VER_APPLE = '1.0.8-1'
@@ -46,15 +46,16 @@ XZ_VER_APPLE = '5.4.4-1'
# Android repo doesn't seem to be getting updated much so manually
# bumping various versions to keep things up to date.
+ANDROID_API_VER = 23
ZLIB_VER_ANDROID = '1.3.1'
-XZ_VER_ANDROID = '5.4.4'
+XZ_VER_ANDROID = '5.6.2'
BZIP2_VER_ANDROID = '1.0.8'
GDBM_VER_ANDROID = '1.23'
LIBFFI_VER_ANDROID = '3.4.6'
LIBUUID_VER_ANDROID = ('2.39', '2.39.3')
NCURSES_VER_ANDROID = '6.4'
READLINE_VER_ANDROID = '8.2'
-SQLITE_VER_ANDROID = ('2024', '3450200')
+SQLITE_VER_ANDROID = ('2024', '3460000')
# Filenames we prune from Python lib dirs in source repo to cut down on
# size.
@@ -349,7 +350,7 @@ def build_android(rootdir: str, arch: str, debug: bool = False) -> None:
# Set specific OpenSSL version.
ftxt = replace_exact(
ftxt,
- "source = 'https://www.openssl.org/source/openssl-3.0.7.tar.gz'",
+ "source = 'https://www.openssl.org/source/openssl-3.0.12.tar.gz'",
f"source = 'https://www.openssl.org/"
f"source/openssl-{OPENSSL_VER_ANDROID}.tar.gz'",
count=1,
@@ -358,7 +359,7 @@ def build_android(rootdir: str, arch: str, debug: bool = False) -> None:
# Set specific ZLib version.
ftxt = replace_exact(
ftxt,
- "source = 'https://www.zlib.net/zlib-1.2.13.tar.gz'",
+ "source = 'https://www.zlib.net/zlib-1.3.1.tar.gz'",
f"source = 'https://www.zlib.net/zlib-{ZLIB_VER_ANDROID}.tar.gz'",
count=1,
)
@@ -366,7 +367,7 @@ def build_android(rootdir: str, arch: str, debug: bool = False) -> None:
# Set specific XZ version.
ftxt = replace_exact(
ftxt,
- "source = 'https://tukaani.org/xz/xz-5.2.7.tar.xz'",
+ "source = 'https://tukaani.org/xz/xz-5.6.2.tar.xz'",
f"source = 'https://tukaani.org/xz/xz-{XZ_VER_ANDROID}.tar.xz'",
count=1,
)
@@ -402,7 +403,7 @@ def build_android(rootdir: str, arch: str, debug: bool = False) -> None:
ftxt = replace_exact(
ftxt,
"source = 'https://mirrors.edge.kernel.org/pub/linux/utils/"
- "util-linux/v2.38/util-linux-2.38.1.tar.xz'",
+ "util-linux/v2.39/util-linux-2.39.2.tar.xz'",
"source = 'https://mirrors.edge.kernel.org/pub/linux/utils/"
f'util-linux/v{LIBUUID_VER_ANDROID[0]}/'
f"util-linux-{LIBUUID_VER_ANDROID[1]}.tar.xz'",
@@ -412,7 +413,7 @@ def build_android(rootdir: str, arch: str, debug: bool = False) -> None:
# Set specific NCurses version.
ftxt = replace_exact(
ftxt,
- "source = 'https://ftp.gnu.org/gnu/ncurses/ncurses-6.3.tar.gz'",
+ "source = 'https://ftp.gnu.org/gnu/ncurses/ncurses-6.4.tar.gz'",
"source = 'https://ftp.gnu.org/gnu/ncurses/"
f"ncurses-{NCURSES_VER_ANDROID}.tar.gz'",
count=1,
@@ -430,7 +431,7 @@ def build_android(rootdir: str, arch: str, debug: bool = False) -> None:
# Set specific SQLite version.
ftxt = replace_exact(
ftxt,
- "source = 'https://sqlite.org/2022/sqlite-autoconf-3390400.tar.gz'",
+ "source = 'https://sqlite.org/2024/sqlite-autoconf-3460000.tar.gz'",
"source = 'https://sqlite.org/"
f'{SQLITE_VER_ANDROID[0]}/'
f"sqlite-autoconf-{SQLITE_VER_ANDROID[1]}.tar.gz'",
@@ -449,12 +450,29 @@ def build_android(rootdir: str, arch: str, debug: bool = False) -> None:
writefile('Android/build_deps.py', ftxt)
+ ftxt = readfile('Android/util.py')
+
+ ftxt = replace_exact(
+ ftxt,
+ "choices=range(30, 40), dest='android_api_level'",
+ "choices=range(23, 40), dest='android_api_level'",
+ )
+ writefile('Android/util.py', ftxt)
+
# Tweak some things in the base build script; grab the right version
# of Python and also inject some code to modify bits of python
# after it is extracted.
ftxt = readfile('build.sh')
- ftxt = replace_exact(ftxt, 'PYVER=3.11.0', f'PYVER={PY_VER_EXACT_ANDROID}')
+ # Repo has gone 30+, but we currently want our own which is lower.
+ ftxt = replace_exact(
+ ftxt,
+ 'COMMON_ARGS="--arch ${ARCH:-arm} --api ${ANDROID_API:-30}"',
+ 'COMMON_ARGS="--arch ${ARCH:-arm} --api ${ANDROID_API:-'
+ + str(ANDROID_API_VER)
+ + '}"',
+ )
+ ftxt = replace_exact(ftxt, 'PYVER=3.12.4', f'PYVER={PY_VER_EXACT_ANDROID}')
ftxt = replace_exact(
ftxt,
' popd\n',