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