From 2a66b5b93f3cba45bd408cae1cafe635ea7a54ad Mon Sep 17 00:00:00 2001
From: Eric Froemling
Date: Tue, 26 Jan 2021 05:00:34 -0800
Subject: [PATCH] Initial 1.6 builds with private party front-end in place
---
.efrocachemap | 86 +-
.idea/dictionaries/ericf.xml | 23 +
CHANGELOG.md | 9 +-
assets/.asset_manifest_public.json | 6 +-
assets/Makefile | 6 +-
assets/src/ba_data/python/_ba.py | 118 ++-
assets/src/ba_data/python/ba/__init__.py | 20 +-
assets/src/ba_data/python/ba/_actor.py | 2 +-
assets/src/ba_data/python/ba/_appconfig.py | 2 +-
assets/src/ba_data/python/ba/_general.py | 31 +-
assets/src/ba_data/python/ba/_music.py | 6 +-
assets/src/ba_data/python/ba/_player.py | 4 +-
assets/src/ba_data/python/ba/_session.py | 2 +-
assets/src/ba_data/python/ba/_team.py | 4 +-
assets/src/ba_data/python/ba/_ui.py | 6 +-
assets/src/ba_data/python/ba/ui/__init__.py | 2 +-
.../python/bastd/ui/account/settings.py | 4 +-
.../ba_data/python/bastd/ui/coop/browser.py | 6 +-
.../python/bastd/ui/gather/__init__.py | 77 +-
.../python/bastd/ui/gather/abouttab.py | 2 +-
.../ba_data/python/bastd/ui/gather/bases.py | 52 --
.../python/bastd/ui/gather/googleplaytab.py | 86 --
.../python/bastd/ui/gather/manualtab.py | 7 +-
.../python/bastd/ui/gather/nearbytab.py | 2 +-
.../python/bastd/ui/gather/privatetab.py | 755 ++++++++++++++++++
.../python/bastd/ui/gather/publictab.py | 43 +-
assets/src/ba_data/python/bastd/ui/kiosk.py | 4 +-
assets/src/ba_data/python/bastd/ui/play.py | 4 +-
.../python/bastd/ui/playlist/browser.py | 4 +-
.../python/bastd/ui/profile/browser.py | 4 +-
.../python/bastd/ui/settings/advanced.py | 6 +-
.../python/bastd/ui/settings/allsettings.py | 6 +-
.../ba_data/python/bastd/ui/settings/audio.py | 4 +-
.../python/bastd/ui/settings/controls.py | 4 +-
.../python/bastd/ui/soundtrack/browser.py | 4 +-
.../ba_data/python/bastd/ui/store/browser.py | 4 +-
.../python/bastd/ui/teamnamescolors.py | 34 +-
assets/src/ba_data/python/bastd/ui/watch.py | 6 +-
.../.idea/dictionaries/ericf.xml | 23 +
config/config.json | 3 +-
docs/ba_module.md | 223 +++---
src/ballistica/ballistica.cc | 4 +-
src/ballistica/graphics/graphics.h | 3 -
src/ballistica/graphics/graphics_server.cc | 4 +-
src/ballistica/input/input.cc | 11 +
src/ballistica/platform/platform.cc | 84 ++
src/ballistica/platform/platform.h | 27 +-
.../python/class/python_class_timer.cc | 31 +-
.../python/methods/python_methods_app.h | 1 +
.../python/methods/python_methods_gameplay.h | 1 +
.../python/methods/python_methods_graphics.h | 1 +
.../python/methods/python_methods_system.cc | 78 ++
.../python/methods/python_methods_ui.cc | 14 +-
src/ballistica/ui/ui.cc | 2 +-
src/ballistica/ui/widget/container_widget.cc | 4 +-
src/ballistica/ui/widget/text_widget.cc | 77 +-
src/ballistica/ui/widget/text_widget.h | 3 +
src/ballistica/ui/widget/widget.h | 3 +-
tools/batools/build.py | 4 +-
tools/efro/terminal.py | 2 +-
tools/efrotools/code.py | 10 +-
61 files changed, 1531 insertions(+), 527 deletions(-)
delete mode 100644 assets/src/ba_data/python/bastd/ui/gather/bases.py
delete mode 100644 assets/src/ba_data/python/bastd/ui/gather/googleplaytab.py
create mode 100644 assets/src/ba_data/python/bastd/ui/gather/privatetab.py
diff --git a/.efrocachemap b/.efrocachemap
index 0ac35110..0b882640 100644
--- a/.efrocachemap
+++ b/.efrocachemap
@@ -420,7 +420,7 @@
"assets/build/ba_data/audio/zoeOw.ogg": "https://files.ballistica.net/cache/ba1/a9/71/9286d55c45c37877f3267850f90b",
"assets/build/ba_data/audio/zoePickup01.ogg": "https://files.ballistica.net/cache/ba1/2f/09/36e691de67eb8f155449a7170861",
"assets/build/ba_data/audio/zoeScream01.ogg": "https://files.ballistica.net/cache/ba1/fd/a8/ad50785ce206e8dc3dcc7358b173",
- "assets/build/ba_data/data/langdata.json": "https://files.ballistica.net/cache/ba1/34/93/4989950b5dd035da056a0291dbb2",
+ "assets/build/ba_data/data/langdata.json": "https://files.ballistica.net/cache/ba1/c2/36/59ab3af6b45307c79ba8c296df5b",
"assets/build/ba_data/data/languages/arabic.json": "https://files.ballistica.net/cache/ba1/0d/25/26de912f111de8189f40aeeddee6",
"assets/build/ba_data/data/languages/belarussian.json": "https://files.ballistica.net/cache/ba1/44/ed/5b972fa848cffb73723533c2ccb7",
"assets/build/ba_data/data/languages/chinese.json": "https://files.ballistica.net/cache/ba1/6a/20/57d91c24e0aeb1fe6b7d6dbbcac9",
@@ -429,27 +429,27 @@
"assets/build/ba_data/data/languages/czech.json": "https://files.ballistica.net/cache/ba1/ce/de/b0f462205cdf687a875abc6f39bd",
"assets/build/ba_data/data/languages/danish.json": "https://files.ballistica.net/cache/ba1/3f/46/e4da3c1d2b0ebf916df55c608b28",
"assets/build/ba_data/data/languages/dutch.json": "https://files.ballistica.net/cache/ba1/d1/07/37b7adc3dbec7328d26c5325f212",
- "assets/build/ba_data/data/languages/english.json": "https://files.ballistica.net/cache/ba1/3f/ee/72767c1e922d3f2cf668147ef143",
+ "assets/build/ba_data/data/languages/english.json": "https://files.ballistica.net/cache/ba1/66/d1/8adfe1479fe6b4c30cd0d0e694d6",
"assets/build/ba_data/data/languages/esperanto.json": "https://files.ballistica.net/cache/ba1/6e/fd/685a4e1da031474d47a1d9eb2731",
"assets/build/ba_data/data/languages/french.json": "https://files.ballistica.net/cache/ba1/18/b2/9c1f6e3ca6e18d6a6fdbdb14e224",
"assets/build/ba_data/data/languages/german.json": "https://files.ballistica.net/cache/ba1/19/ba/b12493cfaa28d27f9bfee0459e20",
- "assets/build/ba_data/data/languages/gibberish.json": "https://files.ballistica.net/cache/ba1/82/9f/bdcff3ac022c125a7232a412568e",
+ "assets/build/ba_data/data/languages/gibberish.json": "https://files.ballistica.net/cache/ba1/43/f3/9e88a199337b7913cb5e7961b1c6",
"assets/build/ba_data/data/languages/greek.json": "https://files.ballistica.net/cache/ba1/51/31/64479524c0ee990b3e97ffdca068",
"assets/build/ba_data/data/languages/hindi.json": "https://files.ballistica.net/cache/ba1/ed/98/37d9457755f7e86e2f2875e3b055",
"assets/build/ba_data/data/languages/hungarian.json": "https://files.ballistica.net/cache/ba1/87/2d/027aa239eb66ea8f496562f4fd83",
"assets/build/ba_data/data/languages/indonesian.json": "https://files.ballistica.net/cache/ba1/a4/01/1fcc28b303858b3d028d26516907",
- "assets/build/ba_data/data/languages/italian.json": "https://files.ballistica.net/cache/ba1/20/ca/d675783cd094030a625e7ce023cf",
+ "assets/build/ba_data/data/languages/italian.json": "https://files.ballistica.net/cache/ba1/6b/5a/6e8e3692347d9ba01aff607af5a8",
"assets/build/ba_data/data/languages/korean.json": "https://files.ballistica.net/cache/ba1/0a/84/bbb6ed2abf66509406f534cbbb52",
"assets/build/ba_data/data/languages/persian.json": "https://files.ballistica.net/cache/ba1/f8/e6/773d3da1cbdb2215956f9d99c348",
"assets/build/ba_data/data/languages/polish.json": "https://files.ballistica.net/cache/ba1/29/72/bcf75316f71373a47739a72ad6da",
- "assets/build/ba_data/data/languages/portuguese.json": "https://files.ballistica.net/cache/ba1/3d/ba/7f4c8846babc5ef59187e87efa3d",
+ "assets/build/ba_data/data/languages/portuguese.json": "https://files.ballistica.net/cache/ba1/29/7d/be03fa23b8d80404b1b56c305edc",
"assets/build/ba_data/data/languages/romanian.json": "https://files.ballistica.net/cache/ba1/44/3c/7cc06ca8d5475e1687d0ed05bdbf",
"assets/build/ba_data/data/languages/russian.json": "https://files.ballistica.net/cache/ba1/07/42/382831beefb1f134eed95d18b20b",
"assets/build/ba_data/data/languages/serbian.json": "https://files.ballistica.net/cache/ba1/05/5b/910b8963f48cd494dc01d5a18a5c",
"assets/build/ba_data/data/languages/slovak.json": "https://files.ballistica.net/cache/ba1/b7/0a/fab820b96e7aa587ee56427ecdc2",
- "assets/build/ba_data/data/languages/spanish.json": "https://files.ballistica.net/cache/ba1/58/3b/2bdbad7748ab0c76b9cf3d65c70e",
+ "assets/build/ba_data/data/languages/spanish.json": "https://files.ballistica.net/cache/ba1/e0/83/32c5bc6544b647dbc599c11123df",
"assets/build/ba_data/data/languages/swedish.json": "https://files.ballistica.net/cache/ba1/50/9f/be006ba19be6a69a57837eb6dca0",
- "assets/build/ba_data/data/languages/turkish.json": "https://files.ballistica.net/cache/ba1/19/fe/c97df315575d999ad2bb63478b5f",
+ "assets/build/ba_data/data/languages/turkish.json": "https://files.ballistica.net/cache/ba1/2d/a3/114ca23a3fb0f2885bfbbabb9727",
"assets/build/ba_data/data/languages/ukrainian.json": "https://files.ballistica.net/cache/ba1/66/b0/e1d71e57673a6fc78e0ea181b76b",
"assets/build/ba_data/data/languages/venetian.json": "https://files.ballistica.net/cache/ba1/e4/3e/243eaa0237361b984fc6c56042be",
"assets/build/ba_data/data/languages/vietnamese.json": "https://files.ballistica.net/cache/ba1/04/52/683a27aaf9aa7c63e7e595f80d08",
@@ -3932,40 +3932,40 @@
"assets/build/windows/Win32/ucrtbased.dll": "https://files.ballistica.net/cache/ba1/b5/85/f8b6d0558ddb87267f34254b1450",
"assets/build/windows/Win32/vc_redist.x86.exe": "https://files.ballistica.net/cache/ba1/1c/e1/4a1a2eddda2f4aebd5f8b64ab08e",
"assets/build/windows/Win32/vcruntime140d.dll": "https://files.ballistica.net/cache/ba1/50/8d/bc2600ac9491f1b14d659709451f",
- "build/prefab/full/linux_arm64/debug/ballisticacore": "https://files.ballistica.net/cache/ba1/1c/5f/2e5338ab577aa7dac9942dda3d43",
- "build/prefab/full/linux_arm64/release/ballisticacore": "https://files.ballistica.net/cache/ba1/50/bb/7a9daf22e3a09c6ab40f6b6909f2",
- "build/prefab/full/linux_arm64_server/debug/dist/ballisticacore_headless": "https://files.ballistica.net/cache/ba1/e5/6f/10a4fc55ee08a4f6e1d8f3b87422",
- "build/prefab/full/linux_arm64_server/release/dist/ballisticacore_headless": "https://files.ballistica.net/cache/ba1/f3/fa/ba9a4fd942854e6d330079a5d5b2",
- "build/prefab/full/linux_x86_64/debug/ballisticacore": "https://files.ballistica.net/cache/ba1/68/5d/09e80048115eaaa434ffe85a7f8f",
- "build/prefab/full/linux_x86_64/release/ballisticacore": "https://files.ballistica.net/cache/ba1/60/0e/804d130b536483882ee3f4299375",
- "build/prefab/full/linux_x86_64_server/debug/dist/ballisticacore_headless": "https://files.ballistica.net/cache/ba1/73/15/f45a3b15110a0cc0493ef17fae4c",
- "build/prefab/full/linux_x86_64_server/release/dist/ballisticacore_headless": "https://files.ballistica.net/cache/ba1/55/e7/618d3ecd8072a67d07b1c1098369",
- "build/prefab/full/mac_arm64/debug/ballisticacore": "https://files.ballistica.net/cache/ba1/54/31/bce32b4e1aef2b4bd27dc9336619",
- "build/prefab/full/mac_arm64/release/ballisticacore": "https://files.ballistica.net/cache/ba1/bf/5a/45640c9cdda02fdffd1c9b3beebe",
- "build/prefab/full/mac_arm64_server/debug/dist/ballisticacore_headless": "https://files.ballistica.net/cache/ba1/ed/ab/1d70d046b60626e74ab6a6d8d733",
- "build/prefab/full/mac_arm64_server/release/dist/ballisticacore_headless": "https://files.ballistica.net/cache/ba1/cb/fb/135c187787329c51b6770a5f5135",
- "build/prefab/full/mac_x86_64/debug/ballisticacore": "https://files.ballistica.net/cache/ba1/1a/1d/fb869851bf6e54e215d1447d1b68",
- "build/prefab/full/mac_x86_64/release/ballisticacore": "https://files.ballistica.net/cache/ba1/64/99/58dfc239e6fdac005d583287507f",
- "build/prefab/full/mac_x86_64_server/debug/dist/ballisticacore_headless": "https://files.ballistica.net/cache/ba1/00/a6/2727ba6b98c46e4ab1414be214a7",
- "build/prefab/full/mac_x86_64_server/release/dist/ballisticacore_headless": "https://files.ballistica.net/cache/ba1/a9/47/954ea39895cad0354cd1b39e4436",
- "build/prefab/full/windows_x86/debug/BallisticaCore.exe": "https://files.ballistica.net/cache/ba1/ac/62/9c8551a51d8f458e59e856cd4a3a",
- "build/prefab/full/windows_x86/release/BallisticaCore.exe": "https://files.ballistica.net/cache/ba1/75/e9/9674fac783d2827e5daf33b91afc",
- "build/prefab/full/windows_x86_server/debug/dist/ballisticacore_headless.exe": "https://files.ballistica.net/cache/ba1/f2/46/c17af441ceb12cd41895c8482475",
- "build/prefab/full/windows_x86_server/release/dist/ballisticacore_headless.exe": "https://files.ballistica.net/cache/ba1/c8/16/000156a1d2728de92b7bcdaa1f22",
- "build/prefab/lib/linux_arm64/debug/libballisticacore_internal.a": "https://files.ballistica.net/cache/ba1/65/e2/82162e0ab11caf2634692d2d7a4e",
- "build/prefab/lib/linux_arm64/release/libballisticacore_internal.a": "https://files.ballistica.net/cache/ba1/6f/4d/baa3f14360c0d379ccb5ae330295",
- "build/prefab/lib/linux_arm64_server/debug/libballisticacore_internal.a": "https://files.ballistica.net/cache/ba1/c9/4a/167038e8603b915bc6bdebc886a5",
- "build/prefab/lib/linux_arm64_server/release/libballisticacore_internal.a": "https://files.ballistica.net/cache/ba1/fa/a3/3f9a8d8856f9db3340adc5488d9b",
- "build/prefab/lib/linux_x86_64/debug/libballisticacore_internal.a": "https://files.ballistica.net/cache/ba1/ef/d0/49b99ce32e5e2b01b056fbac5c67",
- "build/prefab/lib/linux_x86_64/release/libballisticacore_internal.a": "https://files.ballistica.net/cache/ba1/83/25/980050d75bbea49a84652209050c",
- "build/prefab/lib/linux_x86_64_server/debug/libballisticacore_internal.a": "https://files.ballistica.net/cache/ba1/47/aa/e82233695a50974e7e22db4e7146",
- "build/prefab/lib/linux_x86_64_server/release/libballisticacore_internal.a": "https://files.ballistica.net/cache/ba1/2b/45/7f9fbae208890455fce2fbc172d3",
- "build/prefab/lib/mac_arm64/debug/libballisticacore_internal.a": "https://files.ballistica.net/cache/ba1/fc/75/8ea8b8eeb9a1c47c534bfb9e5d3f",
- "build/prefab/lib/mac_arm64/release/libballisticacore_internal.a": "https://files.ballistica.net/cache/ba1/69/a9/01af1b4a126cf517e9bbbfade412",
- "build/prefab/lib/mac_arm64_server/debug/libballisticacore_internal.a": "https://files.ballistica.net/cache/ba1/d5/f5/7c740da86b84653a3d7b23cb11d7",
- "build/prefab/lib/mac_arm64_server/release/libballisticacore_internal.a": "https://files.ballistica.net/cache/ba1/47/64/f4a9fb9a0c338dd0daa0dc0e52a6",
- "build/prefab/lib/mac_x86_64/debug/libballisticacore_internal.a": "https://files.ballistica.net/cache/ba1/74/66/c94da5b860d0581b20c9679d183f",
- "build/prefab/lib/mac_x86_64/release/libballisticacore_internal.a": "https://files.ballistica.net/cache/ba1/0f/c1/90db918c2dce94bd94725b25b2b4",
- "build/prefab/lib/mac_x86_64_server/debug/libballisticacore_internal.a": "https://files.ballistica.net/cache/ba1/0f/49/4269f4e88a55e6712841b7b56f5a",
- "build/prefab/lib/mac_x86_64_server/release/libballisticacore_internal.a": "https://files.ballistica.net/cache/ba1/f7/6c/fe17cf1a3f98cacbe946549631c5"
+ "build/prefab/full/linux_arm64/debug/ballisticacore": "https://files.ballistica.net/cache/ba1/3c/61/69d59f46d61b3aeee054aa5daab1",
+ "build/prefab/full/linux_arm64/release/ballisticacore": "https://files.ballistica.net/cache/ba1/d2/41/e8362cfe9f6fd6dc882858021874",
+ "build/prefab/full/linux_arm64_server/debug/dist/ballisticacore_headless": "https://files.ballistica.net/cache/ba1/83/ff/29cf07587f212d5278bb5ed92e75",
+ "build/prefab/full/linux_arm64_server/release/dist/ballisticacore_headless": "https://files.ballistica.net/cache/ba1/90/e7/c4834a3b41d8f9d837071dc0800a",
+ "build/prefab/full/linux_x86_64/debug/ballisticacore": "https://files.ballistica.net/cache/ba1/ce/3d/0febcd4db42fe5dedb701c60bddf",
+ "build/prefab/full/linux_x86_64/release/ballisticacore": "https://files.ballistica.net/cache/ba1/c5/d8/3d7ca6668af72200a6eea00c6d84",
+ "build/prefab/full/linux_x86_64_server/debug/dist/ballisticacore_headless": "https://files.ballistica.net/cache/ba1/79/f2/3381ea14e11854a9e8804953bb06",
+ "build/prefab/full/linux_x86_64_server/release/dist/ballisticacore_headless": "https://files.ballistica.net/cache/ba1/80/00/a02fdfe67796bc04a78dbd6ed353",
+ "build/prefab/full/mac_arm64/debug/ballisticacore": "https://files.ballistica.net/cache/ba1/19/36/ebf5f0f1c7a2923f7e3c4ee292ad",
+ "build/prefab/full/mac_arm64/release/ballisticacore": "https://files.ballistica.net/cache/ba1/fb/4f/fbddddae2d5963a1ec6f97234db3",
+ "build/prefab/full/mac_arm64_server/debug/dist/ballisticacore_headless": "https://files.ballistica.net/cache/ba1/0d/32/8755d570c2e74316497bdd290f0d",
+ "build/prefab/full/mac_arm64_server/release/dist/ballisticacore_headless": "https://files.ballistica.net/cache/ba1/24/30/863f0c9948669219f66b117502bc",
+ "build/prefab/full/mac_x86_64/debug/ballisticacore": "https://files.ballistica.net/cache/ba1/3c/dc/317d57caa8b27d5b685193cf3de9",
+ "build/prefab/full/mac_x86_64/release/ballisticacore": "https://files.ballistica.net/cache/ba1/cd/8a/3fc0cca1383b6af2a4f8a5ae2cfe",
+ "build/prefab/full/mac_x86_64_server/debug/dist/ballisticacore_headless": "https://files.ballistica.net/cache/ba1/20/16/c0cf342b1971dcbc757abd29bdc5",
+ "build/prefab/full/mac_x86_64_server/release/dist/ballisticacore_headless": "https://files.ballistica.net/cache/ba1/1b/06/f81485b10ac37b58a012bc158952",
+ "build/prefab/full/windows_x86/debug/BallisticaCore.exe": "https://files.ballistica.net/cache/ba1/85/44/92172591b72302dc4909b0e91108",
+ "build/prefab/full/windows_x86/release/BallisticaCore.exe": "https://files.ballistica.net/cache/ba1/d4/e6/8d5a9ffcf32588f8b770a88a80d5",
+ "build/prefab/full/windows_x86_server/debug/dist/ballisticacore_headless.exe": "https://files.ballistica.net/cache/ba1/c7/9c/e55d58caf88ad6bb88f1a5ebfdbf",
+ "build/prefab/full/windows_x86_server/release/dist/ballisticacore_headless.exe": "https://files.ballistica.net/cache/ba1/f2/6e/981553869590e8367854b5df2802",
+ "build/prefab/lib/linux_arm64/debug/libballisticacore_internal.a": "https://files.ballistica.net/cache/ba1/34/21/016123d9ec8293ce92ba910dd00a",
+ "build/prefab/lib/linux_arm64/release/libballisticacore_internal.a": "https://files.ballistica.net/cache/ba1/6f/f8/cf46e7c33a0a237e2c6f19ef4f92",
+ "build/prefab/lib/linux_arm64_server/debug/libballisticacore_internal.a": "https://files.ballistica.net/cache/ba1/10/67/5a3d6131d1b1fdf9f629301b68db",
+ "build/prefab/lib/linux_arm64_server/release/libballisticacore_internal.a": "https://files.ballistica.net/cache/ba1/6b/bc/72d32c36d51acb255b2667c4d386",
+ "build/prefab/lib/linux_x86_64/debug/libballisticacore_internal.a": "https://files.ballistica.net/cache/ba1/7c/9b/af5e799cb4d296074598f11a063f",
+ "build/prefab/lib/linux_x86_64/release/libballisticacore_internal.a": "https://files.ballistica.net/cache/ba1/35/7c/b5f26ca01907df6ea80ff3ae5861",
+ "build/prefab/lib/linux_x86_64_server/debug/libballisticacore_internal.a": "https://files.ballistica.net/cache/ba1/8c/76/9ea770a68773fd4a08fb78855fbb",
+ "build/prefab/lib/linux_x86_64_server/release/libballisticacore_internal.a": "https://files.ballistica.net/cache/ba1/1d/b3/329956f15f5cb76a63bd4fd3e0cc",
+ "build/prefab/lib/mac_arm64/debug/libballisticacore_internal.a": "https://files.ballistica.net/cache/ba1/30/04/7b7c4f847fd992b23719f755981a",
+ "build/prefab/lib/mac_arm64/release/libballisticacore_internal.a": "https://files.ballistica.net/cache/ba1/bc/20/e13686c62052e3e388431d4859aa",
+ "build/prefab/lib/mac_arm64_server/debug/libballisticacore_internal.a": "https://files.ballistica.net/cache/ba1/a3/a1/1bb8a1926628e34054aeca5a0178",
+ "build/prefab/lib/mac_arm64_server/release/libballisticacore_internal.a": "https://files.ballistica.net/cache/ba1/5a/33/c232d2c633bf70915e1a8eecdf95",
+ "build/prefab/lib/mac_x86_64/debug/libballisticacore_internal.a": "https://files.ballistica.net/cache/ba1/84/8c/2930553d642210c81b6342bffb9f",
+ "build/prefab/lib/mac_x86_64/release/libballisticacore_internal.a": "https://files.ballistica.net/cache/ba1/68/22/cc580cef75b9e452de66095ba62b",
+ "build/prefab/lib/mac_x86_64_server/debug/libballisticacore_internal.a": "https://files.ballistica.net/cache/ba1/93/2d/b6482a7ce6957156fe050f4cc9dc",
+ "build/prefab/lib/mac_x86_64_server/release/libballisticacore_internal.a": "https://files.ballistica.net/cache/ba1/d1/a2/1d592f5d21dd39d7b16da8f1c8c4"
}
\ No newline at end of file
diff --git a/.idea/dictionaries/ericf.xml b/.idea/dictionaries/ericf.xml
index fd2e442c..f9fc5ba6 100644
--- a/.idea/dictionaries/ericf.xml
+++ b/.idea/dictionaries/ericf.xml
@@ -45,6 +45,7 @@
adbcfaca
adbpath
addcall
+ addchars
addgame
addlevel
addr
@@ -98,6 +99,7 @@
archs
argh
argparse
+ argsjoined
argtypes
argval
armeabi
@@ -146,6 +148,7 @@
autoretain
autoselect
autotools
+ availmins
availplug
aval
axismotion
@@ -244,6 +247,7 @@
bsuffix
bsui
btnh
+ btnlabel
btnv
btnx
btype
@@ -279,6 +283,7 @@
cameraflash
camerashake
campaignname
+ cancelbtn
capb
capturetheflag
carentity
@@ -286,6 +291,7 @@
cbits
cbot
cbtn
+ cbtnoffs
ccfgs
ccode
ccompiler
@@ -433,6 +439,7 @@
crashlytics
creationflags
creditslist
+ cresult
cryptmodule
cspbd
cspnf
@@ -782,6 +789,7 @@
freeforallendscreen
freeforallsession
freeforallvictory
+ freemins
freepik
freesound
froemling
@@ -966,6 +974,7 @@
homebrew
hometest
hostconfig
+ hostingstate
hostuser
hout
howtoplay
@@ -1201,6 +1210,7 @@
locationlist
locationsingles
locationval
+ lockpath
lockstr
locktype
locs
@@ -1417,6 +1427,7 @@
nosynctool
nosynctools
notdir
+ nowtickets
npos
nprocessors
ntpath
@@ -1439,6 +1450,8 @@
oculus
oenval
offsanchor
+ offsx
+ offsy
ofval
oggenc
oghash
@@ -1591,6 +1604,7 @@
powervr
ppos
pproxy
+ pptabcom
pragmas
prch
prec
@@ -1619,6 +1633,7 @@
printobjects
printpaths
priv
+ privatetab
proactor
proc
procs
@@ -1628,6 +1643,7 @@
profilenames
proj
projconfig
+ projdir
projectpath
projectroot
projroot
@@ -1751,6 +1767,7 @@
reqtype
reqtypes
resample
+ resetbtn
resetinput
resourcetypeinfo
respawn
@@ -1802,6 +1819,7 @@
samsung
sandboxing
sandyrb
+ savebtn
savebutton
saxutils
sbblk
@@ -1844,7 +1862,11 @@
sdkcheck
sdkutils
sdtk
+ selchild
selectmodule
+ selindex
+ selwidget
+ selwidgets
senze
seqtype
seqtypestr
@@ -2145,6 +2167,7 @@
this'll
threadtype
throwiness
+ ticon
timedisplay
timeformat
timemax
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 859bc4dc..b8e1cb68 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,7 +1,14 @@
-### 1.5.30 (20263)
+### 1.6.0 (20268)
+- Added private parties functionality (cloud hosted parties with associated codes making it easier to play with friends)
- The meta subsystem now enables new plugins by default in headless builds.
- Added option to save party in Manual tab
- Slight tidying on the tourney entry popup
+- Env var to override UI scale is now BA_UI_SCALE instead of BA_FORCE_UI_SCALE.
+- Fixed an issue where ba.storagename() could prevent objects on the stack from getting released cleanly
+- Improvements to documentation generation such as link to some external base types.
+- Added ba.clipboard_* functions for copying and pasting text on supported platforms.
+- Implemented clipboard functionality on SDL based builds (such as prefab)
+- Fixed an issue where click locations on scaled text fields could be incorrectly calculated.
### 1.5.29 (20246)
- Exposed ba method/class initing in public C++ layer.
diff --git a/assets/.asset_manifest_public.json b/assets/.asset_manifest_public.json
index 3243d27e..542c6d54 100644
--- a/assets/.asset_manifest_public.json
+++ b/assets/.asset_manifest_public.json
@@ -364,16 +364,14 @@
"ba_data/python/bastd/ui/gather/__init__.py",
"ba_data/python/bastd/ui/gather/__pycache__/__init__.cpython-38.opt-1.pyc",
"ba_data/python/bastd/ui/gather/__pycache__/abouttab.cpython-38.opt-1.pyc",
- "ba_data/python/bastd/ui/gather/__pycache__/bases.cpython-38.opt-1.pyc",
- "ba_data/python/bastd/ui/gather/__pycache__/googleplaytab.cpython-38.opt-1.pyc",
"ba_data/python/bastd/ui/gather/__pycache__/manualtab.cpython-38.opt-1.pyc",
"ba_data/python/bastd/ui/gather/__pycache__/nearbytab.cpython-38.opt-1.pyc",
+ "ba_data/python/bastd/ui/gather/__pycache__/privatetab.cpython-38.opt-1.pyc",
"ba_data/python/bastd/ui/gather/__pycache__/publictab.cpython-38.opt-1.pyc",
"ba_data/python/bastd/ui/gather/abouttab.py",
- "ba_data/python/bastd/ui/gather/bases.py",
- "ba_data/python/bastd/ui/gather/googleplaytab.py",
"ba_data/python/bastd/ui/gather/manualtab.py",
"ba_data/python/bastd/ui/gather/nearbytab.py",
+ "ba_data/python/bastd/ui/gather/privatetab.py",
"ba_data/python/bastd/ui/gather/publictab.py",
"ba_data/python/bastd/ui/getcurrency.py",
"ba_data/python/bastd/ui/getremote.py",
diff --git a/assets/Makefile b/assets/Makefile
index 7b8f4292..9abf9460 100644
--- a/assets/Makefile
+++ b/assets/Makefile
@@ -296,10 +296,9 @@ SCRIPT_TARGETS_PY_PUBLIC = \
build/ba_data/python/bastd/ui/fileselector.py \
build/ba_data/python/bastd/ui/gather/__init__.py \
build/ba_data/python/bastd/ui/gather/abouttab.py \
- build/ba_data/python/bastd/ui/gather/bases.py \
- build/ba_data/python/bastd/ui/gather/googleplaytab.py \
build/ba_data/python/bastd/ui/gather/manualtab.py \
build/ba_data/python/bastd/ui/gather/nearbytab.py \
+ build/ba_data/python/bastd/ui/gather/privatetab.py \
build/ba_data/python/bastd/ui/gather/publictab.py \
build/ba_data/python/bastd/ui/getcurrency.py \
build/ba_data/python/bastd/ui/getremote.py \
@@ -541,10 +540,9 @@ SCRIPT_TARGETS_PYC_PUBLIC = \
build/ba_data/python/bastd/ui/__pycache__/fileselector.cpython-38.opt-1.pyc \
build/ba_data/python/bastd/ui/gather/__pycache__/__init__.cpython-38.opt-1.pyc \
build/ba_data/python/bastd/ui/gather/__pycache__/abouttab.cpython-38.opt-1.pyc \
- build/ba_data/python/bastd/ui/gather/__pycache__/bases.cpython-38.opt-1.pyc \
- build/ba_data/python/bastd/ui/gather/__pycache__/googleplaytab.cpython-38.opt-1.pyc \
build/ba_data/python/bastd/ui/gather/__pycache__/manualtab.cpython-38.opt-1.pyc \
build/ba_data/python/bastd/ui/gather/__pycache__/nearbytab.cpython-38.opt-1.pyc \
+ build/ba_data/python/bastd/ui/gather/__pycache__/privatetab.cpython-38.opt-1.pyc \
build/ba_data/python/bastd/ui/gather/__pycache__/publictab.cpython-38.opt-1.pyc \
build/ba_data/python/bastd/ui/__pycache__/getcurrency.cpython-38.opt-1.pyc \
build/ba_data/python/bastd/ui/__pycache__/getremote.cpython-38.opt-1.pyc \
diff --git a/assets/src/ba_data/python/_ba.py b/assets/src/ba_data/python/_ba.py
index 9d614a65..363d5302 100644
--- a/assets/src/ba_data/python/_ba.py
+++ b/assets/src/ba_data/python/_ba.py
@@ -996,7 +996,7 @@ class Timer:
time: length of time (in seconds by default) that the timer will wait
before firing. Note that the actual delay experienced may vary
- depending on the timetype. (see below)
+ depending on the timetype. (see below)
call: A callable Python object. Note that the timer will retain a
strong reference to the callable for as long as it exists, so you
@@ -1006,28 +1006,11 @@ class Timer:
repeat: if True, the timer will fire repeatedly, with each successive
firing having the same delay as the first.
- timetype can be either 'sim', 'base', or 'real'. It defaults to
- 'sim'. Types are explained below:
+ timetype: A ba.TimeType value determining which timeline the timer is
+ placed onto.
- 'sim' time maps to local simulation time in ba.Activity or ba.Session
- Contexts. This means that it may progress slower in slow-motion play
- modes, stop when the game is paused, etc. This time type is not
- available in UI contexts.
-
- 'base' time is also linked to gameplay in ba.Activity or ba.Session
- Contexts, but it progresses at a constant rate regardless of
- slow-motion states or pausing. It can, however, slow down or stop
- in certain cases such as network outages or game slowdowns due to
- cpu load. Like 'sim' time, this is unavailable in UI contexts.
-
- 'real' time always maps to actual clock time with a bit of filtering
- added, regardless of Context. (the filtering prevents it from going
- backwards or jumping forward by large amounts due to the app being
- backgrounded, system time changing, etc.)
- Real time timers are currently only available in the UI context.
-
- the 'timeformat' arg defaults to SECONDS but can also be MILLISECONDS
- if you want to pass time as milliseconds.
+ timeformat: A ba.TimeFormat value determining how the passed time is
+ interpreted.
# Example: use a Timer object to print repeatedly for a few seconds:
def say_it():
@@ -1035,9 +1018,9 @@ class Timer:
def stop_saying_it():
self.t = None
ba.screenmessage('MUSHROOM MUSHROOM!')
- # create our timer; it will run as long as we hold self.t
+ # Create our timer; it will run as long as we have the self.t ref.
self.t = ba.Timer(0.3, say_it, repeat=True)
- # now fire off a one-shot timer to kill it
+ # Now fire off a one-shot timer to kill it.
ba.timer(3.89, stop_saying_it)
"""
@@ -1579,6 +1562,58 @@ def client_info_query_response(token: str, response: Any) -> None:
return None
+def clipboard_get_text() -> str:
+ """clipboard_get_text() -> str
+
+ Return text currently on the system clipboard.
+
+ Category: General Utility Functions
+
+ Ensure that ba.clipboard_has_text() returns True before calling
+ this function.
+ """
+ return str()
+
+
+def clipboard_has_text() -> bool:
+ """clipboard_has_text() -> bool
+
+ Return whether there is currently text on the clipboard.
+
+ Category: General Utility Functions
+
+ This will return False if no system clipboard is available; no need
+ to call ba.clipboard_available() separately.
+ """
+ return bool()
+
+
+def clipboard_is_supported() -> bool:
+ """clipboard_is_supported() -> bool
+
+ Return whether this platform supports clipboard operations at all.
+
+ Category: General Utility Functions
+
+ If this returns False, UIs should not show 'copy to clipboard'
+ buttons, etc.
+ """
+ return bool()
+
+
+def clipboard_set_text(value: str) -> None:
+ """clipboard_set_text(value: str) -> None
+
+ Copy a string to the system clipboard.
+
+ Category: General Utility Functions
+
+ Ensure that ba.clipboard_available() returns True before adding
+ buttons/etc. that make use of this functionality.
+ """
+ return None
+
+
def columnwidget(edit: ba.Widget = None,
parent: ba.Widget = None,
size: Sequence[float] = None,
@@ -3293,24 +3328,24 @@ def restore_purchases() -> None:
return None
-def rowwidget(edit: Widget = None,
- parent: Widget = None,
+def rowwidget(edit: ba.Widget = None,
+ parent: ba.Widget = None,
size: Sequence[float] = None,
position: Sequence[float] = None,
background: bool = None,
- selected_child: Widget = None,
- visible_child: Widget = None,
+ selected_child: ba.Widget = None,
+ visible_child: ba.Widget = None,
claims_left_right: bool = None,
claims_tab: bool = None,
- selection_loops_to_parent: bool = None) -> Widget:
- """rowwidget(edit: Widget = None, parent: Widget = None,
+ selection_loops_to_parent: bool = None) -> ba.Widget:
+ """rowwidget(edit: ba.Widget = None, parent: ba.Widget = None,
size: Sequence[float] = None,
position: Sequence[float] = None,
- background: bool = None, selected_child: Widget = None,
- visible_child: Widget = None,
+ background: bool = None, selected_child: ba.Widget = None,
+ visible_child: ba.Widget = None,
claims_left_right: bool = None,
claims_tab: bool = None,
- selection_loops_to_parent: bool = None) -> Widget
+ selection_loops_to_parent: bool = None) -> ba.Widget
Create or edit a row widget.
@@ -3320,7 +3355,8 @@ def rowwidget(edit: Widget = None,
a new one is created and returned. Arguments that are not set to None
are applied to the Widget.
"""
- return Widget()
+ import ba # pylint: disable=cyclic-import
+ return ba.Widget()
def run_transactions() -> None:
@@ -3775,8 +3811,8 @@ def submit_score(game: str,
return None
-def textwidget(edit: Widget = None,
- parent: Widget = None,
+def textwidget(edit: ba.Widget = None,
+ parent: ba.Widget = None,
size: Sequence[float] = None,
position: Sequence[float] = None,
text: Union[str, ba.Lstr] = None,
@@ -3787,13 +3823,13 @@ def textwidget(edit: Widget = None,
on_return_press_call: Callable[[], None] = None,
on_activate_call: Callable[[], None] = None,
selectable: bool = None,
- query: Widget = None,
+ query: ba.Widget = None,
max_chars: int = None,
color: Sequence[float] = None,
click_activate: bool = None,
on_select_call: Callable[[], None] = None,
always_highlight: bool = None,
- draw_controller: Widget = None,
+ draw_controller: ba.Widget = None,
scale: float = None,
corner_scale: float = None,
description: Union[str, ba.Lstr] = None,
@@ -3810,16 +3846,16 @@ def textwidget(edit: Widget = None,
big: bool = None,
extra_touch_border_scale: float = None,
res_scale: float = None) -> Widget:
- """textwidget(edit: Widget = None, parent: Widget = None,
+ """textwidget(edit: ba.Widget = None, parent: ba.Widget = None,
size: Sequence[float] = None, position: Sequence[float] = None,
text: Union[str, ba.Lstr] = None, v_align: str = None,
h_align: str = None, editable: bool = None, padding: float = None,
on_return_press_call: Callable[[], None] = None,
on_activate_call: Callable[[], None] = None,
- selectable: bool = None, query: Widget = None, max_chars: int = None,
+ selectable: bool = None, query: ba.Widget = None, max_chars: int = None,
color: Sequence[float] = None, click_activate: bool = None,
on_select_call: Callable[[], None] = None,
- always_highlight: bool = None, draw_controller: Widget = None,
+ always_highlight: bool = None, draw_controller: ba.Widget = None,
scale: float = None, corner_scale: float = None,
description: Union[str, ba.Lstr] = None,
transition_delay: float = None, maxwidth: float = None,
diff --git a/assets/src/ba_data/python/ba/__init__.py b/assets/src/ba_data/python/ba/__init__.py
index 8e3a6e9f..72792c71 100644
--- a/assets/src/ba_data/python/ba/__init__.py
+++ b/assets/src/ba_data/python/ba/__init__.py
@@ -9,16 +9,16 @@ In some specific cases you may need to pull in individual submodules instead.
# pylint: disable=unused-import
# pylint: disable=redefined-builtin
-from _ba import (CollideModel, Context, ContextCall, Data, InputDevice,
- Material, Model, Node, SessionPlayer, Sound, Texture, Timer,
- Vec3, Widget, buttonwidget, camerashake, checkboxwidget,
- columnwidget, containerwidget, do_once, emitfx, getactivity,
- getcollidemodel, getmodel, getnodes, getsession, getsound,
- gettexture, hscrollwidget, imagewidget, log, newactivity,
- newnode, playsound, printnodes, printobjects, pushcall, quit,
- rowwidget, safecolor, screenmessage, scrollwidget,
- set_analytics_screen, charstr, textwidget, time, timer,
- open_url, widget)
+from _ba import (
+ CollideModel, Context, ContextCall, Data, InputDevice, Material, Model,
+ Node, SessionPlayer, Sound, Texture, Timer, Vec3, Widget, buttonwidget,
+ camerashake, checkboxwidget, columnwidget, containerwidget, do_once,
+ emitfx, getactivity, getcollidemodel, getmodel, getnodes, getsession,
+ getsound, gettexture, hscrollwidget, imagewidget, log, newactivity,
+ newnode, playsound, printnodes, printobjects, pushcall, quit, rowwidget,
+ safecolor, screenmessage, scrollwidget, set_analytics_screen, charstr,
+ textwidget, time, timer, open_url, widget, clipboard_is_supported,
+ clipboard_has_text, clipboard_get_text, clipboard_set_text)
from ba._activity import Activity
from ba._plugin import PotentialPlugin, Plugin, PluginSubsystem
from ba._actor import Actor
diff --git a/assets/src/ba_data/python/ba/_actor.py b/assets/src/ba_data/python/ba/_actor.py
index 2751eb3c..5c8f7aaf 100644
--- a/assets/src/ba_data/python/ba/_actor.py
+++ b/assets/src/ba_data/python/ba/_actor.py
@@ -192,7 +192,7 @@ class Actor:
"""Return the ba.Activity this Actor is associated with.
If the Activity no longer exists, raises a ba.ActivityNotFoundError
- or returns None depending on whether 'doraise' is set.
+ or returns None depending on whether 'doraise' is True.
"""
activity = self._activity()
if activity is None and doraise:
diff --git a/assets/src/ba_data/python/ba/_appconfig.py b/assets/src/ba_data/python/ba/_appconfig.py
index 3e9828a7..ceb7c753 100644
--- a/assets/src/ba_data/python/ba/_appconfig.py
+++ b/assets/src/ba_data/python/ba/_appconfig.py
@@ -24,7 +24,7 @@ class AppConfig(dict):
AppConfig data is stored as json on disk on so make sure to only place
json-friendly values in it (dict, list, str, float, int, bool).
- Be aware that tuples will be quietly converted to lists.
+ Be aware that tuples will be quietly converted to lists when stored.
"""
def resolve(self, key: str) -> Any:
diff --git a/assets/src/ba_data/python/ba/_general.py b/assets/src/ba_data/python/ba/_general.py
index a617fac3..57ad9a88 100644
--- a/assets/src/ba_data/python/ba/_general.py
+++ b/assets/src/ba_data/python/ba/_general.py
@@ -38,16 +38,18 @@ T = TypeVar('T')
def existing(obj: Optional[ExistableType]) -> Optional[ExistableType]:
- """Convert invalid references to None for any ba.Existable type.
+ """Convert invalid references to None for any ba.Existable object.
Category: Gameplay Functions
To best support type checking, it is important that invalid references
not be passed around and instead get converted to values of None.
That way the type checker can properly flag attempts to pass dead
- objects into functions expecting only live ones, etc.
- This call can be used on any 'existable' object (one with an exists()
- method) and will convert it to a None value if it does not exist.
+ objects (Optional[FooType]) into functions expecting only live ones
+ (FooType), etc. This call can be used on any 'existable' object
+ (one with an exists() method) and will convert it to a None value
+ if it does not exist.
+
For more info, see notes on 'existables' here:
https://ballistica.net/wiki/Coding-Style-Guide
"""
@@ -358,15 +360,17 @@ def _verify_object_death(wref: ReferenceType) -> None:
def storagename(suffix: str = None) -> str:
- """Generate a (hopefully) unique name for storing things in public places.
+ """Generate a unique name for storing class data in shared places.
Category: General Utility Functions
This consists of a leading underscore, the module path at the
- call site with dots replaced by underscores, the class name, and
- the provided suffix. When storing data in public places such as
- 'customdata' dicts, this minimizes the chance of collisions if a
- module or class is duplicated or renamed.
+ call site with dots replaced by underscores, the containing class's
+ qualified name, and the provided suffix. When storing data in public
+ places such as 'customdata' dicts, this minimizes the chance of
+ collisions with other similarly named classes.
+
+ Note that this will function even if called in the class definition.
# Example: generate a unique name for storage purposes:
class MyThingie:
@@ -374,14 +378,21 @@ def storagename(suffix: str = None) -> str:
# This will give something like '_mymodule_submodule_mythingie_data'.
_STORENAME = ba.storagename('data')
+ # Use that name to store some data in the Activity we were passed.
def __init__(self, activity):
- # Store some data in the Activity we were passed
activity.customdata[self._STORENAME] = {}
"""
frame = inspect.currentframe()
if frame is None:
raise RuntimeError('Cannot get current stack frame.')
fback = frame.f_back
+
+ # Note: We need to explicitly clear frame here to avoid a ref-loop
+ # that keeps all function-dicts in the stack alive until the next
+ # full GC cycle (the stack frame refers to this function's dict,
+ # which refers to the stack frame).
+ del frame
+
if fback is None:
raise RuntimeError('Cannot get parent stack frame.')
modulepath = fback.f_globals.get('__name__')
diff --git a/assets/src/ba_data/python/ba/_music.py b/assets/src/ba_data/python/ba/_music.py
index 0bab82de..c2f92d99 100644
--- a/assets/src/ba_data/python/ba/_music.py
+++ b/assets/src/ba_data/python/ba/_music.py
@@ -12,6 +12,7 @@ import _ba
if TYPE_CHECKING:
from typing import Callable, Any, Optional, Dict, Union, Type
+ import ba
class MusicType(Enum):
@@ -469,8 +470,9 @@ class MusicPlayer:
self._actually_playing = False
-def setmusic(musictype: Optional[MusicType], continuous: bool = False) -> None:
- """Tell the game to play (or stop playing) a certain type of music.
+def setmusic(musictype: Optional[ba.MusicType],
+ continuous: bool = False) -> None:
+ """Set the app to play (or stop playing) a certain type of music.
category: Gameplay Functions
diff --git a/assets/src/ba_data/python/ba/_player.py b/assets/src/ba_data/python/ba/_player.py
index f6ed2f1f..f0592a6f 100644
--- a/assets/src/ba_data/python/ba/_player.py
+++ b/assets/src/ba_data/python/ba/_player.py
@@ -284,8 +284,8 @@ class EmptyPlayer(Player['ba.EmptyTeam']):
Category: Gameplay Classes
- ba.Player and ba.Team are 'Generic' types, and so passing them as
- type arguments when defining a ba.Activity reduces type safety.
+ ba.Player and ba.Team are 'Generic' types, and so passing those top level
+ classes as type arguments when defining a ba.Activity reduces type safety.
For example, activity.teams[0].player will have type 'Any' in that case.
For that reason, it is better to pass EmptyPlayer and EmptyTeam when
defining a ba.Activity that does not need custom types of its own.
diff --git a/assets/src/ba_data/python/ba/_session.py b/assets/src/ba_data/python/ba/_session.py
index fbed5f85..9405ec48 100644
--- a/assets/src/ba_data/python/ba/_session.py
+++ b/assets/src/ba_data/python/ba/_session.py
@@ -17,7 +17,7 @@ if TYPE_CHECKING:
class Session:
- """Defines a high level series of activities with a common purpose.
+ """Defines a high level series of ba.Activities with a common purpose.
category: Gameplay Classes
diff --git a/assets/src/ba_data/python/ba/_team.py b/assets/src/ba_data/python/ba/_team.py
index 6fa91d50..67e254b2 100644
--- a/assets/src/ba_data/python/ba/_team.py
+++ b/assets/src/ba_data/python/ba/_team.py
@@ -200,8 +200,8 @@ class EmptyTeam(Team['ba.EmptyPlayer']):
Category: Gameplay Classes
- ba.Player and ba.Team are 'Generic' types, and so passing them as
- type arguments when defining a ba.Activity reduces type safety.
+ ba.Player and ba.Team are 'Generic' types, and so passing those top level
+ classes as type arguments when defining a ba.Activity reduces type safety.
For example, activity.teams[0].player will have type 'Any' in that case.
For that reason, it is better to pass EmptyPlayer and EmptyTeam when
defining a ba.Activity that does not need custom types of its own.
diff --git a/assets/src/ba_data/python/ba/_ui.py b/assets/src/ba_data/python/ba/_ui.py
index 2e3350a3..842b1f0a 100644
--- a/assets/src/ba_data/python/ba/_ui.py
+++ b/assets/src/ba_data/python/ba/_ui.py
@@ -10,7 +10,7 @@ import _ba
from ba._enums import UIScale
if TYPE_CHECKING:
- from typing import Optional, Dict, Any, Callable, List
+ from typing import Optional, Dict, Any, Callable, List, Type
from ba.ui import UICleanupCheck
import ba
@@ -43,7 +43,7 @@ class UISubsystem:
else:
raise RuntimeError(f'Invalid UIScale value: {interfacetype}')
- self.window_states: Dict = {} # FIXME: Kill this.
+ self.window_states: Dict[Type, Any] = {} # FIXME: Kill this.
self.main_menu_selection: Optional[str] = None # FIXME: Kill this.
self.have_party_queue_window = False
self.quit_window: Any = None
@@ -76,7 +76,7 @@ class UISubsystem:
# this holds true at all aspect ratios.
# UPDATE: A better way to test this is now by setting the environment
- # variable BA_FORCE_UI_SCALE to "small", "medium", or "large".
+ # variable BA_UI_SCALE to "small", "medium", or "large".
# This will affect system UIs not covered by the values below such
# as screen-messages. The below values remain functional, however,
# for cases such as Android where environment variables can't be set
diff --git a/assets/src/ba_data/python/ba/ui/__init__.py b/assets/src/ba_data/python/ba/ui/__init__.py
index 830af4e9..e65453f1 100644
--- a/assets/src/ba_data/python/ba/ui/__init__.py
+++ b/assets/src/ba_data/python/ba/ui/__init__.py
@@ -118,7 +118,7 @@ class UIEntry:
class UIController:
- """Wrangles UILocations.
+ """Wrangles ba.UILocations.
Category: User Interface Classes
"""
diff --git a/assets/src/ba_data/python/bastd/ui/account/settings.py b/assets/src/ba_data/python/bastd/ui/account/settings.py
index a87e7a9a..785f186e 100644
--- a/assets/src/ba_data/python/bastd/ui/account/settings.py
+++ b/assets/src/ba_data/python/bastd/ui/account/settings.py
@@ -1088,13 +1088,13 @@ class AccountSettingsWindow(ba.Window):
sel_name = 'Scroll'
else:
raise ValueError('unrecognized selection')
- ba.app.ui.window_states[self.__class__.__name__] = sel_name
+ ba.app.ui.window_states[type(self)] = sel_name
except Exception:
ba.print_exception(f'Error saving state for {self}.')
def _restore_state(self) -> None:
try:
- sel_name = ba.app.ui.window_states.get(self.__class__.__name__)
+ sel_name = ba.app.ui.window_states.get(type(self))
if sel_name == 'Back':
sel = self._back_button
elif sel_name == 'Scroll':
diff --git a/assets/src/ba_data/python/bastd/ui/coop/browser.py b/assets/src/ba_data/python/bastd/ui/coop/browser.py
index c9867e80..43ba6458 100644
--- a/assets/src/ba_data/python/bastd/ui/coop/browser.py
+++ b/assets/src/ba_data/python/bastd/ui/coop/browser.py
@@ -1542,7 +1542,7 @@ class CoopBrowserWindow(ba.Window):
def _restore_state(self) -> None:
try:
- sel_name = ba.app.ui.window_states.get(self.__class__.__name__,
+ sel_name = ba.app.ui.window_states.get(type(self),
{}).get('sel_name')
if sel_name == 'Back':
sel = self._back_button
@@ -1572,9 +1572,7 @@ class CoopBrowserWindow(ba.Window):
sel_name = 'Scroll'
else:
raise ValueError('unrecognized selection')
- ba.app.ui.window_states[self.__class__.__name__] = {
- 'sel_name': sel_name
- }
+ ba.app.ui.window_states[type(self)] = {'sel_name': sel_name}
except Exception:
ba.print_exception(f'Error saving state for {self}.')
diff --git a/assets/src/ba_data/python/bastd/ui/gather/__init__.py b/assets/src/ba_data/python/bastd/ui/gather/__init__.py
index 8501a6d7..472cef0a 100644
--- a/assets/src/ba_data/python/bastd/ui/gather/__init__.py
+++ b/assets/src/ba_data/python/bastd/ui/gather/__init__.py
@@ -4,22 +4,56 @@
from __future__ import annotations
+import weakref
from enum import Enum
from typing import TYPE_CHECKING
import _ba
import ba
-from bastd.ui.gather.abouttab import AboutGatherTab
-from bastd.ui.gather.manualtab import ManualGatherTab
-from bastd.ui.gather.googleplaytab import GooglePlayGatherTab
-from bastd.ui.gather.publictab import PublicGatherTab
-from bastd.ui.gather.nearbytab import NearbyGatherTab
from bastd.ui.tabs import TabRow
if TYPE_CHECKING:
from typing import (Any, Optional, Tuple, Dict, List, Union, Callable,
Type)
- from bastd.ui.gather.bases import GatherTab
+
+
+class GatherTab:
+ """Defines a tab for use in the gather UI."""
+
+ def __init__(self, window: GatherWindow) -> None:
+ self._window = weakref.ref(window)
+
+ @property
+ def window(self) -> GatherWindow:
+ """The GatherWindow that this tab belongs to."""
+ window = self._window()
+ if window is None:
+ raise ba.NotFoundError("GatherTab's window no longer exists.")
+ return window
+
+ def on_activate(
+ self,
+ parent_widget: ba.Widget,
+ tab_button: ba.Widget,
+ region_width: float,
+ region_height: float,
+ region_left: float,
+ region_bottom: float,
+ ) -> ba.Widget:
+ """Called when the tab becomes the active one.
+
+ The tab should create and return a container widget covering the
+ specified region.
+ """
+
+ def on_deactivate(self) -> None:
+ """Called when the tab will no longer be the active one."""
+
+ def save_state(self) -> None:
+ """Called when the parent window is saving state."""
+
+ def restore_state(self) -> None:
+ """Called when the parent window is restoring state."""
class GatherWindow(ba.Window):
@@ -29,8 +63,8 @@ class GatherWindow(ba.Window):
"""Our available tab types."""
ABOUT = 'about'
INTERNET = 'internet'
- GOOGLE_PLAY = 'google_play'
- LOCAL_NETWORK = 'local_network'
+ PRIVATE = 'private'
+ NEARBY = 'nearby'
MANUAL = 'manual'
def __init__(self,
@@ -38,6 +72,13 @@ class GatherWindow(ba.Window):
origin_widget: ba.Widget = None):
# pylint: disable=too-many-statements
# pylint: disable=too-many-locals
+ # pylint: disable=cyclic-import
+ from bastd.ui.gather.abouttab import AboutGatherTab
+ from bastd.ui.gather.manualtab import ManualGatherTab
+ from bastd.ui.gather.privatetab import PrivateGatherTab
+ from bastd.ui.gather.publictab import PublicGatherTab
+ from bastd.ui.gather.nearbytab import NearbyGatherTab
+
ba.set_analytics_screen('Gather Window')
scale_origin: Optional[Tuple[float, float]]
if origin_widget is not None:
@@ -104,9 +145,6 @@ class GatherWindow(ba.Window):
text=ba.Lstr(resource=self._r + '.titleText'),
maxwidth=550)
- platform = ba.app.platform
- subplatform = ba.app.subplatform
-
scroll_buffer_h = 130 + 2 * x_offs
tab_buffer_h = ((320 if condensed else 250) + 2 * x_offs)
@@ -117,11 +155,10 @@ class GatherWindow(ba.Window):
if _ba.get_account_misc_read_val('enablePublicParties', True):
tabdefs.append((self.TabID.INTERNET,
ba.Lstr(resource=self._r + '.publicText')))
- if platform == 'android' and subplatform == 'google':
- tabdefs.append((self.TabID.GOOGLE_PLAY,
- ba.Lstr(resource=self._r + '.googlePlayText')))
- tabdefs.append((self.TabID.LOCAL_NETWORK,
- ba.Lstr(resource=self._r + '.nearbyText')))
+ tabdefs.append(
+ (self.TabID.PRIVATE, ba.Lstr(resource=self._r + '.privateText')))
+ tabdefs.append(
+ (self.TabID.NEARBY, ba.Lstr(resource=self._r + '.nearbyText')))
tabdefs.append(
(self.TabID.MANUAL, ba.Lstr(resource=self._r + '.manualText')))
@@ -139,9 +176,9 @@ class GatherWindow(ba.Window):
tabtypes: Dict[GatherWindow.TabID, Type[GatherTab]] = {
self.TabID.ABOUT: AboutGatherTab,
self.TabID.MANUAL: ManualGatherTab,
- self.TabID.GOOGLE_PLAY: GooglePlayGatherTab,
+ self.TabID.PRIVATE: PrivateGatherTab,
self.TabID.INTERNET: PublicGatherTab,
- self.TabID.LOCAL_NETWORK: NearbyGatherTab
+ self.TabID.NEARBY: NearbyGatherTab
}
self._tabs: Dict[GatherWindow.TabID, GatherTab] = {}
for tab_id in self._tab_row.tabs:
@@ -234,7 +271,7 @@ class GatherWindow(ba.Window):
sel_name = 'TabContainer'
else:
raise ValueError(f'unrecognized selection: \'{sel}\'')
- ba.app.ui.window_states[self.__class__.__name__] = {
+ ba.app.ui.window_states[type(self)] = {
'sel_name': sel_name,
}
except Exception:
@@ -247,7 +284,7 @@ class GatherWindow(ba.Window):
tab.restore_state()
sel: Optional[ba.Widget]
- winstate = ba.app.ui.window_states.get(self.__class__.__name__, {})
+ winstate = ba.app.ui.window_states.get(type(self), {})
sel_name = winstate.get('sel_name', None)
assert isinstance(sel_name, (str, type(None)))
current_tab = self.TabID.ABOUT
diff --git a/assets/src/ba_data/python/bastd/ui/gather/abouttab.py b/assets/src/ba_data/python/bastd/ui/gather/abouttab.py
index de0a16b6..a76eb11b 100644
--- a/assets/src/ba_data/python/bastd/ui/gather/abouttab.py
+++ b/assets/src/ba_data/python/bastd/ui/gather/abouttab.py
@@ -8,7 +8,7 @@ from typing import TYPE_CHECKING
import ba
import _ba
-from bastd.ui.gather.bases import GatherTab
+from bastd.ui.gather import GatherTab
if TYPE_CHECKING:
from typing import Optional
diff --git a/assets/src/ba_data/python/bastd/ui/gather/bases.py b/assets/src/ba_data/python/bastd/ui/gather/bases.py
deleted file mode 100644
index 4510e11b..00000000
--- a/assets/src/ba_data/python/bastd/ui/gather/bases.py
+++ /dev/null
@@ -1,52 +0,0 @@
-# Released under the MIT License. See LICENSE for details.
-#
-"""Provides UI for inviting/joining friends."""
-
-from __future__ import annotations
-
-import weakref
-from typing import TYPE_CHECKING
-
-import ba
-
-if TYPE_CHECKING:
- from bastd.ui.gather import GatherWindow
-
-
-class GatherTab:
- """Defines a tab for use in the gather UI."""
-
- def __init__(self, window: GatherWindow) -> None:
- self._window = weakref.ref(window)
-
- @property
- def window(self) -> GatherWindow:
- """The GatherWindow that this tab belongs to."""
- window = self._window()
- if window is None:
- raise ba.NotFoundError("GatherTab's window no longer exists.")
- return window
-
- def on_activate(
- self,
- parent_widget: ba.Widget,
- tab_button: ba.Widget,
- region_width: float,
- region_height: float,
- region_left: float,
- region_bottom: float,
- ) -> ba.Widget:
- """Called when the tab becomes the active one.
-
- The tab should create and return a container widget covering the
- specified region.
- """
-
- def on_deactivate(self) -> None:
- """Called when the tab will no longer be the active one."""
-
- def save_state(self) -> None:
- """Called when the parent window is saving state."""
-
- def restore_state(self) -> None:
- """Called when the parent window is restoring state."""
diff --git a/assets/src/ba_data/python/bastd/ui/gather/googleplaytab.py b/assets/src/ba_data/python/bastd/ui/gather/googleplaytab.py
deleted file mode 100644
index de714058..00000000
--- a/assets/src/ba_data/python/bastd/ui/gather/googleplaytab.py
+++ /dev/null
@@ -1,86 +0,0 @@
-# Released under the MIT License. See LICENSE for details.
-#
-"""Defines the Google Play tab in the gather UI."""
-
-from __future__ import annotations
-
-from typing import TYPE_CHECKING
-
-import _ba
-import ba
-from bastd.ui.gather.bases import GatherTab
-
-if TYPE_CHECKING:
- from typing import Optional
- from bastd.ui.gather import GatherWindow
-
-
-class GooglePlayGatherTab(GatherTab):
- """The public tab in the gather UI"""
-
- def __init__(self, window: GatherWindow) -> None:
- super().__init__(window)
- self._container: Optional[ba.Widget] = None
-
- def on_activate(
- self,
- parent_widget: ba.Widget,
- tab_button: ba.Widget,
- region_width: float,
- region_height: float,
- region_left: float,
- region_bottom: float,
- ) -> ba.Widget:
- c_width = region_width
- c_height = 380.0
- self._container = ba.containerwidget(
- parent=parent_widget,
- position=(region_left,
- region_bottom + (region_height - c_height) * 0.5),
- size=(c_width, c_height),
- background=False,
- selection_loops_to_parent=True)
- v = c_height - 30.0
- ba.textwidget(
- parent=self._container,
- position=(c_width * 0.5, v - 140.0),
- color=(0.6, 1.0, 0.6),
- scale=1.3,
- size=(0.0, 0.0),
- maxwidth=c_width * 0.9,
- h_align='center',
- v_align='center',
- text=ba.Lstr(resource='googleMultiplayerDiscontinuedText'))
- return self._container
-
- def _on_google_play_show_invites_press(self) -> None:
- from bastd.ui import account
- if (_ba.get_account_state() != 'signed_in'
- or _ba.get_account_type() != 'Google Play'):
- account.show_sign_in_prompt('Google Play')
- else:
- _ba.show_invites_ui()
-
- def _on_google_play_invite_press(self) -> None:
- from bastd.ui.confirm import ConfirmWindow
- from bastd.ui.account import show_sign_in_prompt
- if (_ba.get_account_state() != 'signed_in'
- or _ba.get_account_type() != 'Google Play'):
- show_sign_in_prompt('Google Play')
- else:
- # If there's google play people connected to us, inform the user
- # that they will get disconnected. Otherwise just go ahead.
- google_player_count = (_ba.get_google_play_party_client_count())
- if google_player_count > 0:
- ConfirmWindow(
- ba.Lstr(resource='gatherWindow.'
- 'googlePlayReInviteText',
- subs=[('${COUNT}', str(google_player_count))]),
- lambda: ba.timer(
- 0.2, _ba.invite_players, timetype=ba.TimeType.REAL),
- width=500,
- height=150,
- ok_text=ba.Lstr(resource='gatherWindow.'
- 'googlePlayInviteText'))
- else:
- ba.timer(0.1, _ba.invite_players, timetype=ba.TimeType.REAL)
diff --git a/assets/src/ba_data/python/bastd/ui/gather/manualtab.py b/assets/src/ba_data/python/bastd/ui/gather/manualtab.py
index e5de8fac..7ae3b1c8 100644
--- a/assets/src/ba_data/python/bastd/ui/gather/manualtab.py
+++ b/assets/src/ba_data/python/bastd/ui/gather/manualtab.py
@@ -9,7 +9,7 @@ from typing import TYPE_CHECKING, cast
from enum import Enum
from dataclasses import dataclass
-from bastd.ui.gather.bases import GatherTab
+from bastd.ui.gather import GatherTab
import _ba
import ba
@@ -161,11 +161,10 @@ class ManualGatherTab(GatherTab):
return self._container
def save_state(self) -> None:
- ba.app.ui.window_states[self.__class__.__name__] = State(
- sub_tab=self._sub_tab)
+ ba.app.ui.window_states[type(self)] = State(sub_tab=self._sub_tab)
def restore_state(self) -> None:
- state = ba.app.ui.window_states.get(self.__class__.__name__)
+ state = ba.app.ui.window_states.get(type(self))
if state is None:
state = State()
assert isinstance(state, State)
diff --git a/assets/src/ba_data/python/bastd/ui/gather/nearbytab.py b/assets/src/ba_data/python/bastd/ui/gather/nearbytab.py
index e0db519e..19fe1c53 100644
--- a/assets/src/ba_data/python/bastd/ui/gather/nearbytab.py
+++ b/assets/src/ba_data/python/bastd/ui/gather/nearbytab.py
@@ -9,7 +9,7 @@ from typing import TYPE_CHECKING
import ba
import _ba
-from bastd.ui.gather.bases import GatherTab
+from bastd.ui.gather import GatherTab
if TYPE_CHECKING:
from typing import Optional, Dict, Any
diff --git a/assets/src/ba_data/python/bastd/ui/gather/privatetab.py b/assets/src/ba_data/python/bastd/ui/gather/privatetab.py
new file mode 100644
index 00000000..331e9d4e
--- /dev/null
+++ b/assets/src/ba_data/python/bastd/ui/gather/privatetab.py
@@ -0,0 +1,755 @@
+# Released under the MIT License. See LICENSE for details.
+#
+"""Defines the Private tab in the gather UI."""
+
+from __future__ import annotations
+
+import os
+import copy
+import time
+from enum import Enum
+from dataclasses import dataclass
+from typing import TYPE_CHECKING, cast
+
+import ba
+import _ba
+from efro.dataclasses import dataclass_from_dict
+from bastd.ui.gather import GatherTab
+from bastd.ui import getcurrency
+
+if TYPE_CHECKING:
+ from typing import Optional, Dict, Any, List
+ from bastd.ui.gather import GatherWindow
+
+# Print a bit of info about queries, etc.
+DEBUG_SERVER_COMMUNICATION = os.environ.get('BA_DEBUG_PPTABCOM') == '1'
+
+
+class SubTabType(Enum):
+ """Available sub-tabs."""
+ JOIN = 'join'
+ HOST = 'host'
+
+
+@dataclass
+class State:
+ """Our core state that persists while the app is running."""
+ sub_tab: SubTabType = SubTabType.JOIN
+
+
+@dataclass
+class ConnectResult:
+ """Info about a server we get back when connecting."""
+ error: Optional[str] = None
+ addr: Optional[str] = None
+ port: Optional[int] = None
+
+
+@dataclass
+class HostingState:
+ """Our combined state of whether we're hosting, whether we can, etc."""
+ unavailable_error: Optional[str] = None
+ party_code: Optional[str] = None
+ able_to_host: bool = False
+ tickets_to_host_now: int = 0
+ minutes_until_free_host: Optional[float] = None
+ free_host_minutes_remaining: Optional[float] = None
+
+
+class PrivateGatherTab(GatherTab):
+ """The private tab in the gather UI"""
+
+ def __init__(self, window: GatherWindow) -> None:
+ super().__init__(window)
+ self._container: Optional[ba.Widget] = None
+ self._state: State = State()
+ self._hostingstate = HostingState()
+ self._join_sub_tab_text: Optional[ba.Widget] = None
+ self._host_sub_tab_text: Optional[ba.Widget] = None
+ self._update_timer: Optional[ba.Timer] = None
+ self._join_party_code_text: Optional[ba.Widget] = None
+ self._c_width: float = 0.0
+ self._c_height: float = 0.0
+ self._last_hosting_state_query_time: Optional[float] = None
+ self._initial_waiting_for_hosting_state = True
+ self._waiting_for_hosting_state = True
+ self._host_playlist_button: Optional[ba.Widget] = None
+ self._host_copy_button: Optional[ba.Widget] = None
+ self._host_connect_button: Optional[ba.Widget] = None
+ self._host_start_stop_button: Optional[ba.Widget] = None
+ self._get_tickets_button: Optional[ba.Widget] = None
+ self._ticket_count_text: Optional[ba.Widget] = None
+ self._showing_not_signed_in_screen = False
+ self._create_time = time.time()
+ self._last_action_send_time: Optional[float] = None
+
+ def on_activate(
+ self,
+ parent_widget: ba.Widget,
+ tab_button: ba.Widget,
+ region_width: float,
+ region_height: float,
+ region_left: float,
+ region_bottom: float,
+ ) -> ba.Widget:
+ self._c_width = region_width
+ self._c_height = region_height - 20
+ self._container = ba.containerwidget(
+ parent=parent_widget,
+ position=(region_left,
+ region_bottom + (region_height - self._c_height) * 0.5),
+ size=(self._c_width, self._c_height),
+ background=False,
+ selection_loops_to_parent=True)
+ v = self._c_height - 30.0
+ self._join_sub_tab_text = ba.textwidget(
+ parent=self._container,
+ position=(self._c_width * 0.5 - 245, v - 13),
+ color=(0.6, 1.0, 0.6),
+ scale=1.3,
+ size=(200, 30),
+ maxwidth=250,
+ h_align='left',
+ v_align='center',
+ click_activate=True,
+ selectable=True,
+ autoselect=True,
+ on_activate_call=lambda: self._set_sub_tab(
+ SubTabType.JOIN,
+ playsound=True,
+ ),
+ text=ba.Lstr(resource='gatherWindow.privatePartyJoinText'))
+ self._host_sub_tab_text = ba.textwidget(
+ parent=self._container,
+ position=(self._c_width * 0.5 + 45, v - 13),
+ color=(0.6, 1.0, 0.6),
+ scale=1.3,
+ size=(200, 30),
+ maxwidth=250,
+ h_align='left',
+ v_align='center',
+ click_activate=True,
+ selectable=True,
+ autoselect=True,
+ on_activate_call=lambda: self._set_sub_tab(
+ SubTabType.HOST,
+ playsound=True,
+ ),
+ text=ba.Lstr(resource='gatherWindow.privatePartyHostText'))
+ ba.widget(edit=self._join_sub_tab_text, up_widget=tab_button)
+ ba.widget(edit=self._host_sub_tab_text,
+ left_widget=self._join_sub_tab_text,
+ up_widget=tab_button)
+ ba.widget(edit=self._join_sub_tab_text,
+ right_widget=self._host_sub_tab_text)
+
+ self._update_timer = ba.Timer(1.0,
+ ba.WeakCall(self._update),
+ repeat=True,
+ timetype=ba.TimeType.REAL)
+
+ # Get a new state query kicked off immediately and show nothing
+ # until it is back.
+ self._initial_waiting_for_hosting_state = True
+ self._last_hosting_state_query_time = None
+ self._last_action_send_time = None # So we don't ignore response.
+ self._update()
+
+ self._set_sub_tab(self._state.sub_tab)
+
+ return self._container
+
+ def on_deactivate(self) -> None:
+ self._update_timer = None
+
+ def _update_currency_ui(self) -> None:
+ # Keep currency count up to date if applicable.
+ try:
+ t_str = str(_ba.get_account_ticket_count())
+ except Exception:
+ t_str = '?'
+ if self._get_tickets_button:
+ ba.buttonwidget(edit=self._get_tickets_button,
+ label=ba.charstr(ba.SpecialChar.TICKET) + t_str)
+ if self._ticket_count_text:
+ ba.textwidget(edit=self._ticket_count_text,
+ text=ba.charstr(ba.SpecialChar.TICKET) + t_str)
+
+ def _update(self) -> None:
+ """Periodic updating."""
+
+ now = ba.time(ba.TimeType.REAL)
+
+ self._update_currency_ui()
+
+ if self._state.sub_tab is SubTabType.HOST:
+
+ # If we're not signed in, just refresh to show that.
+ if (_ba.get_account_state() != 'signed_in'
+ and self._showing_not_signed_in_screen):
+ self._refresh_sub_tab()
+ else:
+
+ # Query an updated state periodically.
+ if (self._last_hosting_state_query_time is None
+ or now - self._last_hosting_state_query_time > 15.0):
+ self._debug_server_comm('querying private party state')
+ if _ba.get_account_state() == 'signed_in':
+ _ba.add_transaction(
+ {'type': 'PRIVATE_PARTY_QUERY'},
+ callback=ba.WeakCall(
+ self._hosting_state_idle_response),
+ )
+ _ba.run_transactions()
+ else:
+ self._hosting_state_idle_response(None)
+ self._last_hosting_state_query_time = now
+
+ def _hosting_state_idle_response(self,
+ result: Optional[Dict[str, Any]]) -> None:
+
+ # This simply passes through to our standard response handler.
+ # The one exception is if we've recently sent an action to the
+ # server (start/stop hosting/etc.) In that case we want to ignore
+ # idle background updates and wait for the response to our action.
+ # (this keeps the button showing 'one moment...' until the change
+ # takes effect, etc.)
+ if (self._last_action_send_time is not None
+ and time.time() - self._last_action_send_time < 5.0):
+ self._debug_server_comm('ignoring private party state response'
+ ' due to recent action')
+ return
+ self._hosting_state_response(result)
+
+ def _hosting_state_response(self, result: Optional[Dict[str,
+ Any]]) -> None:
+ state: Optional[HostingState] = None
+ if result is not None:
+ self._debug_server_comm('got private party state response')
+ try:
+ state = dataclass_from_dict(HostingState, result)
+ except Exception:
+ pass
+ else:
+ self._debug_server_comm('private party state response errored')
+
+ # Hmm I guess let's just ignore failed responses?...
+ # Or should we show some sort of error state to the user?...
+ if result is None or state is None:
+ return
+
+ self._initial_waiting_for_hosting_state = False
+ self._waiting_for_hosting_state = False
+ self._hostingstate = state
+ self._refresh_sub_tab()
+
+ def _set_sub_tab(self, value: SubTabType, playsound: bool = False) -> None:
+ assert self._container
+ if playsound:
+ ba.playsound(ba.getsound('click01'))
+
+ # If switching from join to host, do a fresh state query.
+ if self._state.sub_tab is SubTabType.JOIN and value is SubTabType.HOST:
+ self._last_hosting_state_query_time = None
+ self._initial_waiting_for_hosting_state = True
+ self._last_action_send_time = None # So we don't ignore response.
+ self._update()
+
+ self._state.sub_tab = value
+ active_color = (0.6, 1.0, 0.6)
+ inactive_color = (0.5, 0.4, 0.5)
+ ba.textwidget(
+ edit=self._join_sub_tab_text,
+ color=active_color if value is SubTabType.JOIN else inactive_color)
+ ba.textwidget(
+ edit=self._host_sub_tab_text,
+ color=active_color if value is SubTabType.HOST else inactive_color)
+
+ self._refresh_sub_tab()
+
+ # Kick off an update to get any needed messages sent/etc.
+ ba.pushcall(self._update)
+
+ def _selwidgets(self) -> List[Optional[ba.Widget]]:
+ """An indexed list of widgets we can use for saving/restoring sel."""
+ return [
+ self._host_playlist_button, self._host_copy_button,
+ self._host_connect_button, self._host_start_stop_button,
+ self._get_tickets_button
+ ]
+
+ def _refresh_sub_tab(self) -> None:
+ assert self._container
+
+ # Store an index for our current selection so we can
+ # reselect the equivalent recreated widget if possible.
+ selindex: Optional[int] = None
+ selchild = self._container.get_selected_child()
+ if selchild is not None:
+ try:
+ selindex = self._selwidgets().index(selchild)
+ except ValueError:
+ pass
+
+ # Clear anything existing in the old sub-tab.
+ for widget in self._container.get_children():
+ if widget and widget not in {
+ self._host_sub_tab_text,
+ self._join_sub_tab_text,
+ }:
+ widget.delete()
+
+ if self._state.sub_tab is SubTabType.JOIN:
+ self._build_join_tab()
+ elif self._state.sub_tab is SubTabType.HOST:
+ self._build_host_tab()
+ else:
+ raise RuntimeError('Invalid state.')
+
+ # Select the new equivalent widget if there is one.
+ if selindex is not None:
+ selwidget = self._selwidgets()[selindex]
+ if selwidget:
+ ba.containerwidget(edit=self._container,
+ selected_child=selwidget)
+
+ def _build_join_tab(self) -> None:
+
+ ba.textwidget(parent=self._container,
+ position=(self._c_width * 0.5, self._c_height - 140),
+ color=(0.5, 0.46, 0.5),
+ scale=1.5,
+ size=(0, 0),
+ maxwidth=250,
+ h_align='center',
+ v_align='center',
+ text=ba.Lstr(resource='gatherWindow.partyCodeText'))
+
+ self._join_party_code_text = ba.textwidget(
+ parent=self._container,
+ position=(self._c_width * 0.5 - 150, self._c_height - 250),
+ flatness=1.0,
+ scale=1.5,
+ size=(300, 50),
+ editable=True,
+ description=ba.Lstr(resource='gatherWindow.partyCodeText'),
+ autoselect=True,
+ maxwidth=250,
+ h_align='left',
+ v_align='center',
+ text='')
+ btn = ba.buttonwidget(parent=self._container,
+ size=(300, 70),
+ label=ba.Lstr(resource='gatherWindow.'
+ 'manualConnectText'),
+ position=(self._c_width * 0.5 - 150,
+ self._c_height - 350),
+ on_activate_call=self._connect_press,
+ autoselect=True)
+ ba.textwidget(edit=self._join_party_code_text,
+ on_return_press_call=btn.activate)
+
+ def _on_get_tickets_press(self) -> None:
+
+ if self._waiting_for_hosting_state:
+ return
+
+ # Bring up get-tickets window and then kill ourself (we're on the
+ # overlay layer so we'd show up above it).
+ getcurrency.GetCurrencyWindow(modal=True,
+ origin_widget=self._get_tickets_button)
+ # self._transition_out()
+
+ def _build_host_tab(self) -> None:
+ # pylint: disable=too-many-branches
+ # pylint: disable=too-many-statements
+
+ if _ba.get_account_state() != 'signed_in':
+ ba.textwidget(parent=self._container,
+ size=(0, 0),
+ h_align='center',
+ v_align='center',
+ maxwidth=200,
+ scale=0.8,
+ color=(0.6, 0.56, 0.6),
+ position=(self._c_width * 0.5, self._c_height * 0.5),
+ text=ba.Lstr(resource='notSignedInErrorText'))
+ self._showing_not_signed_in_screen = True
+ return
+ self._showing_not_signed_in_screen = False
+
+ # At first we don't want to show anything until we've gotten a state.
+ if self._initial_waiting_for_hosting_state:
+ ba.textwidget(
+ parent=self._container,
+ size=(0, 0),
+ h_align='center',
+ v_align='center',
+ maxwidth=200,
+ scale=0.8,
+ color=(0.6, 0.56, 0.6),
+ position=(self._c_width * 0.5, self._c_height * 0.5),
+ text=ba.Lstr(
+ value='${A}...',
+ subs=[('${A}', ba.Lstr(resource='store.loadingText'))],
+ ),
+ )
+ return
+
+ # If we're not currently hosting and hosting requires tickets,
+ # Show our count (possibly with a link to purchase more).
+ if (self._hostingstate.party_code is None
+ and self._hostingstate.tickets_to_host_now != 0):
+ if not ba.app.ui.use_toolbars:
+ if ba.app.allow_ticket_purchases:
+ self._get_tickets_button = ba.buttonwidget(
+ parent=self._container,
+ position=(self._c_width - 210 + 125,
+ self._c_height - 44),
+ autoselect=True,
+ scale=0.6,
+ size=(120, 60),
+ textcolor=(0.2, 1, 0.2),
+ label=ba.charstr(ba.SpecialChar.TICKET),
+ color=(0.65, 0.5, 0.8),
+ on_activate_call=self._on_get_tickets_press)
+ else:
+ self._ticket_count_text = ba.textwidget(
+ parent=self._container,
+ scale=0.6,
+ position=(self._c_width - 210 + 125,
+ self._c_height - 44),
+ color=(0.2, 1, 0.2),
+ h_align='center',
+ v_align='center')
+ # Set initial ticket count.
+ self._update_currency_ui()
+
+ v = self._c_height - 90
+ if self._hostingstate.party_code is None:
+ ba.textwidget(
+ parent=self._container,
+ size=(0, 0),
+ h_align='center',
+ v_align='center',
+ maxwidth=self._c_width * 0.9,
+ scale=0.7,
+ flatness=1.0,
+ color=(0.5, 0.46, 0.5),
+ position=(self._c_width * 0.5, v),
+ text=ba.Lstr(
+ resource='gatherWindow.privatePartyCloudDescriptionText'))
+
+ v -= 100
+ if self._hostingstate.party_code is None:
+ # We've got no current party running; show options to set one up.
+ ba.textwidget(parent=self._container,
+ size=(0, 0),
+ h_align='right',
+ v_align='center',
+ maxwidth=200,
+ scale=0.8,
+ color=(0.6, 0.56, 0.6),
+ position=(self._c_width * 0.5 - 210, v),
+ text=ba.Lstr(resource='playlistText'))
+ self._host_playlist_button = ba.buttonwidget(
+ parent=self._container,
+ size=(400, 70),
+ color=(0.6, 0.5, 0.6),
+ textcolor=(0.8, 0.75, 0.8),
+ label='Default Free-For-All Playlist',
+ on_activate_call=lambda: ba.screenmessage(
+ 'TODO: WIRE UP PLAYLIST SELECTION'),
+ position=(self._c_width * 0.5 - 200, v - 35),
+ up_widget=self._host_sub_tab_text,
+ autoselect=True)
+ else:
+ # We've got a current party; show its info.
+ ba.textwidget(
+ parent=self._container,
+ size=(0, 0),
+ h_align='center',
+ v_align='center',
+ maxwidth=600,
+ scale=0.9,
+ color=(0.7, 0.64, 0.7),
+ position=(self._c_width * 0.5, v + 90),
+ text=ba.Lstr(resource='gatherWindow.partyServerRunningText'))
+ ba.textwidget(parent=self._container,
+ size=(0, 0),
+ h_align='center',
+ v_align='center',
+ maxwidth=600,
+ scale=0.7,
+ color=(0.7, 0.64, 0.7),
+ position=(self._c_width * 0.5, v + 50),
+ text=ba.Lstr(resource='gatherWindow.partyCodeText'))
+ ba.textwidget(parent=self._container,
+ size=(0, 0),
+ h_align='center',
+ v_align='center',
+ scale=2.0,
+ color=(0.0, 1.0, 0.0),
+ position=(self._c_width * 0.5, v + 10),
+ text=self._hostingstate.party_code)
+
+ # Also action buttons to copy it and connect to it.
+ if ba.clipboard_is_supported():
+ cbtnoffs = 10
+ self._host_copy_button = ba.buttonwidget(
+ parent=self._container,
+ size=(140, 40),
+ color=(0.6, 0.5, 0.6),
+ textcolor=(0.8, 0.75, 0.8),
+ label=ba.Lstr(resource='gatherWindow.copyCodeText'),
+ on_activate_call=self._host_copy_press,
+ position=(self._c_width * 0.5 - 150, v - 70),
+ autoselect=True)
+ else:
+ cbtnoffs = -70
+ self._host_connect_button = ba.buttonwidget(
+ parent=self._container,
+ size=(140, 40),
+ color=(0.6, 0.5, 0.6),
+ textcolor=(0.8, 0.75, 0.8),
+ label=ba.Lstr(resource='gatherWindow.manualConnectText'),
+ on_activate_call=self._host_connect_press,
+ position=(self._c_width * 0.5 + cbtnoffs, v - 70),
+ autoselect=True)
+
+ v -= 120
+
+ # Line above the main action button:
+
+ # If hosting is unavailable, show the associated reason.
+ if self._hostingstate.unavailable_error is not None:
+ ba.textwidget(
+ parent=self._container,
+ size=(0, 0),
+ h_align='center',
+ v_align='center',
+ maxwidth=self._c_width * 0.9,
+ scale=0.7,
+ flatness=1.0,
+ color=(1.0, 0.0, 0.0),
+ position=(self._c_width * 0.5, v),
+ text=ba.Lstr(translate=('serverResponses',
+ self._hostingstate.unavailable_error)))
+ elif self._hostingstate.free_host_minutes_remaining is not None:
+ # If we've been pre-approved to start/stop for free, show that.
+ ba.textwidget(
+ parent=self._container,
+ size=(0, 0),
+ h_align='center',
+ v_align='center',
+ maxwidth=self._c_width * 0.9,
+ scale=0.7,
+ flatness=1.0,
+ color=((0.7, 0.64, 0.7) if self._hostingstate.party_code else
+ (0.0, 1.0, 0.0)),
+ position=(self._c_width * 0.5, v),
+ text=ba.Lstr(
+ resource='gatherWindow.startStopHostingMinutesText',
+ subs=[(
+ '${MINUTES}',
+ f'{self._hostingstate.free_host_minutes_remaining:.0f}'
+ )]))
+ else:
+ # Otherwise tell whether the free cloud server is available
+ # or will be at some point.
+ if self._hostingstate.party_code is None:
+ if self._hostingstate.tickets_to_host_now == 0:
+ ba.textwidget(
+ parent=self._container,
+ size=(0, 0),
+ h_align='center',
+ v_align='center',
+ maxwidth=self._c_width * 0.9,
+ scale=0.7,
+ flatness=1.0,
+ color=(0.0, 1.0, 0.0),
+ position=(self._c_width * 0.5, v),
+ text=ba.Lstr(
+ resource=
+ 'gatherWindow.freeCloudServerAvailableNowText'))
+ else:
+ if self._hostingstate.minutes_until_free_host is None:
+ ba.textwidget(
+ parent=self._container,
+ size=(0, 0),
+ h_align='center',
+ v_align='center',
+ maxwidth=self._c_width * 0.9,
+ scale=0.7,
+ flatness=1.0,
+ color=(1.0, 0.6, 0.0),
+ position=(self._c_width * 0.5, v),
+ text=ba.Lstr(
+ resource=
+ 'gatherWindow.freeCloudServerNotAvailableText')
+ )
+ else:
+ availmins = self._hostingstate.minutes_until_free_host
+ ba.textwidget(
+ parent=self._container,
+ size=(0, 0),
+ h_align='center',
+ v_align='center',
+ maxwidth=self._c_width * 0.9,
+ scale=0.7,
+ flatness=1.0,
+ color=(1.0, 0.6, 0.0),
+ position=(self._c_width * 0.5, v),
+ text=ba.Lstr(resource='gatherWindow.'
+ 'freeCloudServerAvailableMinutesText',
+ subs=[('${MINUTES}',
+ f'{availmins:.0f}')]))
+
+ v -= 100
+
+ if self._waiting_for_hosting_state:
+ btnlabel = ba.Lstr(resource='oneMomentText')
+ else:
+ if self._hostingstate.unavailable_error is not None:
+ btnlabel = ba.Lstr(
+ resource='gatherWindow.hostingUnavailableText')
+ elif self._hostingstate.party_code is None:
+ ticon = _ba.charstr(ba.SpecialChar.TICKET)
+ nowtickets = self._hostingstate.tickets_to_host_now
+ if nowtickets > 0:
+ btnlabel = ba.Lstr(
+ resource='gatherWindow.startHostingPaidText',
+ subs=[('${COST}', f'{ticon}{nowtickets}')])
+ else:
+ btnlabel = ba.Lstr(
+ resource='gatherWindow.startHostingText')
+ else:
+ btnlabel = ba.Lstr(resource='gatherWindow.stopHostingText')
+
+ self._host_start_stop_button = ba.buttonwidget(
+ parent=self._container,
+ size=(400, 80),
+ color=((0.6, 0.6, 0.6)
+ if self._hostingstate.unavailable_error is not None else
+ (0.5, 1.0,
+ 0.5) if self._waiting_for_hosting_state else None),
+ enable_sound=False,
+ label=btnlabel,
+ textcolor=((0.7, 0.7, 0.7)
+ if self._hostingstate.unavailable_error else None),
+ position=(self._c_width * 0.5 - 200, v),
+ on_activate_call=self._host_button_press,
+ autoselect=True)
+
+ def _host_copy_press(self) -> None:
+ assert self._hostingstate.party_code is not None
+ ba.clipboard_set_text(self._hostingstate.party_code)
+ ba.screenmessage(ba.Lstr(resource='gatherWindow.copyCodeConfirmText'))
+
+ def _host_connect_press(self) -> None:
+ assert self._hostingstate.party_code is not None
+ self._connect_to_party_code(self._hostingstate.party_code)
+
+ def _debug_server_comm(self, msg: str) -> None:
+ if DEBUG_SERVER_COMMUNICATION:
+ print(f'PPTABCOM: {msg} at time '
+ f'{time.time()-self._create_time:.2f}')
+
+ def _connect_to_party_code(self, code: str) -> None:
+ self._debug_server_comm('sending private party connect')
+ _ba.add_transaction(
+ {
+ 'type': 'PRIVATE_PARTY_CONNECT',
+ 'code': code
+ },
+ callback=ba.WeakCall(self._connect_response),
+ )
+ _ba.run_transactions()
+
+ def _host_button_press(self) -> None:
+ if self._waiting_for_hosting_state:
+ return
+
+ if _ba.get_account_state() != 'signed_in':
+ ba.screenmessage(ba.Lstr(resource='notSignedInErrorText'))
+ ba.playsound(ba.getsound('error'))
+ self._refresh_sub_tab()
+ return
+
+ if self._hostingstate.unavailable_error is not None:
+ ba.playsound(ba.getsound('error'))
+ return
+
+ # If we're not hosting, start.
+ if self._hostingstate.party_code is None:
+
+ # If there's a ticket cost, make sure we have enough tickets.
+ if self._hostingstate.tickets_to_host_now > 0:
+ ticket_count: Optional[int]
+ try:
+ ticket_count = _ba.get_account_ticket_count()
+ except Exception:
+ # FIXME: should add a ba.NotSignedInError we can use here.
+ ticket_count = None
+ ticket_cost = self._hostingstate.tickets_to_host_now
+ if ticket_count is not None and ticket_count < ticket_cost:
+ getcurrency.show_get_tickets_prompt()
+ ba.playsound(ba.getsound('error'))
+ return
+ self._last_action_send_time = time.time()
+ _ba.add_transaction({'type': 'PRIVATE_PARTY_START'},
+ callback=ba.WeakCall(
+ self._hosting_state_response))
+ _ba.run_transactions()
+
+ else:
+ self._last_action_send_time = time.time()
+ _ba.add_transaction({'type': 'PRIVATE_PARTY_STOP'},
+ callback=ba.WeakCall(
+ self._hosting_state_response))
+ _ba.run_transactions()
+ ba.playsound(ba.getsound('click01'))
+
+ self._waiting_for_hosting_state = True
+ self._refresh_sub_tab()
+
+ def _connect_press(self) -> None:
+ code: Optional[str] = None
+ if self._join_party_code_text:
+ code = cast(str, ba.textwidget(query=self._join_party_code_text))
+ if not code:
+ ba.screenmessage(
+ ba.Lstr(resource='internal.invalidAddressErrorText'),
+ color=(1, 0, 0))
+ ba.playsound(ba.getsound('error'))
+ return
+ self._connect_to_party_code(code)
+
+ def _connect_response(self, result: Optional[Dict[str, Any]]) -> None:
+ try:
+ if result is None:
+ raise RuntimeError()
+ cresult = dataclass_from_dict(ConnectResult, result)
+ if cresult.error is not None:
+ self._debug_server_comm('got error connect response')
+ ba.screenmessage(
+ ba.Lstr(translate=('serverResponses', cresult.error)),
+ (1, 0, 0))
+ ba.playsound(ba.getsound('error'))
+ return
+ self._debug_server_comm('got valid connect response')
+ assert cresult.addr is not None and cresult.port is not None
+ _ba.connect_to_party(cresult.addr, port=cresult.port)
+ except Exception:
+ self._debug_server_comm('got connect response error')
+ ba.playsound(ba.getsound('error'))
+
+ def save_state(self) -> None:
+ ba.app.ui.window_states[type(self)] = copy.deepcopy(self._state)
+
+ def restore_state(self) -> None:
+ state = ba.app.ui.window_states.get(type(self))
+ if state is None:
+ state = State()
+ assert isinstance(state, State)
+ self._state = state
diff --git a/assets/src/ba_data/python/bastd/ui/gather/publictab.py b/assets/src/ba_data/python/bastd/ui/gather/publictab.py
index 26282a67..b1a326ba 100644
--- a/assets/src/ba_data/python/bastd/ui/gather/publictab.py
+++ b/assets/src/ba_data/python/bastd/ui/gather/publictab.py
@@ -14,7 +14,7 @@ from typing import TYPE_CHECKING, cast
import _ba
import ba
-from bastd.ui.gather.bases import GatherTab
+from bastd.ui.gather import GatherTab
if TYPE_CHECKING:
from typing import Callable, Any, Optional, Dict, Union, Tuple, List
@@ -448,7 +448,7 @@ class PublicGatherTab(GatherTab):
# display these immediately when our UI comes back up which should
# be enough to make things feel nice and crisp while we do a full
# server re-query or whatnot.
- ba.app.ui.window_states[self.__class__.__name__] = State(
+ ba.app.ui.window_states[type(self)] = State(
sub_tab=self._sub_tab,
parties=[(i, copy.copy(p)) for i, p in self._parties_sorted[:40]],
next_entry_index=self._next_entry_index,
@@ -457,7 +457,7 @@ class PublicGatherTab(GatherTab):
have_valid_server_list=self._have_valid_server_list)
def restore_state(self) -> None:
- state = ba.app.ui.window_states.get(self.__class__.__name__)
+ state = ba.app.ui.window_states.get(type(self))
if state is None:
state = State()
assert isinstance(state, State)
@@ -544,9 +544,8 @@ class PublicGatherTab(GatherTab):
position=(270, v + 13),
maxwidth=150,
scale=0.8,
- color=(0.5, 0.5, 0.5),
+ color=(0.5, 0.46, 0.5),
flatness=1.0,
- shadow=0.0,
h_align='right',
v_align='center')
@@ -556,9 +555,8 @@ class PublicGatherTab(GatherTab):
position=(90, v - 8),
maxwidth=60,
scale=0.6,
- color=(0.5, 0.5, 0.5),
+ color=(0.5, 0.46, 0.5),
flatness=1.0,
- shadow=0.0,
h_align='center',
v_align='center')
ba.textwidget(text=ba.Lstr(resource='gatherWindow.partySizeText'),
@@ -567,9 +565,8 @@ class PublicGatherTab(GatherTab):
position=(755, v - 8),
maxwidth=60,
scale=0.6,
- color=(0.5, 0.5, 0.5),
+ color=(0.5, 0.46, 0.5),
flatness=1.0,
- shadow=0.0,
h_align='center',
v_align='center')
ba.textwidget(text=ba.Lstr(resource='gatherWindow.pingText'),
@@ -578,9 +575,8 @@ class PublicGatherTab(GatherTab):
position=(825, v - 8),
maxwidth=60,
scale=0.6,
- color=(0.5, 0.5, 0.5),
+ color=(0.5, 0.46, 0.5),
flatness=1.0,
- shadow=0.0,
h_align='center',
v_align='center')
v -= sub_scroll_height + 23
@@ -617,6 +613,20 @@ class PublicGatherTab(GatherTab):
v -= 25
is_public_enabled = _ba.get_public_party_enabled()
v -= 30
+
+ ba.textwidget(
+ parent=self._container,
+ size=(0, 0),
+ h_align='center',
+ v_align='center',
+ maxwidth=c_width * 0.9,
+ scale=0.7,
+ flatness=1.0,
+ color=(0.5, 0.46, 0.5),
+ position=(region_width * 0.5, v + 10),
+ text=ba.Lstr(resource='gatherWindow.publicHostRouterConfigText'))
+ v -= 30
+
party_name_text = ba.Lstr(
resource='gatherWindow.partyNameText',
fallback_resource='editGameListWindow.nameText')
@@ -710,11 +720,10 @@ class PublicGatherTab(GatherTab):
size=(0, 0),
scale=0.7,
flatness=1.0,
- shadow=0.0,
h_align='center',
v_align='top',
- maxwidth=c_width,
- color=(0.6, 0.6, 0.6),
+ maxwidth=c_width * 0.9,
+ color=(0.6, 0.56, 0.6),
position=(c_width * 0.5, v))
v -= 90
ba.textwidget(
@@ -723,11 +732,10 @@ class PublicGatherTab(GatherTab):
size=(0, 0),
scale=0.7,
flatness=1.0,
- shadow=0.0,
h_align='center',
v_align='center',
maxwidth=c_width * 0.9,
- color=ba.app.ui.infotextcolor,
+ color=(0.5, 0.46, 0.5),
position=(c_width * 0.5, v))
# If public sharing is already on,
@@ -750,8 +758,6 @@ class PublicGatherTab(GatherTab):
self._have_valid_server_list = True
parties_in = result['l']
- # parties_in.reverse()
-
assert isinstance(parties_in, list)
self._pending_party_infos += parties_in
@@ -1060,7 +1066,6 @@ class PublicGatherTab(GatherTab):
callback=ba.WeakCall(self._on_public_party_query_result))
_ba.run_transactions()
else:
- # This will kick us over to a 'not signed in' message.
self._on_public_party_query_result(None)
def _ping_parties_periodically(self) -> None:
diff --git a/assets/src/ba_data/python/bastd/ui/kiosk.py b/assets/src/ba_data/python/bastd/ui/kiosk.py
index d5db0f97..c75caeca 100644
--- a/assets/src/ba_data/python/bastd/ui/kiosk.py
+++ b/assets/src/ba_data/python/bastd/ui/kiosk.py
@@ -316,7 +316,7 @@ class KioskWindow(ba.Window):
repeat=True)
def _restore_state(self) -> None:
- sel_name = ba.app.ui.window_states.get(self.__class__.__name__)
+ sel_name = ba.app.ui.window_states.get(type(self))
sel: Optional[ba.Widget]
if sel_name == 'b1':
sel = self._b1
@@ -355,7 +355,7 @@ class KioskWindow(ba.Window):
sel_name = 'b7'
else:
sel_name = 'b1'
- ba.app.ui.window_states[self.__class__.__name__] = sel_name
+ ba.app.ui.window_states[type(self)] = sel_name
def _update(self) -> None:
# Kiosk-mode is designed to be used signed-out... try for force
diff --git a/assets/src/ba_data/python/bastd/ui/play.py b/assets/src/ba_data/python/bastd/ui/play.py
index deb9b8bf..16bfddab 100644
--- a/assets/src/ba_data/python/bastd/ui/play.py
+++ b/assets/src/ba_data/python/bastd/ui/play.py
@@ -540,13 +540,13 @@ class PlayWindow(ba.Window):
sel_name = 'Back'
else:
raise ValueError(f'unrecognized selection {sel}')
- ba.app.ui.window_states[self.__class__.__name__] = sel_name
+ ba.app.ui.window_states[type(self)] = sel_name
except Exception:
ba.print_exception(f'Error saving state for {self}.')
def _restore_state(self) -> None:
try:
- sel_name = ba.app.ui.window_states.get(self.__class__.__name__)
+ sel_name = ba.app.ui.window_states.get(type(self))
if sel_name == 'Team Games':
sel = self._teams_button
elif sel_name == 'Co-op Games':
diff --git a/assets/src/ba_data/python/bastd/ui/playlist/browser.py b/assets/src/ba_data/python/bastd/ui/playlist/browser.py
index 284ff31f..f478b3c6 100644
--- a/assets/src/ba_data/python/bastd/ui/playlist/browser.py
+++ b/assets/src/ba_data/python/bastd/ui/playlist/browser.py
@@ -628,13 +628,13 @@ class PlaylistBrowserWindow(ba.Window):
sel_name = 'Scroll'
else:
raise Exception('unrecognized selected widget')
- ba.app.ui.window_states[self.__class__.__name__] = sel_name
+ ba.app.ui.window_states[type(self)] = sel_name
except Exception:
ba.print_exception(f'Error saving state for {self}.')
def _restore_state(self) -> None:
try:
- sel_name = ba.app.ui.window_states.get(self.__class__.__name__)
+ sel_name = ba.app.ui.window_states.get(type(self))
if sel_name == 'Back':
sel = self._back_button
elif sel_name == 'Scroll':
diff --git a/assets/src/ba_data/python/bastd/ui/profile/browser.py b/assets/src/ba_data/python/bastd/ui/profile/browser.py
index 833c546d..e82fd29d 100644
--- a/assets/src/ba_data/python/bastd/ui/profile/browser.py
+++ b/assets/src/ba_data/python/bastd/ui/profile/browser.py
@@ -347,13 +347,13 @@ class ProfileBrowserWindow(ba.Window):
sel_name = 'Scroll'
else:
sel_name = 'Back'
- ba.app.ui.window_states[self.__class__.__name__] = sel_name
+ ba.app.ui.window_states[type(self)] = sel_name
except Exception:
ba.print_exception(f'Error saving state for {self}.')
def _restore_state(self) -> None:
try:
- sel_name = ba.app.ui.window_states.get(self.__class__.__name__)
+ sel_name = ba.app.ui.window_states.get(type(self))
if sel_name == 'Scroll':
sel = self._scrollwidget
elif sel_name == 'New':
diff --git a/assets/src/ba_data/python/bastd/ui/settings/advanced.py b/assets/src/ba_data/python/bastd/ui/settings/advanced.py
index dbac1553..1d786905 100644
--- a/assets/src/ba_data/python/bastd/ui/settings/advanced.py
+++ b/assets/src/ba_data/python/bastd/ui/settings/advanced.py
@@ -626,16 +626,14 @@ class AdvancedSettingsWindow(ba.Window):
sel_name = 'Back'
else:
raise ValueError(f'unrecognized selection \'{sel}\'')
- ba.app.ui.window_states[self.__class__.__name__] = {
- 'sel_name': sel_name
- }
+ ba.app.ui.window_states[type(self)] = {'sel_name': sel_name}
except Exception:
ba.print_exception(f'Error saving state for {self.__class__}')
def _restore_state(self) -> None:
# pylint: disable=too-many-branches
try:
- sel_name = ba.app.ui.window_states.get(self.__class__.__name__,
+ sel_name = ba.app.ui.window_states.get(type(self),
{}).get('sel_name')
if sel_name == 'Back':
sel = self._back_button
diff --git a/assets/src/ba_data/python/bastd/ui/settings/allsettings.py b/assets/src/ba_data/python/bastd/ui/settings/allsettings.py
index 32c3e3e8..ede3823d 100644
--- a/assets/src/ba_data/python/bastd/ui/settings/allsettings.py
+++ b/assets/src/ba_data/python/bastd/ui/settings/allsettings.py
@@ -256,15 +256,13 @@ class AllSettingsWindow(ba.Window):
sel_name = 'Back'
else:
raise ValueError(f'unrecognized selection \'{sel}\'')
- ba.app.ui.window_states[self.__class__.__name__] = {
- 'sel_name': sel_name
- }
+ ba.app.ui.window_states[type(self)] = {'sel_name': sel_name}
except Exception:
ba.print_exception(f'Error saving state for {self}.')
def _restore_state(self) -> None:
try:
- sel_name = ba.app.ui.window_states.get(self.__class__.__name__,
+ sel_name = ba.app.ui.window_states.get(type(self),
{}).get('sel_name')
sel: Optional[ba.Widget]
if sel_name == 'Controllers':
diff --git a/assets/src/ba_data/python/bastd/ui/settings/audio.py b/assets/src/ba_data/python/bastd/ui/settings/audio.py
index b84f0fd8..99f98d93 100644
--- a/assets/src/ba_data/python/bastd/ui/settings/audio.py
+++ b/assets/src/ba_data/python/bastd/ui/settings/audio.py
@@ -252,13 +252,13 @@ class AudioSettingsWindow(ba.Window):
sel_name = 'VRHeadRelative'
else:
raise ValueError(f'unrecognized selection \'{sel}\'')
- ba.app.ui.window_states[self.__class__.__name__] = sel_name
+ ba.app.ui.window_states[type(self)] = sel_name
except Exception:
ba.print_exception(f'Error saving state for {self.__class__}.')
def _restore_state(self) -> None:
try:
- sel_name = ba.app.ui.window_states.get(self.__class__.__name__)
+ sel_name = ba.app.ui.window_states.get(type(self))
sel: Optional[ba.Widget]
if sel_name == 'SoundMinus':
sel = self._sound_volume_numedit.minusbutton
diff --git a/assets/src/ba_data/python/bastd/ui/settings/controls.py b/assets/src/ba_data/python/bastd/ui/settings/controls.py
index fd81b5e1..ee06d279 100644
--- a/assets/src/ba_data/python/bastd/ui/settings/controls.py
+++ b/assets/src/ba_data/python/bastd/ui/settings/controls.py
@@ -457,10 +457,10 @@ class ControlsSettingsWindow(ba.Window):
sel_name = 'Wiimotes'
else:
sel_name = 'Back'
- ba.app.ui.window_states[self.__class__.__name__] = sel_name
+ ba.app.ui.window_states[type(self)] = sel_name
def _restore_state(self) -> None:
- sel_name = ba.app.ui.window_states.get(self.__class__.__name__)
+ sel_name = ba.app.ui.window_states.get(type(self))
if sel_name == 'GamePads':
sel = self._gamepads_button
elif sel_name == 'Touch':
diff --git a/assets/src/ba_data/python/bastd/ui/soundtrack/browser.py b/assets/src/ba_data/python/bastd/ui/soundtrack/browser.py
index c1a719f0..7989d329 100644
--- a/assets/src/ba_data/python/bastd/ui/soundtrack/browser.py
+++ b/assets/src/ba_data/python/bastd/ui/soundtrack/browser.py
@@ -463,13 +463,13 @@ class SoundtrackBrowserWindow(ba.Window):
sel_name = 'Back'
else:
raise ValueError(f'unrecognized selection \'{sel}\'')
- ba.app.ui.window_states[self.__class__.__name__] = sel_name
+ ba.app.ui.window_states[type(self)] = sel_name
except Exception:
ba.print_exception(f'Error saving state for {self}.')
def _restore_state(self) -> None:
try:
- sel_name = ba.app.ui.window_states.get(self.__class__.__name__)
+ sel_name = ba.app.ui.window_states.get(type(self))
if sel_name == 'Scroll':
sel = self._scrollwidget
elif sel_name == 'New':
diff --git a/assets/src/ba_data/python/bastd/ui/store/browser.py b/assets/src/ba_data/python/bastd/ui/store/browser.py
index fe53e28a..990f4067 100644
--- a/assets/src/ba_data/python/bastd/ui/store/browser.py
+++ b/assets/src/ba_data/python/bastd/ui/store/browser.py
@@ -1011,7 +1011,7 @@ class StoreBrowserWindow(ba.Window):
sel_name = f'Tab:{selected_tab_ids[0].value}'
else:
raise ValueError(f'unrecognized selection \'{sel}\'')
- ba.app.ui.window_states[self.__class__.__name__] = {
+ ba.app.ui.window_states[type(self)] = {
'sel_name': sel_name,
}
except Exception:
@@ -1021,7 +1021,7 @@ class StoreBrowserWindow(ba.Window):
from efro.util import enum_by_value
try:
sel: Optional[ba.Widget]
- sel_name = ba.app.ui.window_states.get(self.__class__.__name__,
+ sel_name = ba.app.ui.window_states.get(type(self),
{}).get('sel_name')
assert isinstance(sel_name, (str, type(None)))
diff --git a/assets/src/ba_data/python/bastd/ui/teamnamescolors.py b/assets/src/ba_data/python/bastd/ui/teamnamescolors.py
index eecfc951..adfaad66 100644
--- a/assets/src/ba_data/python/bastd/ui/teamnamescolors.py
+++ b/assets/src/ba_data/python/bastd/ui/teamnamescolors.py
@@ -35,6 +35,7 @@ class TeamNamesColorsWindow(popup.PopupWindow):
appconfig = ba.app.config
self._names = list(
appconfig.get('Custom Team Names', DEFAULT_TEAM_NAMES))
+
# We need to flatten the translation since it will be an
# editable string.
self._names = [
@@ -46,7 +47,7 @@ class TeamNamesColorsWindow(popup.PopupWindow):
self._color_buttons: List[ba.Widget] = []
self._color_text_fields: List[ba.Widget] = []
- ba.buttonwidget(
+ resetbtn = ba.buttonwidget(
parent=self.root_widget,
label=ba.Lstr(resource='settingsWindowAdvanced.resetText'),
autoselect=True,
@@ -77,20 +78,27 @@ class TeamNamesColorsWindow(popup.PopupWindow):
description=ba.Lstr(resource='nameText'),
editable=True,
padding=4))
- ba.buttonwidget(parent=self.root_widget,
- label=ba.Lstr(resource='cancelText'),
- autoselect=True,
- on_activate_call=self._on_cancel_press,
- size=(150, 50),
- position=(self._width * 0.5 - 200, 20))
- ba.buttonwidget(parent=self.root_widget,
- label=ba.Lstr(resource='saveText'),
- autoselect=True,
- on_activate_call=self._save,
- size=(150, 50),
- position=(self._width * 0.5 + 50, 20))
+ ba.widget(edit=self._color_text_fields[0],
+ down_widget=self._color_text_fields[1])
+ ba.widget(edit=self._color_text_fields[1],
+ up_widget=self._color_text_fields[0])
+ ba.widget(edit=self._color_text_fields[0], up_widget=resetbtn)
+
+ cancelbtn = ba.buttonwidget(parent=self.root_widget,
+ label=ba.Lstr(resource='cancelText'),
+ autoselect=True,
+ on_activate_call=self._on_cancel_press,
+ size=(150, 50),
+ position=(self._width * 0.5 - 200, 20))
+ savebtn = ba.buttonwidget(parent=self.root_widget,
+ label=ba.Lstr(resource='saveText'),
+ autoselect=True,
+ on_activate_call=self._save,
+ size=(150, 50),
+ position=(self._width * 0.5 + 50, 20))
ba.containerwidget(edit=self.root_widget,
selected_child=self._color_buttons[0])
+ ba.widget(edit=savebtn, left_widget=cancelbtn)
self._update()
def _color_click(self, i: int) -> None:
diff --git a/assets/src/ba_data/python/bastd/ui/watch.py b/assets/src/ba_data/python/bastd/ui/watch.py
index 09d7e26e..b27a381a 100644
--- a/assets/src/ba_data/python/bastd/ui/watch.py
+++ b/assets/src/ba_data/python/bastd/ui/watch.py
@@ -496,9 +496,7 @@ class WatchWindow(ba.Window):
sel_name = 'TabContainer'
else:
raise ValueError(f'unrecognized selection {sel}')
- ba.app.ui.window_states[self.__class__.__name__] = {
- 'sel_name': sel_name
- }
+ ba.app.ui.window_states[type(self)] = {'sel_name': sel_name}
except Exception:
ba.print_exception(f'Error saving state for {self}.')
@@ -506,7 +504,7 @@ class WatchWindow(ba.Window):
from efro.util import enum_by_value
try:
sel: Optional[ba.Widget]
- sel_name = ba.app.ui.window_states.get(self.__class__.__name__,
+ sel_name = ba.app.ui.window_states.get(type(self),
{}).get('sel_name')
assert isinstance(sel_name, (str, type(None)))
try:
diff --git a/ballisticacore-cmake/.idea/dictionaries/ericf.xml b/ballisticacore-cmake/.idea/dictionaries/ericf.xml
index e0e5dd97..736e7a3b 100644
--- a/ballisticacore-cmake/.idea/dictionaries/ericf.xml
+++ b/ballisticacore-cmake/.idea/dictionaries/ericf.xml
@@ -17,6 +17,7 @@
aclass's
activityplayer
addcall
+ addchars
addrs
adjoint
adminset
@@ -47,6 +48,7 @@
appname
appnameupper
appstate
+ argsjoined
asci
assigninput
athome
@@ -54,6 +56,7 @@
audiocache
automagically
autoselect
+ availmins
avel
avels
axismotion
@@ -114,6 +117,7 @@
bsstd
bstat
bsuuid
+ btnlabel
bufs
buildconfig
buildnumber
@@ -131,8 +135,10 @@
camalign
camelback
camerashake
+ cancelbtn
capitan
cargs
+ cbtnoffs
ccdd
ccontext
ccylinder
@@ -177,6 +183,7 @@
cpuid
crashenv
crashlytics
+ cresult
crom
crosswire
crvel
@@ -329,6 +336,7 @@
fread
freeform
freeifaddrs
+ freemins
freqs
froemling
fromini
@@ -411,6 +419,7 @@
hostactivity
hostcmd
hostinfo
+ hostingstate
hotkeys
hotplug
hscrollwidget
@@ -491,6 +500,7 @@
linearsize
listobj
llock
+ lockpath
lockstr
locktype
logmsg
@@ -596,6 +606,7 @@
nonlint
noone
nothin
+ nowtickets
nptr
nsize
ntoa
@@ -613,6 +624,8 @@
obvs
oculus
oenval
+ offsx
+ offsy
oiffsss
oldname
oooo
@@ -668,6 +681,7 @@
positivez
postinit
powerup
+ pptabcom
precalc
predeclare
prefs
@@ -679,9 +693,11 @@
printnodes
printobjects
priv
+ privatetab
profilers
prog
proj
+ projdir
prolly
psmx
pspec
@@ -739,6 +755,7 @@
reprfunc
rerase
resends
+ resetbtn
resetinput
resync
retrysecs
@@ -759,6 +776,7 @@
safecolor
samsung
sapspace
+ savebtn
savebutton
scancode
scenetime
@@ -767,6 +785,10 @@
sdkcheck
sdl's
sdlk
+ selchild
+ selindex
+ selwidget
+ selwidgets
seqlen
seqtype
seqtypestr
@@ -884,6 +906,7 @@
theres
threadname
threadtype
+ ticon
tiltage
timedisplay
timeformat
diff --git a/config/config.json b/config/config.json
index 624a6822..0ccbddfd 100644
--- a/config/config.json
+++ b/config/config.json
@@ -30,7 +30,8 @@
"requests",
"typing_extensions",
"cpplint",
- "ansiwrap"
+ "ansiwrap",
+ "filelock"
],
"python_paths": [
"assets/src/ba_data/python",
diff --git a/docs/ba_module.md b/docs/ba_module.md
index 90db98a0..65a29ba0 100644
--- a/docs/ba_module.md
+++ b/docs/ba_module.md
@@ -1,5 +1,5 @@
-last updated on 2021-01-15 for Ballistica version 1.5.30 build 20267
+last updated on 2021-01-26 for Ballistica version 1.6.0 build 20278
This page documents the Python classes and functions in the 'ba' module,
which are the ones most relevant to modding in Ballistica. If you come across something you feel should be included here or could be better explained, please let me know. Happy modding!
@@ -85,6 +85,10 @@
Inherits from: ba.DependencyComponent, typing.Generic
+Inherits from: ba.DependencyComponent, typing.Generic
Units of execution wrangled by a ba.Session.
Category: Gameplay Classes
@@ -659,7 +663,7 @@ is a convenient way to access this same functionality.
Inherits from: ba.NotFoundError, Exception, BaseException
+Inherits from: ba.NotFoundError, Exception, BaseException
Exception raised when an expected ba.Activity does not exist.
Category: Exception Classes
@@ -784,7 +788,7 @@ even if myactor is set to None.
Return the ba.Activity this Actor is associated with.
If the Activity no longer exists, raises a ba.ActivityNotFoundError
-or returns None depending on whether 'doraise' is set.
+or returns None depending on whether 'doraise' is True.
@@ -823,7 +827,7 @@ likely result in errors.
Inherits from: ba.NotFoundError, Exception, BaseException
+Inherits from: ba.NotFoundError, Exception, BaseException
Exception raised when an expected ba.Actor does not exist.
Category: Exception Classes
@@ -1010,7 +1014,7 @@ to resume.
Inherits from: dict
+Inherits from: builtins.dict
A special dict that holds the game's persistent configuration values.
Category: App Classes
@@ -1023,7 +1027,7 @@ to resume.
AppConfig data is stored as json on disk on so make sure to only place
json-friendly values in it (dict, list, str, float, int, bool).
- Be aware that tuples will be quietly converted to lists.
+ Be aware that tuples will be quietly converted to lists when stored.
Methods Defined or Overridden:
@@ -1596,7 +1600,7 @@ start_long_action(callback_when_done=ba.ContextC
Inherits from: Exception, BaseException
+Inherits from: Exception, BaseException
Exception raised when a call is made in an invalid context.
Category: Exception Classes
@@ -1606,10 +1610,10 @@ start_long_action(callback_when_done=ba.ContextC
Methods:
-<all methods inherited from builtins.Exception>
+<all methods inherited from Exception>
Inherits from: ba.GameActivity, ba.Activity, ba.DependencyComponent, typing.Generic
+Inherits from: ba.GameActivity, ba.Activity, ba.DependencyComponent, typing.Generic
Base class for cooperative-mode games.
Category: Gameplay Classes
@@ -1841,7 +1845,7 @@ the data object is requested and when it's value is accessed.
Inherits from: enum.Enum
+Inherits from: enum.Enum
A reason for a death.
Category: Enums
@@ -1858,7 +1862,7 @@ the data object is requested and when it's value is accessed.
Inherits from: ba.NotFoundError, Exception, BaseException
+Inherits from: ba.NotFoundError, Exception, BaseException
Exception raised when an expected delegate object does not exist.
Category: Exception Classes
@@ -1868,7 +1872,7 @@ the data object is requested and when it's value is accessed.
<all methods inherited from ba.NotFoundError>
Inherits from: typing.Generic
+Inherits from: typing.Generic
A dependency on a DependencyComponent (with an optional config).
Category: Dependency Classes
@@ -1941,7 +1945,7 @@ on the dep config value. (for instance a map required by a game type)
Inherits from: Exception, BaseException
+Inherits from: Exception, BaseException
Exception raised when one or more ba.Dependency items are missing.
Category: Exception Classes
@@ -1968,7 +1972,7 @@ on the dep config value. (for instance a map required by a game type)
Inherits from: typing.Generic
+Inherits from: typing.Generic
Set of resolved dependencies and their associated data.
Category: Dependency Classes
@@ -2129,13 +2133,13 @@ its time with lingering corpses, sound effects, etc.
Inherits from: ba.Player, typing.Generic
+Inherits from: ba.Player, typing.Generic
An empty player for use by Activities that don't need to define one.
Category: Gameplay Classes
- ba.Player and ba.Team are 'Generic' types, and so passing them as
- type arguments when defining a ba.Activity reduces type safety.
+
ba.Player and ba.Team are 'Generic' types, and so passing those top level
+ classes as type arguments when defining a ba.Activity reduces type safety.
For example, activity.teams[0].player will have type 'Any' in that case.
For that reason, it is better to pass EmptyPlayer and EmptyTeam when
defining a ba.Activity that does not need custom types of its own.
@@ -2192,13 +2196,13 @@ its time with lingering corpses, sound effects, etc.
<all methods inherited from ba.Player>
Inherits from: ba.Team, typing.Generic
+Inherits from: ba.Team, typing.Generic
An empty player for use by Activities that don't need to define one.
Category: Gameplay Classes
- ba.Player and ba.Team are 'Generic' types, and so passing them as
- type arguments when defining a ba.Activity reduces type safety.
+
ba.Player and ba.Team are 'Generic' types, and so passing those top level
+ classes as type arguments when defining a ba.Activity reduces type safety.
For example, activity.teams[0].player will have type 'Any' in that case.
For that reason, it is better to pass EmptyPlayer and EmptyTeam when
defining a ba.Activity that does not need custom types of its own.
@@ -2234,7 +2238,7 @@ its time with lingering corpses, sound effects, etc.
<all methods inherited from ba.Team>
Inherits from: typing.Protocol, typing.Generic
+Inherits from: typing.Protocol, typing.Generic
A Protocol for objects supporting an exists() method.
Category: Protocols
@@ -2337,8 +2341,8 @@ its time with lingering corpses, sound effects, etc.
Inherits from: ba.Activity, ba.DependencyComponent, typing.Generic
-Common base class for all game ba.Activities.
+Inherits from: ba.Activity, ba.DependencyComponent, typing.Generic
+Common base class for all game ba.Activities.
Category: Gameplay Classes
@@ -2990,7 +2994,7 @@ prefs, etc.
Inherits from: ba.NotFoundError, Exception, BaseException
+Inherits from: ba.NotFoundError, Exception, BaseException
Exception raised when an expected ba.InputDevice does not exist.
Category: Exception Classes
@@ -3000,7 +3004,7 @@ prefs, etc.
<all methods inherited from ba.NotFoundError>
Inherits from: enum.Enum
+Inherits from: enum.Enum
Types of input a controller can send to the game.
Category: Enums
@@ -4053,7 +4057,7 @@ signify that the default soundtrack should be used..
Inherits from: enum.Enum
+Inherits from: enum.Enum
Influences behavior when playing music.
Category: Enums
@@ -4156,7 +4160,7 @@ account what is supported locally.
Inherits from: enum.Enum
+Inherits from: enum.Enum
Types of music available to play in-game.
Category: Enums
@@ -4368,7 +4372,7 @@ even if myactor is set to None.
Inherits from: ba.NotFoundError, Exception, BaseException
+Inherits from: ba.NotFoundError, Exception, BaseException
Exception raised when an expected ba.Node does not exist.
Category: Exception Classes
@@ -4378,14 +4382,14 @@ even if myactor is set to None.
<all methods inherited from ba.NotFoundError>
Inherits from: Exception, BaseException
+Inherits from: Exception, BaseException
Exception raised when a referenced object does not exist.
Category: Exception Classes
Methods:
-<all methods inherited from builtins.Exception>
+<all methods inherited from Exception>
<top level class>
@@ -4404,7 +4408,7 @@ even if myactor is set to None.
Inherits from: enum.Enum
+Inherits from: enum.Enum
Permissions that can be requested from the OS.
Category: Enums
@@ -4462,7 +4466,7 @@ even if myactor is set to None.
Inherits from: typing.Generic
+Inherits from: typing.Generic
A player in a specific ba.Activity.
Category: Gameplay Classes
@@ -4657,7 +4661,7 @@ the type-checker properly identifies the returned value as one.
Inherits from: ba.NotFoundError, Exception, BaseException
+Inherits from: ba.NotFoundError, Exception, BaseException
Exception raised when an expected ba.Player does not exist.
Category: Exception Classes
@@ -4944,7 +4948,7 @@ change this. Defaults to an empty string.
Inherits from: enum.Enum
+Inherits from: enum.Enum
Type of scores.
Category: Enums
@@ -5010,7 +5014,7 @@ Pass 0 or a negative number for no ban time.
<top level class>
-Defines a high level series of activities with a common purpose.
+Defines a high level series of ba.Activities with a common purpose.
Category: Gameplay Classes
@@ -5201,7 +5205,7 @@ session.setactivity(foo) and then ba.newnode(
Inherits from: ba.NotFoundError, Exception, BaseException
+Inherits from: ba.NotFoundError, Exception, BaseException
Exception raised when an expected ba.Session does not exist.
Category: Exception Classes
@@ -5345,7 +5349,7 @@ other players.
Inherits from: ba.NotFoundError, Exception, BaseException
+Inherits from: ba.NotFoundError, Exception, BaseException
Exception raised when an expected ba.SessionPlayer does not exist.
Category: Exception Classes
@@ -5411,7 +5415,7 @@ of the session.
Inherits from: ba.NotFoundError, Exception, BaseException
+Inherits from: ba.NotFoundError, Exception, BaseException
Exception raised when an expected ba.SessionTeam does not exist.
Category: Exception Classes
@@ -5463,7 +5467,7 @@ of the session.
Inherits from: enum.Enum
+Inherits from: enum.Enum
Special characters the game can print.
Category: Enums
@@ -5680,7 +5684,7 @@ of the session.
Inherits from: typing.Generic
+Inherits from: typing.Generic
A team in a specific ba.Activity.
Category: Gameplay Classes
@@ -5729,7 +5733,7 @@ of the session.
Inherits from: ba.GameActivity, ba.Activity, ba.DependencyComponent, typing.Generic
+Inherits from: ba.GameActivity, ba.Activity, ba.DependencyComponent, typing.Generic
Base class for teams and free-for-all mode games.
Category: Gameplay Classes
@@ -5859,7 +5863,7 @@ False otherwise.
Inherits from: ba.NotFoundError, Exception, BaseException
+Inherits from: ba.NotFoundError, Exception, BaseException
Exception raised when an expected ba.Team does not exist.
Category: Exception Classes
@@ -5895,7 +5899,7 @@ False otherwise.
Inherits from: enum.Enum
+Inherits from: enum.Enum
Specifies the format time values are provided in.
Category: Enums
@@ -5927,7 +5931,7 @@ you should use the ba.timer() function instead.
time: length of time (in seconds by default) that the timer will wait
before firing. Note that the actual delay experienced may vary
- depending on the timetype. (see below)
+depending on the timetype. (see below)
call: A callable Python object. Note that the timer will retain a
strong reference to the callable for as long as it exists, so you
@@ -5937,28 +5941,11 @@ desired.
repeat: if True, the timer will fire repeatedly, with each successive
firing having the same delay as the first.
-timetype can be either 'sim', 'base', or 'real'. It defaults to
-'sim'. Types are explained below:
+timetype: A ba.TimeType value determining which timeline the timer is
+placed onto.
-'sim' time maps to local simulation time in ba.Activity or ba.Session
-Contexts. This means that it may progress slower in slow-motion play
-modes, stop when the game is paused, etc. This time type is not
-available in UI contexts.
-
-'base' time is also linked to gameplay in ba.Activity or ba.Session
-Contexts, but it progresses at a constant rate regardless of
- slow-motion states or pausing. It can, however, slow down or stop
-in certain cases such as network outages or game slowdowns due to
-cpu load. Like 'sim' time, this is unavailable in UI contexts.
-
-'real' time always maps to actual clock time with a bit of filtering
-added, regardless of Context. (the filtering prevents it from going
-backwards or jumping forward by large amounts due to the app being
-backgrounded, system time changing, etc.)
-Real time timers are currently only available in the UI context.
-
-the 'timeformat' arg defaults to SECONDS but can also be MILLISECONDS
-if you want to pass time as milliseconds.
+timeformat: A ba.TimeFormat value determining how the passed time is
+interpreted.
# Example: use a Timer object to print repeatedly for a few seconds:
def say_it():
@@ -5966,14 +5953,14 @@ def say_it():
def stop_saying_it():
self.t = None
ba.screenmessage('MUSHROOM MUSHROOM!')
-# create our timer; it will run as long as we hold self.t
+# Create our timer; it will run as long as we have the self.t ref.
self.t = ba.Timer(0.3, say_it, repeat=True)
-# now fire off a one-shot timer to kill it
+# Now fire off a one-shot timer to kill it.
ba.timer(3.89, stop_saying_it)
Inherits from: enum.Enum
+Inherits from: enum.Enum
Specifies the type of time for various operations to target/use.
Category: Enums
@@ -6001,7 +5988,7 @@ self.t = ba.Timer(0.3, say_it, repeat=True)
<top level class>
-Wrangles UILocations.
+Wrangles ba.UILocations.
Category: User Interface Classes
@@ -6022,7 +6009,7 @@ self.t = ba.Timer(0.3, say_it, repeat=True)
Inherits from: enum.Enum
+Inherits from: enum.Enum
The overall scale the UI is being rendered for. Note that this is
independent of pixel resolution. For example, a phone and a desktop PC
might render the game at similar pixel resolutions but the size they
@@ -6306,7 +6293,7 @@ widgets.
Inherits from: ba.NotFoundError, Exception, BaseException
+Inherits from: ba.NotFoundError, Exception, BaseException
Exception raised when an expected ba.Widget does not exist.
Category: Exception Classes
@@ -6473,6 +6460,50 @@ them elsewhere will be meaningless.
a new one is created and returned. Arguments that are not set to None
are applied to the Widget.
+
+clipboard_get_text() -> str
+
+Return text currently on the system clipboard.
+
+Category: General Utility Functions
+
+Ensure that ba.clipboard_has_text() returns True before calling
+ this function.
+
+
+clipboard_has_text() -> bool
+
+Return whether there is currently text on the clipboard.
+
+Category: General Utility Functions
+
+This will return False if no system clipboard is available; no need
+ to call ba.clipboard_available() separately.
+
+
+clipboard_is_supported() -> bool
+
+Return whether this platform supports clipboard operations at all.
+
+Category: General Utility Functions
+
+If this returns False, UIs should not show 'copy to clipboard'
+buttons, etc.
+
+
+clipboard_set_text(value: str) -> None
+
+Copy a string to the system clipboard.
+
+Category: General Utility Functions
+
+Ensure that ba.clipboard_available() returns True before adding
+ buttons/etc. that make use of this functionality.
+
columnwidget(edit: ba.Widget = None,
@@ -6582,17 +6613,19 @@ settings, exiting element counts, or other factors.
existing(obj: Optional[ExistableType]) -> Optional[ExistableType]
-Convert invalid references to None for any ba.Existable type.
+Convert invalid references to None for any ba.Existable object.
Category: Gameplay Functions
To best support type checking, it is important that invalid references
not be passed around and instead get converted to values of None.
That way the type checker can properly flag attempts to pass dead
-objects into functions expecting only live ones, etc.
-This call can be used on any 'existable' object (one with an exists()
-method) and will convert it to a None value if it does not exist.
-For more info, see notes on 'existables' here:
+objects (Optional[FooType]) into functions expecting only live ones
+(FooType), etc. This call can be used on any 'existable' object
+(one with an exists() method) and will convert it to a None value
+if it does not exist.
+
+For more info, see notes on 'existables' here:
https://ballistica.net/wiki/Coding-Style-Guide
@@ -6989,14 +7022,14 @@ app running.
rowwidget(edit: Widget = None, parent: Widget = None,
+rowwidget(edit: ba.Widget = None, parent: ba.Widget = None,
size: Sequence[float] = None,
position: Sequence[float] = None,
- background: bool = None, selected_child: Widget = None,
- visible_child: Widget = None,
+ background: bool = None, selected_child: ba.Widget = None,
+ visible_child: ba.Widget = None,
claims_left_right: bool = None,
claims_tab: bool = None,
- selection_loops_to_parent: bool = None) -> Widget
+ selection_loops_to_parent: bool = None) -> ba.Widget
Create or edit a row widget.
@@ -7076,9 +7109,9 @@ are applied to the Widget.
setmusic(musictype: Optional[MusicType], continuous: bool = False) -> None
+setmusic(musictype: Optional[ba.MusicType], continuous: bool = False) -> None
-Tell the game to play (or stop playing) a certain type of music.
+Set the app to play (or stop playing) a certain type of music.
Category: Gameplay Functions
@@ -7103,15 +7136,17 @@ playing, the playing track will not be restarted.
storagename(suffix: str = None) -> str
-Generate a (hopefully) unique name for storing things in public places.
+Generate a unique name for storing class data in shared places.
Category: General Utility Functions
This consists of a leading underscore, the module path at the
-call site with dots replaced by underscores, the class name, and
-the provided suffix. When storing data in public places such as
-'customdata' dicts, this minimizes the chance of collisions if a
-module or class is duplicated or renamed.
+call site with dots replaced by underscores, the containing class's
+qualified name, and the provided suffix. When storing data in public
+places such as 'customdata' dicts, this minimizes the chance of
+collisions with other similarly named classes.
+
+Note that this will function even if called in the class definition.
# Example: generate a unique name for storage purposes:
class MyThingie:
@@ -7119,22 +7154,22 @@ class MyThingie:
# This will give something like '_mymodule_submodule_mythingie_data'.
_STORENAME = ba.storagename('data')
- def __init__(self, activity):
- # Store some data in the Activity we were passed
- activity.customdata[self._STORENAME] = {}
+ # Use that name to store some data in the Activity we were passed.
+ def __init__(self, activity):
+ activity.customdata[self._STORENAME] = {}
textwidget(edit: Widget = None, parent: Widget = None,
+textwidget(edit: ba.Widget = None, parent: ba.Widget = None,
size: Sequence[float] = None, position: Sequence[float] = None,
text: Union[str, ba.Lstr] = None, v_align: str = None,
h_align: str = None, editable: bool = None, padding: float = None,
on_return_press_call: Callable[[], None] = None,
on_activate_call: Callable[[], None] = None,
- selectable: bool = None, query: Widget = None, max_chars: int = None,
+ selectable: bool = None, query: ba.Widget = None, max_chars: int = None,
color: Sequence[float] = None, click_activate: bool = None,
on_select_call: Callable[[], None] = None,
- always_highlight: bool = None, draw_controller: Widget = None,
+ always_highlight: bool = None, draw_controller: ba.Widget = None,
scale: float = None, corner_scale: float = None,
description: Union[str, ba.Lstr] = None,
transition_delay: float = None, maxwidth: float = None,
diff --git a/src/ballistica/ballistica.cc b/src/ballistica/ballistica.cc
index 476cbae6..064ce01a 100644
--- a/src/ballistica/ballistica.cc
+++ b/src/ballistica/ballistica.cc
@@ -21,8 +21,8 @@
namespace ballistica {
// These are set automatically via script; don't change here.
-const int kAppBuildNumber = 20268;
-const char* kAppVersion = "1.5.30";
+const int kAppBuildNumber = 20279;
+const char* kAppVersion = "1.6.0";
// Our standalone globals.
// These are separated out for easy access.
diff --git a/src/ballistica/graphics/graphics.h b/src/ballistica/graphics/graphics.h
index 5a842041..57a327e4 100644
--- a/src/ballistica/graphics/graphics.h
+++ b/src/ballistica/graphics/graphics.h
@@ -263,9 +263,6 @@ class Graphics {
internal_components_inited_ = val;
}
auto set_gyro_vals(const Vector3f& vals) -> void { gyro_vals_ = vals; }
- // auto draw_overlay_bounds() const -> bool { return draw_overlay_bounds_; }
- // auto set_draw_overlay_bounds(bool val) -> void { draw_overlay_bounds_ =
- // val; }
auto show_net_info() const -> bool { return show_net_info_; }
auto set_show_net_info(bool val) -> void { show_net_info_ = val; }
auto debug_graph_1() const -> NetGraph* { return debug_graph_1_.get(); }
diff --git a/src/ballistica/graphics/graphics_server.cc b/src/ballistica/graphics/graphics_server.cc
index 61696137..91ce188f 100644
--- a/src/ballistica/graphics/graphics_server.cc
+++ b/src/ballistica/graphics/graphics_server.cc
@@ -286,8 +286,8 @@ void GraphicsServer::SetScreen(bool fullscreen, int width, int height,
// we request fullscreen-windows for full-screen situations and that's it.
// (otherwise we may wind up with huge windows due to passing in desktop
// resolutions and retina wonkiness)
- width = 800;
- height = 600;
+ width = static_cast(kBaseVirtualResX * 0.8f);
+ height = static_cast(kBaseVirtualResY * 0.8f);
// We should never have to recreate the context after the initial time on
// our modern builds.
diff --git a/src/ballistica/input/input.cc b/src/ballistica/input/input.cc
index 312d44cf..d5468354 100644
--- a/src/ballistica/input/input.cc
+++ b/src/ballistica/input/input.cc
@@ -1209,13 +1209,24 @@ void Input::HandleKeyPress(const SDL_Keysym* keysym) {
if (!repeat_press && keysym->sym == SDLK_q
&& ((keysym->mod & KMOD_CTRL) || (keysym->mod & KMOD_GUI))) { // NOLINT
g_game->PushConfirmQuitCall();
+ return;
}
}
+
+ // Let the console intercept stuff if it wants at this point.
if (g_app_globals->console != nullptr
&& g_app_globals->console->HandleKeyPress(keysym)) {
return;
}
+ // Ctrl-V or Cmd-V sends paste commands to any interested text fields.
+ // Command-Q or Control-Q quits.
+ if (!repeat_press && keysym->sym == SDLK_v
+ && ((keysym->mod & KMOD_CTRL) || (keysym->mod & KMOD_GUI))) { // NOLINT
+ g_ui->SendWidgetMessage(WidgetMessage(WidgetMessage::Type::kPaste));
+ return;
+ }
+
bool handled = false;
// None of the following stuff accepts key repeats.
diff --git a/src/ballistica/platform/platform.cc b/src/ballistica/platform/platform.cc
index cac35412..175d7efe 100644
--- a/src/ballistica/platform/platform.cc
+++ b/src/ballistica/platform/platform.cc
@@ -1361,4 +1361,88 @@ auto Platform::GetCurrentSeconds() -> int64_t {
.count();
}
+auto Platform::ClipboardIsSupported() -> bool {
+ // We only call our actual virtual function once.
+ if (!have_clipboard_is_supported_) {
+ clipboard_is_supported_ = DoClipboardIsSupported();
+ have_clipboard_is_supported_ = true;
+ }
+ return clipboard_is_supported_;
+}
+
+auto Platform::ClipboardHasText() -> bool {
+ // If subplatform says they don't support clipboards, don't even ask.
+ if (!ClipboardIsSupported()) {
+ return false;
+ }
+ return DoClipboardHasText();
+}
+
+auto Platform::ClipboardSetText(const std::string& text) -> void {
+ // If subplatform says they don't support clipboards, this is an error.
+ if (!ClipboardIsSupported()) {
+ throw Exception("ClipboardSetText called with no clipboard support.",
+ PyExcType::kRuntime);
+ }
+ DoClipboardSetText(text);
+}
+
+auto Platform::ClipboardGetText() -> std::string {
+ // If subplatform says they don't support clipboards, this is an error.
+ if (!ClipboardIsSupported()) {
+ throw Exception("ClipboardGetText called with no clipboard support.",
+ PyExcType::kRuntime);
+ }
+ return DoClipboardGetText();
+}
+
+auto Platform::DoClipboardIsSupported() -> bool {
+ // Go through SDL functionality on SDL based platforms;
+ // otherwise default to no clipboard.
+#if BA_SDL2_BUILD && !BA_OSTYPE_IOS_TVOS
+ return true;
+#else
+ return false;
+#endif
+}
+
+auto Platform::DoClipboardHasText() -> bool {
+ // Go through SDL functionality on SDL based platforms;
+ // otherwise default to no clipboard.
+#if BA_SDL2_BUILD && !BA_OSTYPE_IOS_TVOS
+ return SDL_HasClipboardText();
+#else
+ // Shouldn't get here since we default to no clipboard support.
+ FatalError("Shouldn't get here.");
+ return false;
+#endif
+}
+
+auto Platform::DoClipboardSetText(const std::string& text) -> void {
+ // Go through SDL functionality on SDL based platforms;
+ // otherwise default to no clipboard.
+#if BA_SDL2_BUILD && !BA_OSTYPE_IOS_TVOS
+ SDL_SetClipboardText(text.c_str());
+#else
+ // Shouldn't get here since we default to no clipboard support.
+ FatalError("Shouldn't get here.");
+#endif
+}
+
+auto Platform::DoClipboardGetText() -> std::string {
+ // Go through SDL functionality on SDL based platforms;
+ // otherwise default to no clipboard.
+#if BA_SDL2_BUILD && !BA_OSTYPE_IOS_TVOS
+ char* out = SDL_GetClipboardText();
+ if (out == nullptr) {
+ throw Exception("Error fetching clipboard contents.", PyExcType::kRuntime);
+ }
+ return out;
+#else
+ // Shouldn't get here since we default to no clipboard support.
+ FatalError("Shouldn't get here.");
+ return "";
+#endif
+}
+
} // namespace ballistica
diff --git a/src/ballistica/platform/platform.h b/src/ballistica/platform/platform.h
index 96897d39..1eda05b4 100644
--- a/src/ballistica/platform/platform.h
+++ b/src/ballistica/platform/platform.h
@@ -103,7 +103,25 @@ class Platform {
virtual auto GetCWD() -> std::string;
// Unlink a file.
- virtual void Unlink(const char* path);
+ virtual auto Unlink(const char* path) -> void;
+
+#pragma mark CLIPBOARD ---------------------------------------------------------
+
+ /// Return whether clipboard operations are supported at all.
+ /// This gets called when determining whether to display clipboard related
+ /// UI elements/etc.
+ auto ClipboardIsSupported() -> bool;
+
+ /// Return whether there is currently text on the clipboard.
+ auto ClipboardHasText() -> bool;
+
+ /// Set current clipboard text. Raises an Exception if clipboard is
+ /// unsupported.
+ auto ClipboardSetText(const std::string& text) -> void;
+
+ /// Return current text from the clipboard. Raises an Exception if
+ /// clipboard is unsupported or if there's no text on the clipboard.
+ auto ClipboardGetText() -> std::string;
#pragma mark PRINTING/LOGGING --------------------------------------------------
@@ -493,6 +511,11 @@ class Platform {
// Generate a random UUID string.
virtual auto GenerateUUID() -> std::string;
+ virtual auto DoClipboardIsSupported() -> bool;
+ virtual auto DoClipboardHasText() -> bool;
+ virtual auto DoClipboardSetText(const std::string& text) -> void;
+ virtual auto DoClipboardGetText() -> std::string;
+
private:
int py_call_num_{};
bool using_custom_app_python_dir_{};
@@ -500,6 +523,8 @@ class Platform {
bool have_has_touchscreen_value_{};
bool have_touchscreen_{};
bool is_tegra_k1_{};
+ bool have_clipboard_is_supported_{};
+ bool clipboard_is_supported_{};
millisecs_t starttime_{};
std::string device_uuid_;
bool have_device_uuid_{};
diff --git a/src/ballistica/python/class/python_class_timer.cc b/src/ballistica/python/class/python_class_timer.cc
index e432f2a7..9e730975 100644
--- a/src/ballistica/python/class/python_class_timer.cc
+++ b/src/ballistica/python/class/python_class_timer.cc
@@ -32,7 +32,7 @@ void PythonClassTimer::SetupType(PyTypeObject* obj) {
"you should use the ba.timer() function instead.\n"
"\n"
"time: length of time (in seconds by default) that the timer will wait\n"
- "before firing. Note that the actual delay experienced may vary\n "
+ "before firing. Note that the actual delay experienced may vary\n"
"depending on the timetype. (see below)\n"
"\n"
"call: A callable Python object. Note that the timer will retain a\n"
@@ -43,28 +43,11 @@ void PythonClassTimer::SetupType(PyTypeObject* obj) {
"repeat: if True, the timer will fire repeatedly, with each successive\n"
"firing having the same delay as the first.\n"
"\n"
- "timetype can be either 'sim', 'base', or 'real'. It defaults to\n"
- "'sim'. Types are explained below:\n"
+ "timetype: A ba.TimeType value determining which timeline the timer is\n"
+ "placed onto.\n"
"\n"
- "'sim' time maps to local simulation time in ba.Activity or ba.Session\n"
- "Contexts. This means that it may progress slower in slow-motion play\n"
- "modes, stop when the game is paused, etc. This time type is not\n"
- "available in UI contexts.\n"
- "\n"
- "'base' time is also linked to gameplay in ba.Activity or ba.Session\n"
- "Contexts, but it progresses at a constant rate regardless of\n "
- "slow-motion states or pausing. It can, however, slow down or stop\n"
- "in certain cases such as network outages or game slowdowns due to\n"
- "cpu load. Like 'sim' time, this is unavailable in UI contexts.\n"
- "\n"
- "'real' time always maps to actual clock time with a bit of filtering\n"
- "added, regardless of Context. (the filtering prevents it from going\n"
- "backwards or jumping forward by large amounts due to the app being\n"
- "backgrounded, system time changing, etc.)\n"
- "Real time timers are currently only available in the UI context.\n"
- "\n"
- "the 'timeformat' arg defaults to SECONDS but can also be MILLISECONDS\n"
- "if you want to pass time as milliseconds.\n"
+ "timeformat: A ba.TimeFormat value determining how the passed time is\n"
+ "interpreted.\n"
"\n"
"# Example: use a Timer object to print repeatedly for a few seconds:\n"
"def say_it():\n"
@@ -72,9 +55,9 @@ void PythonClassTimer::SetupType(PyTypeObject* obj) {
"def stop_saying_it():\n"
" self.t = None\n"
" ba.screenmessage('MUSHROOM MUSHROOM!')\n"
- "# create our timer; it will run as long as we hold self.t\n"
+ "# Create our timer; it will run as long as we have the self.t ref.\n"
"self.t = ba.Timer(0.3, say_it, repeat=True)\n"
- "# now fire off a one-shot timer to kill it\n"
+ "# Now fire off a one-shot timer to kill it.\n"
"ba.timer(3.89, stop_saying_it)";
obj->tp_new = tp_new;
obj->tp_dealloc = (destructor)tp_dealloc;
diff --git a/src/ballistica/python/methods/python_methods_app.h b/src/ballistica/python/methods/python_methods_app.h
index 1bba6607..5ed747b2 100644
--- a/src/ballistica/python/methods/python_methods_app.h
+++ b/src/ballistica/python/methods/python_methods_app.h
@@ -16,4 +16,5 @@ class PythonMethodsApp {
};
} // namespace ballistica
+
#endif // BALLISTICA_PYTHON_METHODS_PYTHON_METHODS_APP_H_
diff --git a/src/ballistica/python/methods/python_methods_gameplay.h b/src/ballistica/python/methods/python_methods_gameplay.h
index 7ee229ce..e0773426 100644
--- a/src/ballistica/python/methods/python_methods_gameplay.h
+++ b/src/ballistica/python/methods/python_methods_gameplay.h
@@ -16,4 +16,5 @@ class PythonMethodsGameplay {
};
} // namespace ballistica
+
#endif // BALLISTICA_PYTHON_METHODS_PYTHON_METHODS_GAMEPLAY_H_
diff --git a/src/ballistica/python/methods/python_methods_graphics.h b/src/ballistica/python/methods/python_methods_graphics.h
index 1ae2f90b..1f3577ba 100644
--- a/src/ballistica/python/methods/python_methods_graphics.h
+++ b/src/ballistica/python/methods/python_methods_graphics.h
@@ -16,4 +16,5 @@ class PythonMethodsGraphics {
};
} // namespace ballistica
+
#endif // BALLISTICA_PYTHON_METHODS_PYTHON_METHODS_GRAPHICS_H_
diff --git a/src/ballistica/python/methods/python_methods_system.cc b/src/ballistica/python/methods/python_methods_system.cc
index d7604e7a..b3608db4 100644
--- a/src/ballistica/python/methods/python_methods_system.cc
+++ b/src/ballistica/python/methods/python_methods_system.cc
@@ -32,6 +32,46 @@ namespace ballistica {
#pragma ide diagnostic ignored "hicpp-signed-bitwise"
#pragma ide diagnostic ignored "RedundantCast"
+auto PyClipboardIsSupported(PyObject* self) -> PyObject* {
+ BA_PYTHON_TRY;
+ if (g_platform->ClipboardIsSupported()) {
+ Py_RETURN_TRUE;
+ }
+ Py_RETURN_FALSE;
+ BA_PYTHON_CATCH;
+}
+
+auto PyClipboardHasText(PyObject* self) -> PyObject* {
+ BA_PYTHON_TRY;
+ if (g_platform->ClipboardHasText()) {
+ Py_RETURN_TRUE;
+ }
+ Py_RETURN_FALSE;
+ BA_PYTHON_CATCH;
+}
+
+auto PyClipboardSetText(PyObject* self, PyObject* args, PyObject* keywds)
+ -> PyObject* {
+ BA_PYTHON_TRY;
+ Platform::SetLastPyCall("clipboard_set_text");
+ const char* value;
+ static const char* kwlist[] = {"value", nullptr};
+ if (!PyArg_ParseTupleAndKeywords(args, keywds, "s",
+ const_cast(kwlist), &value)) {
+ return nullptr;
+ }
+ g_platform->ClipboardSetText(value);
+ Py_RETURN_NONE;
+ BA_PYTHON_CATCH;
+}
+
+auto PyClipboardGetText(PyObject* self) -> PyObject* {
+ BA_PYTHON_TRY;
+ return PyUnicode_FromString(g_platform->ClipboardGetText().c_str());
+ Py_RETURN_FALSE;
+ BA_PYTHON_CATCH;
+}
+
auto PyIsRunningOnOuya(PyObject* self, PyObject* args) -> PyObject* {
BA_PYTHON_TRY;
Platform::SetLastPyCall("is_running_on_ouya");
@@ -743,6 +783,44 @@ auto PyApp(PyObject* self, PyObject* args, PyObject* keywds) -> PyObject* {
auto PythonMethodsSystem::GetMethods() -> std::vector {
return {
+ {"clipboard_is_supported", (PyCFunction)PyClipboardIsSupported,
+ METH_NOARGS,
+ "clipboard_is_supported() -> bool\n"
+ "\n"
+ "Return whether this platform supports clipboard operations at all.\n"
+ "\n"
+ "Category: General Utility Functions\n"
+ "\n"
+ "If this returns False, UIs should not show 'copy to clipboard'\n"
+ "buttons, etc."},
+ {"clipboard_has_text", (PyCFunction)PyClipboardHasText, METH_NOARGS,
+ "clipboard_has_text() -> bool\n"
+ "\n"
+ "Return whether there is currently text on the clipboard.\n"
+ "\n"
+ "Category: General Utility Functions\n"
+ "\n"
+ "This will return False if no system clipboard is available; no need\n"
+ " to call ba.clipboard_available() separately."},
+ {"clipboard_set_text", (PyCFunction)PyClipboardSetText,
+ METH_VARARGS | METH_KEYWORDS,
+ "clipboard_set_text(value: str) -> None\n"
+ "\n"
+ "Copy a string to the system clipboard.\n"
+ "\n"
+ "Category: General Utility Functions\n"
+ "\n"
+ "Ensure that ba.clipboard_available() returns True before adding\n"
+ " buttons/etc. that make use of this functionality."},
+ {"clipboard_get_text", (PyCFunction)PyClipboardGetText, METH_NOARGS,
+ "clipboard_get_text() -> str\n"
+ "\n"
+ "Return text currently on the system clipboard.\n"
+ "\n"
+ "Category: General Utility Functions\n"
+ "\n"
+ "Ensure that ba.clipboard_has_text() returns True before calling\n"
+ " this function."},
{"printobjects", (PyCFunction)PyPrintObjects,
METH_VARARGS | METH_KEYWORDS,
"printobjects() -> None\n"
diff --git a/src/ballistica/python/methods/python_methods_ui.cc b/src/ballistica/python/methods/python_methods_ui.cc
index 2d156c44..88c1062c 100644
--- a/src/ballistica/python/methods/python_methods_ui.cc
+++ b/src/ballistica/python/methods/python_methods_ui.cc
@@ -2636,14 +2636,14 @@ auto PythonMethodsUI::GetMethods() -> std::vector {
"are applied to the Widget."},
{"rowwidget", (PyCFunction)PyRowWidget, METH_VARARGS | METH_KEYWORDS,
- "rowwidget(edit: Widget = None, parent: Widget = None,\n"
+ "rowwidget(edit: ba.Widget = None, parent: ba.Widget = None,\n"
" size: Sequence[float] = None,\n"
" position: Sequence[float] = None,\n"
- " background: bool = None, selected_child: Widget = None,\n"
- " visible_child: Widget = None,\n"
+ " background: bool = None, selected_child: ba.Widget = None,\n"
+ " visible_child: ba.Widget = None,\n"
" claims_left_right: bool = None,\n"
" claims_tab: bool = None,\n"
- " selection_loops_to_parent: bool = None) -> Widget\n"
+ " selection_loops_to_parent: bool = None) -> ba.Widget\n"
"\n"
"Create or edit a row widget.\n"
"\n"
@@ -2699,17 +2699,17 @@ auto PythonMethodsUI::GetMethods() -> std::vector {
"are applied to the Widget."},
{"textwidget", (PyCFunction)PyTextWidget, METH_VARARGS | METH_KEYWORDS,
- "textwidget(edit: Widget = None, parent: Widget = None,\n"
+ "textwidget(edit: ba.Widget = None, parent: ba.Widget = None,\n"
" size: Sequence[float] = None, position: Sequence[float] = None,\n"
" text: Union[str, ba.Lstr] = None, v_align: str = None,\n"
" h_align: str = None, editable: bool = None, padding: float = None,\n"
" on_return_press_call: Callable[[], None] = None,\n"
" on_activate_call: Callable[[], None] = None,\n"
- " selectable: bool = None, query: Widget = None, max_chars: int = "
+ " selectable: bool = None, query: ba.Widget = None, max_chars: int = "
"None,\n"
" color: Sequence[float] = None, click_activate: bool = None,\n"
" on_select_call: Callable[[], None] = None,\n"
- " always_highlight: bool = None, draw_controller: Widget = None,\n"
+ " always_highlight: bool = None, draw_controller: ba.Widget = None,\n"
" scale: float = None, corner_scale: float = None,\n"
" description: Union[str, ba.Lstr] = None,\n"
" transition_delay: float = None, maxwidth: float = None,\n"
diff --git a/src/ballistica/ui/ui.cc b/src/ballistica/ui/ui.cc
index 81a95274..5520d981 100644
--- a/src/ballistica/ui/ui.cc
+++ b/src/ballistica/ui/ui.cc
@@ -29,7 +29,7 @@ UI::UI() {
assert(g_platform);
// Allow overriding via an environment variable.
- auto* ui_override = getenv("BA_FORCE_UI_SCALE");
+ auto* ui_override = getenv("BA_UI_SCALE");
bool force_test_small{};
bool force_test_medium{};
bool force_test_large{};
diff --git a/src/ballistica/ui/widget/container_widget.cc b/src/ballistica/ui/widget/container_widget.cc
index 2f370c5d..faa4632f 100644
--- a/src/ballistica/ui/widget/container_widget.cc
+++ b/src/ballistica/ui/widget/container_widget.cc
@@ -296,6 +296,7 @@ auto ContainerWidget::HandleMessage(const WidgetMessage& m) -> bool {
switch (m.type) {
case WidgetMessage::Type::kTextInput:
case WidgetMessage::Type::kKey:
+ case WidgetMessage::Type::kPaste:
if (selected_widget_) {
bool val = selected_widget_->HandleMessage(m);
if (val != 0) {
@@ -1218,8 +1219,7 @@ void ContainerWidget::SetTransition(TransitionType t) {
// stack, update the toolbar for the new topmost input-accepting window
// *immediately* (otherwise we'd have to wait for our transition to complete
// before the toolbar switches).
- if (transitioning_ && transitioning_out_ && parent != nullptr
- && parent->is_main_window_stack_) {
+ if (transitioning_ && transitioning_out_ && parent->is_main_window_stack_) {
g_ui->root_widget()->UpdateForFocusedWindow();
}
}
diff --git a/src/ballistica/ui/widget/text_widget.cc b/src/ballistica/ui/widget/text_widget.cc
index b193915b..d54a7c9d 100644
--- a/src/ballistica/ui/widget/text_widget.cc
+++ b/src/ballistica/ui/widget/text_widget.cc
@@ -116,8 +116,8 @@ void TextWidget::Draw(RenderPass* pass, bool draw_transparent) {
// Center-scale.
{
- // We should really be scaling our bounds and things, but for now lets just
- // do a hacky overall scale.
+ // We should really be scaling our bounds and things,
+ // but for now lets just do a hacky overall scale.
EmptyComponent c(pass);
c.SetTransparent(true);
c.PushTransform();
@@ -178,6 +178,7 @@ void TextWidget::Draw(RenderPass* pass, bool draw_transparent) {
highlight_center_y_ = b2 - b_border + highlight_height_ * 0.5f;
highlight_dirty_ = false;
}
+
SimpleComponent c(pass);
c.SetTransparent(true);
c.SetPremultiplied(true);
@@ -596,6 +597,16 @@ auto TextWidget::HandleMessage(const WidgetMessage& m) -> bool {
bottom_overlap = 3.0f * extra_touch_border_scale_;
}
+ // If we're doing inline editing, handle clipboard paste.
+ if (editable() && !ShouldUseStringEditDialog()
+ && m.type == WidgetMessage::Type::kPaste) {
+ if (g_platform->ClipboardIsSupported()) {
+ if (g_platform->ClipboardHasText()) {
+ // Just enter it char by char as if we had typed it...
+ AddCharsToText(g_platform->ClipboardGetText());
+ }
+ }
+ }
// If we're doing inline editing, handle some key events.
if (m.has_keysym && !ShouldUseStringEditDialog()) {
last_carat_change_time_ = g_game->master_time();
@@ -769,22 +780,7 @@ auto TextWidget::HandleMessage(const WidgetMessage& m) -> bool {
} else {
// Otherwise apply the text directly.
if (editable() && m.sval != nullptr) {
- std::vector unichars =
- Utils::UnicodeFromUTF8(text_raw_, "jcjwf8f");
- int len = static_cast(unichars.size());
- std::vector sval =
- Utils::UnicodeFromUTF8(*m.sval, "j4958fbv");
- for (unsigned int i : sval) {
- if (len < max_chars_) {
- text_group_dirty_ = true;
- if (carat_position_ > len) carat_position_ = len;
- unichars.insert(unichars.begin() + carat_position_, i);
- len++;
- carat_position_++;
- }
- }
- text_raw_ = Utils::UTF8FromUnicode(unichars);
- text_translation_dirty_ = true;
+ AddCharsToText(*m.sval);
return true;
}
}
@@ -794,8 +790,8 @@ auto TextWidget::HandleMessage(const WidgetMessage& m) -> bool {
if (!IsSelectable()) {
return false;
}
- float x = m.fval1;
- float y = m.fval2;
+ float x{ScaleAdjustedX(m.fval1)};
+ float y{ScaleAdjustedY(m.fval2)};
bool claimed = (m.fval3 > 0.0f);
if (claimed) {
mouse_over_ = clear_mouse_over_ = false;
@@ -813,8 +809,9 @@ auto TextWidget::HandleMessage(const WidgetMessage& m) -> bool {
if (!IsSelectable()) {
return false;
}
- float x = m.fval1;
- float y = m.fval2;
+ float x{ScaleAdjustedX(m.fval1)};
+ float y{ScaleAdjustedY(m.fval2)};
+
auto click_count = static_cast(m.fval3);
// See if a click is in our clear button.
@@ -856,8 +853,8 @@ auto TextWidget::HandleMessage(const WidgetMessage& m) -> bool {
}
}
case WidgetMessage::Type::kMouseUp: {
- float x = m.fval1;
- float y = m.fval2;
+ float x{ScaleAdjustedX(m.fval1)};
+ float y{ScaleAdjustedY(m.fval2)};
bool claimed = (m.fval3 > 0.0f);
if (clear_pressed_ && !claimed && editable()
@@ -903,6 +900,38 @@ auto TextWidget::HandleMessage(const WidgetMessage& m) -> bool {
return false;
}
+auto TextWidget::ScaleAdjustedX(float x) -> float {
+ // Account for our center_scale_ value.
+ float offsx = x - width_ * 0.5f;
+ return width_ * 0.5f + offsx / center_scale_;
+}
+
+auto TextWidget::ScaleAdjustedY(float y) -> float {
+ // Account for our center_scale_ value.
+ float offsy = y - height_ * 0.5f;
+ return height_ * 0.5f + offsy / center_scale_;
+}
+
+auto TextWidget::AddCharsToText(const std::string& addchars) -> void {
+ assert(editable());
+ std::vector unichars = Utils::UnicodeFromUTF8(text_raw_, "jcjwf8f");
+ int len = static_cast(unichars.size());
+ std::vector sval = Utils::UnicodeFromUTF8(addchars, "j4958fbv");
+ for (unsigned int i : sval) {
+ if (len < max_chars_) {
+ text_group_dirty_ = true;
+ if (carat_position_ > len) {
+ carat_position_ = len;
+ }
+ unichars.insert(unichars.begin() + carat_position_, i);
+ len++;
+ carat_position_++;
+ }
+ }
+ text_raw_ = Utils::UTF8FromUnicode(unichars);
+ text_translation_dirty_ = true;
+}
+
void TextWidget::UpdateTranslation() {
// Apply subs/resources to get our actual text if need be.
if (text_translation_dirty_) {
diff --git a/src/ballistica/ui/widget/text_widget.h b/src/ballistica/ui/widget/text_widget.h
index eef982ab..8359d89b 100644
--- a/src/ballistica/ui/widget/text_widget.h
+++ b/src/ballistica/ui/widget/text_widget.h
@@ -89,6 +89,9 @@ class TextWidget : public Widget {
}
private:
+ auto ScaleAdjustedX(float x) -> float;
+ auto ScaleAdjustedY(float y) -> float;
+ auto AddCharsToText(const std::string& addchars) -> void;
auto ShouldUseStringEditDialog() const -> bool;
void BringUpEditDialog();
void UpdateTranslation();
diff --git a/src/ballistica/ui/widget/widget.h b/src/ballistica/ui/widget/widget.h
index edda2fda..c6e7fdda 100644
--- a/src/ballistica/ui/widget/widget.h
+++ b/src/ballistica/ui/widget/widget.h
@@ -37,7 +37,8 @@ struct WidgetMessage {
kMouseWheelVelocityH,
kMouseMove,
kScrollMouseDown,
- kTextInput
+ kTextInput,
+ kPaste
};
Type type{};
diff --git a/tools/batools/build.py b/tools/batools/build.py
index 77e2727f..ac437641 100644
--- a/tools/batools/build.py
+++ b/tools/batools/build.py
@@ -30,10 +30,10 @@ class PipRequirement:
PIP_REQUIREMENTS = [
PipRequirement(modulename='pylint', minversion=[2, 6, 0]),
- PipRequirement(modulename='mypy', minversion=[0, 790]),
+ PipRequirement(modulename='mypy', minversion=[0, 800]),
PipRequirement(modulename='yapf', minversion=[0, 30, 0]),
PipRequirement(modulename='cpplint', minversion=[1, 5, 4]),
- PipRequirement(modulename='pytest', minversion=[6, 1, 2]),
+ PipRequirement(modulename='pytest', minversion=[6, 2, 1]),
PipRequirement(modulename='typing_extensions'),
PipRequirement(modulename='pytz'),
PipRequirement(modulename='ansiwrap'),
diff --git a/tools/efro/terminal.py b/tools/efro/terminal.py
index 0b904a54..aafa34fd 100644
--- a/tools/efro/terminal.py
+++ b/tools/efro/terminal.py
@@ -112,7 +112,7 @@ def _windows_enable_color() -> bool:
# open CONOUT$ instead
fdout = os.open('CONOUT$', os.O_RDWR)
try:
- hout = msvcrt.get_osfhandle(fdout)
+ hout = msvcrt.get_osfhandle(fdout) # type: ignore
old_mode = wintypes.DWORD()
kernel32.GetConsoleMode(hout, ctypes.byref(old_mode))
mode = (new_mode & mask) | (old_mode.value & ~mask)
diff --git a/tools/efrotools/code.py b/tools/efrotools/code.py
index fddf53c4..ce0c1339 100644
--- a/tools/efrotools/code.py
+++ b/tools/efrotools/code.py
@@ -703,12 +703,10 @@ def _run_idea_inspections(projroot: Path,
if result.returncode != 0:
# In verbose mode this stuff got printed already.
if not verbose:
- stdout = (
- result.stdout.decode() if isinstance( # type: ignore
- result.stdout, bytes) else str(result.stdout))
- stderr = (
- result.stderr.decode() if isinstance( # type: ignore
- result.stdout, bytes) else str(result.stdout))
+ stdout = (result.stdout.decode() if isinstance(
+ result.stdout, bytes) else str(result.stdout))
+ stderr = (result.stderr.decode() if isinstance(
+ result.stdout, bytes) else str(result.stdout))
print(f'{displayname} inspection failure stdout:\n{stdout}' +
f'{displayname} inspection failure stderr:\n{stderr}')
raise RuntimeError(f'{displayname} inspection failed.')