mirror of
https://github.com/imayushsaini/Bombsquad-Ballistica-Modded-Server.git
synced 2026-08-15 13:04:30 +00:00
syncing with 1.7.53
This commit is contained in:
parent
4e5156073d
commit
06138dbc15
132 changed files with 4946 additions and 2561 deletions
12
dist/ba_data/python/babase/__init__.py
vendored
12
dist/ba_data/python/babase/__init__.py
vendored
|
|
@ -142,6 +142,7 @@ from babase._apputils import (
|
|||
)
|
||||
from babase._cloud import CloudSubscription
|
||||
from babase._devconsole import (
|
||||
DevConsoleButtonDef,
|
||||
DevConsoleTab,
|
||||
DevConsoleTabEntry,
|
||||
DevConsoleSubsystem,
|
||||
|
|
@ -179,7 +180,14 @@ from babase._general import (
|
|||
)
|
||||
from babase._language import Lstr, LanguageSubsystem
|
||||
from babase._locale import LocaleSubsystem
|
||||
from babase._logging import balog, accountlog, applog, lifecyclelog, netlog
|
||||
from babase._logging import (
|
||||
balog,
|
||||
accountlog,
|
||||
applog,
|
||||
lifecyclelog,
|
||||
netlog,
|
||||
uilog,
|
||||
)
|
||||
from babase._login import LoginAdapter, LoginInfo
|
||||
|
||||
from babase._mgen.enums import (
|
||||
|
|
@ -250,6 +258,7 @@ __all__ = [
|
|||
'ContextError',
|
||||
'ContextRef',
|
||||
'DelegateNotFoundError',
|
||||
'DevConsoleButtonDef',
|
||||
'DevConsoleTab',
|
||||
'DevConsoleTabEntry',
|
||||
'DevConsoleSubsystem',
|
||||
|
|
@ -371,6 +380,7 @@ __all__ = [
|
|||
'supports_unicode_display',
|
||||
'TeamNotFoundError',
|
||||
'timestring',
|
||||
'uilog',
|
||||
'UIScale',
|
||||
'unlock_all_input',
|
||||
'update_internal_logger_levels',
|
||||
|
|
|
|||
12
dist/ba_data/python/babase/_app.py
vendored
12
dist/ba_data/python/babase/_app.py
vendored
|
|
@ -202,10 +202,16 @@ class App:
|
|||
return _babase.app_is_active()
|
||||
|
||||
@property
|
||||
def mode(self) -> AppMode | None:
|
||||
"""The app's current mode."""
|
||||
def mode(self) -> AppMode:
|
||||
"""The app's current mode.
|
||||
|
||||
Raises :class:`ValueError` if no mode is set.
|
||||
"""
|
||||
assert _babase.in_logic_thread()
|
||||
return self._mode
|
||||
mode = self._mode
|
||||
if mode is None:
|
||||
raise ValueError('No app-mode set.')
|
||||
return mode
|
||||
|
||||
@property
|
||||
def asyncio_loop(self) -> asyncio.AbstractEventLoop:
|
||||
|
|
|
|||
10
dist/ba_data/python/babase/_appmode.py
vendored
10
dist/ba_data/python/babase/_appmode.py
vendored
|
|
@ -6,7 +6,7 @@ from __future__ import annotations
|
|||
from typing import TYPE_CHECKING
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from babase import AppIntent
|
||||
from babase import AppIntent, DevConsoleButtonDef
|
||||
|
||||
|
||||
class AppMode:
|
||||
|
|
@ -125,3 +125,11 @@ class AppMode:
|
|||
)
|
||||
if babase.asset_loads_allowed():
|
||||
babase.getsimplesound('cashRegister').play()
|
||||
|
||||
def get_dev_console_ui_tab_buttons(self) -> list[DevConsoleButtonDef]:
|
||||
"""Define buttons to show up in the UI dev console.
|
||||
|
||||
This can be useful for exposing UI code examples or debugging
|
||||
functionality.
|
||||
"""
|
||||
return []
|
||||
|
|
|
|||
13
dist/ba_data/python/babase/_devconsole.py
vendored
13
dist/ba_data/python/babase/_devconsole.py
vendored
|
|
@ -14,6 +14,19 @@ if TYPE_CHECKING:
|
|||
from typing import Callable, Any, Literal
|
||||
|
||||
|
||||
class DevConsoleButtonDef:
|
||||
"""A barebones way to define a custom button for the dev console.
|
||||
|
||||
Note that a :class:`DevConsoleTab` should use its
|
||||
:meth:`DevConsoleTab.button()` method to create buttons; this is
|
||||
instead for allowing basic customization.
|
||||
"""
|
||||
|
||||
def __init__(self, name: str, call: Callable[[], Any]) -> None:
|
||||
self.name = name
|
||||
self.call = call
|
||||
|
||||
|
||||
class DevConsoleTab:
|
||||
"""Base class for a :class:`~babase.DevConsoleSubsystem` tab."""
|
||||
|
||||
|
|
|
|||
55
dist/ba_data/python/babase/_devconsoletabs.py
vendored
55
dist/ba_data/python/babase/_devconsoletabs.py
vendored
|
|
@ -119,14 +119,30 @@ class DevConsoleTabUI(DevConsoleTab):
|
|||
def refresh(self) -> None:
|
||||
from babase._mgen.enums import UIScale
|
||||
|
||||
xoffs = -305
|
||||
yoffs = 10
|
||||
xoffs = -305.0
|
||||
yoffs = 10.0
|
||||
|
||||
custom_buttons = _babase.app.mode.get_dev_console_ui_tab_buttons()
|
||||
cboffs = 50
|
||||
cbwidth = 180
|
||||
cbspacing = 10
|
||||
|
||||
if custom_buttons:
|
||||
cbtotalwidth = (
|
||||
len(custom_buttons) * cbwidth
|
||||
+ max(0, len(custom_buttons) - 1) * cbspacing
|
||||
+ cboffs
|
||||
)
|
||||
else:
|
||||
cbtotalwidth = 0
|
||||
|
||||
xoffs -= cbtotalwidth * 0.5
|
||||
|
||||
self.text(
|
||||
'A UI should either fit in the virtual safe area'
|
||||
' or dynamically respond to screen size changes.',
|
||||
scale=0.6,
|
||||
pos=(xoffs + 15, yoffs + 65),
|
||||
pos=(xoffs + 8, yoffs + 65),
|
||||
h_align='left',
|
||||
v_align='center',
|
||||
)
|
||||
|
|
@ -165,6 +181,19 @@ class DevConsoleTabUI(DevConsoleTab):
|
|||
)
|
||||
x += bwidth + 2
|
||||
|
||||
if custom_buttons:
|
||||
x += cboffs
|
||||
for custom_button in custom_buttons:
|
||||
self.button(
|
||||
custom_button.name,
|
||||
pos=(xoffs + x, yoffs + 15),
|
||||
size=(cbwidth, 40),
|
||||
label_scale=0.6,
|
||||
call=custom_button.call,
|
||||
corner_radius=10.0,
|
||||
)
|
||||
x += cbwidth + cbspacing
|
||||
|
||||
def toggle_ui_overlay(self) -> None:
|
||||
"""Toggle UI overlay drawing."""
|
||||
_babase.set_draw_virtual_safe_area_bounds(
|
||||
|
|
@ -544,12 +573,15 @@ class DevConsoleTabLogging(DevConsoleTab):
|
|||
index = 0
|
||||
effectivelevel = logger.getEffectiveLevel()
|
||||
notsetname = 'Not Set'
|
||||
bradius = 5.0
|
||||
bspacing = 2.0
|
||||
tab.button(
|
||||
notsetname,
|
||||
pos=(x + width - bwidth * 6.5 + xoffs + 1.0, y + 5.0),
|
||||
size=(bwidth * 1.0 - 2.0, height - 10),
|
||||
size=(bwidth * 1.0 - bspacing, height - 10),
|
||||
label_scale=btextscale,
|
||||
style='white_bright' if level == logging.NOTSET else 'black',
|
||||
corner_radius=bradius,
|
||||
call=partial(
|
||||
self._set_entry_val, entry_index, entry, logging.NOTSET
|
||||
),
|
||||
|
|
@ -558,13 +590,14 @@ class DevConsoleTabLogging(DevConsoleTab):
|
|||
tab.button(
|
||||
'Debug',
|
||||
pos=(x + width - bwidth * 5 + xoffs + 1.0, y + 5.0),
|
||||
size=(bwidth - 2.0, height - 10),
|
||||
size=(bwidth - bspacing, height - 10),
|
||||
label_scale=btextscale,
|
||||
style=(
|
||||
'white_bright'
|
||||
if level == logging.DEBUG
|
||||
else 'blue' if effectivelevel <= logging.DEBUG else 'black'
|
||||
),
|
||||
corner_radius=bradius,
|
||||
call=partial(
|
||||
self._set_entry_val, entry_index, entry, logging.DEBUG
|
||||
),
|
||||
|
|
@ -573,26 +606,28 @@ class DevConsoleTabLogging(DevConsoleTab):
|
|||
tab.button(
|
||||
'Info',
|
||||
pos=(x + width - bwidth * 4 + xoffs + 1.0, y + 5.0),
|
||||
size=(bwidth - 2.0, height - 10),
|
||||
size=(bwidth - bspacing, height - 10),
|
||||
label_scale=btextscale,
|
||||
style=(
|
||||
'white_bright'
|
||||
if level == logging.INFO
|
||||
else 'white' if effectivelevel <= logging.INFO else 'black'
|
||||
),
|
||||
corner_radius=bradius,
|
||||
call=partial(self._set_entry_val, entry_index, entry, logging.INFO),
|
||||
)
|
||||
index += 1
|
||||
tab.button(
|
||||
'Warning',
|
||||
pos=(x + width - bwidth * 3 + xoffs + 1.0, y + 5.0),
|
||||
size=(bwidth - 2.0, height - 10),
|
||||
size=(bwidth - bspacing, height - 10),
|
||||
label_scale=btextscale,
|
||||
style=(
|
||||
'white_bright'
|
||||
if level == logging.WARNING
|
||||
else 'yellow' if effectivelevel <= logging.WARNING else 'black'
|
||||
),
|
||||
corner_radius=bradius,
|
||||
call=partial(
|
||||
self._set_entry_val, entry_index, entry, logging.WARNING
|
||||
),
|
||||
|
|
@ -601,13 +636,14 @@ class DevConsoleTabLogging(DevConsoleTab):
|
|||
tab.button(
|
||||
'Error',
|
||||
pos=(x + width - bwidth * 2 + xoffs + 1.0, y + 5.0),
|
||||
size=(bwidth - 2.0, height - 10),
|
||||
size=(bwidth - bspacing, height - 10),
|
||||
label_scale=btextscale,
|
||||
style=(
|
||||
'white_bright'
|
||||
if level == logging.ERROR
|
||||
else 'red' if effectivelevel <= logging.ERROR else 'black'
|
||||
),
|
||||
corner_radius=bradius,
|
||||
call=partial(
|
||||
self._set_entry_val, entry_index, entry, logging.ERROR
|
||||
),
|
||||
|
|
@ -616,7 +652,7 @@ class DevConsoleTabLogging(DevConsoleTab):
|
|||
tab.button(
|
||||
'Critical',
|
||||
pos=(x + width - bwidth * 1 + xoffs + 1.0, y + 5.0),
|
||||
size=(bwidth - 2.0, height - 10),
|
||||
size=(bwidth - bspacing, height - 10),
|
||||
label_scale=btextscale,
|
||||
style=(
|
||||
'white_bright'
|
||||
|
|
@ -625,6 +661,7 @@ class DevConsoleTabLogging(DevConsoleTab):
|
|||
'purple' if effectivelevel <= logging.CRITICAL else 'black'
|
||||
)
|
||||
),
|
||||
corner_radius=bradius,
|
||||
call=partial(
|
||||
self._set_entry_val, entry_index, entry, logging.CRITICAL
|
||||
),
|
||||
|
|
|
|||
1
dist/ba_data/python/babase/_logging.py
vendored
1
dist/ba_data/python/babase/_logging.py
vendored
|
|
@ -31,6 +31,7 @@ cloudsublog = logging.getLogger(ClientLoggerName.CLOUD_SUBSCRIPTION.value)
|
|||
accountlog = logging.getLogger(ClientLoggerName.ACCOUNT.value)
|
||||
accountclientv2log = logging.getLogger(ClientLoggerName.ACCOUNT_CLIENT_V2.value)
|
||||
loginadapterlog = logging.getLogger(ClientLoggerName.LOGIN_ADAPTER.value)
|
||||
uilog = logging.getLogger(ClientLoggerName.UI.value)
|
||||
|
||||
|
||||
def description_for_logger(logger: str) -> str | None:
|
||||
|
|
|
|||
314
dist/ba_data/python/baclassic/_appmode.py
vendored
314
dist/ba_data/python/baclassic/_appmode.py
vendored
|
|
@ -1,7 +1,6 @@
|
|||
# Released under the MIT License. See LICENSE for details.
|
||||
#
|
||||
"""Contains ClassicAppMode."""
|
||||
# pylint: disable=too-many-lines
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
|
|
@ -13,8 +12,8 @@ from typing import TYPE_CHECKING, override
|
|||
|
||||
from efro.error import CommunicationError
|
||||
import bacommon.bs
|
||||
import babase
|
||||
import bauiv1
|
||||
from babase import AppMode
|
||||
import bauiv1 as bui
|
||||
from bauiv1lib.connectivity import wait_for_connectivity
|
||||
from bauiv1lib.account.signin import show_sign_in_prompt
|
||||
|
||||
|
|
@ -29,7 +28,7 @@ if TYPE_CHECKING:
|
|||
|
||||
|
||||
# ba_meta export babase.AppMode
|
||||
class ClassicAppMode(babase.AppMode):
|
||||
class ClassicAppMode(AppMode):
|
||||
"""AppMode for the classic BombSquad experience."""
|
||||
|
||||
_ACCOUNT_STATE_CONFIG_KEY = 'ClassicAccountState'
|
||||
|
|
@ -42,17 +41,17 @@ class ClassicAppMode(babase.AppMode):
|
|||
self._on_connectivity_changed_callback: CallbackRegistration | None = (
|
||||
None
|
||||
)
|
||||
self._test_sub: babase.CloudSubscription | None = None
|
||||
self._account_data_sub: babase.CloudSubscription | None = None
|
||||
self._test_sub: bui.CloudSubscription | None = None
|
||||
self._account_data_sub: bui.CloudSubscription | None = None
|
||||
|
||||
self._have_account_values = False
|
||||
self._have_connectivity = False
|
||||
self._current_account_id: str | None = None
|
||||
|
||||
self._purchase_ui_pause: bauiv1.RootUIUpdatePause | None = None
|
||||
self._purchase_ui_pause: bui.RootUIUpdatePause | None = None
|
||||
self._last_tokens_value = 0
|
||||
|
||||
self._purchases_update_timer: babase.AppTimer | None = None
|
||||
self._purchases_update_timer: bui.AppTimer | None = None
|
||||
self._purchase_request_in_flight = False
|
||||
self._target_purchases_state: str | None = None
|
||||
|
||||
|
|
@ -62,18 +61,16 @@ class ClassicAppMode(babase.AppMode):
|
|||
|
||||
@override
|
||||
@classmethod
|
||||
def can_handle_intent(cls, intent: babase.AppIntent) -> bool:
|
||||
def can_handle_intent(cls, intent: bui.AppIntent) -> bool:
|
||||
# We support default and exec intents currently.
|
||||
return isinstance(
|
||||
intent, babase.AppIntentExec | babase.AppIntentDefault
|
||||
)
|
||||
return isinstance(intent, bui.AppIntentExec | bui.AppIntentDefault)
|
||||
|
||||
@override
|
||||
def handle_intent(self, intent: babase.AppIntent) -> None:
|
||||
if isinstance(intent, babase.AppIntentExec):
|
||||
def handle_intent(self, intent: bui.AppIntent) -> None:
|
||||
if isinstance(intent, bui.AppIntentExec):
|
||||
_baclassic.classic_app_mode_handle_app_intent_exec(intent.code)
|
||||
return
|
||||
assert isinstance(intent, babase.AppIntentDefault)
|
||||
assert isinstance(intent, bui.AppIntentDefault)
|
||||
_baclassic.classic_app_mode_handle_app_intent_default()
|
||||
|
||||
@override
|
||||
|
|
@ -82,7 +79,7 @@ class ClassicAppMode(babase.AppMode):
|
|||
# Let the native layer do its thing.
|
||||
_baclassic.classic_app_mode_activate()
|
||||
|
||||
app = babase.app
|
||||
app = bui.app
|
||||
plus = app.plus
|
||||
assert plus is not None
|
||||
|
||||
|
|
@ -160,7 +157,7 @@ class ClassicAppMode(babase.AppMode):
|
|||
@override
|
||||
def on_deactivate(self) -> None:
|
||||
|
||||
classic = babase.app.classic
|
||||
classic = bui.app.classic
|
||||
|
||||
# Store latest league vis vals for any active account.
|
||||
self._save_account_state()
|
||||
|
|
@ -183,11 +180,11 @@ class ClassicAppMode(babase.AppMode):
|
|||
|
||||
@override
|
||||
def on_app_active_changed(self) -> None:
|
||||
if not babase.app.active:
|
||||
if not bui.app.active:
|
||||
# If we're going inactive, ask for the main ui, which should
|
||||
# have the side effect of pausing the action if we're in a
|
||||
# game.
|
||||
babase.request_main_ui()
|
||||
bui.request_main_ui()
|
||||
|
||||
# Also store any league vis state for the active account.
|
||||
# this may be our last chance to do this on mobile.
|
||||
|
|
@ -208,13 +205,13 @@ class ClassicAppMode(babase.AppMode):
|
|||
# need to explicitly kill this pause if we are deactivated since
|
||||
# we wouldn't get the on_purchase_process_end() call; the next
|
||||
# app-mode would.
|
||||
self._purchase_ui_pause = bauiv1.RootUIUpdatePause()
|
||||
self._purchase_ui_pause = bui.RootUIUpdatePause()
|
||||
|
||||
# Also grab our last known token count here to plug into animations.
|
||||
# We need to do this here before the purchase gets submitted so that
|
||||
# we know we're seeing the old value.
|
||||
assert babase.app.classic is not None
|
||||
self._last_tokens_value = babase.app.classic.tokens
|
||||
assert bui.app.classic is not None
|
||||
self._last_tokens_value = bui.app.classic.tokens
|
||||
|
||||
@override
|
||||
def on_purchase_process_end(
|
||||
|
|
@ -259,7 +256,7 @@ class ClassicAppMode(babase.AppMode):
|
|||
'Unhandled item_id in on_purchase_process_end: %s', item_id
|
||||
)
|
||||
|
||||
assert babase.app.classic is not None
|
||||
assert bui.app.classic is not None
|
||||
effects: list[bacommon.bs.ClientEffect] = [
|
||||
bacommon.bs.ClientEffectTokensAnimation(
|
||||
duration=anim_time,
|
||||
|
|
@ -276,23 +273,23 @@ class ClassicAppMode(babase.AppMode):
|
|||
sound=bacommon.bs.ClientEffectSound.Sound.CASH_REGISTER
|
||||
),
|
||||
]
|
||||
babase.app.classic.run_bs_client_effects(effects)
|
||||
bui.app.classic.run_bs_client_effects(effects)
|
||||
|
||||
elif item_id.startswith('gold_pass'):
|
||||
babase.screenmessage(
|
||||
babase.Lstr(
|
||||
bui.screenmessage(
|
||||
bui.Lstr(
|
||||
translate=('serverResponses', 'You got a ${ITEM}!'),
|
||||
subs=[
|
||||
(
|
||||
'${ITEM}',
|
||||
babase.Lstr(resource='goldPass.goldPassText'),
|
||||
bui.Lstr(resource='goldPass.goldPassText'),
|
||||
)
|
||||
],
|
||||
),
|
||||
color=(0, 1, 0),
|
||||
)
|
||||
if babase.asset_loads_allowed():
|
||||
babase.getsimplesound('cashRegister').play()
|
||||
if bui.asset_loads_allowed():
|
||||
bui.getsound('cashRegister').play()
|
||||
|
||||
else:
|
||||
|
||||
|
|
@ -300,15 +297,15 @@ class ClassicAppMode(babase.AppMode):
|
|||
logging.warning(
|
||||
'on_purchase_process_end got unexpected item_id: %s.', item_id
|
||||
)
|
||||
babase.screenmessage(
|
||||
babase.Lstr(
|
||||
bui.screenmessage(
|
||||
bui.Lstr(
|
||||
translate=('serverResponses', 'You got a ${ITEM}!'),
|
||||
subs=[('${ITEM}', item_id)],
|
||||
),
|
||||
color=(0, 1, 0),
|
||||
)
|
||||
if babase.asset_loads_allowed():
|
||||
babase.getsimplesound('cashRegister').play()
|
||||
if bui.asset_loads_allowed():
|
||||
bui.getsound('cashRegister').play()
|
||||
|
||||
def on_engine_will_reset(self) -> None:
|
||||
"""Called just before classic resets the engine.
|
||||
|
|
@ -337,9 +334,9 @@ class ClassicAppMode(babase.AppMode):
|
|||
return
|
||||
|
||||
self._purchase_request_in_flight = True
|
||||
babase.accountlog.debug('Requesting latest purchases state...')
|
||||
bui.accountlog.debug('Requesting latest purchases state...')
|
||||
|
||||
plus = babase.app.plus
|
||||
plus = bui.app.plus
|
||||
assert plus is not None
|
||||
if plus.accounts.primary is None:
|
||||
raise RuntimeError(
|
||||
|
|
@ -349,7 +346,7 @@ class ClassicAppMode(babase.AppMode):
|
|||
with plus.accounts.primary:
|
||||
plus.cloud.send_message_cb(
|
||||
bacommon.bs.GetClassicPurchasesMessage(),
|
||||
on_response=babase.WeakCall(
|
||||
on_response=bui.WeakCall(
|
||||
self._on_get_classic_purchases_response
|
||||
),
|
||||
)
|
||||
|
|
@ -365,12 +362,12 @@ class ClassicAppMode(babase.AppMode):
|
|||
# No biggie; we expect these when offline/etc.
|
||||
pass
|
||||
else:
|
||||
babase.netlog.exception('Error requesting classic purchases.')
|
||||
bui.netlog.exception('Error requesting classic purchases.')
|
||||
return
|
||||
|
||||
# If we're no longer looking for a state, we can abort early.
|
||||
if self._target_purchases_state is None:
|
||||
babase.accountlog.debug(
|
||||
bui.accountlog.debug(
|
||||
'No longer looking for new purchases state; aborting fetch.'
|
||||
)
|
||||
self._purchases_update_timer = None
|
||||
|
|
@ -387,10 +384,10 @@ class ClassicAppMode(babase.AppMode):
|
|||
self._current_purchases = frozenset(response.purchases)
|
||||
self._current_purchases_state = state
|
||||
|
||||
assert babase.app.classic is not None
|
||||
babase.app.classic.purchases = self._current_purchases
|
||||
assert bui.app.classic is not None
|
||||
bui.app.classic.purchases = self._current_purchases
|
||||
|
||||
babase.accountlog.debug(
|
||||
bui.accountlog.debug(
|
||||
'Updated purchases state to %s: (%s items)',
|
||||
state,
|
||||
len(self._current_purchases),
|
||||
|
|
@ -402,15 +399,15 @@ class ClassicAppMode(babase.AppMode):
|
|||
return hashlib.md5(','.join(sorted(purchases)).encode()).hexdigest()
|
||||
|
||||
def _update_for_primary_account(
|
||||
self, account: babase.AccountV2Handle | None
|
||||
self, account: bui.AccountV2Handle | None
|
||||
) -> None:
|
||||
"""Update subscriptions/etc. for a new primary account state."""
|
||||
assert babase.in_logic_thread()
|
||||
plus = babase.app.plus
|
||||
assert bui.in_logic_thread()
|
||||
plus = bui.app.plus
|
||||
|
||||
assert plus is not None
|
||||
|
||||
classic = babase.app.classic
|
||||
classic = bui.app.classic
|
||||
assert classic is not None
|
||||
|
||||
if account is not None:
|
||||
|
|
@ -512,7 +509,7 @@ class ClassicAppMode(babase.AppMode):
|
|||
) -> None:
|
||||
achp = round(val.achievements / max(val.achievements_total, 1) * 100.0)
|
||||
|
||||
babase.accountlog.debug('Got new classic account data.')
|
||||
bui.accountlog.debug('Got new classic account data.')
|
||||
|
||||
chest0 = val.chests.get('0')
|
||||
chest1 = val.chests.get('1')
|
||||
|
|
@ -521,7 +518,7 @@ class ClassicAppMode(babase.AppMode):
|
|||
|
||||
# Keep a few handy values on classic updated with the latest
|
||||
# data.
|
||||
classic = babase.app.classic
|
||||
classic = bui.app.classic
|
||||
assert classic is not None
|
||||
classic.remove_ads = val.remove_ads
|
||||
classic.gold_pass = val.gold_pass
|
||||
|
|
@ -533,14 +530,14 @@ class ClassicAppMode(babase.AppMode):
|
|||
# If they want us to ask for a review (and we haven't yet), do
|
||||
# so.
|
||||
if val.Flag.ASK_FOR_REVIEW in val.flags:
|
||||
cfg = babase.app.config
|
||||
cfg = bui.app.config
|
||||
if (
|
||||
not cfg.get(self._ASKED_FOR_REVIEW_CONFIG_KEY, False)
|
||||
and babase.native_review_request_supported()
|
||||
and bui.native_review_request_supported()
|
||||
):
|
||||
cfg[self._ASKED_FOR_REVIEW_CONFIG_KEY] = True
|
||||
cfg.commit()
|
||||
babase.native_review_request()
|
||||
bui.native_review_request()
|
||||
|
||||
# If someone replaced our purchases in the classic subsystem,
|
||||
# fix it.
|
||||
|
|
@ -556,7 +553,7 @@ class ClassicAppMode(babase.AppMode):
|
|||
self._target_purchases_state is not None
|
||||
and self._current_purchases_state != self._target_purchases_state
|
||||
):
|
||||
babase.accountlog.debug(
|
||||
bui.accountlog.debug(
|
||||
'Account purchases state is %s; we have %s. Will fetch new.',
|
||||
self._target_purchases_state,
|
||||
self._current_purchases_state,
|
||||
|
|
@ -566,7 +563,7 @@ class ClassicAppMode(babase.AppMode):
|
|||
# doing its thing.
|
||||
pass
|
||||
else:
|
||||
self._purchases_update_timer = babase.AppTimer(
|
||||
self._purchases_update_timer = bui.AppTimer(
|
||||
3.456, self._update_purchases, repeat=True
|
||||
)
|
||||
self._possibly_request_purchases()
|
||||
|
|
@ -596,7 +593,7 @@ class ClassicAppMode(babase.AppMode):
|
|||
inbox_count=val.inbox_count,
|
||||
inbox_count_is_max=val.inbox_count_is_max,
|
||||
inbox_announce_text=(
|
||||
babase.Lstr(resource='unclaimedPrizesText').evaluate()
|
||||
bui.Lstr(resource='unclaimedPrizesText').evaluate()
|
||||
if val.inbox_contains_prize
|
||||
else ''
|
||||
),
|
||||
|
|
@ -678,15 +675,15 @@ class ClassicAppMode(babase.AppMode):
|
|||
def _root_ui_menu_press(self) -> None:
|
||||
from babase import menu_press
|
||||
|
||||
ui = babase.app.ui_v1
|
||||
ui = bui.app.ui_v1
|
||||
|
||||
# If *any* main-window is up, kill it and resume play.
|
||||
old_window = ui.get_main_window()
|
||||
if old_window is not None:
|
||||
|
||||
bauiv1.getsound('swish').play()
|
||||
bui.getsound('swish').play()
|
||||
|
||||
classic = babase.app.classic
|
||||
classic = bui.app.classic
|
||||
assert classic is not None
|
||||
classic.resume()
|
||||
|
||||
|
|
@ -698,126 +695,31 @@ class ClassicAppMode(babase.AppMode):
|
|||
def _root_ui_account_press(self) -> None:
|
||||
from bauiv1lib.account.settings import AccountSettingsWindow
|
||||
|
||||
self._auxiliary_window_nav(
|
||||
bui.app.ui_v1.auxiliary_window_activate(
|
||||
win_type=AccountSettingsWindow,
|
||||
win_create_call=lambda: AccountSettingsWindow(
|
||||
origin_widget=bauiv1.get_special_widget('account_button')
|
||||
origin_widget=bui.get_special_widget('account_button')
|
||||
),
|
||||
)
|
||||
|
||||
def _root_ui_squad_press(self) -> None:
|
||||
btn = bauiv1.get_special_widget('squad_button')
|
||||
btn = bui.get_special_widget('squad_button')
|
||||
center = btn.get_screen_space_center()
|
||||
if bauiv1.app.classic is not None:
|
||||
bauiv1.app.classic.party_icon_activate(center)
|
||||
if bui.app.classic is not None:
|
||||
bui.app.classic.party_icon_activate(center)
|
||||
else:
|
||||
logging.warning('party_icon_activate: no classic.')
|
||||
|
||||
def _root_ui_settings_press(self) -> None:
|
||||
from bauiv1lib.settings.allsettings import AllSettingsWindow
|
||||
|
||||
self._auxiliary_window_nav(
|
||||
bui.app.ui_v1.auxiliary_window_activate(
|
||||
win_type=AllSettingsWindow,
|
||||
win_create_call=lambda: AllSettingsWindow(
|
||||
origin_widget=bauiv1.get_special_widget('settings_button')
|
||||
origin_widget=bui.get_special_widget('settings_button')
|
||||
),
|
||||
)
|
||||
|
||||
def _auxiliary_window_nav(
|
||||
self,
|
||||
win_type: type[bauiv1.MainWindow],
|
||||
win_create_call: Callable[[], bauiv1.MainWindow],
|
||||
) -> None:
|
||||
"""Navigate to or away from an Auxiliary window.
|
||||
|
||||
Auxiliary windows can be thought of as 'side quests' in the
|
||||
window hierarchy; places such as settings windows or league
|
||||
ranking windows that the user might want to visit without losing
|
||||
their place in the regular hierarchy.
|
||||
"""
|
||||
# pylint: disable=unidiomatic-typecheck
|
||||
|
||||
ui = babase.app.ui_v1
|
||||
|
||||
current_main_window = ui.get_main_window()
|
||||
|
||||
# Scan our ancestors for auxiliary states matching our type as
|
||||
# well as auxiliary states in general.
|
||||
aux_matching_state: bauiv1.MainWindowState | None = None
|
||||
aux_state: bauiv1.MainWindowState | None = None
|
||||
|
||||
if current_main_window is None:
|
||||
raise RuntimeError(
|
||||
'Not currently handling no-top-level-window case.'
|
||||
)
|
||||
|
||||
state = current_main_window.main_window_back_state
|
||||
while state is not None:
|
||||
assert state.window_type is not None
|
||||
if state.is_auxiliary:
|
||||
if state.window_type is win_type:
|
||||
aux_matching_state = state
|
||||
else:
|
||||
aux_state = state
|
||||
|
||||
state = state.parent
|
||||
|
||||
# If there's an ancestor auxiliary window-state matching our
|
||||
# type, back out past it (example: poking settings, navigating
|
||||
# down a level or two, and then poking settings again should
|
||||
# back out of settings).
|
||||
if aux_matching_state is not None:
|
||||
current_main_window.main_window_back_state = (
|
||||
aux_matching_state.parent
|
||||
)
|
||||
current_main_window.main_window_back()
|
||||
return
|
||||
|
||||
# If there's an ancestory auxiliary state *not* matching our
|
||||
# type, crop the state and swap in our new auxiliary UI
|
||||
# (example: poking settings, then poking account, then poking
|
||||
# back should end up where things were before the settings
|
||||
# poke).
|
||||
if aux_state is not None:
|
||||
# Blow away the window stack and build a fresh one.
|
||||
ui.clear_main_window()
|
||||
ui.set_main_window(
|
||||
win_create_call(),
|
||||
from_window=False, # Disable from-check.
|
||||
back_state=aux_state.parent,
|
||||
suppress_warning=True,
|
||||
is_auxiliary=True,
|
||||
)
|
||||
return
|
||||
|
||||
# Ok, no auxiliary states found. Now if current window is
|
||||
# auxiliary and the type matches, simply do a back.
|
||||
if (
|
||||
current_main_window.main_window_is_auxiliary
|
||||
and type(current_main_window) is win_type
|
||||
):
|
||||
current_main_window.main_window_back()
|
||||
return
|
||||
|
||||
# If current window is auxiliary but type doesn't match,
|
||||
# swap it out for our new auxiliary UI.
|
||||
if current_main_window.main_window_is_auxiliary:
|
||||
ui.clear_main_window()
|
||||
ui.set_main_window(
|
||||
win_create_call(),
|
||||
from_window=False, # Disable from-check.
|
||||
back_state=current_main_window.main_window_back_state,
|
||||
suppress_warning=True,
|
||||
is_auxiliary=True,
|
||||
)
|
||||
return
|
||||
|
||||
# Ok, no existing auxiliary stuff was found period. Just
|
||||
# navigate forward to this UI.
|
||||
current_main_window.main_window_replace(
|
||||
win_create_call(), is_auxiliary=True
|
||||
)
|
||||
|
||||
def _root_ui_achievements_press(self) -> None:
|
||||
from bauiv1lib.achievements import AchievementsWindow
|
||||
|
||||
|
|
@ -825,12 +727,10 @@ class ClassicAppMode(babase.AppMode):
|
|||
return
|
||||
|
||||
wait_for_connectivity(
|
||||
on_connected=lambda: self._auxiliary_window_nav(
|
||||
on_connected=lambda: bui.app.ui_v1.auxiliary_window_activate(
|
||||
win_type=AchievementsWindow,
|
||||
win_create_call=lambda: AchievementsWindow(
|
||||
origin_widget=bauiv1.get_special_widget(
|
||||
'achievements_button'
|
||||
)
|
||||
origin_widget=bui.get_special_widget('achievements_button')
|
||||
),
|
||||
)
|
||||
)
|
||||
|
|
@ -842,10 +742,10 @@ class ClassicAppMode(babase.AppMode):
|
|||
return
|
||||
|
||||
wait_for_connectivity(
|
||||
on_connected=lambda: self._auxiliary_window_nav(
|
||||
on_connected=lambda: bui.app.ui_v1.auxiliary_window_activate(
|
||||
win_type=InboxWindow,
|
||||
win_create_call=lambda: InboxWindow(
|
||||
origin_widget=bauiv1.get_special_widget('inbox_button')
|
||||
origin_widget=bui.get_special_widget('inbox_button')
|
||||
),
|
||||
)
|
||||
)
|
||||
|
|
@ -857,10 +757,10 @@ class ClassicAppMode(babase.AppMode):
|
|||
return
|
||||
|
||||
wait_for_connectivity(
|
||||
on_connected=lambda: self._auxiliary_window_nav(
|
||||
on_connected=lambda: bui.app.ui_v1.auxiliary_window_activate(
|
||||
win_type=StoreBrowserWindow,
|
||||
win_create_call=lambda: StoreBrowserWindow(
|
||||
origin_widget=bauiv1.get_special_widget('store_button')
|
||||
origin_widget=bui.get_special_widget('store_button')
|
||||
),
|
||||
)
|
||||
)
|
||||
|
|
@ -869,14 +769,14 @@ class ClassicAppMode(babase.AppMode):
|
|||
from bauiv1lib.resourcetypeinfo import ResourceTypeInfoWindow
|
||||
|
||||
ResourceTypeInfoWindow(
|
||||
'tickets', origin_widget=bauiv1.get_special_widget('tickets_meter')
|
||||
'tickets', origin_widget=bui.get_special_widget('tickets_meter')
|
||||
)
|
||||
|
||||
def _root_ui_tokens_meter_press(self) -> None:
|
||||
from bauiv1lib.resourcetypeinfo import ResourceTypeInfoWindow
|
||||
|
||||
ResourceTypeInfoWindow(
|
||||
'tokens', origin_widget=bauiv1.get_special_widget('tokens_meter')
|
||||
'tokens', origin_widget=bui.get_special_widget('tokens_meter')
|
||||
)
|
||||
|
||||
def _root_ui_trophy_meter_press(self) -> None:
|
||||
|
|
@ -885,10 +785,10 @@ class ClassicAppMode(babase.AppMode):
|
|||
if not self._ensure_signed_in_v1():
|
||||
return
|
||||
|
||||
self._auxiliary_window_nav(
|
||||
bui.app.ui_v1.auxiliary_window_activate(
|
||||
win_type=LeagueRankWindow,
|
||||
win_create_call=lambda: LeagueRankWindow(
|
||||
origin_widget=bauiv1.get_special_widget('trophy_meter')
|
||||
origin_widget=bui.get_special_widget('trophy_meter')
|
||||
),
|
||||
)
|
||||
|
||||
|
|
@ -896,7 +796,7 @@ class ClassicAppMode(babase.AppMode):
|
|||
from bauiv1lib.resourcetypeinfo import ResourceTypeInfoWindow
|
||||
|
||||
ResourceTypeInfoWindow(
|
||||
'xp', origin_widget=bauiv1.get_special_widget('level_meter')
|
||||
'xp', origin_widget=bui.get_special_widget('level_meter')
|
||||
)
|
||||
|
||||
def _root_ui_inventory_press(self) -> None:
|
||||
|
|
@ -905,19 +805,19 @@ class ClassicAppMode(babase.AppMode):
|
|||
if not self._ensure_signed_in_v1():
|
||||
return
|
||||
|
||||
self._auxiliary_window_nav(
|
||||
bui.app.ui_v1.auxiliary_window_activate(
|
||||
win_type=InventoryWindow,
|
||||
win_create_call=lambda: InventoryWindow(
|
||||
origin_widget=bauiv1.get_special_widget('inventory_button')
|
||||
origin_widget=bui.get_special_widget('inventory_button')
|
||||
),
|
||||
)
|
||||
|
||||
def _ensure_signed_in(self) -> bool:
|
||||
"""Make sure we're signed in (requiring modern v2 accounts)."""
|
||||
plus = bauiv1.app.plus
|
||||
plus = bui.app.plus
|
||||
if plus is None:
|
||||
bauiv1.screenmessage('This requires plus.', color=(1, 0, 0))
|
||||
bauiv1.getsound('error').play()
|
||||
bui.screenmessage('This requires plus.', color=(1, 0, 0))
|
||||
bui.getsound('error').play()
|
||||
return False
|
||||
if plus.accounts.primary is None:
|
||||
show_sign_in_prompt()
|
||||
|
|
@ -926,10 +826,10 @@ class ClassicAppMode(babase.AppMode):
|
|||
|
||||
def _ensure_signed_in_v1(self) -> bool:
|
||||
"""Make sure we're signed in (allowing legacy v1-only accounts)."""
|
||||
plus = bauiv1.app.plus
|
||||
plus = bui.app.plus
|
||||
if plus is None:
|
||||
bauiv1.screenmessage('This requires plus.', color=(1, 0, 0))
|
||||
bauiv1.getsound('error').play()
|
||||
bui.screenmessage('This requires plus.', color=(1, 0, 0))
|
||||
bui.getsound('error').play()
|
||||
return False
|
||||
if plus.get_v1_account_state() != 'signed_in':
|
||||
show_sign_in_prompt()
|
||||
|
|
@ -942,10 +842,10 @@ class ClassicAppMode(babase.AppMode):
|
|||
if not self._ensure_signed_in():
|
||||
return
|
||||
|
||||
self._auxiliary_window_nav(
|
||||
bui.app.ui_v1.auxiliary_window_activate(
|
||||
win_type=GetTokensWindow,
|
||||
win_create_call=lambda: GetTokensWindow(
|
||||
origin_widget=bauiv1.get_special_widget('get_tokens_button')
|
||||
origin_widget=bui.get_special_widget('get_tokens_button')
|
||||
),
|
||||
)
|
||||
|
||||
|
|
@ -980,11 +880,11 @@ class ClassicAppMode(babase.AppMode):
|
|||
raise RuntimeError(f'Invalid index {index}')
|
||||
|
||||
wait_for_connectivity(
|
||||
on_connected=lambda: self._auxiliary_window_nav(
|
||||
on_connected=lambda: bui.app.ui_v1.auxiliary_window_activate(
|
||||
win_type=winclass,
|
||||
win_create_call=lambda: winclass(
|
||||
index=index,
|
||||
origin_widget=bauiv1.get_special_widget(widgetid),
|
||||
origin_widget=bui.get_special_widget(widgetid),
|
||||
),
|
||||
)
|
||||
)
|
||||
|
|
@ -1001,24 +901,24 @@ class ClassicAppMode(babase.AppMode):
|
|||
assert 'a' not in vals
|
||||
vals['a'] = self._current_account_id
|
||||
|
||||
assert babase.app.classic is not None
|
||||
assert bui.app.classic is not None
|
||||
|
||||
assert 'p' not in vals
|
||||
vals['p'] = list(babase.app.classic.purchases)
|
||||
vals['p'] = list(bui.app.classic.purchases)
|
||||
|
||||
cfg = babase.app.config
|
||||
cfg = bui.app.config
|
||||
cfg[self._ACCOUNT_STATE_CONFIG_KEY] = vals
|
||||
cfg.commit()
|
||||
|
||||
def _restore_account_state(self) -> None:
|
||||
# If we've got a stored state for the current account, restore
|
||||
# it.
|
||||
assert babase.app.classic is not None
|
||||
assert bui.app.classic is not None
|
||||
|
||||
if self._current_account_id is None:
|
||||
return
|
||||
|
||||
cfg = babase.app.config
|
||||
cfg = bui.app.config
|
||||
vals = cfg.get(self._ACCOUNT_STATE_CONFIG_KEY)
|
||||
|
||||
if not isinstance(vals, dict):
|
||||
|
|
@ -1036,12 +936,46 @@ class ClassicAppMode(babase.AppMode):
|
|||
if isinstance(purchases, list):
|
||||
|
||||
if not all(isinstance(p, str) for p in purchases):
|
||||
babase.balog.exception('Invalid purchases state on restore.')
|
||||
bui.balog.exception('Invalid purchases state on restore.')
|
||||
else:
|
||||
self._current_purchases = frozenset(purchases)
|
||||
self._current_purchases_state = self._state_from_purchases(
|
||||
purchases
|
||||
)
|
||||
babase.app.classic.purchases = self._current_purchases
|
||||
bui.app.classic.purchases = self._current_purchases
|
||||
|
||||
_baclassic.set_account_state(vals)
|
||||
|
||||
@override
|
||||
def get_dev_console_ui_tab_buttons(
|
||||
self,
|
||||
) -> list[bui.DevConsoleButtonDef]:
|
||||
return [
|
||||
bui.DevConsoleButtonDef(
|
||||
'MainWindow Template',
|
||||
bui.WeakCall(self._main_win_template_press),
|
||||
),
|
||||
bui.DevConsoleButtonDef(
|
||||
'CloudUI Test', bui.WeakCall(self._cloud_ui_test_press)
|
||||
),
|
||||
]
|
||||
|
||||
def _main_win_template_press(self) -> None:
|
||||
from bauiv1lib.template import show_template_main_window
|
||||
|
||||
# Unintuitively, swish sounds come from buttons, not windows.
|
||||
# And dev-console buttons don't make sounds. So we need to
|
||||
# explicitly do so here.
|
||||
bui.getsound('swish').play()
|
||||
|
||||
show_template_main_window()
|
||||
|
||||
def _cloud_ui_test_press(self) -> None:
|
||||
from bauiv1 import show_cloud_ui_window
|
||||
|
||||
# Unintuitively, swish sounds come from buttons, not windows.
|
||||
# And dev-console buttons don't make sounds. So we need to
|
||||
# explicitly do so here.
|
||||
bui.getsound('swish').play()
|
||||
|
||||
show_cloud_ui_window()
|
||||
|
|
|
|||
18
dist/ba_data/python/baclassic/_appsubsystem.py
vendored
18
dist/ba_data/python/baclassic/_appsubsystem.py
vendored
|
|
@ -753,7 +753,7 @@ class ClassicAppSubsystem(babase.AppSubsystem):
|
|||
origin_widget: bauiv1.Widget | None = None,
|
||||
selected_profile: str | None = None,
|
||||
) -> None:
|
||||
"""(internal)"""
|
||||
"""Pop up a browser window from within a game."""
|
||||
from bauiv1lib.profile.browser import ProfileBrowserWindow
|
||||
|
||||
main_window = babase.app.ui_v1.get_main_window()
|
||||
|
|
@ -772,6 +772,7 @@ class ClassicAppSubsystem(babase.AppSubsystem):
|
|||
minimal_toolbar=True,
|
||||
),
|
||||
is_top_level=True,
|
||||
back_state=None,
|
||||
suppress_warning=True,
|
||||
)
|
||||
|
||||
|
|
@ -830,7 +831,10 @@ class ClassicAppSubsystem(babase.AppSubsystem):
|
|||
transition='scale_in', origin_widget=menu_button
|
||||
),
|
||||
is_top_level=True,
|
||||
back_state=None,
|
||||
suppress_warning=True,
|
||||
# Reset selections to default for consistency.
|
||||
restore_shared_state=False,
|
||||
)
|
||||
|
||||
def save_ui_state(self) -> None:
|
||||
|
|
@ -868,7 +872,10 @@ class ClassicAppSubsystem(babase.AppSubsystem):
|
|||
from bauiv1lib.kiosk import KioskWindow
|
||||
|
||||
app.ui_v1.set_main_window(
|
||||
KioskWindow(), is_top_level=True, suppress_warning=True
|
||||
KioskWindow(),
|
||||
is_top_level=True,
|
||||
back_state=None,
|
||||
suppress_warning=True,
|
||||
)
|
||||
else:
|
||||
# If there's a saved ui state, restore that.
|
||||
|
|
@ -881,6 +888,7 @@ class ClassicAppSubsystem(babase.AppSubsystem):
|
|||
app.ui_v1.set_main_window(
|
||||
MainMenuWindow(transition=None),
|
||||
is_top_level=True,
|
||||
back_state=None,
|
||||
suppress_warning=True,
|
||||
)
|
||||
|
||||
|
|
@ -895,17 +903,17 @@ class ClassicAppSubsystem(babase.AppSubsystem):
|
|||
|
||||
@staticmethod
|
||||
def basic_client_ui_button_label_str(
|
||||
label: bacommon.bs.BasicClientUI.ButtonLabel,
|
||||
label: bacommon.bs.BasicCloudDialog.ButtonLabel,
|
||||
) -> babase.Lstr:
|
||||
"""Given a client-ui label, return an Lstr."""
|
||||
import bacommon.bs
|
||||
|
||||
cls = bacommon.bs.BasicClientUI.ButtonLabel
|
||||
cls = bacommon.bs.BasicCloudDialog.ButtonLabel
|
||||
if label is cls.UNKNOWN:
|
||||
# Server should not be sending us unknown stuff; make noise
|
||||
# if they do.
|
||||
logging.error(
|
||||
'Got BasicClientUI.ButtonLabel.UNKNOWN; should not happen.'
|
||||
'Got BasicCloudDialog.ButtonLabel.UNKNOWN; should not happen.'
|
||||
)
|
||||
return babase.Lstr(value='<error>')
|
||||
|
||||
|
|
|
|||
139
dist/ba_data/python/bacommon/bs/__init__.py
vendored
Normal file
139
dist/ba_data/python/bacommon/bs/__init__.py
vendored
Normal file
|
|
@ -0,0 +1,139 @@
|
|||
# Released under the MIT License. See LICENSE for details.
|
||||
#
|
||||
"""Functionality related to bombsquad classic."""
|
||||
|
||||
from bacommon.bs._account import (
|
||||
ClassicAccountLiveData,
|
||||
)
|
||||
from bacommon.bs._bs import (
|
||||
TOKENS1_COUNT,
|
||||
TOKENS2_COUNT,
|
||||
TOKENS3_COUNT,
|
||||
TOKENS4_COUNT,
|
||||
)
|
||||
from bacommon.bs._chest import (
|
||||
ClassicChestAppearance,
|
||||
)
|
||||
from bacommon.bs._clienteffect import (
|
||||
ClientEffect,
|
||||
ClientEffectChestWaitTimeAnimation,
|
||||
ClientEffectDelay,
|
||||
ClientEffectScreenMessage,
|
||||
ClientEffectSound,
|
||||
ClientEffectTicketsAnimation,
|
||||
ClientEffectTokensAnimation,
|
||||
ClientEffectTypeID,
|
||||
ClientEffectUnknown,
|
||||
)
|
||||
from bacommon.bs._clouddialog import (
|
||||
BasicCloudDialog,
|
||||
BasicCloudDialogComponent,
|
||||
BasicCloudDialogBsClassicTourneyResult,
|
||||
BasicCloudDialogComponentLink,
|
||||
BasicCloudDialogComponentText,
|
||||
BasicCloudDialogComponentTypeID,
|
||||
BasicCloudDialogComponentUnknown,
|
||||
BasicCloudDialogDisplayItems,
|
||||
BasicCloudDialogExpireTime,
|
||||
CloudDialog,
|
||||
CloudDialogAction,
|
||||
CloudDialogTypeID,
|
||||
CloudDialogWrapper,
|
||||
UnknownCloudDialog,
|
||||
)
|
||||
from bacommon.bs._cloudui import CloudUITypeID, CloudUI
|
||||
from bacommon.bs._displayitem import (
|
||||
ChestDisplayItem,
|
||||
DisplayItem,
|
||||
DisplayItemTypeID,
|
||||
DisplayItemWrapper,
|
||||
TestDisplayItem,
|
||||
TicketsDisplayItem,
|
||||
TokensDisplayItem,
|
||||
UnknownDisplayItem,
|
||||
)
|
||||
from bacommon.bs._msg import (
|
||||
ChestActionMessage,
|
||||
ChestActionResponse,
|
||||
ChestInfoMessage,
|
||||
ChestInfoResponse,
|
||||
CloudDialogActionMessage,
|
||||
CloudDialogActionResponse,
|
||||
GetClassicPurchasesMessage,
|
||||
GetClassicPurchasesResponse,
|
||||
GlobalProfileCheckMessage,
|
||||
GlobalProfileCheckResponse,
|
||||
InboxRequestMessage,
|
||||
InboxRequestResponse,
|
||||
LegacyRequest,
|
||||
LegacyResponse,
|
||||
PrivatePartyMessage,
|
||||
PrivatePartyResponse,
|
||||
ScoreSubmitMessage,
|
||||
ScoreSubmitResponse,
|
||||
SendInfoMessage,
|
||||
SendInfoResponse,
|
||||
)
|
||||
|
||||
|
||||
__all__ = [
|
||||
'BasicCloudDialog',
|
||||
'BasicCloudDialogComponent',
|
||||
'BasicCloudDialogBsClassicTourneyResult',
|
||||
'BasicCloudDialogComponentLink',
|
||||
'BasicCloudDialogComponentText',
|
||||
'BasicCloudDialogComponentTypeID',
|
||||
'BasicCloudDialogComponentUnknown',
|
||||
'BasicCloudDialogDisplayItems',
|
||||
'BasicCloudDialogExpireTime',
|
||||
'ChestActionMessage',
|
||||
'ChestActionResponse',
|
||||
'ChestDisplayItem',
|
||||
'ChestInfoMessage',
|
||||
'ChestInfoResponse',
|
||||
'ClassicAccountLiveData',
|
||||
'ClassicChestAppearance',
|
||||
'ClientEffect',
|
||||
'ClientEffectChestWaitTimeAnimation',
|
||||
'ClientEffectDelay',
|
||||
'ClientEffectScreenMessage',
|
||||
'ClientEffectSound',
|
||||
'ClientEffectTicketsAnimation',
|
||||
'ClientEffectTokensAnimation',
|
||||
'ClientEffectTypeID',
|
||||
'ClientEffectUnknown',
|
||||
'CloudDialog',
|
||||
'CloudDialogAction',
|
||||
'CloudDialogActionMessage',
|
||||
'CloudDialogActionResponse',
|
||||
'CloudDialogTypeID',
|
||||
'CloudDialogWrapper',
|
||||
'CloudUI',
|
||||
'CloudUITypeID',
|
||||
'DisplayItem',
|
||||
'DisplayItemTypeID',
|
||||
'DisplayItemWrapper',
|
||||
'GetClassicPurchasesMessage',
|
||||
'GetClassicPurchasesResponse',
|
||||
'GlobalProfileCheckMessage',
|
||||
'GlobalProfileCheckResponse',
|
||||
'InboxRequestMessage',
|
||||
'InboxRequestResponse',
|
||||
'LegacyRequest',
|
||||
'LegacyResponse',
|
||||
'PrivatePartyMessage',
|
||||
'PrivatePartyResponse',
|
||||
'ScoreSubmitMessage',
|
||||
'ScoreSubmitResponse',
|
||||
'SendInfoMessage',
|
||||
'SendInfoResponse',
|
||||
'TestDisplayItem',
|
||||
'TicketsDisplayItem',
|
||||
'TOKENS1_COUNT',
|
||||
'TOKENS2_COUNT',
|
||||
'TOKENS3_COUNT',
|
||||
'TOKENS4_COUNT',
|
||||
'TokensDisplayItem',
|
||||
'UnknownCloudDialog',
|
||||
'UnknownDisplayItem',
|
||||
]
|
||||
73
dist/ba_data/python/bacommon/bs/_account.py
vendored
Normal file
73
dist/ba_data/python/bacommon/bs/_account.py
vendored
Normal file
|
|
@ -0,0 +1,73 @@
|
|||
# Released under the MIT License. See LICENSE for details.
|
||||
#
|
||||
"""BombSquad specific bits."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import datetime
|
||||
from enum import Enum
|
||||
from dataclasses import dataclass
|
||||
from typing import Annotated
|
||||
|
||||
from efro.dataclassio import ioprepped, IOAttrs
|
||||
from bacommon.bs._chest import ClassicChestAppearance
|
||||
|
||||
|
||||
@ioprepped
|
||||
@dataclass
|
||||
class ClassicAccountLiveData:
|
||||
"""Live account data fed to the client in the bs classic app mode."""
|
||||
|
||||
@dataclass
|
||||
class Chest:
|
||||
"""A lovely chest."""
|
||||
|
||||
appearance: Annotated[
|
||||
ClassicChestAppearance,
|
||||
IOAttrs('a', enum_fallback=ClassicChestAppearance.UNKNOWN),
|
||||
]
|
||||
create_time: Annotated[datetime.datetime, IOAttrs('c')]
|
||||
unlock_time: Annotated[datetime.datetime, IOAttrs('t')]
|
||||
unlock_tokens: Annotated[int, IOAttrs('k')]
|
||||
ad_allow_time: Annotated[datetime.datetime | None, IOAttrs('at')]
|
||||
|
||||
class LeagueType(Enum):
|
||||
"""Type of league we are in."""
|
||||
|
||||
BRONZE = 'b'
|
||||
SILVER = 's'
|
||||
GOLD = 'g'
|
||||
DIAMOND = 'd'
|
||||
|
||||
class Flag(Enum):
|
||||
"""Flags set for our account."""
|
||||
|
||||
ASK_FOR_REVIEW = 'r'
|
||||
|
||||
tickets: Annotated[int, IOAttrs('ti')]
|
||||
|
||||
tokens: Annotated[int, IOAttrs('to')]
|
||||
gold_pass: Annotated[bool, IOAttrs('g')]
|
||||
remove_ads: Annotated[bool, IOAttrs('r')]
|
||||
|
||||
achievements: Annotated[int, IOAttrs('a')]
|
||||
achievements_total: Annotated[int, IOAttrs('at')]
|
||||
|
||||
league_type: Annotated[LeagueType | None, IOAttrs('lt')]
|
||||
league_num: Annotated[int | None, IOAttrs('ln')]
|
||||
league_rank: Annotated[int | None, IOAttrs('lr')]
|
||||
|
||||
level: Annotated[int, IOAttrs('lv')]
|
||||
xp: Annotated[int, IOAttrs('xp')]
|
||||
xpmax: Annotated[int, IOAttrs('xpm')]
|
||||
|
||||
inbox_count: Annotated[int, IOAttrs('ibc')]
|
||||
inbox_count_is_max: Annotated[bool, IOAttrs('ibcm')]
|
||||
inbox_contains_prize: Annotated[bool, IOAttrs('icp')]
|
||||
|
||||
chests: Annotated[dict[str, Chest], IOAttrs('c')]
|
||||
|
||||
# State id of our purchases for builds 22459+.
|
||||
purchases_state: Annotated[str | None, IOAttrs('p')]
|
||||
|
||||
flags: Annotated[set[Flag], IOAttrs('f', soft_default_factory=set)]
|
||||
9
dist/ba_data/python/bacommon/bs/_bs.py
vendored
Normal file
9
dist/ba_data/python/bacommon/bs/_bs.py
vendored
Normal file
|
|
@ -0,0 +1,9 @@
|
|||
# Released under the MIT License. See LICENSE for details.
|
||||
#
|
||||
"""BombSquad specific bits."""
|
||||
|
||||
# Token counts for our various packs.
|
||||
TOKENS1_COUNT = 50
|
||||
TOKENS2_COUNT = 500
|
||||
TOKENS3_COUNT = 1200
|
||||
TOKENS4_COUNT = 2600
|
||||
46
dist/ba_data/python/bacommon/bs/_chest.py
vendored
Normal file
46
dist/ba_data/python/bacommon/bs/_chest.py
vendored
Normal file
|
|
@ -0,0 +1,46 @@
|
|||
# Released under the MIT License. See LICENSE for details.
|
||||
#
|
||||
"""BombSquad specific bits."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from enum import Enum
|
||||
from typing import assert_never
|
||||
|
||||
|
||||
class ClassicChestAppearance(Enum):
|
||||
"""Appearances bombsquad classic chests can have."""
|
||||
|
||||
UNKNOWN = 'u'
|
||||
DEFAULT = 'd'
|
||||
L1 = 'l1'
|
||||
L2 = 'l2'
|
||||
L3 = 'l3'
|
||||
L4 = 'l4'
|
||||
L5 = 'l5'
|
||||
L6 = 'l6'
|
||||
|
||||
@property
|
||||
def pretty_name(self) -> str:
|
||||
"""Pretty name for the chest in English."""
|
||||
# pylint: disable=too-many-return-statements
|
||||
cls = type(self)
|
||||
|
||||
if self is cls.UNKNOWN:
|
||||
return 'Unknown Chest'
|
||||
if self is cls.DEFAULT:
|
||||
return 'Chest'
|
||||
if self is cls.L1:
|
||||
return 'L1 Chest'
|
||||
if self is cls.L2:
|
||||
return 'L2 Chest'
|
||||
if self is cls.L3:
|
||||
return 'L3 Chest'
|
||||
if self is cls.L4:
|
||||
return 'L4 Chest'
|
||||
if self is cls.L5:
|
||||
return 'L5 Chest'
|
||||
if self is cls.L6:
|
||||
return 'L6 Chest'
|
||||
|
||||
assert_never(self)
|
||||
180
dist/ba_data/python/bacommon/bs/_clienteffect.py
vendored
Normal file
180
dist/ba_data/python/bacommon/bs/_clienteffect.py
vendored
Normal file
|
|
@ -0,0 +1,180 @@
|
|||
# Released under the MIT License. See LICENSE for details.
|
||||
#
|
||||
"""ClientEffect related functionality."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import datetime
|
||||
from enum import Enum
|
||||
from dataclasses import dataclass, field
|
||||
from typing import Annotated, override, assert_never
|
||||
|
||||
from efro.dataclassio import ioprepped, IOAttrs, IOMultiType
|
||||
|
||||
|
||||
class ClientEffectTypeID(Enum):
|
||||
"""Type ID for each of our subclasses."""
|
||||
|
||||
UNKNOWN = 'u'
|
||||
SCREEN_MESSAGE = 'm'
|
||||
SOUND = 's'
|
||||
DELAY = 'd'
|
||||
CHEST_WAIT_TIME_ANIMATION = 't'
|
||||
TICKETS_ANIMATION = 'ta'
|
||||
TOKENS_ANIMATION = 'toa'
|
||||
|
||||
|
||||
class ClientEffect(IOMultiType[ClientEffectTypeID]):
|
||||
"""Something that can happen on the client.
|
||||
|
||||
This can include screen messages, sounds, visual effects, etc.
|
||||
"""
|
||||
|
||||
@override
|
||||
@classmethod
|
||||
def get_type_id(cls) -> ClientEffectTypeID:
|
||||
# Require child classes to supply this themselves. If we did a
|
||||
# full type registry/lookup here it would require us to import
|
||||
# everything and would prevent lazy loading.
|
||||
raise NotImplementedError()
|
||||
|
||||
@override
|
||||
@classmethod
|
||||
def get_type(cls, type_id: ClientEffectTypeID) -> type[ClientEffect]:
|
||||
"""Return the subclass for each of our type-ids."""
|
||||
# pylint: disable=cyclic-import
|
||||
# pylint: disable=too-many-return-statements
|
||||
|
||||
t = ClientEffectTypeID
|
||||
if type_id is t.UNKNOWN:
|
||||
return ClientEffectUnknown
|
||||
if type_id is t.SCREEN_MESSAGE:
|
||||
return ClientEffectScreenMessage
|
||||
if type_id is t.SOUND:
|
||||
return ClientEffectSound
|
||||
if type_id is t.DELAY:
|
||||
return ClientEffectDelay
|
||||
if type_id is t.CHEST_WAIT_TIME_ANIMATION:
|
||||
return ClientEffectChestWaitTimeAnimation
|
||||
if type_id is t.TICKETS_ANIMATION:
|
||||
return ClientEffectTicketsAnimation
|
||||
if type_id is t.TOKENS_ANIMATION:
|
||||
return ClientEffectTokensAnimation
|
||||
|
||||
# Important to make sure we provide all types.
|
||||
assert_never(type_id)
|
||||
|
||||
@override
|
||||
@classmethod
|
||||
def get_unknown_type_fallback(cls) -> ClientEffect:
|
||||
# If we encounter some future message type we don't know
|
||||
# anything about, drop in a placeholder.
|
||||
return ClientEffectUnknown()
|
||||
|
||||
|
||||
@ioprepped
|
||||
@dataclass
|
||||
class ClientEffectUnknown(ClientEffect):
|
||||
"""Fallback substitute for types we don't recognize."""
|
||||
|
||||
@override
|
||||
@classmethod
|
||||
def get_type_id(cls) -> ClientEffectTypeID:
|
||||
return ClientEffectTypeID.UNKNOWN
|
||||
|
||||
|
||||
@ioprepped
|
||||
@dataclass
|
||||
class ClientEffectScreenMessage(ClientEffect):
|
||||
"""Display a screen-message."""
|
||||
|
||||
message: Annotated[str, IOAttrs('m')]
|
||||
subs: Annotated[list[str], IOAttrs('s')] = field(default_factory=list)
|
||||
color: Annotated[tuple[float, float, float], IOAttrs('c')] = (1.0, 1.0, 1.0)
|
||||
|
||||
@override
|
||||
@classmethod
|
||||
def get_type_id(cls) -> ClientEffectTypeID:
|
||||
return ClientEffectTypeID.SCREEN_MESSAGE
|
||||
|
||||
|
||||
@ioprepped
|
||||
@dataclass
|
||||
class ClientEffectSound(ClientEffect):
|
||||
"""Play a sound."""
|
||||
|
||||
class Sound(Enum):
|
||||
"""Sounds that can be made alongside the message."""
|
||||
|
||||
UNKNOWN = 'u'
|
||||
CASH_REGISTER = 'c'
|
||||
ERROR = 'e'
|
||||
POWER_DOWN = 'p'
|
||||
GUN_COCKING = 'g'
|
||||
|
||||
sound: Annotated[Sound, IOAttrs('s', enum_fallback=Sound.UNKNOWN)]
|
||||
volume: Annotated[float, IOAttrs('v')] = 1.0
|
||||
|
||||
@override
|
||||
@classmethod
|
||||
def get_type_id(cls) -> ClientEffectTypeID:
|
||||
return ClientEffectTypeID.SOUND
|
||||
|
||||
|
||||
@ioprepped
|
||||
@dataclass
|
||||
class ClientEffectChestWaitTimeAnimation(ClientEffect):
|
||||
"""Animate chest wait time changing."""
|
||||
|
||||
chestid: Annotated[str, IOAttrs('c')]
|
||||
duration: Annotated[float, IOAttrs('u')]
|
||||
startvalue: Annotated[datetime.datetime, IOAttrs('o')]
|
||||
endvalue: Annotated[datetime.datetime, IOAttrs('n')]
|
||||
|
||||
@override
|
||||
@classmethod
|
||||
def get_type_id(cls) -> ClientEffectTypeID:
|
||||
return ClientEffectTypeID.CHEST_WAIT_TIME_ANIMATION
|
||||
|
||||
|
||||
@ioprepped
|
||||
@dataclass
|
||||
class ClientEffectTicketsAnimation(ClientEffect):
|
||||
"""Animate tickets count."""
|
||||
|
||||
duration: Annotated[float, IOAttrs('u')]
|
||||
startvalue: Annotated[int, IOAttrs('s')]
|
||||
endvalue: Annotated[int, IOAttrs('e')]
|
||||
|
||||
@override
|
||||
@classmethod
|
||||
def get_type_id(cls) -> ClientEffectTypeID:
|
||||
return ClientEffectTypeID.TICKETS_ANIMATION
|
||||
|
||||
|
||||
@ioprepped
|
||||
@dataclass
|
||||
class ClientEffectTokensAnimation(ClientEffect):
|
||||
"""Animate tokens count."""
|
||||
|
||||
duration: Annotated[float, IOAttrs('u')]
|
||||
startvalue: Annotated[int, IOAttrs('s')]
|
||||
endvalue: Annotated[int, IOAttrs('e')]
|
||||
|
||||
@override
|
||||
@classmethod
|
||||
def get_type_id(cls) -> ClientEffectTypeID:
|
||||
return ClientEffectTypeID.TOKENS_ANIMATION
|
||||
|
||||
|
||||
@ioprepped
|
||||
@dataclass
|
||||
class ClientEffectDelay(ClientEffect):
|
||||
"""Delay effect processing."""
|
||||
|
||||
seconds: Annotated[float, IOAttrs('s')]
|
||||
|
||||
@override
|
||||
@classmethod
|
||||
def get_type_id(cls) -> ClientEffectTypeID:
|
||||
return ClientEffectTypeID.DELAY
|
||||
308
dist/ba_data/python/bacommon/bs/_clouddialog.py
vendored
Normal file
308
dist/ba_data/python/bacommon/bs/_clouddialog.py
vendored
Normal file
|
|
@ -0,0 +1,308 @@
|
|||
# Released under the MIT License. See LICENSE for details.
|
||||
#
|
||||
"""Simple cloud-defined UIs for things like notifications."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import datetime
|
||||
from enum import Enum
|
||||
from dataclasses import dataclass, field
|
||||
from typing import Annotated, override, assert_never
|
||||
|
||||
from efro.dataclassio import ioprepped, IOAttrs, IOMultiType
|
||||
|
||||
from bacommon.bs._displayitem import DisplayItemWrapper
|
||||
|
||||
|
||||
class CloudDialogTypeID(Enum):
|
||||
"""Type ID for each of our subclasses."""
|
||||
|
||||
UNKNOWN = 'u'
|
||||
BASIC = 'b'
|
||||
|
||||
|
||||
class CloudDialog(IOMultiType[CloudDialogTypeID]):
|
||||
"""Small self-contained ui bit provided by the cloud.
|
||||
|
||||
These take care of updating and/or dismissing themselves based on
|
||||
user input. Useful for things such as inbox messages. For more
|
||||
complex UI construction, look at :class:`CloudUI`.
|
||||
"""
|
||||
|
||||
@override
|
||||
@classmethod
|
||||
def get_type_id(cls) -> CloudDialogTypeID:
|
||||
# Require child classes to supply this themselves. If we did a
|
||||
# full type registry/lookup here it would require us to import
|
||||
# everything and would prevent lazy loading.
|
||||
raise NotImplementedError()
|
||||
|
||||
@override
|
||||
@classmethod
|
||||
def get_type(cls, type_id: CloudDialogTypeID) -> type[CloudDialog]:
|
||||
"""Return the subclass for each of our type-ids."""
|
||||
# pylint: disable=cyclic-import
|
||||
out: type[CloudDialog]
|
||||
|
||||
t = CloudDialogTypeID
|
||||
if type_id is t.UNKNOWN:
|
||||
out = UnknownCloudDialog
|
||||
elif type_id is t.BASIC:
|
||||
out = BasicCloudDialog
|
||||
else:
|
||||
# Important to make sure we provide all types.
|
||||
assert_never(type_id)
|
||||
return out
|
||||
|
||||
@override
|
||||
@classmethod
|
||||
def get_unknown_type_fallback(cls) -> CloudDialog:
|
||||
# If we encounter some future message type we don't know
|
||||
# anything about, drop in a placeholder.
|
||||
return UnknownCloudDialog()
|
||||
|
||||
|
||||
@ioprepped
|
||||
@dataclass
|
||||
class UnknownCloudDialog(CloudDialog):
|
||||
"""Fallback type for unrecognized entries."""
|
||||
|
||||
@override
|
||||
@classmethod
|
||||
def get_type_id(cls) -> CloudDialogTypeID:
|
||||
return CloudDialogTypeID.UNKNOWN
|
||||
|
||||
|
||||
class BasicCloudDialogComponentTypeID(Enum):
|
||||
"""Type ID for each of our subclasses."""
|
||||
|
||||
UNKNOWN = 'u'
|
||||
TEXT = 't'
|
||||
LINK = 'l'
|
||||
BS_CLASSIC_TOURNEY_RESULT = 'ct'
|
||||
DISPLAY_ITEMS = 'di'
|
||||
EXPIRE_TIME = 'd'
|
||||
|
||||
|
||||
class BasicCloudDialogComponent(IOMultiType[BasicCloudDialogComponentTypeID]):
|
||||
"""Top level class for our multitype."""
|
||||
|
||||
@override
|
||||
@classmethod
|
||||
def get_type_id(cls) -> BasicCloudDialogComponentTypeID:
|
||||
# Require child classes to supply this themselves. If we did a
|
||||
# full type registry/lookup here it would require us to import
|
||||
# everything and would prevent lazy loading.
|
||||
raise NotImplementedError()
|
||||
|
||||
@override
|
||||
@classmethod
|
||||
def get_type(
|
||||
cls, type_id: BasicCloudDialogComponentTypeID
|
||||
) -> type[BasicCloudDialogComponent]:
|
||||
"""Return the subclass for each of our type-ids."""
|
||||
# pylint: disable=cyclic-import
|
||||
|
||||
t = BasicCloudDialogComponentTypeID
|
||||
if type_id is t.UNKNOWN:
|
||||
return BasicCloudDialogComponentUnknown
|
||||
if type_id is t.TEXT:
|
||||
return BasicCloudDialogComponentText
|
||||
if type_id is t.LINK:
|
||||
return BasicCloudDialogComponentLink
|
||||
if type_id is t.BS_CLASSIC_TOURNEY_RESULT:
|
||||
return BasicCloudDialogBsClassicTourneyResult
|
||||
if type_id is t.DISPLAY_ITEMS:
|
||||
return BasicCloudDialogDisplayItems
|
||||
if type_id is t.EXPIRE_TIME:
|
||||
return BasicCloudDialogExpireTime
|
||||
|
||||
# Important to make sure we provide all types.
|
||||
assert_never(type_id)
|
||||
|
||||
@override
|
||||
@classmethod
|
||||
def get_unknown_type_fallback(cls) -> BasicCloudDialogComponent:
|
||||
# If we encounter some future message type we don't know
|
||||
# anything about, drop in a placeholder.
|
||||
return BasicCloudDialogComponentUnknown()
|
||||
|
||||
|
||||
@ioprepped
|
||||
@dataclass
|
||||
class BasicCloudDialogComponentUnknown(BasicCloudDialogComponent):
|
||||
"""An unknown basic client component type.
|
||||
|
||||
In practice these should never show up since the master-server
|
||||
generates these on the fly for the client and so should not send
|
||||
clients one they can't digest.
|
||||
"""
|
||||
|
||||
@override
|
||||
@classmethod
|
||||
def get_type_id(cls) -> BasicCloudDialogComponentTypeID:
|
||||
return BasicCloudDialogComponentTypeID.UNKNOWN
|
||||
|
||||
|
||||
@ioprepped
|
||||
@dataclass
|
||||
class BasicCloudDialogComponentText(BasicCloudDialogComponent):
|
||||
"""Show some text in the inbox message."""
|
||||
|
||||
text: Annotated[str, IOAttrs('t')]
|
||||
subs: Annotated[list[str], IOAttrs('s', store_default=False)] = field(
|
||||
default_factory=list
|
||||
)
|
||||
scale: Annotated[float, IOAttrs('sc', store_default=False)] = 1.0
|
||||
color: Annotated[
|
||||
tuple[float, float, float, float], IOAttrs('c', store_default=False)
|
||||
] = (1.0, 1.0, 1.0, 1.0)
|
||||
spacing_top: Annotated[float, IOAttrs('st', store_default=False)] = 0.0
|
||||
spacing_bottom: Annotated[float, IOAttrs('sb', store_default=False)] = 0.0
|
||||
|
||||
@override
|
||||
@classmethod
|
||||
def get_type_id(cls) -> BasicCloudDialogComponentTypeID:
|
||||
return BasicCloudDialogComponentTypeID.TEXT
|
||||
|
||||
|
||||
@ioprepped
|
||||
@dataclass
|
||||
class BasicCloudDialogComponentLink(BasicCloudDialogComponent):
|
||||
"""Show a link in the inbox message."""
|
||||
|
||||
url: Annotated[str, IOAttrs('u')]
|
||||
label: Annotated[str, IOAttrs('l')]
|
||||
subs: Annotated[list[str], IOAttrs('s', store_default=False)] = field(
|
||||
default_factory=list
|
||||
)
|
||||
spacing_top: Annotated[float, IOAttrs('st', store_default=False)] = 0.0
|
||||
spacing_bottom: Annotated[float, IOAttrs('sb', store_default=False)] = 0.0
|
||||
|
||||
@override
|
||||
@classmethod
|
||||
def get_type_id(cls) -> BasicCloudDialogComponentTypeID:
|
||||
return BasicCloudDialogComponentTypeID.LINK
|
||||
|
||||
|
||||
@ioprepped
|
||||
@dataclass
|
||||
class BasicCloudDialogBsClassicTourneyResult(BasicCloudDialogComponent):
|
||||
"""Show info about a classic tourney."""
|
||||
|
||||
tournament_id: Annotated[str, IOAttrs('t')]
|
||||
game: Annotated[str, IOAttrs('g')]
|
||||
players: Annotated[int, IOAttrs('p')]
|
||||
rank: Annotated[int, IOAttrs('r')]
|
||||
trophy: Annotated[str | None, IOAttrs('tr')]
|
||||
prizes: Annotated[list[DisplayItemWrapper], IOAttrs('pr')]
|
||||
|
||||
@override
|
||||
@classmethod
|
||||
def get_type_id(cls) -> BasicCloudDialogComponentTypeID:
|
||||
return BasicCloudDialogComponentTypeID.BS_CLASSIC_TOURNEY_RESULT
|
||||
|
||||
|
||||
@ioprepped
|
||||
@dataclass
|
||||
class BasicCloudDialogDisplayItems(BasicCloudDialogComponent):
|
||||
"""Show some display-items."""
|
||||
|
||||
items: Annotated[list[DisplayItemWrapper], IOAttrs('d')]
|
||||
width: Annotated[float, IOAttrs('w')] = 100.0
|
||||
spacing_top: Annotated[float, IOAttrs('st', store_default=False)] = 0.0
|
||||
spacing_bottom: Annotated[float, IOAttrs('sb', store_default=False)] = 0.0
|
||||
|
||||
@override
|
||||
@classmethod
|
||||
def get_type_id(cls) -> BasicCloudDialogComponentTypeID:
|
||||
return BasicCloudDialogComponentTypeID.DISPLAY_ITEMS
|
||||
|
||||
|
||||
@ioprepped
|
||||
@dataclass
|
||||
class BasicCloudDialogExpireTime(BasicCloudDialogComponent):
|
||||
"""Show expire-time."""
|
||||
|
||||
time: Annotated[datetime.datetime, IOAttrs('d')]
|
||||
spacing_top: Annotated[float, IOAttrs('st', store_default=False)] = 0.0
|
||||
spacing_bottom: Annotated[float, IOAttrs('sb', store_default=False)] = 0.0
|
||||
|
||||
@override
|
||||
@classmethod
|
||||
def get_type_id(cls) -> BasicCloudDialogComponentTypeID:
|
||||
return BasicCloudDialogComponentTypeID.EXPIRE_TIME
|
||||
|
||||
|
||||
@ioprepped
|
||||
@dataclass
|
||||
class BasicCloudDialog(CloudDialog):
|
||||
"""A basic UI for the client."""
|
||||
|
||||
class ButtonLabel(Enum):
|
||||
"""Distinct button labels we support."""
|
||||
|
||||
UNKNOWN = 'u'
|
||||
OK = 'o'
|
||||
APPLY = 'a'
|
||||
CANCEL = 'c'
|
||||
ACCEPT = 'ac'
|
||||
DECLINE = 'dn'
|
||||
IGNORE = 'ig'
|
||||
CLAIM = 'cl'
|
||||
DISCARD = 'd'
|
||||
|
||||
class InteractionStyle(Enum):
|
||||
"""Overall interaction styles we support."""
|
||||
|
||||
UNKNOWN = 'u'
|
||||
BUTTON_POSITIVE = 'p'
|
||||
BUTTON_POSITIVE_NEGATIVE = 'pn'
|
||||
|
||||
components: Annotated[list[BasicCloudDialogComponent], IOAttrs('s')]
|
||||
|
||||
interaction_style: Annotated[
|
||||
InteractionStyle, IOAttrs('i', enum_fallback=InteractionStyle.UNKNOWN)
|
||||
] = InteractionStyle.BUTTON_POSITIVE
|
||||
|
||||
button_label_positive: Annotated[
|
||||
ButtonLabel, IOAttrs('p', enum_fallback=ButtonLabel.UNKNOWN)
|
||||
] = ButtonLabel.OK
|
||||
|
||||
button_label_negative: Annotated[
|
||||
ButtonLabel, IOAttrs('n', enum_fallback=ButtonLabel.UNKNOWN)
|
||||
] = ButtonLabel.CANCEL
|
||||
|
||||
@override
|
||||
@classmethod
|
||||
def get_type_id(cls) -> CloudDialogTypeID:
|
||||
return CloudDialogTypeID.BASIC
|
||||
|
||||
def contains_unknown_elements(self) -> bool:
|
||||
"""Whether something within us is an unknown type or enum."""
|
||||
return (
|
||||
self.interaction_style is self.InteractionStyle.UNKNOWN
|
||||
or self.button_label_positive is self.ButtonLabel.UNKNOWN
|
||||
or self.button_label_negative is self.ButtonLabel.UNKNOWN
|
||||
or any(
|
||||
c.get_type_id() is BasicCloudDialogComponentTypeID.UNKNOWN
|
||||
for c in self.components
|
||||
)
|
||||
)
|
||||
|
||||
|
||||
@ioprepped
|
||||
@dataclass
|
||||
class CloudDialogWrapper:
|
||||
"""Wrapper for a CloudDialog and its common data."""
|
||||
|
||||
id: Annotated[str, IOAttrs('i')]
|
||||
createtime: Annotated[datetime.datetime, IOAttrs('c')]
|
||||
ui: Annotated[CloudDialog, IOAttrs('e')]
|
||||
|
||||
|
||||
class CloudDialogAction(Enum):
|
||||
"""Types of actions we can run."""
|
||||
|
||||
BUTTON_PRESS_POSITIVE = 'p'
|
||||
BUTTON_PRESS_NEGATIVE = 'n'
|
||||
180
dist/ba_data/python/bacommon/bs/_cloudui.py
vendored
Normal file
180
dist/ba_data/python/bacommon/bs/_cloudui.py
vendored
Normal file
|
|
@ -0,0 +1,180 @@
|
|||
# Released under the MIT License. See LICENSE for details.
|
||||
#
|
||||
"""Full UIs defined in the cloud - similar to a basic form of html"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from enum import Enum
|
||||
from dataclasses import dataclass
|
||||
from typing import Annotated, override, assert_never
|
||||
|
||||
from efro.dataclassio import ioprepped, IOAttrs, IOMultiType
|
||||
|
||||
|
||||
class CloudUITypeID(Enum):
|
||||
"""Type ID for each of our subclasses."""
|
||||
|
||||
UNKNOWN = 'u'
|
||||
V1 = 'v1'
|
||||
|
||||
|
||||
class CloudUI(IOMultiType[CloudUITypeID]):
|
||||
"""UI defined by the cloud.
|
||||
|
||||
Conceptually similar to a basic html page, except using app UI.
|
||||
"""
|
||||
|
||||
@override
|
||||
@classmethod
|
||||
def get_type_id(cls) -> CloudUITypeID:
|
||||
# Require child classes to supply this themselves. If we did a
|
||||
# full type registry/lookup here it would require us to import
|
||||
# everything and would prevent lazy loading.
|
||||
raise NotImplementedError()
|
||||
|
||||
@override
|
||||
@classmethod
|
||||
def get_type(cls, type_id: CloudUITypeID) -> type[CloudUI]:
|
||||
"""Return the subclass for each of our type-ids."""
|
||||
# pylint: disable=cyclic-import
|
||||
out: type[CloudUI]
|
||||
|
||||
t = CloudUITypeID
|
||||
if type_id is t.UNKNOWN:
|
||||
out = UnknownCloudUI
|
||||
elif type_id is t.V1:
|
||||
out = V1CloudUI
|
||||
else:
|
||||
# Important to make sure we provide all types.
|
||||
assert_never(type_id)
|
||||
return out
|
||||
|
||||
@override
|
||||
@classmethod
|
||||
def get_unknown_type_fallback(cls) -> CloudUI:
|
||||
# If we encounter some future message type we don't know
|
||||
# anything about, drop in a placeholder.
|
||||
return UnknownCloudUI()
|
||||
|
||||
|
||||
@ioprepped
|
||||
@dataclass
|
||||
class UnknownCloudUI(CloudUI):
|
||||
"""Fallback type for unrecognized UI types.
|
||||
|
||||
Will show the client a 'cannot display this UI' placeholder page.
|
||||
"""
|
||||
|
||||
@override
|
||||
@classmethod
|
||||
def get_type_id(cls) -> CloudUITypeID:
|
||||
return CloudUITypeID.UNKNOWN
|
||||
|
||||
|
||||
class V1CloudUIComponentTypeID(Enum):
|
||||
"""Type ID for each of our subclasses."""
|
||||
|
||||
UNKNOWN = 'u'
|
||||
TEXT = 't'
|
||||
|
||||
|
||||
class V1CloudUIComponent(IOMultiType[V1CloudUIComponentTypeID]):
|
||||
"""Top level class for our multitype."""
|
||||
|
||||
@override
|
||||
@classmethod
|
||||
def get_type_id(cls) -> V1CloudUIComponentTypeID:
|
||||
# Require child classes to supply this themselves. If we did a
|
||||
# full type registry/lookup here it would require us to import
|
||||
# everything and would prevent lazy loading.
|
||||
raise NotImplementedError()
|
||||
|
||||
@override
|
||||
@classmethod
|
||||
def get_type(
|
||||
cls, type_id: V1CloudUIComponentTypeID
|
||||
) -> type[V1CloudUIComponent]:
|
||||
"""Return the subclass for each of our type-ids."""
|
||||
# pylint: disable=cyclic-import
|
||||
|
||||
t = V1CloudUIComponentTypeID
|
||||
if type_id is t.UNKNOWN:
|
||||
return V1CloudUIComponentUnknown
|
||||
if type_id is t.TEXT:
|
||||
return V1CloudUIComponentText
|
||||
|
||||
# Important to make sure we provide all types.
|
||||
assert_never(type_id)
|
||||
|
||||
@override
|
||||
@classmethod
|
||||
def get_unknown_type_fallback(cls) -> V1CloudUIComponent:
|
||||
# If we encounter some future message type we don't know
|
||||
# anything about, drop in a placeholder.
|
||||
return V1CloudUIComponentUnknown()
|
||||
|
||||
|
||||
@ioprepped
|
||||
@dataclass
|
||||
class V1CloudUIComponentUnknown(V1CloudUIComponent):
|
||||
"""An unknown basic client component type.
|
||||
|
||||
In practice these should never show up since the master-server
|
||||
generates these on the fly for the client and so should not send
|
||||
clients one they can't digest.
|
||||
"""
|
||||
|
||||
@override
|
||||
@classmethod
|
||||
def get_type_id(cls) -> V1CloudUIComponentTypeID:
|
||||
return V1CloudUIComponentTypeID.UNKNOWN
|
||||
|
||||
|
||||
@ioprepped
|
||||
@dataclass
|
||||
class V1CloudUIComponentText(V1CloudUIComponent):
|
||||
"""Show some text over a button."""
|
||||
|
||||
text: Annotated[str, IOAttrs('t')]
|
||||
# position: Annotated[float, IOAttrs('p', store_default=False)] = 1.0
|
||||
# scale: Annotated[float, IOAttrs('s', store_default=False)] = 1.0
|
||||
# color: Annotated[
|
||||
# tuple[float, float, float, float], IOAttrs('c', store_default=False)
|
||||
# ] = (1.0, 1.0, 1.0, 1.0)
|
||||
|
||||
@override
|
||||
@classmethod
|
||||
def get_type_id(cls) -> V1CloudUIComponentTypeID:
|
||||
return V1CloudUIComponentTypeID.TEXT
|
||||
|
||||
|
||||
@ioprepped
|
||||
@dataclass
|
||||
class V1CloudUIButton:
|
||||
"""A button in our cloud ui."""
|
||||
|
||||
color: Annotated[tuple[float, float, float], IOAttrs('cl')]
|
||||
size: Annotated[tuple[float, float], IOAttrs('sz')]
|
||||
components: Annotated[list[V1CloudUIComponent], IOAttrs('c')]
|
||||
scale: Annotated[float, IOAttrs('sc', store_default=False)] = 1.0
|
||||
|
||||
|
||||
@ioprepped
|
||||
@dataclass
|
||||
class V1CloudUIRow:
|
||||
"""A row in our cloud ui."""
|
||||
|
||||
buttons: Annotated[list[V1CloudUIButton], IOAttrs('b')]
|
||||
|
||||
|
||||
@ioprepped
|
||||
@dataclass
|
||||
class V1CloudUI(CloudUI):
|
||||
"""Version 1 of our cloud-defined UI type."""
|
||||
|
||||
rows: Annotated[list[V1CloudUIRow], IOAttrs('r')]
|
||||
|
||||
@override
|
||||
@classmethod
|
||||
def get_type_id(cls) -> CloudUITypeID:
|
||||
return CloudUITypeID.V1
|
||||
182
dist/ba_data/python/bacommon/bs/_displayitem.py
vendored
Normal file
182
dist/ba_data/python/bacommon/bs/_displayitem.py
vendored
Normal file
|
|
@ -0,0 +1,182 @@
|
|||
# Released under the MIT License. See LICENSE for details.
|
||||
#
|
||||
"""DisplayItem related functionality."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from enum import Enum
|
||||
from dataclasses import dataclass
|
||||
from typing import Annotated, override, assert_never
|
||||
|
||||
from efro.util import pairs_to_flat
|
||||
from efro.dataclassio import ioprepped, IOAttrs, IOMultiType
|
||||
|
||||
from bacommon.bs._chest import ClassicChestAppearance
|
||||
|
||||
|
||||
class DisplayItemTypeID(Enum):
|
||||
"""Type ID for each of our subclasses."""
|
||||
|
||||
UNKNOWN = 'u'
|
||||
TICKETS = 't'
|
||||
TOKENS = 'k'
|
||||
TEST = 's'
|
||||
CHEST = 'c'
|
||||
|
||||
|
||||
class DisplayItem(IOMultiType[DisplayItemTypeID]):
|
||||
"""Some amount of something that can be shown or described.
|
||||
|
||||
Used to depict chest contents, inventory, rewards, etc.
|
||||
"""
|
||||
|
||||
@override
|
||||
@classmethod
|
||||
def get_type_id(cls) -> DisplayItemTypeID:
|
||||
# Require child classes to supply this themselves. If we did a
|
||||
# full type registry/lookup here it would require us to import
|
||||
# everything and would prevent lazy loading.
|
||||
raise NotImplementedError()
|
||||
|
||||
@override
|
||||
@classmethod
|
||||
def get_type(cls, type_id: DisplayItemTypeID) -> type[DisplayItem]:
|
||||
"""Return the subclass for each of our type-ids."""
|
||||
# pylint: disable=cyclic-import
|
||||
|
||||
t = DisplayItemTypeID
|
||||
if type_id is t.UNKNOWN:
|
||||
return UnknownDisplayItem
|
||||
if type_id is t.TICKETS:
|
||||
return TicketsDisplayItem
|
||||
if type_id is t.TOKENS:
|
||||
return TokensDisplayItem
|
||||
if type_id is t.TEST:
|
||||
return TestDisplayItem
|
||||
if type_id is t.CHEST:
|
||||
return ChestDisplayItem
|
||||
|
||||
# Important to make sure we provide all types.
|
||||
assert_never(type_id)
|
||||
|
||||
def get_description(self) -> tuple[str, list[tuple[str, str]]]:
|
||||
"""Return a string description and subs for the item.
|
||||
|
||||
These decriptions are baked into the DisplayItemWrapper and
|
||||
should be accessed from there when available. This allows
|
||||
clients to give descriptions even for newer display items they
|
||||
don't recognize.
|
||||
"""
|
||||
raise NotImplementedError()
|
||||
|
||||
# Implement fallbacks so client can digest item lists even if they
|
||||
# contain unrecognized stuff. DisplayItemWrapper contains basic
|
||||
# baked down info that they can still use in such cases.
|
||||
@override
|
||||
@classmethod
|
||||
def get_unknown_type_fallback(cls) -> DisplayItem:
|
||||
return UnknownDisplayItem()
|
||||
|
||||
|
||||
@ioprepped
|
||||
@dataclass
|
||||
class UnknownDisplayItem(DisplayItem):
|
||||
"""Something we don't know how to display."""
|
||||
|
||||
@override
|
||||
@classmethod
|
||||
def get_type_id(cls) -> DisplayItemTypeID:
|
||||
return DisplayItemTypeID.UNKNOWN
|
||||
|
||||
@override
|
||||
def get_description(self) -> tuple[str, list[tuple[str, str]]]:
|
||||
import logging
|
||||
|
||||
# Make noise but don't break.
|
||||
logging.exception(
|
||||
'UnknownDisplayItem.get_description() should never be called.'
|
||||
' Always access descriptions on the DisplayItemWrapper.'
|
||||
)
|
||||
return 'Unknown', []
|
||||
|
||||
|
||||
@ioprepped
|
||||
@dataclass
|
||||
class TicketsDisplayItem(DisplayItem):
|
||||
"""Some amount of tickets."""
|
||||
|
||||
count: Annotated[int, IOAttrs('c')]
|
||||
|
||||
@override
|
||||
@classmethod
|
||||
def get_type_id(cls) -> DisplayItemTypeID:
|
||||
return DisplayItemTypeID.TICKETS
|
||||
|
||||
@override
|
||||
def get_description(self) -> tuple[str, list[tuple[str, str]]]:
|
||||
return '${C} Tickets', [('${C}', str(self.count))]
|
||||
|
||||
|
||||
@ioprepped
|
||||
@dataclass
|
||||
class TokensDisplayItem(DisplayItem):
|
||||
"""Some amount of tokens."""
|
||||
|
||||
count: Annotated[int, IOAttrs('c')]
|
||||
|
||||
@override
|
||||
@classmethod
|
||||
def get_type_id(cls) -> DisplayItemTypeID:
|
||||
return DisplayItemTypeID.TOKENS
|
||||
|
||||
@override
|
||||
def get_description(self) -> tuple[str, list[tuple[str, str]]]:
|
||||
return '${C} Tokens', [('${C}', str(self.count))]
|
||||
|
||||
|
||||
@ioprepped
|
||||
@dataclass
|
||||
class TestDisplayItem(DisplayItem):
|
||||
"""Fills usable space for a display-item - good for calibration."""
|
||||
|
||||
@override
|
||||
@classmethod
|
||||
def get_type_id(cls) -> DisplayItemTypeID:
|
||||
return DisplayItemTypeID.TEST
|
||||
|
||||
@override
|
||||
def get_description(self) -> tuple[str, list[tuple[str, str]]]:
|
||||
return 'Test Display Item Here', []
|
||||
|
||||
|
||||
@ioprepped
|
||||
@dataclass
|
||||
class ChestDisplayItem(DisplayItem):
|
||||
"""Display a chest."""
|
||||
|
||||
appearance: Annotated[ClassicChestAppearance, IOAttrs('a')]
|
||||
|
||||
@override
|
||||
@classmethod
|
||||
def get_type_id(cls) -> DisplayItemTypeID:
|
||||
return DisplayItemTypeID.CHEST
|
||||
|
||||
@override
|
||||
def get_description(self) -> tuple[str, list[tuple[str, str]]]:
|
||||
return self.appearance.pretty_name, []
|
||||
|
||||
|
||||
@ioprepped
|
||||
@dataclass
|
||||
class DisplayItemWrapper:
|
||||
"""Wraps a DisplayItem and common info."""
|
||||
|
||||
item: Annotated[DisplayItem, IOAttrs('i')]
|
||||
description: Annotated[str, IOAttrs('d')]
|
||||
description_subs: Annotated[list[str] | None, IOAttrs('s')]
|
||||
|
||||
@classmethod
|
||||
def for_display_item(cls, item: DisplayItem) -> DisplayItemWrapper:
|
||||
"""Convenience method to wrap a DisplayItem."""
|
||||
desc, subs = item.get_description()
|
||||
return DisplayItemWrapper(item, desc, pairs_to_flat(subs))
|
||||
336
dist/ba_data/python/bacommon/bs/_msg.py
vendored
Normal file
336
dist/ba_data/python/bacommon/bs/_msg.py
vendored
Normal file
|
|
@ -0,0 +1,336 @@
|
|||
# Released under the MIT License. See LICENSE for details.
|
||||
#
|
||||
"""BombSquad specific bits."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import datetime
|
||||
from enum import Enum
|
||||
from dataclasses import dataclass, field
|
||||
from typing import Annotated, override
|
||||
|
||||
from efro.dataclassio import ioprepped, IOAttrs
|
||||
from efro.message import Message, Response
|
||||
|
||||
from bacommon.bs._displayitem import DisplayItemWrapper
|
||||
from bacommon.bs._clienteffect import ClientEffect
|
||||
from bacommon.bs._clouddialog import CloudDialogAction, CloudDialogWrapper
|
||||
from bacommon.bs._chest import ClassicChestAppearance
|
||||
|
||||
|
||||
@ioprepped
|
||||
@dataclass
|
||||
class ChestActionMessage(Message):
|
||||
"""Request action about a chest."""
|
||||
|
||||
class Action(Enum):
|
||||
"""Types of actions we can request."""
|
||||
|
||||
# Unlocking (for free or with tokens).
|
||||
UNLOCK = 'u'
|
||||
|
||||
# Watched an ad to reduce wait.
|
||||
AD = 'ad'
|
||||
|
||||
action: Annotated[Action, IOAttrs('a')]
|
||||
|
||||
# Tokens we are paying (only applies to unlock).
|
||||
token_payment: Annotated[int, IOAttrs('t')]
|
||||
|
||||
chest_id: Annotated[str, IOAttrs('i')]
|
||||
|
||||
@override
|
||||
@classmethod
|
||||
def get_response_types(cls) -> list[type[Response] | None]:
|
||||
return [ChestActionResponse]
|
||||
|
||||
|
||||
@ioprepped
|
||||
@dataclass
|
||||
class ChestActionResponse(Response):
|
||||
"""Here's the results of that action you asked for, boss."""
|
||||
|
||||
# Tokens that were actually charged.
|
||||
tokens_charged: Annotated[int, IOAttrs('t')] = 0
|
||||
|
||||
# If present, signifies the chest has been opened and we should show
|
||||
# the user this stuff that was in it.
|
||||
contents: Annotated[list[DisplayItemWrapper] | None, IOAttrs('c')] = None
|
||||
|
||||
# If contents are present, which of the chest's prize-sets they
|
||||
# represent.
|
||||
prizeindex: Annotated[int, IOAttrs('i')] = 0
|
||||
|
||||
# Printable error if something goes wrong.
|
||||
error: Annotated[str | None, IOAttrs('e')] = None
|
||||
|
||||
# Printable warning. Shown in orange with an error sound. Does not
|
||||
# mean the action failed; only that there's something to tell the
|
||||
# users such as 'It looks like you are faking ad views; stop it or
|
||||
# you won't have ad options anymore.'
|
||||
warning: Annotated[str | None, IOAttrs('w', store_default=False)] = None
|
||||
|
||||
# Printable success message. Shown in green with a cash-register
|
||||
# sound. Can be used for things like successful wait reductions via
|
||||
# ad views. Used in builds earlier than 22311; can remove once
|
||||
# 22311+ is ubiquitous.
|
||||
success_msg: Annotated[str | None, IOAttrs('s', store_default=False)] = None
|
||||
|
||||
# Effects to show on the client. Replaces warning and success_msg in
|
||||
# build 22311 or newer.
|
||||
effects: Annotated[
|
||||
list[ClientEffect], IOAttrs('fx', store_default=False)
|
||||
] = field(default_factory=list)
|
||||
|
||||
|
||||
@ioprepped
|
||||
@dataclass
|
||||
class CloudDialogActionMessage(Message):
|
||||
"""Do something to a client ui."""
|
||||
|
||||
id: Annotated[str, IOAttrs('i')]
|
||||
action: Annotated[CloudDialogAction, IOAttrs('a')]
|
||||
|
||||
@override
|
||||
@classmethod
|
||||
def get_response_types(cls) -> list[type[Response] | None]:
|
||||
return [CloudDialogActionResponse]
|
||||
|
||||
|
||||
@ioprepped
|
||||
@dataclass
|
||||
class CloudDialogActionResponse(Response):
|
||||
"""Did something to that inbox entry, boss."""
|
||||
|
||||
class ErrorType(Enum):
|
||||
"""Types of errors that may have occurred."""
|
||||
|
||||
# Probably a future error type we don't recognize.
|
||||
UNKNOWN = 'u'
|
||||
|
||||
# Something went wrong on the server, but specifics are not
|
||||
# relevant.
|
||||
INTERNAL = 'i'
|
||||
|
||||
# The entry expired on the server. In various cases such as 'ok'
|
||||
# buttons this can generally be ignored.
|
||||
EXPIRED = 'e'
|
||||
|
||||
error_type: Annotated[
|
||||
ErrorType | None, IOAttrs('et', enum_fallback=ErrorType.UNKNOWN)
|
||||
]
|
||||
|
||||
# User facing error message in the case of errors.
|
||||
error_message: Annotated[str | None, IOAttrs('em')]
|
||||
|
||||
effects: Annotated[list[ClientEffect], IOAttrs('fx')]
|
||||
|
||||
|
||||
@ioprepped
|
||||
@dataclass
|
||||
class GetClassicPurchasesMessage(Message):
|
||||
"""Asking for current account's classic purchases."""
|
||||
|
||||
@override
|
||||
@classmethod
|
||||
def get_response_types(cls) -> list[type[Response] | None]:
|
||||
return [GetClassicPurchasesResponse]
|
||||
|
||||
|
||||
@ioprepped
|
||||
@dataclass
|
||||
class GetClassicPurchasesResponse(Response):
|
||||
"""Here's those classic purchases ya asked for boss."""
|
||||
|
||||
purchases: Annotated[set[str], IOAttrs('p')]
|
||||
|
||||
|
||||
@ioprepped
|
||||
@dataclass
|
||||
class GlobalProfileCheckMessage(Message):
|
||||
"""Is this global profile name available?"""
|
||||
|
||||
name: Annotated[str, IOAttrs('n')]
|
||||
|
||||
@override
|
||||
@classmethod
|
||||
def get_response_types(cls) -> list[type[Response] | None]:
|
||||
return [GlobalProfileCheckResponse]
|
||||
|
||||
|
||||
@ioprepped
|
||||
@dataclass
|
||||
class GlobalProfileCheckResponse(Response):
|
||||
"""Here's that profile check ya asked for boss."""
|
||||
|
||||
available: Annotated[bool, IOAttrs('a')]
|
||||
ticket_cost: Annotated[int, IOAttrs('tc')]
|
||||
|
||||
|
||||
@ioprepped
|
||||
@dataclass
|
||||
class InboxRequestMessage(Message):
|
||||
"""Message requesting our inbox."""
|
||||
|
||||
@override
|
||||
@classmethod
|
||||
def get_response_types(cls) -> list[type[Response] | None]:
|
||||
return [InboxRequestResponse]
|
||||
|
||||
|
||||
@ioprepped
|
||||
@dataclass
|
||||
class InboxRequestResponse(Response):
|
||||
"""Here's that inbox contents you asked for, boss."""
|
||||
|
||||
wrappers: Annotated[list[CloudDialogWrapper], IOAttrs('w')]
|
||||
|
||||
# Printable error if something goes wrong.
|
||||
error: Annotated[str | None, IOAttrs('e')] = None
|
||||
|
||||
|
||||
@ioprepped
|
||||
@dataclass
|
||||
class LegacyRequest(Message):
|
||||
"""A generic request for the legacy master server."""
|
||||
|
||||
request: Annotated[str, IOAttrs('r')]
|
||||
request_type: Annotated[str, IOAttrs('t')]
|
||||
user_agent_string: Annotated[str, IOAttrs('u')]
|
||||
data: Annotated[str, IOAttrs('d')]
|
||||
|
||||
@override
|
||||
@classmethod
|
||||
def get_response_types(cls) -> list[type[Response] | None]:
|
||||
return [LegacyResponse]
|
||||
|
||||
|
||||
@ioprepped
|
||||
@dataclass
|
||||
class LegacyResponse(Response):
|
||||
"""Response for generic legacy request."""
|
||||
|
||||
data: Annotated[str | None, IOAttrs('d')]
|
||||
zipped: Annotated[bool, IOAttrs('z')]
|
||||
|
||||
|
||||
@ioprepped
|
||||
@dataclass
|
||||
class ChestInfoMessage(Message):
|
||||
"""Request info about a chest."""
|
||||
|
||||
chest_id: Annotated[str, IOAttrs('i')]
|
||||
|
||||
@override
|
||||
@classmethod
|
||||
def get_response_types(cls) -> list[type[Response] | None]:
|
||||
return [ChestInfoResponse]
|
||||
|
||||
|
||||
@ioprepped
|
||||
@dataclass
|
||||
class ChestInfoResponse(Response):
|
||||
"""Here's that chest info you asked for, boss."""
|
||||
|
||||
@dataclass
|
||||
class Chest:
|
||||
"""A lovely chest."""
|
||||
|
||||
@dataclass
|
||||
class PrizeSet:
|
||||
"""A possible set of prizes for this chest."""
|
||||
|
||||
weight: Annotated[float, IOAttrs('w')]
|
||||
contents: Annotated[list[DisplayItemWrapper], IOAttrs('c')]
|
||||
|
||||
appearance: Annotated[
|
||||
ClassicChestAppearance,
|
||||
IOAttrs('a', enum_fallback=ClassicChestAppearance.UNKNOWN),
|
||||
]
|
||||
|
||||
# How much it costs to unlock *now*.
|
||||
unlock_tokens: Annotated[int, IOAttrs('tk')]
|
||||
|
||||
# When it unlocks on its own.
|
||||
unlock_time: Annotated[datetime.datetime, IOAttrs('t')]
|
||||
|
||||
# Possible prizes we contain.
|
||||
prizesets: Annotated[list[PrizeSet], IOAttrs('p')]
|
||||
|
||||
# Are ads allowed now?
|
||||
ad_allow: Annotated[bool, IOAttrs('aa')]
|
||||
|
||||
chest: Annotated[Chest | None, IOAttrs('c')]
|
||||
user_tokens: Annotated[int | None, IOAttrs('t')]
|
||||
|
||||
|
||||
@ioprepped
|
||||
@dataclass
|
||||
class PrivatePartyMessage(Message):
|
||||
"""Message asking about info we need for private-party UI."""
|
||||
|
||||
need_datacode: Annotated[bool, IOAttrs('d')]
|
||||
|
||||
@override
|
||||
@classmethod
|
||||
def get_response_types(cls) -> list[type[Response] | None]:
|
||||
return [PrivatePartyResponse]
|
||||
|
||||
|
||||
@ioprepped
|
||||
@dataclass
|
||||
class PrivatePartyResponse(Response):
|
||||
"""Here's that private party UI info you asked for, boss."""
|
||||
|
||||
success: Annotated[bool, IOAttrs('s')]
|
||||
tokens: Annotated[int, IOAttrs('t')]
|
||||
gold_pass: Annotated[bool, IOAttrs('g')]
|
||||
datacode: Annotated[str | None, IOAttrs('d')]
|
||||
|
||||
|
||||
@ioprepped
|
||||
@dataclass
|
||||
class ScoreSubmitMessage(Message):
|
||||
"""Let the server know we got some score in something."""
|
||||
|
||||
score_token: Annotated[str, IOAttrs('t')]
|
||||
|
||||
@override
|
||||
@classmethod
|
||||
def get_response_types(cls) -> list[type[Response] | None]:
|
||||
return [ScoreSubmitResponse]
|
||||
|
||||
|
||||
@ioprepped
|
||||
@dataclass
|
||||
class ScoreSubmitResponse(Response):
|
||||
"""Did something to that inbox entry, boss."""
|
||||
|
||||
# Things we should show on our end.
|
||||
effects: Annotated[list[ClientEffect], IOAttrs('fx')]
|
||||
|
||||
|
||||
@ioprepped
|
||||
@dataclass
|
||||
class SendInfoMessage(Message):
|
||||
"""User is using the send-info function."""
|
||||
|
||||
description: Annotated[str, IOAttrs('c')]
|
||||
|
||||
@override
|
||||
@classmethod
|
||||
def get_response_types(cls) -> list[type[Response] | None]:
|
||||
return [SendInfoResponse]
|
||||
|
||||
|
||||
@ioprepped
|
||||
@dataclass
|
||||
class SendInfoResponse(Response):
|
||||
"""Response to sending info to the server."""
|
||||
|
||||
handled: Annotated[bool, IOAttrs('v')]
|
||||
message: Annotated[str | None, IOAttrs('m', store_default=False)] = None
|
||||
effects: Annotated[
|
||||
list[ClientEffect], IOAttrs('e', store_default=False)
|
||||
] = field(default_factory=list)
|
||||
legacy_code: Annotated[str | None, IOAttrs('l', store_default=False)] = None
|
||||
3
dist/ba_data/python/bacommon/logging.py
vendored
3
dist/ba_data/python/bacommon/logging.py
vendored
|
|
@ -41,6 +41,7 @@ class ClientLoggerName(Enum):
|
|||
ACCOUNT_CLIENT_V2 = 'ba.accountclientv2'
|
||||
ACCOUNT = 'ba.account'
|
||||
LOGIN_ADAPTER = 'ba.loginadapter'
|
||||
UI = 'ba.ui'
|
||||
|
||||
@property
|
||||
def description(self) -> str:
|
||||
|
|
@ -86,6 +87,8 @@ class ClientLoggerName(Enum):
|
|||
return 'account functionality'
|
||||
if self is cls.LOGIN_ADAPTER:
|
||||
return 'support for particular login types'
|
||||
if self is cls.UI:
|
||||
return 'anything user-interface related'
|
||||
assert_never(self)
|
||||
|
||||
|
||||
|
|
|
|||
4
dist/ba_data/python/baenv.py
vendored
4
dist/ba_data/python/baenv.py
vendored
|
|
@ -56,8 +56,8 @@ logger = logging.getLogger('ba.env')
|
|||
|
||||
# Build number and version of the ballistica binary we expect to be
|
||||
# using.
|
||||
TARGET_BALLISTICA_BUILD = 22535
|
||||
TARGET_BALLISTICA_VERSION = '1.7.51'
|
||||
TARGET_BALLISTICA_BUILD = 22584
|
||||
TARGET_BALLISTICA_VERSION = '1.7.53'
|
||||
|
||||
|
||||
@dataclass
|
||||
|
|
|
|||
4
dist/ba_data/python/baplus/_cloud.py
vendored
4
dist/ba_data/python/baplus/_cloud.py
vendored
|
|
@ -250,9 +250,9 @@ class CloudSubsystem(babase.AppSubsystem):
|
|||
@overload
|
||||
def send_message_cb(
|
||||
self,
|
||||
msg: bacommon.bs.ClientUIActionMessage,
|
||||
msg: bacommon.bs.CloudDialogActionMessage,
|
||||
on_response: Callable[
|
||||
[bacommon.bs.ClientUIActionResponse | Exception], None
|
||||
[bacommon.bs.CloudDialogActionResponse | Exception], None
|
||||
],
|
||||
) -> None: ...
|
||||
|
||||
|
|
|
|||
|
|
@ -260,7 +260,6 @@ class CoopSession(Session):
|
|||
with activity.context:
|
||||
activity.end(results={'outcome': 'restart'}, force=True)
|
||||
|
||||
# noinspection PyUnresolvedReferences
|
||||
@override
|
||||
def on_activity_end(
|
||||
self, activity: bascenev1.Activity, results: Any
|
||||
|
|
|
|||
|
|
@ -258,7 +258,6 @@ class GameActivity[PlayerT: bascenev1.Player, TeamT: bascenev1.Team](
|
|||
"""Return a name for this particular game instance."""
|
||||
return self.get_display_string(self.settings_raw)
|
||||
|
||||
# noinspection PyUnresolvedReferences
|
||||
def get_instance_scoreboard_display_string(self) -> babase.Lstr:
|
||||
"""Return a name for this particular game instance.
|
||||
|
||||
|
|
|
|||
5
dist/ba_data/python/bascenev1/_gameutils.py
vendored
5
dist/ba_data/python/bascenev1/_gameutils.py
vendored
|
|
@ -83,7 +83,6 @@ def animate(
|
|||
# FIXME: Even if we are looping we should have a way to die once we
|
||||
# get disconnected.
|
||||
if not loop:
|
||||
# noinspection PyUnresolvedReferences
|
||||
_bascenev1.timer(
|
||||
(int(mult * items[-1][0]) + 1000) / 1000.0, curve.delete
|
||||
)
|
||||
|
|
@ -146,8 +145,6 @@ def animate_array(
|
|||
# If we're not looping, set a timer to kill this
|
||||
# curve after its done its job.
|
||||
if not loop:
|
||||
# (PyCharm seems to think item is a float, not a tuple)
|
||||
# noinspection PyUnresolvedReferences
|
||||
_bascenev1.timer(
|
||||
(int(mult * items[-1][0]) + 1000) / 1000.0,
|
||||
curve.delete,
|
||||
|
|
@ -159,8 +156,6 @@ def animate_array(
|
|||
# FIXME: Even if we are looping we should have a way to die
|
||||
# once we get disconnected.
|
||||
if not loop:
|
||||
# (PyCharm seems to think item is a float, not a tuple)
|
||||
# noinspection PyUnresolvedReferences
|
||||
_bascenev1.timer(
|
||||
(int(mult * items[-1][0]) + 1000) / 1000.0, combine.delete
|
||||
)
|
||||
|
|
|
|||
1
dist/ba_data/python/bascenev1/_lobby.py
vendored
1
dist/ba_data/python/bascenev1/_lobby.py
vendored
|
|
@ -502,7 +502,6 @@ class Chooser:
|
|||
self._profileindex = self._profilenames.index(self._profilename)
|
||||
else:
|
||||
self._profileindex = 0
|
||||
# noinspection PyUnresolvedReferences
|
||||
self._profilename = self._profilenames[self._profileindex]
|
||||
|
||||
def update_position(self) -> None:
|
||||
|
|
|
|||
|
|
@ -101,7 +101,6 @@ class MultiTeamScoreScreenActivity(bs.ScoreScreenActivity):
|
|||
player_records = []
|
||||
valid_players = list(self.stats.get_records().items())
|
||||
|
||||
# noinspection PyUnresolvedReferences
|
||||
def _get_player_score_set_entry(
|
||||
player: bs.SessionPlayer,
|
||||
) -> bs.PlayerRecord | None:
|
||||
|
|
|
|||
|
|
@ -700,7 +700,6 @@ class RaceGame(bs.TeamGameActivity[Player, Team]):
|
|||
# FIXME: This is not type-safe!
|
||||
# This call is expected to always return an Actor!
|
||||
# Perhaps we need something like can_spawn_player()...
|
||||
# noinspection PyTypeChecker
|
||||
return None # type: ignore
|
||||
pos = self._regions[player.last_region].pos
|
||||
|
||||
|
|
|
|||
|
|
@ -72,7 +72,6 @@ class Point(Enum):
|
|||
class Spawn:
|
||||
"""Defines a bot spawn event."""
|
||||
|
||||
# noinspection PyUnresolvedReferences
|
||||
type: type[SpazBot]
|
||||
path: int = 0
|
||||
point: Point | None = None
|
||||
|
|
|
|||
|
|
@ -4,7 +4,6 @@
|
|||
# This file was automatically generated from "big_g.ma"
|
||||
# pylint: disable=all
|
||||
points = {}
|
||||
# noinspection PyDictCreation
|
||||
boxes = {}
|
||||
boxes['area_of_interest_bounds'] = (
|
||||
(-0.4011866709, 2.331310176, -0.5426286416)
|
||||
|
|
|
|||
|
|
@ -4,7 +4,6 @@
|
|||
# This file was automatically generated from "bridgit.ma"
|
||||
# pylint: disable=all
|
||||
points = {}
|
||||
# noinspection PyDictCreation
|
||||
boxes = {}
|
||||
boxes['area_of_interest_bounds'] = (
|
||||
(-0.2457963347, 3.828181068, -1.528362695)
|
||||
|
|
|
|||
|
|
@ -4,7 +4,6 @@
|
|||
# This file was automatically generated from "courtyard.ma"
|
||||
# pylint: disable=all
|
||||
points = {}
|
||||
# noinspection PyDictCreation
|
||||
boxes = {}
|
||||
boxes['area_of_interest_bounds'] = (
|
||||
(0.3544110667, 3.958431362, -2.175025358)
|
||||
|
|
|
|||
|
|
@ -4,7 +4,6 @@
|
|||
# This file was automatically generated from "crag_castle.ma"
|
||||
# pylint: disable=all
|
||||
points = {}
|
||||
# noinspection PyDictCreation
|
||||
boxes = {}
|
||||
boxes['area_of_interest_bounds'] = (
|
||||
(0.7033834902, 6.55869393, -3.153439808)
|
||||
|
|
|
|||
|
|
@ -4,7 +4,6 @@
|
|||
# This file was automatically generated from "doom_shroom.ma"
|
||||
# pylint: disable=all
|
||||
points = {}
|
||||
# noinspection PyDictCreation
|
||||
boxes = {}
|
||||
boxes['area_of_interest_bounds'] = (
|
||||
(0.4687647786, 2.320345088, -3.219423694)
|
||||
|
|
|
|||
|
|
@ -4,7 +4,6 @@
|
|||
# This file was automatically generated from "football_stadium.ma"
|
||||
# pylint: disable=all
|
||||
points = {}
|
||||
# noinspection PyDictCreation
|
||||
boxes = {}
|
||||
boxes['area_of_interest_bounds'] = (
|
||||
(0.0, 1.185751251, 0.4326226188)
|
||||
|
|
|
|||
|
|
@ -4,7 +4,6 @@
|
|||
# This file was automatically generated from "happy_thoughts.ma"
|
||||
# pylint: disable=all
|
||||
points = {}
|
||||
# noinspection PyDictCreation
|
||||
boxes = {}
|
||||
boxes['area_of_interest_bounds'] = (
|
||||
(-1.045859963, 12.67722855, -5.401537075)
|
||||
|
|
|
|||
|
|
@ -4,7 +4,6 @@
|
|||
# This file was automatically generated from "hockey_stadium.ma"
|
||||
# pylint: disable=all
|
||||
points = {}
|
||||
# noinspection PyDictCreation
|
||||
boxes = {}
|
||||
boxes['area_of_interest_bounds'] = (
|
||||
(0.0, 0.7956858119, 0.0)
|
||||
|
|
|
|||
|
|
@ -4,7 +4,6 @@
|
|||
# This file was automatically generated from "lake_frigid.ma"
|
||||
# pylint: disable=all
|
||||
points = {}
|
||||
# noinspection PyDictCreation
|
||||
boxes = {}
|
||||
boxes['area_of_interest_bounds'] = (
|
||||
(0.622753268, 3.958431362, -2.48708008)
|
||||
|
|
|
|||
|
|
@ -4,7 +4,6 @@
|
|||
# This file was automatically generated from "monkey_face.ma"
|
||||
# pylint: disable=all
|
||||
points = {}
|
||||
# noinspection PyDictCreation
|
||||
boxes = {}
|
||||
boxes['area_of_interest_bounds'] = (
|
||||
(-1.657177611, 4.132574186, -1.580485661)
|
||||
|
|
|
|||
|
|
@ -4,7 +4,6 @@
|
|||
# This file was automatically generated from "rampage.ma"
|
||||
# pylint: disable=all
|
||||
points = {}
|
||||
# noinspection PyDictCreation
|
||||
boxes = {}
|
||||
boxes['area_of_interest_bounds'] = (
|
||||
(0.3544110667, 5.616383286, -4.066055072)
|
||||
|
|
|
|||
|
|
@ -4,7 +4,6 @@
|
|||
# This file was automatically generated from "roundabout.ma"
|
||||
# pylint: disable=all
|
||||
points = {}
|
||||
# noinspection PyDictCreation
|
||||
boxes = {}
|
||||
boxes['area_of_interest_bounds'] = (
|
||||
(-1.552280404, 3.189001207, -2.40908495)
|
||||
|
|
|
|||
|
|
@ -4,7 +4,6 @@
|
|||
# This file was automatically generated from "step_right_up.ma"
|
||||
# pylint: disable=all
|
||||
points = {}
|
||||
# noinspection PyDictCreation
|
||||
boxes = {}
|
||||
boxes['area_of_interest_bounds'] = (
|
||||
(0.3544110667, 6.07676405, -2.271833016)
|
||||
|
|
|
|||
|
|
@ -4,7 +4,6 @@
|
|||
# This file was automatically generated from "the_pad.ma"
|
||||
# pylint: disable=all
|
||||
points = {}
|
||||
# noinspection PyDictCreation
|
||||
boxes = {}
|
||||
boxes['area_of_interest_bounds'] = (
|
||||
(0.3544110667, 4.493562578, -2.518391331)
|
||||
|
|
|
|||
|
|
@ -4,7 +4,6 @@
|
|||
# This file was automatically generated from "tip_top.ma"
|
||||
# pylint: disable=all
|
||||
points = {}
|
||||
# noinspection PyDictCreation
|
||||
boxes = {}
|
||||
boxes['area_of_interest_bounds'] = (
|
||||
(0.004375512593, 7.141135803, -0.01745294675)
|
||||
|
|
|
|||
|
|
@ -4,7 +4,6 @@
|
|||
# This file was automatically generated from "tower_d.ma"
|
||||
# pylint: disable=all
|
||||
points = {}
|
||||
# noinspection PyDictCreation
|
||||
boxes = {}
|
||||
boxes['area_of_interest_bounds'] = (
|
||||
(-0.4714933293, 2.887077774, -1.505479919)
|
||||
|
|
|
|||
|
|
@ -4,7 +4,6 @@
|
|||
# This file was automatically generated from "zig_zag.ma"
|
||||
# pylint: disable=all
|
||||
points = {}
|
||||
# noinspection PyDictCreation
|
||||
boxes = {}
|
||||
boxes['area_of_interest_bounds'] = (
|
||||
(-1.807378035, 3.943412768, -1.61304303)
|
||||
|
|
|
|||
13
dist/ba_data/python/bascenev1lib/maps.py
vendored
13
dist/ba_data/python/bascenev1lib/maps.py
vendored
|
|
@ -42,7 +42,6 @@ def register_all_maps() -> None:
|
|||
class HockeyStadium(bs.Map):
|
||||
"""Stadium map used for ice hockey games."""
|
||||
|
||||
# noinspection PyUnresolvedReferences
|
||||
from bascenev1lib.mapdata import hockey_stadium as defs
|
||||
|
||||
name = 'Hockey Stadium'
|
||||
|
|
@ -207,7 +206,6 @@ class FootballStadium(bs.Map):
|
|||
class Bridgit(bs.Map):
|
||||
"""Map with a narrow bridge in the middle."""
|
||||
|
||||
# noinspection PyUnresolvedReferences
|
||||
from bascenev1lib.mapdata import bridgit as defs
|
||||
|
||||
name = 'Bridgit'
|
||||
|
|
@ -316,7 +314,6 @@ class Bridgit(bs.Map):
|
|||
class BigG(bs.Map):
|
||||
"""Large G shaped map for racing"""
|
||||
|
||||
# noinspection PyUnresolvedReferences
|
||||
from bascenev1lib.mapdata import big_g as defs
|
||||
|
||||
name = 'Big G'
|
||||
|
|
@ -430,7 +427,6 @@ class BigG(bs.Map):
|
|||
class Roundabout(bs.Map):
|
||||
"""CTF map featuring two platforms and a long way around between them"""
|
||||
|
||||
# noinspection PyUnresolvedReferences
|
||||
from bascenev1lib.mapdata import roundabout as defs
|
||||
|
||||
name = 'Roundabout'
|
||||
|
|
@ -538,7 +534,6 @@ class Roundabout(bs.Map):
|
|||
class MonkeyFace(bs.Map):
|
||||
"""Map sorta shaped like a monkey face; teehee!"""
|
||||
|
||||
# noinspection PyUnresolvedReferences
|
||||
from bascenev1lib.mapdata import monkey_face as defs
|
||||
|
||||
name = 'Monkey Face'
|
||||
|
|
@ -646,7 +641,6 @@ class MonkeyFace(bs.Map):
|
|||
class ZigZag(bs.Map):
|
||||
"""A very long zig-zaggy map"""
|
||||
|
||||
# noinspection PyUnresolvedReferences
|
||||
from bascenev1lib.mapdata import zig_zag as defs
|
||||
|
||||
name = 'Zigzag'
|
||||
|
|
@ -757,7 +751,6 @@ class ZigZag(bs.Map):
|
|||
class ThePad(bs.Map):
|
||||
"""A simple square shaped map with a raised edge."""
|
||||
|
||||
# noinspection PyUnresolvedReferences
|
||||
from bascenev1lib.mapdata import the_pad as defs
|
||||
|
||||
name = 'The Pad'
|
||||
|
|
@ -849,7 +842,6 @@ class ThePad(bs.Map):
|
|||
class DoomShroom(bs.Map):
|
||||
"""A giant mushroom. Of doom!"""
|
||||
|
||||
# noinspection PyUnresolvedReferences
|
||||
from bascenev1lib.mapdata import doom_shroom as defs
|
||||
|
||||
name = 'Doom Shroom'
|
||||
|
|
@ -949,7 +941,6 @@ class DoomShroom(bs.Map):
|
|||
class LakeFrigid(bs.Map):
|
||||
"""An icy lake fit for racing."""
|
||||
|
||||
# noinspection PyUnresolvedReferences
|
||||
from bascenev1lib.mapdata import lake_frigid as defs
|
||||
|
||||
name = 'Lake Frigid'
|
||||
|
|
@ -1039,7 +1030,6 @@ class LakeFrigid(bs.Map):
|
|||
class TipTop(bs.Map):
|
||||
"""A pointy map good for king-of-the-hill-ish games."""
|
||||
|
||||
# noinspection PyUnresolvedReferences
|
||||
from bascenev1lib.mapdata import tip_top as defs
|
||||
|
||||
name = 'Tip Top'
|
||||
|
|
@ -1120,7 +1110,6 @@ class TipTop(bs.Map):
|
|||
class CragCastle(bs.Map):
|
||||
"""A lovely castle map."""
|
||||
|
||||
# noinspection PyUnresolvedReferences
|
||||
from bascenev1lib.mapdata import crag_castle as defs
|
||||
|
||||
name = 'Crag Castle'
|
||||
|
|
@ -1343,7 +1332,6 @@ class TowerD(bs.Map):
|
|||
class HappyThoughts(bs.Map):
|
||||
"""Flying map."""
|
||||
|
||||
# noinspection PyUnresolvedReferences
|
||||
from bascenev1lib.mapdata import happy_thoughts as defs
|
||||
|
||||
name = 'Happy Thoughts'
|
||||
|
|
@ -1463,7 +1451,6 @@ class HappyThoughts(bs.Map):
|
|||
class StepRightUp(bs.Map):
|
||||
"""Wide stepped map good for CTF or Assault."""
|
||||
|
||||
# noinspection PyUnresolvedReferences
|
||||
from bascenev1lib.mapdata import step_right_up as defs
|
||||
|
||||
name = 'Step Right Up'
|
||||
|
|
|
|||
1
dist/ba_data/python/bascenev1lib/tutorial.py
vendored
1
dist/ba_data/python/bascenev1lib/tutorial.py
vendored
|
|
@ -234,7 +234,6 @@ class RemoveGloves:
|
|||
def run(self, a: TutorialActivity) -> None:
|
||||
# pylint: disable=protected-access
|
||||
assert a.current_spaz is not None
|
||||
# noinspection PyProtectedMember
|
||||
a.current_spaz._gloves_wear_off()
|
||||
|
||||
|
||||
|
|
|
|||
42
dist/ba_data/python/bauiv1/__init__.py
vendored
42
dist/ba_data/python/bauiv1/__init__.py
vendored
|
|
@ -16,8 +16,9 @@ from __future__ import annotations
|
|||
|
||||
import logging
|
||||
|
||||
# from efro.util import set_canonical_module_names
|
||||
from babase import (
|
||||
accountlog,
|
||||
AccountV2Handle,
|
||||
add_clean_frame_callback,
|
||||
allows_ticket_sales,
|
||||
app,
|
||||
|
|
@ -34,7 +35,13 @@ from babase import (
|
|||
AppTime,
|
||||
apptimer,
|
||||
AppTimer,
|
||||
asset_loads_allowed,
|
||||
balog,
|
||||
Call,
|
||||
DevConsoleButtonDef,
|
||||
DevConsoleTab,
|
||||
DevConsoleTabEntry,
|
||||
DevConsoleSubsystem,
|
||||
fullscreen_control_available,
|
||||
fullscreen_control_get,
|
||||
fullscreen_control_key_shortcut,
|
||||
|
|
@ -43,6 +50,7 @@ from babase import (
|
|||
clipboard_is_supported,
|
||||
clipboard_set_text,
|
||||
commit_app_config,
|
||||
CloudSubscription,
|
||||
ContextRef,
|
||||
displaytime,
|
||||
DisplayTime,
|
||||
|
|
@ -90,6 +98,7 @@ from babase import (
|
|||
pushcall,
|
||||
quit,
|
||||
QuitType,
|
||||
request_main_ui,
|
||||
request_permission,
|
||||
safecolor,
|
||||
screenmessage,
|
||||
|
|
@ -103,6 +112,7 @@ from babase import (
|
|||
supports_vsync,
|
||||
supports_unicode_display,
|
||||
timestring,
|
||||
uilog,
|
||||
UIScale,
|
||||
unlock_all_input,
|
||||
utc_now_cloud,
|
||||
|
|
@ -116,6 +126,7 @@ from _bauiv1 import (
|
|||
columnwidget,
|
||||
containerwidget,
|
||||
get_qrcode_texture,
|
||||
get_selected_widget,
|
||||
get_special_widget,
|
||||
getmesh,
|
||||
getsound,
|
||||
|
|
@ -136,20 +147,26 @@ from _bauiv1 import (
|
|||
uibounds,
|
||||
Widget,
|
||||
widget,
|
||||
widget_by_id,
|
||||
)
|
||||
from bauiv1._cloudui import show_cloud_ui_window
|
||||
from bauiv1._keyboard import Keyboard
|
||||
from bauiv1._uitypes import (
|
||||
uicleanupcheck,
|
||||
RootUIUpdatePause,
|
||||
)
|
||||
from bauiv1._appsubsystem import UIV1AppSubsystem
|
||||
from bauiv1._window import (
|
||||
Window,
|
||||
MainWindowState,
|
||||
BasicMainWindowState,
|
||||
uicleanupcheck,
|
||||
MainWindow,
|
||||
RootUIUpdatePause,
|
||||
MainWindowAutoRecreateSuppress,
|
||||
)
|
||||
from bauiv1._appsubsystem import UIV1AppSubsystem
|
||||
|
||||
__all__ = [
|
||||
'accountlog',
|
||||
'AccountV2Handle',
|
||||
'add_clean_frame_callback',
|
||||
'allows_ticket_sales',
|
||||
'app',
|
||||
|
|
@ -167,9 +184,15 @@ __all__ = [
|
|||
'AppTime',
|
||||
'apptimer',
|
||||
'AppTimer',
|
||||
'asset_loads_allowed',
|
||||
'balog',
|
||||
'BasicMainWindowState',
|
||||
'buttonwidget',
|
||||
'Call',
|
||||
'DevConsoleButtonDef',
|
||||
'DevConsoleTab',
|
||||
'DevConsoleTabEntry',
|
||||
'DevConsoleSubsystem',
|
||||
'fullscreen_control_available',
|
||||
'fullscreen_control_get',
|
||||
'fullscreen_control_key_shortcut',
|
||||
|
|
@ -181,6 +204,7 @@ __all__ = [
|
|||
'columnwidget',
|
||||
'commit_app_config',
|
||||
'containerwidget',
|
||||
'CloudSubscription',
|
||||
'ContextRef',
|
||||
'displaytime',
|
||||
'DisplayTime',
|
||||
|
|
@ -197,6 +221,7 @@ __all__ = [
|
|||
'get_qrcode_texture',
|
||||
'get_remote_app_name',
|
||||
'get_replays_dir',
|
||||
'get_selected_widget',
|
||||
'get_special_widget',
|
||||
'get_string_height',
|
||||
'get_string_width',
|
||||
|
|
@ -241,6 +266,7 @@ __all__ = [
|
|||
'quit',
|
||||
'QuitType',
|
||||
'reload_hooks',
|
||||
'request_main_ui',
|
||||
'request_permission',
|
||||
'root_ui_pause_updates',
|
||||
'root_ui_resume_updates',
|
||||
|
|
@ -253,6 +279,7 @@ __all__ = [
|
|||
'set_low_level_config_value',
|
||||
'set_party_window_open',
|
||||
'set_main_ui_input_device',
|
||||
'show_cloud_ui_window',
|
||||
'shutdown_suppress_begin',
|
||||
'shutdown_suppress_end',
|
||||
'Sound',
|
||||
|
|
@ -266,22 +293,19 @@ __all__ = [
|
|||
'timestring',
|
||||
'uibounds',
|
||||
'uicleanupcheck',
|
||||
'uilog',
|
||||
'UIScale',
|
||||
'UIV1AppSubsystem',
|
||||
'unlock_all_input',
|
||||
'utc_now_cloud',
|
||||
'WeakCall',
|
||||
'widget',
|
||||
'widget_by_id',
|
||||
'Widget',
|
||||
'Window',
|
||||
'workspaces_in_use',
|
||||
]
|
||||
|
||||
# We want stuff to show up as bauiv1.Foo instead of bauiv1._sub.Foo.
|
||||
# UPDATE: Trying without this for now. Seems like this might cause more
|
||||
# harm than good. Can flip it back on if it is missed.
|
||||
# set_canonical_module_names(globals())
|
||||
|
||||
# Sanity check: we want to keep ballistica's dependencies and
|
||||
# bootstrapping order clearly defined; let's check a few particular
|
||||
# modules to make sure they never directly or indirectly import us
|
||||
|
|
|
|||
279
dist/ba_data/python/bauiv1/_appsubsystem.py
vendored
279
dist/ba_data/python/bauiv1/_appsubsystem.py
vendored
|
|
@ -4,12 +4,14 @@
|
|||
|
||||
from __future__ import annotations
|
||||
|
||||
import os
|
||||
import time
|
||||
import logging
|
||||
import inspect
|
||||
import weakref
|
||||
import warnings
|
||||
from enum import Enum
|
||||
from dataclasses import dataclass
|
||||
from typing import TYPE_CHECKING, override
|
||||
|
||||
from efro.util import empty_weakref
|
||||
|
|
@ -20,14 +22,13 @@ import _bauiv1
|
|||
if TYPE_CHECKING:
|
||||
from typing import Any, Callable
|
||||
|
||||
from bauiv1._uitypes import (
|
||||
UICleanupCheck,
|
||||
Window,
|
||||
MainWindow,
|
||||
MainWindowState,
|
||||
)
|
||||
from bauiv1._window import Window, MainWindow, MainWindowState
|
||||
import bauiv1
|
||||
|
||||
# Set environment variable BA_DEBUG_UI_CLEANUP_CHECKS to 1
|
||||
# to print detailed info about what is getting cleaned up when.
|
||||
DEBUG_UI_CLEANUP_CHECKS = os.environ.get('BA_DEBUG_UI_CLEANUP_CHECKS') == '1'
|
||||
|
||||
|
||||
class UIV1AppSubsystem(babase.AppSubsystem):
|
||||
"""Consolidated UI functionality for the app.
|
||||
|
|
@ -57,7 +58,7 @@ class UIV1AppSubsystem(babase.AppSubsystem):
|
|||
CHEST_SLOT_3 = 'chest_slot_3'
|
||||
|
||||
def __init__(self) -> None:
|
||||
from bauiv1._uitypes import MainWindow
|
||||
from bauiv1._window import MainWindow
|
||||
|
||||
super().__init__()
|
||||
|
||||
|
|
@ -73,12 +74,7 @@ class UIV1AppSubsystem(babase.AppSubsystem):
|
|||
# For storing arbitrary class-level state data for Windows or
|
||||
# other UI related classes.
|
||||
self.window_states: dict[type, Any] = {}
|
||||
|
||||
self._uiscale: babase.UIScale
|
||||
self._update_ui_scale()
|
||||
|
||||
self.cleanupchecks: list[UICleanupCheck] = []
|
||||
self.upkeeptimer: babase.AppTimer | None = None
|
||||
self.main_window_shared_states: dict = {}
|
||||
|
||||
self.title_color = (0.72, 0.7, 0.75)
|
||||
self.heading_color = (0.72, 0.7, 0.75)
|
||||
|
|
@ -86,10 +82,15 @@ class UIV1AppSubsystem(babase.AppSubsystem):
|
|||
|
||||
self.window_auto_recreate_suppress_count = 0
|
||||
|
||||
self._uiscale: babase.UIScale
|
||||
self._update_ui_scale()
|
||||
self._upkeeptimer: babase.AppTimer | None = None
|
||||
self._cleanupchecks: list[_UICleanupCheck] = []
|
||||
self._last_win_recreate_screen_size: tuple[float, float] | None = None
|
||||
self._last_win_recreate_uiscale: bauiv1.UIScale | None = None
|
||||
self._last_win_recreate_time: float | None = None
|
||||
self._win_recreate_timer: babase.AppTimer | None = None
|
||||
self._base_ids: dict[str, int] = {}
|
||||
|
||||
# Elements in our root UI will call anything here when
|
||||
# activated.
|
||||
|
|
@ -97,6 +98,15 @@ class UIV1AppSubsystem(babase.AppSubsystem):
|
|||
UIV1AppSubsystem.RootUIElement, Callable[[], None]
|
||||
] = {}
|
||||
|
||||
def new_id_prefix(self, name: str) -> str:
|
||||
"""Generate a unique id given a base name.
|
||||
|
||||
Useful to ensure widgets have globally unique ids even if
|
||||
a particular window type is instantiated multiple times.
|
||||
"""
|
||||
val = self._base_ids[name] = self._base_ids.get(name, 0) + 1
|
||||
return f'{name}{val}'
|
||||
|
||||
def _update_ui_scale(self) -> None:
|
||||
uiscalestr = babase.get_ui_scale()
|
||||
if uiscalestr == 'large':
|
||||
|
|
@ -121,7 +131,7 @@ class UIV1AppSubsystem(babase.AppSubsystem):
|
|||
|
||||
@override
|
||||
def reset(self) -> None:
|
||||
from bauiv1._uitypes import MainWindow
|
||||
from bauiv1._window import MainWindow
|
||||
|
||||
self.root_ui_calls.clear()
|
||||
self._main_window = empty_weakref(MainWindow)
|
||||
|
|
@ -134,10 +144,9 @@ class UIV1AppSubsystem(babase.AppSubsystem):
|
|||
|
||||
@override
|
||||
def on_app_loading(self) -> None:
|
||||
from bauiv1._uitypes import ui_upkeep
|
||||
|
||||
# Kick off our periodic UI upkeep.
|
||||
self.upkeeptimer = babase.AppTimer(2.6543, ui_upkeep, repeat=True)
|
||||
self._upkeeptimer = babase.AppTimer(2.6543, self._upkeep, repeat=True)
|
||||
|
||||
def get_main_window(self) -> bauiv1.MainWindow | None:
|
||||
"""Return main window, if any."""
|
||||
|
|
@ -147,12 +156,13 @@ class UIV1AppSubsystem(babase.AppSubsystem):
|
|||
self,
|
||||
window: bauiv1.MainWindow,
|
||||
*,
|
||||
back_state: MainWindowState | None,
|
||||
from_window: bauiv1.MainWindow | None | bool = True,
|
||||
is_back: bool = False,
|
||||
is_top_level: bool = False,
|
||||
is_auxiliary: bool = False,
|
||||
back_state: MainWindowState | None = None,
|
||||
suppress_warning: bool = False,
|
||||
restore_shared_state: bool = True,
|
||||
) -> None:
|
||||
"""Set the current 'main' window.
|
||||
|
||||
|
|
@ -163,10 +173,10 @@ class UIV1AppSubsystem(babase.AppSubsystem):
|
|||
The caller is responsible for cleaning up any previous main
|
||||
window.
|
||||
"""
|
||||
# pylint: disable=too-many-locals
|
||||
# pylint: disable=too-many-branches
|
||||
# pylint: disable=too-many-statements
|
||||
from bauiv1._uitypes import MainWindow
|
||||
# pylint: disable=too-many-locals
|
||||
from bauiv1._window import MainWindow
|
||||
|
||||
# If we haven't grabbed initial uiscale or screen size for
|
||||
# recreate comparision purposes, this is a good time to do so.
|
||||
|
|
@ -191,6 +201,10 @@ class UIV1AppSubsystem(babase.AppSubsystem):
|
|||
|
||||
# We used to accept Widgets but now want MainWindows.
|
||||
if not isinstance(window, MainWindow):
|
||||
|
||||
# if callable(window):
|
||||
# window = window()
|
||||
# else:
|
||||
raise RuntimeError(
|
||||
f'set_main_window() now takes a MainWindow as its "window" arg.'
|
||||
f' You passed a {type(window)}.',
|
||||
|
|
@ -293,38 +307,23 @@ class UIV1AppSubsystem(babase.AppSubsystem):
|
|||
if is_top_level:
|
||||
# Top level windows don't have or expect anywhere to go
|
||||
# back to.
|
||||
window.main_window_back_state = None
|
||||
elif back_state is not None:
|
||||
window.main_window_back_state = back_state
|
||||
else:
|
||||
oldwin = self._main_window()
|
||||
if oldwin is None:
|
||||
# We currenty only hold weak refs to windows so that
|
||||
# they are free to die on their own, but we expect
|
||||
# the main menu window to keep itself alive as long
|
||||
# as its the main one. Holler if that seems to not
|
||||
# be happening.
|
||||
logging.warning(
|
||||
'set_main_window: No old MainWindow found'
|
||||
' and is_top_level is False;'
|
||||
' this should not happen.'
|
||||
)
|
||||
window.main_window_back_state = None
|
||||
else:
|
||||
window.main_window_back_state = self.save_main_window_state(
|
||||
oldwin
|
||||
)
|
||||
assert back_state is None
|
||||
window.main_window_back_state = back_state
|
||||
|
||||
self._main_window = window_weakref
|
||||
self._main_window_widget = window_widget
|
||||
|
||||
# Now that we're all set up, restore any state.
|
||||
if restore_shared_state:
|
||||
window.main_window_restore_shared_state()
|
||||
|
||||
def has_main_window(self) -> bool:
|
||||
"""Return whether a main menu window is present."""
|
||||
return bool(self._main_window_widget)
|
||||
|
||||
def clear_main_window(self, transition: str | None = None) -> None:
|
||||
"""Clear any existing main window."""
|
||||
from bauiv1._uitypes import MainWindow
|
||||
from bauiv1._window import MainWindow
|
||||
|
||||
main_window = self._main_window()
|
||||
if main_window:
|
||||
|
|
@ -358,6 +357,23 @@ class UIV1AppSubsystem(babase.AppSubsystem):
|
|||
|
||||
return winstate
|
||||
|
||||
def save_current_main_window_state(self) -> MainWindowState | None:
|
||||
"""Save state for the current window, if any."""
|
||||
# Calc a back-state from the current window.
|
||||
current_main_win = self._main_window()
|
||||
if current_main_win is None:
|
||||
# We currenty only hold weak refs to windows so that
|
||||
# they are free to die on their own, but we expect
|
||||
# the main menu window to keep itself alive as long
|
||||
# as its the main one. Holler if that seems to not
|
||||
# be happening.
|
||||
babase.uilog.warning(
|
||||
'save_current_main_window_state: No old MainWindow found;'
|
||||
' this should not happen.'
|
||||
)
|
||||
return None
|
||||
return self.save_main_window_state(current_main_win)
|
||||
|
||||
def restore_main_window_state(self, state: MainWindowState) -> None:
|
||||
"""Restore UI to a saved state."""
|
||||
existing = self.get_main_window()
|
||||
|
|
@ -411,6 +427,142 @@ class UIV1AppSubsystem(babase.AppSubsystem):
|
|||
|
||||
self._schedule_main_win_recreate()
|
||||
|
||||
def add_ui_cleanup_check(self, obj: Any, widget: bauiv1.Widget) -> None:
|
||||
"""Checks to ensure a widget-owning object gets cleaned up properly.
|
||||
|
||||
This adds a check which will print an error message if the provided
|
||||
object still exists ~5 seconds after the provided bauiv1.Widget
|
||||
dies.
|
||||
|
||||
This is a good sanity check for any sort of object that wraps or
|
||||
controls a bauiv1.Widget. For instance, a 'Window' class instance
|
||||
has no reason to still exist once its root container bauiv1.Widget
|
||||
has fully transitioned out and been destroyed. Circular references
|
||||
or careless strong referencing can lead to such objects never
|
||||
getting destroyed, however, and this helps detect such cases to
|
||||
avoid memory leaks.
|
||||
"""
|
||||
if DEBUG_UI_CLEANUP_CHECKS:
|
||||
print(f'adding uicleanup to {obj}')
|
||||
if not isinstance(widget, _bauiv1.Widget):
|
||||
raise TypeError('widget arg is not a bauiv1.Widget')
|
||||
|
||||
if bool(False):
|
||||
|
||||
def foobar() -> None:
|
||||
"""Just testing."""
|
||||
if DEBUG_UI_CLEANUP_CHECKS:
|
||||
print('uicleanupcheck widget dying...')
|
||||
|
||||
widget.add_delete_callback(foobar)
|
||||
|
||||
self._cleanupchecks.append(
|
||||
_UICleanupCheck(
|
||||
obj=weakref.ref(obj), widget=widget, widget_death_time=None
|
||||
)
|
||||
)
|
||||
|
||||
def auxiliary_window_activate(
|
||||
self,
|
||||
win_type: type[bauiv1.MainWindow],
|
||||
win_create_call: Callable[[], bauiv1.MainWindow],
|
||||
) -> None:
|
||||
"""Navigate to or away from an Auxiliary window.
|
||||
|
||||
Auxiliary windows can be thought of as 'side quests' in the
|
||||
window hierarchy; places such as settings windows or league
|
||||
ranking windows that the user might want to visit without losing
|
||||
their place in the regular hierarchy.
|
||||
|
||||
Calling this method with a MainWindow of the provided type
|
||||
already in the stack will back out past it (effectively toggling
|
||||
the 'side quest' back off).
|
||||
|
||||
Calling this method with a *different* auxiliary window in the
|
||||
stack will back out past that and replace it with this
|
||||
(effectively ending the old side-quest and starting a new one).
|
||||
"""
|
||||
# pylint: disable=unidiomatic-typecheck
|
||||
|
||||
current_main_window = self.get_main_window()
|
||||
|
||||
# Scan our ancestors for auxiliary states matching our type as
|
||||
# well as auxiliary states in general.
|
||||
aux_matching_state: bauiv1.MainWindowState | None = None
|
||||
aux_state: bauiv1.MainWindowState | None = None
|
||||
|
||||
if current_main_window is None:
|
||||
raise RuntimeError(
|
||||
'Not currently handling no-top-level-window case.'
|
||||
)
|
||||
|
||||
state = current_main_window.main_window_back_state
|
||||
while state is not None:
|
||||
assert state.window_type is not None
|
||||
if state.is_auxiliary:
|
||||
if state.window_type is win_type:
|
||||
aux_matching_state = state
|
||||
else:
|
||||
aux_state = state
|
||||
|
||||
state = state.parent
|
||||
|
||||
# If there's an ancestor auxiliary window-state matching our
|
||||
# type, back out past it (example: poking settings, navigating
|
||||
# down a level or two, and then poking settings again should
|
||||
# back out of settings).
|
||||
if aux_matching_state is not None:
|
||||
current_main_window.main_window_back_state = (
|
||||
aux_matching_state.parent
|
||||
)
|
||||
current_main_window.main_window_back()
|
||||
return
|
||||
|
||||
# If there's an ancestory auxiliary state *not* matching our
|
||||
# type, crop the state and swap in our new auxiliary UI
|
||||
# (example: poking settings, then poking account, then poking
|
||||
# back should end up where things were before the settings
|
||||
# poke).
|
||||
if aux_state is not None:
|
||||
# Blow away the window stack and build a fresh one.
|
||||
self.clear_main_window()
|
||||
self.set_main_window(
|
||||
win_create_call(),
|
||||
from_window=False, # Disable from-check.
|
||||
back_state=aux_state.parent,
|
||||
suppress_warning=True,
|
||||
is_auxiliary=True,
|
||||
)
|
||||
return
|
||||
|
||||
# Ok, no auxiliary states found. Now if current window is
|
||||
# auxiliary and the type matches, simply do a back.
|
||||
if (
|
||||
current_main_window.main_window_is_auxiliary
|
||||
and type(current_main_window) is win_type
|
||||
):
|
||||
current_main_window.main_window_back()
|
||||
return
|
||||
|
||||
# If current window is auxiliary but type doesn't match,
|
||||
# swap it out for our new auxiliary UI.
|
||||
if current_main_window.main_window_is_auxiliary:
|
||||
self.clear_main_window()
|
||||
self.set_main_window(
|
||||
win_create_call(),
|
||||
from_window=False, # Disable from-check.
|
||||
back_state=current_main_window.main_window_back_state,
|
||||
suppress_warning=True,
|
||||
is_auxiliary=True,
|
||||
)
|
||||
return
|
||||
|
||||
# Ok, no existing auxiliary stuff was found period. Just
|
||||
# navigate forward to this UI.
|
||||
current_main_window.main_window_replace(
|
||||
win_create_call, is_auxiliary=True
|
||||
)
|
||||
|
||||
def _schedule_main_win_recreate(self) -> None:
|
||||
|
||||
# If there is a timer set already, do nothing.
|
||||
|
|
@ -483,3 +635,48 @@ class UIV1AppSubsystem(babase.AppSubsystem):
|
|||
# future recreates.
|
||||
self._last_win_recreate_uiscale = uiscale
|
||||
self._last_win_recreate_screen_size = virtual_screen_size
|
||||
|
||||
def _upkeep(self) -> None:
|
||||
"""Run UI cleanup checks, etc. should be called periodically."""
|
||||
|
||||
assert babase.app.classic is not None
|
||||
remainingchecks = []
|
||||
now = babase.apptime()
|
||||
for check in self._cleanupchecks:
|
||||
obj = check.obj()
|
||||
|
||||
# If the object has died, ignore and don't re-add.
|
||||
if obj is None:
|
||||
if DEBUG_UI_CLEANUP_CHECKS:
|
||||
print('uicleanupcheck object is dead; hooray!')
|
||||
continue
|
||||
|
||||
# If the widget hadn't died yet, note if it has.
|
||||
if check.widget_death_time is None:
|
||||
remainingchecks.append(check)
|
||||
if not check.widget:
|
||||
check.widget_death_time = now
|
||||
else:
|
||||
# Widget was already dead; complain if its been too long.
|
||||
if now - check.widget_death_time > 5.0:
|
||||
print(
|
||||
'WARNING:',
|
||||
obj,
|
||||
'is still alive 5 second after its Widget died;'
|
||||
' you might have a memory leak. Look for circular'
|
||||
' references or outside things referencing your Window'
|
||||
' class instance. See efro.debug module'
|
||||
' for tools that can help debug this sort of thing.',
|
||||
)
|
||||
else:
|
||||
remainingchecks.append(check)
|
||||
self._cleanupchecks = remainingchecks
|
||||
|
||||
|
||||
@dataclass
|
||||
class _UICleanupCheck:
|
||||
"""Holds info about a uicleanupcheck target."""
|
||||
|
||||
obj: weakref.ref
|
||||
widget: bauiv1.Widget
|
||||
widget_death_time: float | None
|
||||
|
|
|
|||
304
dist/ba_data/python/bauiv1/_cloudui.py
vendored
Normal file
304
dist/ba_data/python/bauiv1/_cloudui.py
vendored
Normal file
|
|
@ -0,0 +1,304 @@
|
|||
# Released under the MIT License. See LICENSE for details.
|
||||
#
|
||||
"""UIs provided by the cloud (similar-ish to html in concept)."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import random
|
||||
from dataclasses import dataclass
|
||||
from typing import TYPE_CHECKING, override, Annotated
|
||||
|
||||
from efro.dataclassio import ioprepped, IOAttrs
|
||||
import babase
|
||||
from bauiv1._window import MainWindow, BasicMainWindowState
|
||||
import _bauiv1
|
||||
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from bauiv1._window import MainWindowState
|
||||
|
||||
|
||||
def show_cloud_ui_window() -> None:
|
||||
"""Bust out a cloud-ui window."""
|
||||
|
||||
# Pop up an auxiliary window wherever we are in the nav stack.
|
||||
babase.app.ui_v1.auxiliary_window_activate(
|
||||
win_type=CloudUIWindow,
|
||||
win_create_call=lambda: CloudUIWindow(state=None),
|
||||
)
|
||||
|
||||
|
||||
@ioprepped
|
||||
@dataclass
|
||||
class CloudUIButton:
|
||||
"""Represents a button in a cloud-ui."""
|
||||
|
||||
|
||||
@ioprepped
|
||||
@dataclass
|
||||
class CloudUIRow:
|
||||
"""Represents a row in a cloud-ui."""
|
||||
|
||||
buttons: Annotated[list[CloudUIButton], IOAttrs('b')]
|
||||
|
||||
|
||||
@ioprepped
|
||||
@dataclass
|
||||
class CloudUIRoot:
|
||||
"""Represents an entire cloud-ui."""
|
||||
|
||||
title: Annotated[str, IOAttrs('t')]
|
||||
rows: Annotated[list[CloudUIRow], IOAttrs('r')]
|
||||
|
||||
|
||||
class CloudUIWindow(MainWindow):
|
||||
"""An example of a well-behaved main-window."""
|
||||
|
||||
@dataclass
|
||||
class _State:
|
||||
root: CloudUIRoot | None
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
state: _State | None,
|
||||
*,
|
||||
transition: str | None = 'in_right',
|
||||
origin_widget: _bauiv1.Widget | None = None,
|
||||
auxiliary_style: bool = True,
|
||||
):
|
||||
ui = babase.app.ui_v1
|
||||
|
||||
self._state: CloudUIWindow._State | None = None
|
||||
|
||||
# We want to display differently whether we're an auxiliary
|
||||
# window or not, but unfortunately that value is not yet
|
||||
# available until we're added to the main-window-stack so it
|
||||
# must be explicitly passed in.
|
||||
self._auxiliary_style = auxiliary_style
|
||||
|
||||
# Calc scale and size for our backing window. For medium & large
|
||||
# ui-scale we aim for a window small enough to always be fully
|
||||
# visible on-screen and for small mode we aim for a window big
|
||||
# enough that we never see the window edges; only the window
|
||||
# texture covering the whole screen.
|
||||
uiscale = ui.uiscale
|
||||
self._width = 1400 if uiscale is babase.UIScale.SMALL else 750
|
||||
self._height = 1200 if uiscale is babase.UIScale.SMALL else 500
|
||||
scale = (
|
||||
1.5
|
||||
if uiscale is babase.UIScale.SMALL
|
||||
else 1.2 if uiscale is babase.UIScale.MEDIUM else 1.0
|
||||
)
|
||||
|
||||
# Do some fancy math to calculate our visible area; this will be
|
||||
# limited by the screen size in small mode and our backing size
|
||||
# otherwise.
|
||||
screensize = babase.get_virtual_screen_size()
|
||||
self._vis_width = min(self._width - 100, screensize[0] / scale)
|
||||
self._vis_height = min(self._height - 100, screensize[1] / scale)
|
||||
self._vis_top = 0.5 * self._height + 0.5 * self._vis_height
|
||||
self._vis_left = 0.5 * self._width - 0.5 * self._vis_width
|
||||
|
||||
# Nudge our vis area up a bit when we can see the full backing
|
||||
# (visual fudge factor).
|
||||
if uiscale is not babase.UIScale.SMALL:
|
||||
self._vis_top += 12.0
|
||||
|
||||
super().__init__(
|
||||
root_widget=_bauiv1.containerwidget(
|
||||
size=(self._width, self._height),
|
||||
toolbar_visibility='menu_full',
|
||||
toolbar_cancel_button_style=(
|
||||
'close' if auxiliary_style else 'back'
|
||||
),
|
||||
scale=scale,
|
||||
),
|
||||
transition=transition,
|
||||
origin_widget=origin_widget,
|
||||
# We respond to screen size changes only at small ui-scale;
|
||||
# in other cases we assume our window remains fully visible
|
||||
# always (flip to windowed mode and resize the app window to
|
||||
# confirm this).
|
||||
refresh_on_screen_size_changes=uiscale is babase.UIScale.SMALL,
|
||||
)
|
||||
# Avoid complaints if nothing is selected under us.
|
||||
_bauiv1.widget(edit=self._root_widget, allow_preserve_selection=False)
|
||||
|
||||
# Title.
|
||||
self._title = _bauiv1.textwidget(
|
||||
parent=self._root_widget,
|
||||
position=(self._width * 0.5, self._vis_top - 20),
|
||||
size=(0, 0),
|
||||
text='',
|
||||
color=ui.title_color,
|
||||
scale=0.9 if uiscale is babase.UIScale.SMALL else 1.0,
|
||||
# Make sure we avoid overlapping meters in small mode.
|
||||
maxwidth=(130 if uiscale is babase.UIScale.SMALL else 200),
|
||||
h_align='center',
|
||||
v_align='center',
|
||||
)
|
||||
|
||||
# For small UI-scale we use the system back/close button;
|
||||
# otherwise we make our own.
|
||||
if uiscale is babase.UIScale.SMALL:
|
||||
_bauiv1.containerwidget(
|
||||
edit=self._root_widget, on_cancel_call=self.main_window_back
|
||||
)
|
||||
else:
|
||||
btn = _bauiv1.buttonwidget(
|
||||
parent=self._root_widget,
|
||||
id=f'{self.main_window_id_prefix}|close',
|
||||
scale=0.8,
|
||||
position=(self._vis_left - 15, self._vis_top - 30),
|
||||
size=(50, 50) if auxiliary_style else (60, 55),
|
||||
extra_touch_border_scale=2.0,
|
||||
button_type=None if auxiliary_style else 'backSmall',
|
||||
on_activate_call=self.main_window_back,
|
||||
autoselect=True,
|
||||
label=babase.charstr(
|
||||
babase.SpecialChar.CLOSE
|
||||
if auxiliary_style
|
||||
else babase.SpecialChar.BACK
|
||||
),
|
||||
)
|
||||
_bauiv1.containerwidget(edit=self._root_widget, cancel_button=btn)
|
||||
|
||||
# Show our vis-area bounds (for debugging).
|
||||
if bool(True):
|
||||
# Skip top-left since its always overlapping back/close
|
||||
# buttons.
|
||||
if bool(False):
|
||||
_bauiv1.textwidget(
|
||||
parent=self._root_widget,
|
||||
position=(self._vis_left, self._vis_top),
|
||||
size=(0, 0),
|
||||
color=(1, 1, 1, 0.5),
|
||||
scale=0.5,
|
||||
text='TL',
|
||||
h_align='left',
|
||||
v_align='top',
|
||||
)
|
||||
_bauiv1.textwidget(
|
||||
parent=self._root_widget,
|
||||
position=(self._vis_left + self._vis_width, self._vis_top),
|
||||
size=(0, 0),
|
||||
color=(1, 1, 1, 0.5),
|
||||
scale=0.5,
|
||||
text='TR',
|
||||
h_align='right',
|
||||
v_align='top',
|
||||
)
|
||||
_bauiv1.textwidget(
|
||||
parent=self._root_widget,
|
||||
position=(self._vis_left, self._vis_top - self._vis_height),
|
||||
size=(0, 0),
|
||||
color=(1, 1, 1, 0.5),
|
||||
scale=0.5,
|
||||
text='BL',
|
||||
h_align='left',
|
||||
v_align='bottom',
|
||||
)
|
||||
_bauiv1.textwidget(
|
||||
parent=self._root_widget,
|
||||
position=(
|
||||
self._vis_left + self._vis_width,
|
||||
self._vis_top - self._vis_height,
|
||||
),
|
||||
size=(0, 0),
|
||||
scale=0.5,
|
||||
color=(1, 1, 1, 0.5),
|
||||
text='BR',
|
||||
h_align='right',
|
||||
v_align='bottom',
|
||||
)
|
||||
|
||||
self._spinner: _bauiv1.Widget | None = _bauiv1.spinnerwidget(
|
||||
parent=self._root_widget,
|
||||
position=(
|
||||
self._vis_left + self._vis_width * 0.5,
|
||||
self._vis_top - self._vis_height * 0.5,
|
||||
),
|
||||
size=48,
|
||||
style='bomb',
|
||||
)
|
||||
|
||||
if state is not None:
|
||||
self._set_state(state)
|
||||
else:
|
||||
if random.random() < 0.3:
|
||||
babase.apptimer(1.0, babase.WeakCall(self._on_error_response))
|
||||
else:
|
||||
babase.apptimer(1.0, babase.WeakCall(self._on_response))
|
||||
|
||||
def _on_error_response(self) -> None:
|
||||
self._set_state(self._State(None))
|
||||
|
||||
def _on_response(self) -> None:
|
||||
self._set_state(self._State(CloudUIRoot(title='Testing', rows=[])))
|
||||
|
||||
def _set_state(self, state: _State) -> None:
|
||||
"""Set a final state (error or page contents).
|
||||
|
||||
This state may be instantly restored if the window is recreated
|
||||
(depending on cache lifespan/etc.)
|
||||
"""
|
||||
|
||||
assert self._state is None
|
||||
self._state = state
|
||||
|
||||
if self._spinner:
|
||||
self._spinner.delete()
|
||||
self._spinner = None
|
||||
|
||||
if self._state.root is None:
|
||||
_bauiv1.textwidget(
|
||||
edit=self._title,
|
||||
literal=False, # Allow Lstr.
|
||||
text=babase.Lstr(resource='errorText'),
|
||||
)
|
||||
_bauiv1.textwidget(
|
||||
parent=self._root_widget,
|
||||
position=(
|
||||
self._vis_left + 0.5 * self._vis_width,
|
||||
self._vis_top - 0.5 * self._vis_height,
|
||||
),
|
||||
size=(0, 0),
|
||||
scale=0.6,
|
||||
text=babase.Lstr(resource='store.loadErrorText'),
|
||||
h_align='center',
|
||||
v_align='center',
|
||||
)
|
||||
else:
|
||||
_bauiv1.textwidget(
|
||||
edit=self._title,
|
||||
literal=True, # Never interpret as Lstr.
|
||||
text=self._state.root.title,
|
||||
)
|
||||
|
||||
@override
|
||||
def get_main_window_state(self) -> MainWindowState:
|
||||
# Support recreating our window for back/refresh purposes.
|
||||
cls = type(self)
|
||||
|
||||
# IMPORTANT - Pull values from self HERE; if we do it in the
|
||||
# lambda below it'll keep self alive which will lead to
|
||||
# 'ui-not-getting-cleaned-up' warnings and memory leaks.
|
||||
auxiliary_style = self._auxiliary_style
|
||||
state = self._state
|
||||
|
||||
return BasicMainWindowState(
|
||||
create_call=lambda transition, origin_widget: cls(
|
||||
state=state,
|
||||
transition=transition,
|
||||
origin_widget=origin_widget,
|
||||
auxiliary_style=auxiliary_style,
|
||||
),
|
||||
)
|
||||
|
||||
@override
|
||||
def main_window_should_preserve_selection(self) -> bool:
|
||||
return True
|
||||
|
||||
@override
|
||||
def get_main_window_shared_state_id(self) -> str | None:
|
||||
return 'cloudui'
|
||||
413
dist/ba_data/python/bauiv1/_uitypes.py
vendored
413
dist/ba_data/python/bauiv1/_uitypes.py
vendored
|
|
@ -1,13 +1,10 @@
|
|||
# Released under the MIT License. See LICENSE for details.
|
||||
#
|
||||
"""Provide top level UI related functionality."""
|
||||
"""Misc UI related types."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import os
|
||||
import weakref
|
||||
import logging
|
||||
from dataclasses import dataclass
|
||||
import warnings
|
||||
from typing import TYPE_CHECKING, override
|
||||
|
||||
import babase
|
||||
|
|
@ -19,405 +16,21 @@ if TYPE_CHECKING:
|
|||
|
||||
import bauiv1
|
||||
|
||||
# Set environment variable BA_DEBUG_UI_CLEANUP_CHECKS to 1
|
||||
# to print detailed info about what is getting cleaned up when.
|
||||
DEBUG_UI_CLEANUP_CHECKS = os.environ.get('BA_DEBUG_UI_CLEANUP_CHECKS') == '1'
|
||||
|
||||
|
||||
class Window:
|
||||
"""A basic window.
|
||||
|
||||
Essentially wraps a ContainerWidget with some higher level
|
||||
functionality.
|
||||
"""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
root_widget: bauiv1.Widget,
|
||||
cleanupcheck: bool = True,
|
||||
prevent_main_window_auto_recreate: bool = True,
|
||||
):
|
||||
self._root_widget = root_widget
|
||||
|
||||
# By default, the presence of any generic windows prevents the
|
||||
# app from running its fancy main-window-auto-recreate mechanism
|
||||
# on screen-resizes and whatnot. This avoids things like
|
||||
# temporary popup windows getting stuck under auto-re-created
|
||||
# main-windows.
|
||||
self._window_main_window_auto_recreate_suppress = (
|
||||
MainWindowAutoRecreateSuppress()
|
||||
if prevent_main_window_auto_recreate
|
||||
else None
|
||||
)
|
||||
|
||||
# Generally we complain if we outlive our root widget.
|
||||
if cleanupcheck:
|
||||
uicleanupcheck(self, root_widget)
|
||||
|
||||
def get_root_widget(self) -> bauiv1.Widget:
|
||||
"""Return the root widget."""
|
||||
return self._root_widget
|
||||
|
||||
|
||||
class MainWindow(Window):
|
||||
"""A special type of window that can be set as 'main'.
|
||||
|
||||
The UI system has at most one main window at any given time.
|
||||
MainWindows support high level functionality such as saving and
|
||||
restoring states, allowing them to be automatically recreated when
|
||||
navigating back from other locations or when something like ui-scale
|
||||
changes.
|
||||
"""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
root_widget: bauiv1.Widget,
|
||||
*,
|
||||
transition: str | None,
|
||||
origin_widget: bauiv1.Widget | None,
|
||||
cleanupcheck: bool = True,
|
||||
refresh_on_screen_size_changes: bool = False,
|
||||
):
|
||||
"""Create a MainWindow given a root widget and transition info.
|
||||
|
||||
Automatically handles in and out transitions on the provided
|
||||
widget, so there is no need to set transitions when creating it.
|
||||
"""
|
||||
# A back-state supplied by the ui system.
|
||||
self.main_window_back_state: MainWindowState | None = None
|
||||
|
||||
self.main_window_is_top_level: bool = False
|
||||
|
||||
# Windows that size tailor themselves to exact screen dimensions
|
||||
# can pass True for this. Generally this only applies to small
|
||||
# ui scale and at larger scales windows simply fit in the
|
||||
# virtual safe area.
|
||||
self.refreshes_on_screen_size_changes = refresh_on_screen_size_changes
|
||||
|
||||
# Windows can be flagged as auxiliary when not related to the
|
||||
# main UI task at hand. UI code may choose to handle auxiliary
|
||||
# windows in special ways, such as by implicitly replacing
|
||||
# existing auxiliary windows with new ones instead of keeping
|
||||
# old ones as back targets.
|
||||
self.main_window_is_auxiliary: bool = False
|
||||
|
||||
self._main_window_transition = transition
|
||||
self._main_window_origin_widget = origin_widget
|
||||
super().__init__(
|
||||
root_widget,
|
||||
cleanupcheck=cleanupcheck,
|
||||
prevent_main_window_auto_recreate=False,
|
||||
)
|
||||
|
||||
scale_origin: tuple[float, float] | None
|
||||
if origin_widget is not None:
|
||||
self._main_window_transition_out = 'out_scale'
|
||||
scale_origin = origin_widget.get_screen_space_center()
|
||||
transition = 'in_scale'
|
||||
else:
|
||||
self._main_window_transition_out = 'out_right'
|
||||
scale_origin = None
|
||||
_bauiv1.containerwidget(
|
||||
edit=root_widget,
|
||||
transition=transition,
|
||||
scale_origin_stack_offset=scale_origin,
|
||||
)
|
||||
|
||||
def main_window_close(self, transition: str | None = None) -> None:
|
||||
"""Get window transitioning out if still alive."""
|
||||
|
||||
# no-op if our underlying widget is dead or on its way out.
|
||||
if not self._root_widget or self._root_widget.transitioning_out:
|
||||
return
|
||||
|
||||
# Transition ourself out.
|
||||
try:
|
||||
self.on_main_window_close()
|
||||
except Exception:
|
||||
logging.exception('Error in on_main_window_close() for %s.', self)
|
||||
|
||||
# Note: normally transition of None means instant, but we use
|
||||
# that to mean 'do the default' so we support a special
|
||||
# 'instant' string.
|
||||
if transition == 'instant':
|
||||
self._root_widget.delete()
|
||||
else:
|
||||
_bauiv1.containerwidget(
|
||||
edit=self._root_widget,
|
||||
transition=(
|
||||
self._main_window_transition_out
|
||||
if transition is None
|
||||
else transition
|
||||
),
|
||||
)
|
||||
|
||||
def main_window_has_control(self) -> bool:
|
||||
"""Is this MainWindow allowed to change the global main window?
|
||||
|
||||
It is a good idea to make sure this is True before calling
|
||||
main_window_replace(). This prevents fluke UI breakage such as
|
||||
multiple simultaneous events causing a MainWindow to spawn
|
||||
multiple replacements for itself.
|
||||
"""
|
||||
# We are allowed to change main windows if we are the current one
|
||||
# AND our underlying widget is still alive and not transitioning out.
|
||||
return (
|
||||
babase.app.ui_v1.get_main_window() is self
|
||||
and bool(self._root_widget)
|
||||
and not self._root_widget.transitioning_out
|
||||
)
|
||||
|
||||
def main_window_back(self) -> None:
|
||||
"""Move back in the main window stack.
|
||||
|
||||
Is a no-op if the main window does not have control;
|
||||
no need to check main_window_has_control() first.
|
||||
"""
|
||||
|
||||
# Users should always check main_window_has_control() before
|
||||
# calling us. Error if it seems they did not.
|
||||
if not self.main_window_has_control():
|
||||
return
|
||||
|
||||
uiv1 = babase.app.ui_v1
|
||||
|
||||
# Get the 'back' window coming in.
|
||||
if not self.main_window_is_top_level:
|
||||
|
||||
back_state = self.main_window_back_state
|
||||
if back_state is None:
|
||||
raise RuntimeError(
|
||||
f'Main window {self} provides no back-state.'
|
||||
)
|
||||
|
||||
# Valid states should have values here.
|
||||
assert back_state.is_top_level is not None
|
||||
assert back_state.is_auxiliary is not None
|
||||
assert back_state.window_type is not None
|
||||
|
||||
# When leaving an auxiliary window, scale the destination
|
||||
# window in instead of sliding to convey that its more of a
|
||||
# 'swapping out' than a 'back' action.
|
||||
backwin = back_state.create_window(
|
||||
transition=(
|
||||
'in_scale' if self.main_window_is_auxiliary else 'in_left'
|
||||
)
|
||||
)
|
||||
|
||||
uiv1.set_main_window(
|
||||
backwin,
|
||||
from_window=self,
|
||||
is_back=True,
|
||||
back_state=back_state,
|
||||
suppress_warning=True,
|
||||
)
|
||||
|
||||
# Transition ourself out.
|
||||
self.main_window_close()
|
||||
|
||||
def main_window_replace(
|
||||
self,
|
||||
new_window: MainWindow,
|
||||
back_state: MainWindowState | None = None,
|
||||
is_auxiliary: bool = False,
|
||||
) -> None:
|
||||
"""Replace ourself with a new MainWindow."""
|
||||
|
||||
# Users should always check main_window_has_control() *before*
|
||||
# creating new MainWindows and passing them in here. Kill the
|
||||
# passed window and Error if it seems they did not.
|
||||
if not self.main_window_has_control():
|
||||
new_window.get_root_widget().delete()
|
||||
raise RuntimeError(
|
||||
f'main_window_replace() called on a not-in-control window'
|
||||
f' ({self}); always check main_window_has_control() before'
|
||||
f' calling main_window_replace().'
|
||||
)
|
||||
|
||||
# For auxiliary windows, use scale to give a feel that we're
|
||||
# switching over to a totally separate 'side quest' ui. For
|
||||
# regular back/forward relationships, shove the old out the left
|
||||
# to give the feel that we're adding to a nav stack.
|
||||
if is_auxiliary:
|
||||
transition = 'out_scale'
|
||||
else:
|
||||
transition = 'out_left'
|
||||
|
||||
# Transition ourself out.
|
||||
try:
|
||||
self.on_main_window_close()
|
||||
except Exception:
|
||||
logging.exception('Error in on_main_window_close() for %s.', self)
|
||||
|
||||
_bauiv1.containerwidget(edit=self._root_widget, transition=transition)
|
||||
babase.app.ui_v1.set_main_window(
|
||||
new_window,
|
||||
from_window=self,
|
||||
back_state=back_state,
|
||||
is_auxiliary=is_auxiliary,
|
||||
suppress_warning=True,
|
||||
)
|
||||
|
||||
def on_main_window_close(self) -> None:
|
||||
"""Called before transitioning out a main window.
|
||||
|
||||
A good opportunity to save window state/etc.
|
||||
"""
|
||||
|
||||
def get_main_window_state(self) -> MainWindowState:
|
||||
"""Return a WindowState to recreate this window, if supported."""
|
||||
raise NotImplementedError()
|
||||
|
||||
|
||||
class MainWindowState:
|
||||
"""Persistent state for a specific MainWindow.
|
||||
|
||||
This allows MainWindows to be automatically recreated for back-button
|
||||
purposes, when switching app-modes, etc.
|
||||
"""
|
||||
|
||||
def __init__(self) -> None:
|
||||
# The window that back/cancel navigation should take us to.
|
||||
self.parent: MainWindowState | None = None
|
||||
self.is_top_level: bool | None = None
|
||||
self.is_auxiliary: bool | None = None
|
||||
self.window_type: type[MainWindow] | None = None
|
||||
self.selection: str | None = None
|
||||
|
||||
def create_window(
|
||||
self,
|
||||
transition: Literal['in_right', 'in_left', 'in_scale'] | None = None,
|
||||
origin_widget: bauiv1.Widget | None = None,
|
||||
) -> MainWindow:
|
||||
"""Create a window based on this state.
|
||||
|
||||
WindowState child classes should override this to recreate their
|
||||
particular type of window.
|
||||
"""
|
||||
raise NotImplementedError()
|
||||
|
||||
|
||||
class BasicMainWindowState(MainWindowState):
|
||||
"""A basic MainWindowState holding a lambda to recreate a MainWindow."""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
create_call: Callable[
|
||||
[
|
||||
Literal['in_right', 'in_left', 'in_scale'] | None,
|
||||
bauiv1.Widget | None,
|
||||
],
|
||||
bauiv1.MainWindow,
|
||||
],
|
||||
) -> None:
|
||||
super().__init__()
|
||||
self.create_call = create_call
|
||||
|
||||
@override
|
||||
def create_window(
|
||||
self,
|
||||
transition: Literal['in_right', 'in_left', 'in_scale'] | None = None,
|
||||
origin_widget: bauiv1.Widget | None = None,
|
||||
) -> bauiv1.MainWindow:
|
||||
return self.create_call(transition, origin_widget)
|
||||
|
||||
|
||||
class MainWindowAutoRecreateSuppress:
|
||||
"""Suppresses main-window auto-recreate while in existence.
|
||||
|
||||
Can be instantiated and held by windows or processes within windows
|
||||
for the purpose of preventing the main-window auto-recreate
|
||||
mechanism from firing. This mechanism normally fires when the screen
|
||||
is resized or the ui-scale is changed, allowing windows to be
|
||||
recreated to adapt to the new configuration.
|
||||
"""
|
||||
|
||||
def __init__(self) -> None:
|
||||
babase.app.ui_v1.window_auto_recreate_suppress_count += 1
|
||||
|
||||
def __del__(self) -> None:
|
||||
babase.app.ui_v1.window_auto_recreate_suppress_count -= 1
|
||||
|
||||
|
||||
@dataclass
|
||||
class UICleanupCheck:
|
||||
"""Holds info about a uicleanupcheck target."""
|
||||
|
||||
obj: weakref.ref
|
||||
widget: bauiv1.Widget
|
||||
widget_death_time: float | None
|
||||
|
||||
|
||||
# REMOVE WHEN API 9 SUPPORT ENDS
|
||||
def uicleanupcheck(obj: Any, widget: bauiv1.Widget) -> None:
|
||||
"""Checks to ensure a widget-owning object gets cleaned up properly.
|
||||
|
||||
This adds a check which will print an error message if the provided
|
||||
object still exists ~5 seconds after the provided bauiv1.Widget dies.
|
||||
|
||||
This is a good sanity check for any sort of object that wraps or
|
||||
controls a bauiv1.Widget. For instance, a 'Window' class instance has
|
||||
no reason to still exist once its root container bauiv1.Widget has fully
|
||||
transitioned out and been destroyed. Circular references or careless
|
||||
strong referencing can lead to such objects never getting destroyed,
|
||||
however, and this helps detect such cases to avoid memory leaks.
|
||||
"""
|
||||
if DEBUG_UI_CLEANUP_CHECKS:
|
||||
print(f'adding uicleanup to {obj}')
|
||||
if not isinstance(widget, _bauiv1.Widget):
|
||||
raise TypeError('widget arg is not a bauiv1.Widget')
|
||||
|
||||
if bool(False):
|
||||
|
||||
def foobar() -> None:
|
||||
"""Just testing."""
|
||||
if DEBUG_UI_CLEANUP_CHECKS:
|
||||
print('uicleanupcheck widget dying...')
|
||||
|
||||
widget.add_delete_callback(foobar)
|
||||
|
||||
assert babase.app.classic is not None
|
||||
babase.app.ui_v1.cleanupchecks.append(
|
||||
UICleanupCheck(
|
||||
obj=weakref.ref(obj), widget=widget, widget_death_time=None
|
||||
)
|
||||
.. deprecated:: 1.7.51
|
||||
Use :meth:`UIV1AppSubsystem.add_ui_cleanup_check()`.
|
||||
Will be removed when api 9 support ends.
|
||||
"""
|
||||
warnings.warn(
|
||||
'bauiv1.uicleanupcheck() will be removed when api 9 support ends;'
|
||||
' use ba*.app.ui_v1.add_ui_cleanup_check() instead.',
|
||||
DeprecationWarning,
|
||||
stacklevel=2,
|
||||
)
|
||||
|
||||
|
||||
def ui_upkeep() -> None:
|
||||
"""Run UI cleanup checks, etc. should be called periodically."""
|
||||
assert babase.app.classic is not None
|
||||
ui = babase.app.ui_v1
|
||||
remainingchecks = []
|
||||
now = babase.apptime()
|
||||
for check in ui.cleanupchecks:
|
||||
obj = check.obj()
|
||||
|
||||
# If the object has died, ignore and don't re-add.
|
||||
if obj is None:
|
||||
if DEBUG_UI_CLEANUP_CHECKS:
|
||||
print('uicleanupcheck object is dead; hooray!')
|
||||
continue
|
||||
|
||||
# If the widget hadn't died yet, note if it has.
|
||||
if check.widget_death_time is None:
|
||||
remainingchecks.append(check)
|
||||
if not check.widget:
|
||||
check.widget_death_time = now
|
||||
else:
|
||||
# Widget was already dead; complain if its been too long.
|
||||
if now - check.widget_death_time > 5.0:
|
||||
print(
|
||||
'WARNING:',
|
||||
obj,
|
||||
'is still alive 5 second after its Widget died;'
|
||||
' you might have a memory leak. Look for circular'
|
||||
' references or outside things referencing your Window'
|
||||
' class instance. See efro.debug module'
|
||||
' for tools that can help debug this sort of thing.',
|
||||
)
|
||||
else:
|
||||
remainingchecks.append(check)
|
||||
ui.cleanupchecks = remainingchecks
|
||||
babase.app.ui_v1.add_ui_cleanup_check(obj, widget)
|
||||
|
||||
|
||||
class TextWidgetStringEditAdapter(babase.StringEditAdapter):
|
||||
|
|
|
|||
604
dist/ba_data/python/bauiv1/_window.py
vendored
Normal file
604
dist/ba_data/python/bauiv1/_window.py
vendored
Normal file
|
|
@ -0,0 +1,604 @@
|
|||
# Released under the MIT License. See LICENSE for details.
|
||||
#
|
||||
"""Window related UI bits."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import logging
|
||||
import warnings
|
||||
from typing import TYPE_CHECKING, override
|
||||
|
||||
import babase
|
||||
|
||||
import _bauiv1
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from typing import Any, Type, Literal, Callable
|
||||
|
||||
import bauiv1
|
||||
|
||||
|
||||
class Window:
|
||||
"""A basic window.
|
||||
|
||||
Essentially wraps a ContainerWidget with some higher level
|
||||
functionality.
|
||||
"""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
root_widget: bauiv1.Widget,
|
||||
cleanupcheck: bool = True,
|
||||
prevent_main_window_auto_recreate: bool = True,
|
||||
):
|
||||
self._root_widget = root_widget
|
||||
|
||||
# By default, the presence of any generic windows prevents the
|
||||
# app from running its fancy main-window-auto-recreate mechanism
|
||||
# on screen-resizes and whatnot. This avoids things like
|
||||
# temporary popup windows getting stuck under auto-re-created
|
||||
# main-windows.
|
||||
self._window_main_window_auto_recreate_suppress = (
|
||||
MainWindowAutoRecreateSuppress()
|
||||
if prevent_main_window_auto_recreate
|
||||
else None
|
||||
)
|
||||
|
||||
# Generally we complain if we outlive our root widget.
|
||||
if cleanupcheck:
|
||||
babase.app.ui_v1.add_ui_cleanup_check(self, root_widget)
|
||||
|
||||
def get_root_widget(self) -> bauiv1.Widget:
|
||||
"""Return the root widget."""
|
||||
return self._root_widget
|
||||
|
||||
|
||||
class MainWindow(Window):
|
||||
"""A special type of window that can be set as 'main'.
|
||||
|
||||
The UI system has at most one main window at any given time.
|
||||
MainWindows support high level functionality such as saving and
|
||||
restoring states, allowing them to be automatically recreated when
|
||||
navigating back from other locations or when something like ui-scale
|
||||
changes.
|
||||
"""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
root_widget: bauiv1.Widget,
|
||||
*,
|
||||
transition: str | None,
|
||||
origin_widget: bauiv1.Widget | None,
|
||||
cleanupcheck: bool = True,
|
||||
refresh_on_screen_size_changes: bool = False,
|
||||
):
|
||||
"""Create a MainWindow given a root widget and transition info.
|
||||
|
||||
Automatically handles in and out transitions on the provided
|
||||
widget, so there is no need to set transitions when creating it.
|
||||
"""
|
||||
|
||||
self.main_window_id_prefix = babase.app.ui_v1.new_id_prefix(
|
||||
type(self).__name__.lower()
|
||||
)
|
||||
|
||||
# A back-state supplied by the ui system.
|
||||
self.main_window_back_state: MainWindowState | None = None
|
||||
|
||||
self.main_window_is_top_level: bool = False
|
||||
|
||||
# Windows that size tailor themselves to exact screen dimensions
|
||||
# can pass True for this. Generally this only applies to small
|
||||
# ui scale and at larger scales windows simply fit in the
|
||||
# virtual safe area.
|
||||
self.refreshes_on_screen_size_changes = refresh_on_screen_size_changes
|
||||
|
||||
# Windows can be flagged as auxiliary when not related to the
|
||||
# main UI task at hand. UI code may choose to handle auxiliary
|
||||
# windows in special ways, such as by implicitly replacing
|
||||
# existing auxiliary windows with new ones instead of keeping
|
||||
# old ones as back targets.
|
||||
self.main_window_is_auxiliary: bool = False
|
||||
|
||||
self._main_window_transition = transition
|
||||
self._main_window_origin_widget = origin_widget
|
||||
super().__init__(
|
||||
root_widget,
|
||||
cleanupcheck=cleanupcheck,
|
||||
prevent_main_window_auto_recreate=False,
|
||||
)
|
||||
|
||||
scale_origin: tuple[float, float] | None
|
||||
if origin_widget is not None:
|
||||
self._main_window_transition_out = 'out_scale'
|
||||
scale_origin = origin_widget.get_screen_space_center()
|
||||
transition = 'in_scale'
|
||||
else:
|
||||
self._main_window_transition_out = 'out_right'
|
||||
scale_origin = None
|
||||
_bauiv1.containerwidget(
|
||||
edit=root_widget,
|
||||
transition=transition,
|
||||
scale_origin_stack_offset=scale_origin,
|
||||
)
|
||||
|
||||
def main_window_save_shared_state(self) -> None:
|
||||
"""Save shared state (such as widget selection).
|
||||
|
||||
This is automatically called just before main-windows are
|
||||
destroyed, but the user may opt to call it at other times such
|
||||
as before refreshing a UI (so that selection can be restored
|
||||
after the refresh, etc.)
|
||||
|
||||
State contained here is intended to operate on
|
||||
already-constructed UI; state that influences which UI is
|
||||
contructed should go through other mechanisms.
|
||||
"""
|
||||
# pylint: disable=assignment-from-none
|
||||
key = self.get_main_window_shared_state_id()
|
||||
assert isinstance(key, str | None)
|
||||
keyfin = type(self) if key is None else key
|
||||
|
||||
shared_state: dict = {}
|
||||
|
||||
# Save selection if desired.
|
||||
if self._get_main_window_should_preserve_selection():
|
||||
sel = _bauiv1.get_selected_widget()
|
||||
if sel is None:
|
||||
selfin = None
|
||||
else:
|
||||
if sel.allow_preserve_selection:
|
||||
selfin = sel.id
|
||||
if selfin is not None:
|
||||
pre = f'{self.main_window_id_prefix}|'
|
||||
if selfin.startswith(pre):
|
||||
selfin = f'$(WIN)|{selfin.removeprefix(pre)}'
|
||||
babase.uilog.debug(
|
||||
"Saving ui selection from '%s': '%s'.",
|
||||
self.main_window_id_prefix,
|
||||
selfin,
|
||||
)
|
||||
else:
|
||||
# if not sel.allow_preserve_selection:
|
||||
babase.uilog.warning(
|
||||
'main_window_should_preserve_selection()'
|
||||
' returned True for %s but no id was assigned'
|
||||
' to the currently selected widget %s. All'
|
||||
' selectable widgets must be assigned unique'
|
||||
' ids for selection-preserving to work'
|
||||
' properly.',
|
||||
self,
|
||||
sel,
|
||||
)
|
||||
else:
|
||||
selfin = None
|
||||
babase.uilog.debug(
|
||||
"Not saving ui selection from '%s';"
|
||||
' selected widget disallows it (%s).',
|
||||
self.main_window_id_prefix,
|
||||
sel,
|
||||
)
|
||||
|
||||
shared_state['selection'] = selfin
|
||||
|
||||
# Allow win to save any custom state. (Do this after selection
|
||||
# save so user can manipulate save output if they want).
|
||||
try:
|
||||
self.main_window_do_save_shared_state(shared_state)
|
||||
except Exception:
|
||||
logging.exception(
|
||||
'Error in main_window_do_save_shared_state() for %s.', self
|
||||
)
|
||||
assert isinstance(shared_state, dict)
|
||||
|
||||
babase.uilog.debug(
|
||||
"Saving shared state from '%s' using key %r.",
|
||||
self.main_window_id_prefix,
|
||||
keyfin,
|
||||
)
|
||||
babase.app.ui_v1.main_window_shared_states[keyfin] = shared_state
|
||||
|
||||
def main_window_restore_shared_state(self) -> None:
|
||||
"""Restore shared state (such as widget selection), if any.
|
||||
|
||||
This is automatically called just after main-windows are
|
||||
created, but the user may opt to call it at other times such as
|
||||
after explicitly refreshing some UI.
|
||||
|
||||
State contained here is intended to operate on
|
||||
already-constructed UI; state that influences which UI is
|
||||
contructed should go through other mechanisms.
|
||||
"""
|
||||
|
||||
# pylint: disable=assignment-from-none
|
||||
key = self.get_main_window_shared_state_id()
|
||||
assert isinstance(key, str | None)
|
||||
keyfin = type(self) if key is None else key
|
||||
babase.uilog.debug(
|
||||
"Restoring shared state to '%s' using key %r.",
|
||||
self.main_window_id_prefix,
|
||||
keyfin,
|
||||
)
|
||||
shared_state = babase.app.ui_v1.main_window_shared_states.get(keyfin)
|
||||
if shared_state is None:
|
||||
shared_state = {}
|
||||
assert isinstance(shared_state, dict)
|
||||
|
||||
# Allow win to restore any custom state. (Do this before
|
||||
# selection restore so user can manipulate input if they want).
|
||||
try:
|
||||
self.main_window_do_restore_shared_state(shared_state)
|
||||
except Exception:
|
||||
logging.exception(
|
||||
'Error in main_window_do_restore_shared_state() for %s.', self
|
||||
)
|
||||
|
||||
# Restore selection if desired.
|
||||
if self._get_main_window_should_preserve_selection():
|
||||
sel = shared_state.get('selection')
|
||||
if isinstance(sel, str):
|
||||
babase.uilog.debug(
|
||||
"Restoring ui selection to '%s': '%s'.",
|
||||
self.main_window_id_prefix,
|
||||
sel,
|
||||
)
|
||||
pre = '$(WIN)|'
|
||||
if sel.startswith(pre):
|
||||
sel = (
|
||||
f'{self.main_window_id_prefix}|{sel.removeprefix(pre)}'
|
||||
)
|
||||
widget = _bauiv1.widget_by_id(sel)
|
||||
if widget is not None:
|
||||
if widget.selectable:
|
||||
widget.global_select()
|
||||
widget.scroll_into_view()
|
||||
else:
|
||||
babase.uilog.debug(
|
||||
"Unable to restore selection '%s';"
|
||||
' widget is not selectable.',
|
||||
sel,
|
||||
)
|
||||
|
||||
else:
|
||||
# We expect this to happen sometimes (windows may come
|
||||
# up with different UIs visible/etc.). Let's note it but
|
||||
# subtly.
|
||||
babase.uilog.debug(
|
||||
"Unable to restore selection '%s'; widget not found.",
|
||||
sel,
|
||||
)
|
||||
|
||||
def main_window_close(self, transition: str | None = None) -> None:
|
||||
"""Get window transitioning out if still alive."""
|
||||
|
||||
# no-op if our underlying widget is dead or on its way out.
|
||||
if not self._root_widget or self._root_widget.transitioning_out:
|
||||
return
|
||||
|
||||
# Save selection, etc.
|
||||
self.main_window_save_shared_state()
|
||||
|
||||
# Give the user a chance to do whatever.
|
||||
try:
|
||||
self.on_main_window_close()
|
||||
except Exception:
|
||||
logging.exception('Error in on_main_window_close() for %s.', self)
|
||||
|
||||
# Transition ourself out.
|
||||
|
||||
# Note: normally transition of None means instant, but we use
|
||||
# that to mean 'do the default' so we support a special
|
||||
# 'instant' string.
|
||||
if transition == 'instant':
|
||||
self._root_widget.delete()
|
||||
else:
|
||||
_bauiv1.containerwidget(
|
||||
edit=self._root_widget,
|
||||
transition=(
|
||||
self._main_window_transition_out
|
||||
if transition is None
|
||||
else transition
|
||||
),
|
||||
)
|
||||
|
||||
def main_window_has_control(self) -> bool:
|
||||
"""Is this MainWindow allowed to change the global main window?
|
||||
|
||||
This is called internally by methods such as
|
||||
:meth:`main_window_replace()` and :meth:`main_window_back()` so
|
||||
generally you do not need to call it directly when using those.
|
||||
However you may still opt to check this if doing other actions
|
||||
besides main-window navigation (such as displaying pop-ups).
|
||||
"""
|
||||
# We are allowed to change main windows if we are the current one
|
||||
# AND our underlying widget is still alive and not transitioning out.
|
||||
return (
|
||||
babase.app.ui_v1.get_main_window() is self
|
||||
and bool(self._root_widget)
|
||||
and not self._root_widget.transitioning_out
|
||||
)
|
||||
|
||||
def main_window_back(self) -> None:
|
||||
"""Move back in the main window stack.
|
||||
|
||||
Is a no-op if the main window does not have control;
|
||||
no need to check main_window_has_control() first.
|
||||
"""
|
||||
|
||||
# Users should always check main_window_has_control() before
|
||||
# calling us. Error if it seems they did not.
|
||||
if not self.main_window_has_control():
|
||||
return
|
||||
|
||||
uiv1 = babase.app.ui_v1
|
||||
|
||||
# Get ourself transitioning out.
|
||||
self.main_window_close()
|
||||
|
||||
# Get the 'back' window coming in.
|
||||
if not self.main_window_is_top_level:
|
||||
|
||||
back_state = self.main_window_back_state
|
||||
if back_state is None:
|
||||
raise RuntimeError(
|
||||
f'Main window {self} provides no back-state.'
|
||||
)
|
||||
|
||||
# Valid states should have values here.
|
||||
assert back_state.is_top_level is not None
|
||||
assert back_state.is_auxiliary is not None
|
||||
assert back_state.window_type is not None
|
||||
|
||||
# When leaving an auxiliary window, scale the destination
|
||||
# window in instead of sliding to convey that its more of a
|
||||
# 'swapping out' than a 'back' action.
|
||||
backwin = back_state.create_window(
|
||||
transition=(
|
||||
'in_scale' if self.main_window_is_auxiliary else 'in_left'
|
||||
)
|
||||
)
|
||||
|
||||
uiv1.set_main_window(
|
||||
backwin,
|
||||
from_window=self,
|
||||
is_back=True,
|
||||
back_state=back_state,
|
||||
suppress_warning=True,
|
||||
)
|
||||
|
||||
def main_window_replace(
|
||||
self,
|
||||
new_window: MainWindow | Callable[[], MainWindow],
|
||||
back_state: MainWindowState | None = None,
|
||||
is_auxiliary: bool = False,
|
||||
) -> MainWindow | None:
|
||||
"""Replace ourself with a new MainWindow.
|
||||
|
||||
Returns the new MainWindow. Will no-op and return None if
|
||||
we are not allowed to replace the MainWindow.
|
||||
"""
|
||||
|
||||
ui = babase.app.ui_v1
|
||||
|
||||
# If they didn't provide an explicit back-state, calc one to
|
||||
# recreate this window.
|
||||
if back_state is None:
|
||||
back_state = ui.save_current_main_window_state()
|
||||
|
||||
# Save selection, etc.
|
||||
self.main_window_save_shared_state()
|
||||
|
||||
if not isinstance(new_window, MainWindow):
|
||||
# If we're not in control, we're not allowed to change things.
|
||||
if not self.main_window_has_control():
|
||||
babase.uilog.debug(
|
||||
'main_window_replace:'
|
||||
' no-op due to main_window_has_control() returning False.',
|
||||
stack_info=True,
|
||||
)
|
||||
return None
|
||||
|
||||
new_window = new_window()
|
||||
else:
|
||||
# We originally were passed MainWindows directly, but want
|
||||
# to phase this out, as it prevents our automatic selection
|
||||
# save/restore from working (we need to save the old
|
||||
# selection *before* the replacement window is created since
|
||||
# the creation itself will change the selection).
|
||||
warnings.warn(
|
||||
'Passing MainWindow objects to main_window_replace() is'
|
||||
' deprecated and will be removed when api 9 support ends.'
|
||||
' You should instead pass calls to generate MainWindow objects.'
|
||||
' So `main_window_replace(MyWin(some_arg))` would become'
|
||||
' `main_win_replace(lambda: MyWin(some_arg))`.',
|
||||
DeprecationWarning,
|
||||
stacklevel=2,
|
||||
)
|
||||
|
||||
# In this old path, users should always check
|
||||
# main_window_has_control() *before* creating new
|
||||
# MainWindows and passing them in here. Kill the passed
|
||||
# window and Error if it seems they did not.
|
||||
if not self.main_window_has_control():
|
||||
new_window.get_root_widget().delete()
|
||||
raise RuntimeError(
|
||||
f'main_window_replace() called on a not-in-control window'
|
||||
f' ({self}); always check main_window_has_control() before'
|
||||
f' calling main_window_replace().'
|
||||
)
|
||||
|
||||
# Give user a chance to do whatever.
|
||||
try:
|
||||
self.on_main_window_close()
|
||||
except Exception:
|
||||
logging.exception('Error in on_main_window_close() for %s.', self)
|
||||
|
||||
# For auxiliary windows, use scale to give a feel that we're
|
||||
# switching over to a totally separate 'side quest' ui. For
|
||||
# regular back/forward relationships, shove the old out the left
|
||||
# to give the feel that we're adding to a nav stack.
|
||||
if is_auxiliary:
|
||||
transition = 'out_scale'
|
||||
else:
|
||||
transition = 'out_left'
|
||||
|
||||
# Transition ourself out.
|
||||
_bauiv1.containerwidget(edit=self._root_widget, transition=transition)
|
||||
babase.app.ui_v1.set_main_window(
|
||||
new_window,
|
||||
from_window=self,
|
||||
back_state=back_state,
|
||||
is_auxiliary=is_auxiliary,
|
||||
suppress_warning=True,
|
||||
)
|
||||
return new_window
|
||||
|
||||
def on_main_window_close(self) -> None:
|
||||
"""Called before transitioning out a main window.
|
||||
|
||||
A good opportunity to save window state/etc.
|
||||
"""
|
||||
|
||||
def get_main_window_state(self) -> MainWindowState:
|
||||
"""Return a WindowState to recreate this specific window.
|
||||
|
||||
Used to gracefully return to a window from another window or ui
|
||||
system.
|
||||
"""
|
||||
raise NotImplementedError()
|
||||
|
||||
def main_window_should_preserve_selection(self) -> bool | None:
|
||||
"""Whether this window should auto-save/restore selection.
|
||||
|
||||
If enabled, selection will be stored in the window's shared
|
||||
state. See :meth:`~bauiv1.MainWindow.get_main_window_shared_state_id()`
|
||||
for more info about main-window shared-state.
|
||||
|
||||
The default value of None results in a warning to explicitly
|
||||
override this (as the implicit default will change from False to
|
||||
True after api 9 support ends).
|
||||
"""
|
||||
return None
|
||||
|
||||
def get_main_window_shared_state_id(self) -> str | None:
|
||||
"""Provide a custom id for window shared state.
|
||||
|
||||
Unlike :class:`~bauiv1.MainWindowState`, which is used to save
|
||||
and restore a single main-window instance, shared-state is
|
||||
intended to hold values that can apply to multiple instances of
|
||||
a window.
|
||||
|
||||
By default, shared state uses the window class as an index (so
|
||||
is shared by all windows of the same class), but this method can
|
||||
be overridden to provide more distinct states. For example, a
|
||||
store-page main-window class might want to keep distinct states
|
||||
for different sub-pages it can display instead of having a
|
||||
single state for the whole class.
|
||||
|
||||
Note that shared state only persists for the current run of the
|
||||
app.
|
||||
"""
|
||||
return None
|
||||
|
||||
def main_window_do_save_shared_state(self, state: dict) -> None:
|
||||
"""Save state into the provided shared state dict.
|
||||
|
||||
Can be overridden by subclasses to save custom data.
|
||||
"""
|
||||
|
||||
def main_window_do_restore_shared_state(self, state: dict) -> None:
|
||||
"""Restore state from the provided shared state dict.
|
||||
|
||||
Can be overridden by subclasses to restore custom data.
|
||||
"""
|
||||
|
||||
def _get_main_window_should_preserve_selection(self) -> bool:
|
||||
# pylint: disable=assignment-from-none
|
||||
val = self.main_window_should_preserve_selection()
|
||||
if val is None:
|
||||
warnings.warn(
|
||||
f'{type(self)} should override'
|
||||
f' main_window_should_preserve_selection()'
|
||||
' to return True or False.'
|
||||
f' The current default is False (for backward compatibility)'
|
||||
f' but it will change to True when api 9 support ends.',
|
||||
FutureWarning,
|
||||
stacklevel=2,
|
||||
)
|
||||
val = False
|
||||
return val
|
||||
|
||||
|
||||
class MainWindowState:
|
||||
"""Persistent state for a specific MainWindow.
|
||||
|
||||
This allows MainWindows to be automatically recreated for back-button
|
||||
purposes, when switching app-modes, etc.
|
||||
"""
|
||||
|
||||
def __init__(self) -> None:
|
||||
# The window that back/cancel navigation should take us to.
|
||||
self.parent: MainWindowState | None = None
|
||||
self.is_top_level: bool | None = None
|
||||
self.is_auxiliary: bool | None = None
|
||||
self.window_type: type[MainWindow] | None = None
|
||||
|
||||
def create_window(
|
||||
self,
|
||||
transition: Literal['in_right', 'in_left', 'in_scale'] | None = None,
|
||||
origin_widget: bauiv1.Widget | None = None,
|
||||
) -> MainWindow:
|
||||
"""Create a window based on this state.
|
||||
|
||||
WindowState child classes should override this to recreate their
|
||||
particular type of window.
|
||||
"""
|
||||
raise NotImplementedError()
|
||||
|
||||
|
||||
class BasicMainWindowState(MainWindowState):
|
||||
"""A basic MainWindowState.
|
||||
|
||||
Holds some call to recreate a window and optionally a selection to
|
||||
restore.
|
||||
"""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
create_call: Callable[
|
||||
[
|
||||
Literal['in_right', 'in_left', 'in_scale'] | None,
|
||||
bauiv1.Widget | None,
|
||||
],
|
||||
bauiv1.MainWindow,
|
||||
],
|
||||
) -> None:
|
||||
super().__init__()
|
||||
self.create_call = create_call
|
||||
|
||||
@override
|
||||
def create_window(
|
||||
self,
|
||||
transition: Literal['in_right', 'in_left', 'in_scale'] | None = None,
|
||||
origin_widget: bauiv1.Widget | None = None,
|
||||
) -> bauiv1.MainWindow:
|
||||
win = self.create_call(transition, origin_widget)
|
||||
|
||||
return win
|
||||
|
||||
|
||||
class MainWindowAutoRecreateSuppress:
|
||||
"""Suppresses main-window auto-recreate while in existence.
|
||||
|
||||
Can be instantiated and held by windows or processes within windows
|
||||
for the purpose of preventing the main-window auto-recreate
|
||||
mechanism from firing. This mechanism normally fires when the screen
|
||||
is resized or the ui-scale is changed, allowing main-windows to be
|
||||
recreated to adapt to the new configuration.
|
||||
"""
|
||||
|
||||
def __init__(self) -> None:
|
||||
babase.app.ui_v1.window_auto_recreate_suppress_count += 1
|
||||
|
||||
def __del__(self) -> None:
|
||||
babase.app.ui_v1.window_auto_recreate_suppress_count -= 1
|
||||
|
|
@ -13,7 +13,7 @@ import babase
|
|||
|
||||
import _bauiv1
|
||||
from bauiv1._keyboard import Keyboard
|
||||
from bauiv1._uitypes import Window
|
||||
from bauiv1._window import Window
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from babase import StringEditAdapter
|
||||
|
|
|
|||
110
dist/ba_data/python/bauiv1lib/account/settings.py
vendored
110
dist/ba_data/python/bauiv1lib/account/settings.py
vendored
|
|
@ -13,6 +13,7 @@ from bacommon.login import LoginType
|
|||
import bacommon.cloud
|
||||
import bauiv1 as bui
|
||||
|
||||
from bauiv1lib.utils import scroll_fade_bottom, scroll_fade_top
|
||||
from bauiv1lib.connectivity import wait_for_connectivity
|
||||
|
||||
|
||||
|
|
@ -90,6 +91,11 @@ class AccountSettingsWindow(bui.MainWindow):
|
|||
self._scroll_height = target_height - 33
|
||||
scroll_bottom = yoffs - 61 - self._scroll_height
|
||||
|
||||
# Go with full-screen scrollable area in small ui.
|
||||
if uiscale is bui.UIScale.SMALL:
|
||||
self._scroll_height += 35
|
||||
scroll_bottom -= 3
|
||||
|
||||
self._sign_in_button = None
|
||||
self._sign_in_text = None
|
||||
|
||||
|
|
@ -115,7 +121,7 @@ class AccountSettingsWindow(bui.MainWindow):
|
|||
super().__init__(
|
||||
root_widget=bui.containerwidget(
|
||||
size=(self._width, self._height),
|
||||
toolbar_visibility=('menu_full'),
|
||||
toolbar_visibility='menu_full',
|
||||
toolbar_cancel_button_style=(
|
||||
'close' if auxiliary_style else 'back'
|
||||
),
|
||||
|
|
@ -134,6 +140,7 @@ class AccountSettingsWindow(bui.MainWindow):
|
|||
else:
|
||||
self._back_button = btn = bui.buttonwidget(
|
||||
parent=self._root_widget,
|
||||
id=f'{self.main_window_id_prefix}|back',
|
||||
position=(51, yoffs - 52.0),
|
||||
size=(60, 56),
|
||||
scale=0.8,
|
||||
|
|
@ -149,6 +156,37 @@ class AccountSettingsWindow(bui.MainWindow):
|
|||
)
|
||||
bui.containerwidget(edit=self._root_widget, cancel_button=btn)
|
||||
|
||||
self._scrollwidget = bui.scrollwidget(
|
||||
parent=self._root_widget,
|
||||
highlight=False,
|
||||
size=(self._scroll_width, self._scroll_height),
|
||||
position=(
|
||||
self._width * 0.5 - self._scroll_width * 0.5,
|
||||
scroll_bottom,
|
||||
),
|
||||
claims_left_right=True,
|
||||
selection_loops_to_parent=True,
|
||||
border_opacity=0.4,
|
||||
)
|
||||
|
||||
# With full-screen scrolling, fade content as it approaches
|
||||
# toolbars.
|
||||
if uiscale is bui.UIScale.SMALL:
|
||||
scroll_fade_top(
|
||||
self._root_widget,
|
||||
self._width * 0.5 - self._scroll_width * 0.5,
|
||||
scroll_bottom,
|
||||
self._scroll_width,
|
||||
self._scroll_height,
|
||||
)
|
||||
scroll_fade_bottom(
|
||||
self._root_widget,
|
||||
self._width * 0.5 - self._scroll_width * 0.5,
|
||||
scroll_bottom,
|
||||
self._scroll_width,
|
||||
self._scroll_height,
|
||||
)
|
||||
|
||||
titleyoffs = -45.0 if uiscale is bui.UIScale.SMALL else -28.0
|
||||
titlescale = 0.7 if uiscale is bui.UIScale.SMALL else 1.0
|
||||
bui.textwidget(
|
||||
|
|
@ -166,21 +204,8 @@ class AccountSettingsWindow(bui.MainWindow):
|
|||
v_align='center',
|
||||
)
|
||||
|
||||
self._scrollwidget = bui.scrollwidget(
|
||||
parent=self._root_widget,
|
||||
highlight=False,
|
||||
size=(self._scroll_width, self._scroll_height),
|
||||
position=(
|
||||
self._width * 0.5 - self._scroll_width * 0.5,
|
||||
scroll_bottom,
|
||||
),
|
||||
claims_left_right=True,
|
||||
selection_loops_to_parent=True,
|
||||
border_opacity=0.4,
|
||||
)
|
||||
self._subcontainer: bui.Widget | None = None
|
||||
self._refresh()
|
||||
self._restore_state()
|
||||
|
||||
@override
|
||||
def get_main_window_state(self) -> bui.MainWindowState:
|
||||
|
|
@ -193,8 +218,8 @@ class AccountSettingsWindow(bui.MainWindow):
|
|||
)
|
||||
|
||||
@override
|
||||
def on_main_window_close(self) -> None:
|
||||
self._save_state()
|
||||
def main_window_should_preserve_selection(self) -> bool:
|
||||
return True
|
||||
|
||||
def _update(self) -> None:
|
||||
plus = bui.app.plus
|
||||
|
|
@ -399,6 +424,12 @@ class AccountSettingsWindow(bui.MainWindow):
|
|||
if self._subcontainer is not None:
|
||||
self._subcontainer.delete()
|
||||
self._sub_height = 90.0
|
||||
|
||||
# For fullscreen scrollable, account for toolbar.
|
||||
uiscale = bui.app.ui_v1.uiscale
|
||||
if uiscale is bui.UIScale.SMALL:
|
||||
self._sub_height += 35
|
||||
|
||||
if show_signed_in_as:
|
||||
self._sub_height += signed_in_as_space
|
||||
self._sub_height += via_space * len(via_lines)
|
||||
|
|
@ -449,6 +480,10 @@ class AccountSettingsWindow(bui.MainWindow):
|
|||
first_selectable = None
|
||||
v = self._sub_height - 10.0
|
||||
|
||||
# For fullscreen scrollable, account for toolbar.
|
||||
if uiscale is bui.UIScale.SMALL:
|
||||
v -= 35
|
||||
|
||||
assert bui.app.classic is not None
|
||||
self._account_name_text: bui.Widget | None
|
||||
if show_signed_in_as:
|
||||
|
|
@ -580,8 +615,9 @@ class AccountSettingsWindow(bui.MainWindow):
|
|||
if show_google_play_sign_in_button:
|
||||
button_width = 350
|
||||
v -= sign_in_button_space
|
||||
self._sign_in_google_play_button = btn = bui.buttonwidget(
|
||||
btn = bui.buttonwidget(
|
||||
parent=self._subcontainer,
|
||||
id=f'{self.main_window_id_prefix}|signingoogleplay',
|
||||
position=((self._sub_width - button_width) * 0.5, v - 20),
|
||||
autoselect=True,
|
||||
size=(button_width, 60),
|
||||
|
|
@ -620,8 +656,9 @@ class AccountSettingsWindow(bui.MainWindow):
|
|||
if show_game_center_sign_in_button:
|
||||
button_width = 350
|
||||
v -= sign_in_button_space
|
||||
self._sign_in_google_play_button = btn = bui.buttonwidget(
|
||||
btn = bui.buttonwidget(
|
||||
parent=self._subcontainer,
|
||||
id=f'{self.main_window_id_prefix}|signingamecenter',
|
||||
position=((self._sub_width - button_width) * 0.5, v - 20),
|
||||
autoselect=True,
|
||||
size=(button_width, 60),
|
||||
|
|
@ -662,6 +699,7 @@ class AccountSettingsWindow(bui.MainWindow):
|
|||
v -= sign_in_button_space
|
||||
self._sign_in_v2_proxy_button = btn = bui.buttonwidget(
|
||||
parent=self._subcontainer,
|
||||
id=f'{self.main_window_id_prefix}|signinv2',
|
||||
position=((self._sub_width - button_width) * 0.5, v - 20),
|
||||
autoselect=True,
|
||||
size=(button_width, 60),
|
||||
|
|
@ -729,6 +767,7 @@ class AccountSettingsWindow(bui.MainWindow):
|
|||
v -= sign_in_button_space + deprecated_space
|
||||
self._sign_in_device_button = btn = bui.buttonwidget(
|
||||
parent=self._subcontainer,
|
||||
id=f'{self.main_window_id_prefix}|signindevice',
|
||||
position=((self._sub_width - button_width) * 0.5, v - 20),
|
||||
autoselect=True,
|
||||
size=(button_width, 60),
|
||||
|
|
@ -816,6 +855,7 @@ class AccountSettingsWindow(bui.MainWindow):
|
|||
v -= manage_account_button_space
|
||||
self._manage_button = btn = bui.buttonwidget(
|
||||
parent=self._subcontainer,
|
||||
id=f'{self.main_window_id_prefix}|manage',
|
||||
position=((self._sub_width - button_width) * 0.5, v),
|
||||
autoselect=True,
|
||||
size=(button_width, 60),
|
||||
|
|
@ -837,6 +877,7 @@ class AccountSettingsWindow(bui.MainWindow):
|
|||
v -= create_account_button_space
|
||||
self._create_button = btn = bui.buttonwidget(
|
||||
parent=self._subcontainer,
|
||||
id=f'{self.main_window_id_prefix}|create',
|
||||
position=((self._sub_width - button_width) * 0.5, v - 30),
|
||||
autoselect=True,
|
||||
size=(button_width, 60),
|
||||
|
|
@ -870,6 +911,7 @@ class AccountSettingsWindow(bui.MainWindow):
|
|||
)
|
||||
self._game_center_button = btn = bui.buttonwidget(
|
||||
parent=self._subcontainer,
|
||||
id=f'{self.main_window_id_prefix}|gamecenter',
|
||||
position=((self._sub_width - button_width) * 0.5, v),
|
||||
color=(0.55, 0.5, 0.6),
|
||||
textcolor=(0.75, 0.7, 0.8),
|
||||
|
|
@ -914,6 +956,7 @@ class AccountSettingsWindow(bui.MainWindow):
|
|||
v -= leaderboards_button_space
|
||||
self._leaderboards_button = btn = bui.buttonwidget(
|
||||
parent=self._subcontainer,
|
||||
id=f'{self.main_window_id_prefix}|leaderboards',
|
||||
position=((self._sub_width - button_width) * 0.5, v),
|
||||
color=(0.55, 0.5, 0.6),
|
||||
textcolor=(0.75, 0.7, 0.8),
|
||||
|
|
@ -977,6 +1020,7 @@ class AccountSettingsWindow(bui.MainWindow):
|
|||
v -= sign_out_button_space
|
||||
self._sign_out_button = btn = bui.buttonwidget(
|
||||
parent=self._subcontainer,
|
||||
id=f'{self.main_window_id_prefix}|signout',
|
||||
position=((self._sub_width - button_width) * 0.5, v),
|
||||
size=(button_width, 60),
|
||||
label=bui.Lstr(resource=f'{self._r}.signOutText'),
|
||||
|
|
@ -996,6 +1040,7 @@ class AccountSettingsWindow(bui.MainWindow):
|
|||
v -= cancel_sign_in_button_space
|
||||
self._cancel_sign_in_button = btn = bui.buttonwidget(
|
||||
parent=self._subcontainer,
|
||||
id=f'{self.main_window_id_prefix}|cancelsignin',
|
||||
position=((self._sub_width - button_width) * 0.5, v),
|
||||
size=(button_width, 60),
|
||||
label=bui.Lstr(resource='cancelText'),
|
||||
|
|
@ -1015,6 +1060,7 @@ class AccountSettingsWindow(bui.MainWindow):
|
|||
v -= delete_account_button_space
|
||||
self._delete_account_button = btn = bui.buttonwidget(
|
||||
parent=self._subcontainer,
|
||||
id=f'{self.main_window_id_prefix}|deleteaccount',
|
||||
position=((self._sub_width - button_width) * 0.5, v),
|
||||
size=(button_width, 60),
|
||||
label=bui.Lstr(resource=f'{self._r}.deleteAccountText'),
|
||||
|
|
@ -1355,34 +1401,6 @@ class AccountSettingsWindow(bui.MainWindow):
|
|||
assert self._sign_in_v2_proxy_button is not None
|
||||
V2ProxySignInWindow(origin_widget=self._sign_in_v2_proxy_button)
|
||||
|
||||
def _save_state(self) -> None:
|
||||
try:
|
||||
sel = self._root_widget.get_selected_child()
|
||||
if sel == self._back_button:
|
||||
sel_name = 'Back'
|
||||
elif sel == self._scrollwidget:
|
||||
sel_name = 'Scroll'
|
||||
else:
|
||||
raise ValueError('unrecognized selection')
|
||||
assert bui.app.classic is not None
|
||||
bui.app.ui_v1.window_states[type(self)] = sel_name
|
||||
except Exception:
|
||||
logging.exception('Error saving state for %s.', self)
|
||||
|
||||
def _restore_state(self) -> None:
|
||||
try:
|
||||
assert bui.app.classic is not None
|
||||
sel_name = bui.app.ui_v1.window_states.get(type(self))
|
||||
if sel_name == 'Back':
|
||||
sel = self._back_button
|
||||
elif sel_name == 'Scroll':
|
||||
sel = self._scrollwidget
|
||||
else:
|
||||
sel = self._back_button
|
||||
bui.containerwidget(edit=self._root_widget, selected_child=sel)
|
||||
except Exception:
|
||||
logging.exception('Error restoring state for %s.', self)
|
||||
|
||||
|
||||
def show_what_is_legacy_unlinking_page() -> None:
|
||||
"""Show the webpage describing legacy unlinking."""
|
||||
|
|
|
|||
|
|
@ -35,13 +35,16 @@ def _show_account_settings() -> None:
|
|||
if isinstance(prev_main_window, AccountSettingsWindow):
|
||||
return
|
||||
|
||||
ui = bui.app.ui_v1
|
||||
|
||||
# Set our new main window.
|
||||
bui.app.ui_v1.set_main_window(
|
||||
ui.set_main_window(
|
||||
AccountSettingsWindow(
|
||||
close_once_signed_in=True,
|
||||
origin_widget=bui.get_special_widget('account_button'),
|
||||
),
|
||||
from_window=False,
|
||||
back_state=ui.save_current_main_window_state(),
|
||||
from_window=False, # Don't check where we're coming from.
|
||||
is_auxiliary=True,
|
||||
suppress_warning=True,
|
||||
)
|
||||
|
|
|
|||
|
|
@ -23,6 +23,7 @@ class V2ProxySignInWindow(bui.Window):
|
|||
self._proxyid: str | None = None
|
||||
self._proxykey: str | None = None
|
||||
self._overlay_web_browser_open = False
|
||||
self._idprefix = bui.app.ui_v1.new_id_prefix('resourcetypeinfo')
|
||||
|
||||
assert bui.app.classic is not None
|
||||
uiscale = bui.app.ui_v1.uiscale
|
||||
|
|
@ -256,6 +257,7 @@ class V2ProxySignInWindow(bui.Window):
|
|||
if bui.is_browser_likely_available():
|
||||
bui.buttonwidget(
|
||||
parent=self._root_widget,
|
||||
id=f'{self._idprefix}|address',
|
||||
position=(
|
||||
(self._width * 0.5 - button_width * 0.5),
|
||||
self._height - 185,
|
||||
|
|
|
|||
132
dist/ba_data/python/bauiv1lib/achievements.py
vendored
132
dist/ba_data/python/bauiv1lib/achievements.py
vendored
|
|
@ -6,6 +6,7 @@ from __future__ import annotations
|
|||
|
||||
from typing import override
|
||||
|
||||
from bauiv1lib.utils import scroll_fade_bottom, scroll_fade_top
|
||||
import bauiv1 as bui
|
||||
|
||||
|
||||
|
|
@ -58,10 +59,15 @@ class AchievementsWindow(bui.MainWindow):
|
|||
scroll_height = target_height - 25
|
||||
scroll_bottom = yoffs - 54 - scroll_height
|
||||
|
||||
# Go with full-screen scrollable area in small ui.
|
||||
if uiscale is bui.UIScale.SMALL:
|
||||
scroll_height += 30
|
||||
scroll_bottom -= 3
|
||||
|
||||
super().__init__(
|
||||
root_widget=bui.containerwidget(
|
||||
size=(self._width, self._height),
|
||||
toolbar_visibility=('menu_full'),
|
||||
toolbar_visibility='menu_full',
|
||||
toolbar_cancel_button_style=(
|
||||
'close' if auxiliary_style else 'back'
|
||||
),
|
||||
|
|
@ -81,6 +87,7 @@ class AchievementsWindow(bui.MainWindow):
|
|||
else:
|
||||
self._back_button = bui.buttonwidget(
|
||||
parent=self._root_widget,
|
||||
id=f'{self.main_window_id_prefix}|back',
|
||||
autoselect=True,
|
||||
position=(50, yoffs - 48),
|
||||
size=(60, 60),
|
||||
|
|
@ -100,6 +107,43 @@ class AchievementsWindow(bui.MainWindow):
|
|||
achievements = bui.app.classic.ach.achievements
|
||||
num_complete = len([a for a in achievements if a.complete])
|
||||
|
||||
self._scrollwidget = bui.scrollwidget(
|
||||
parent=self._root_widget,
|
||||
id=f'{self.main_window_id_prefix}|scroll',
|
||||
size=(scroll_width, scroll_height),
|
||||
position=(self._width * 0.5 - scroll_width * 0.5, scroll_bottom),
|
||||
capture_arrows=True,
|
||||
simple_culling_v=10,
|
||||
border_opacity=0.4,
|
||||
)
|
||||
bui.containerwidget(
|
||||
edit=self._root_widget, selected_child=self._scrollwidget
|
||||
)
|
||||
bui.widget(edit=self._scrollwidget, autoselect=True)
|
||||
if uiscale is bui.UIScale.SMALL:
|
||||
bui.widget(
|
||||
edit=self._scrollwidget,
|
||||
left_widget=bui.get_special_widget('back_button'),
|
||||
)
|
||||
|
||||
# With full-screen scrolling, fade content as it approaches
|
||||
# toolbars.
|
||||
if uiscale is bui.UIScale.SMALL:
|
||||
scroll_fade_top(
|
||||
self._root_widget,
|
||||
self._width * 0.5 - scroll_width * 0.5,
|
||||
scroll_bottom,
|
||||
scroll_width,
|
||||
scroll_height,
|
||||
)
|
||||
scroll_fade_bottom(
|
||||
self._root_widget,
|
||||
self._width * 0.5 - scroll_width * 0.5,
|
||||
scroll_bottom,
|
||||
scroll_width,
|
||||
scroll_height,
|
||||
)
|
||||
|
||||
# In small UI mode when the screen is narrow enough we need to
|
||||
# go with a smaller title to avoid it overlapping with toolbar
|
||||
# bits.
|
||||
|
|
@ -144,59 +188,6 @@ class AchievementsWindow(bui.MainWindow):
|
|||
color=bui.app.ui_v1.title_color,
|
||||
)
|
||||
|
||||
self._scrollwidget = bui.scrollwidget(
|
||||
parent=self._root_widget,
|
||||
size=(scroll_width, scroll_height),
|
||||
position=(self._width * 0.5 - scroll_width * 0.5, scroll_bottom),
|
||||
capture_arrows=True,
|
||||
simple_culling_v=10,
|
||||
border_opacity=0.4,
|
||||
)
|
||||
bui.widget(edit=self._scrollwidget, autoselect=True)
|
||||
if uiscale is bui.UIScale.SMALL:
|
||||
bui.widget(
|
||||
edit=self._scrollwidget,
|
||||
left_widget=bui.get_special_widget('back_button'),
|
||||
)
|
||||
|
||||
# Add some blotches so our contents fades out as it approaches
|
||||
# the bottom toolbar.
|
||||
if uiscale is bui.UIScale.SMALL:
|
||||
blotchwidth = 500.0
|
||||
blotchheight = 200.0
|
||||
bimg = bui.imagewidget(
|
||||
parent=self._root_widget,
|
||||
texture=bui.gettexture('uiAtlas'),
|
||||
mesh_transparent=bui.getmesh('windowBGBlotch'),
|
||||
position=(
|
||||
self._width * 0.5
|
||||
- scroll_width * 0.5
|
||||
+ 60.0
|
||||
- blotchwidth * 0.5,
|
||||
scroll_bottom - blotchheight * 0.5,
|
||||
),
|
||||
size=(blotchwidth, blotchheight),
|
||||
color=(0.4, 0.37, 0.49),
|
||||
# color=(1, 0, 0),
|
||||
)
|
||||
bui.widget(edit=bimg, depth_range=(0.9, 1.0))
|
||||
bimg = bui.imagewidget(
|
||||
parent=self._root_widget,
|
||||
texture=bui.gettexture('uiAtlas'),
|
||||
mesh_transparent=bui.getmesh('windowBGBlotch'),
|
||||
position=(
|
||||
self._width * 0.5
|
||||
+ scroll_width * 0.5
|
||||
- 60.0
|
||||
- blotchwidth * 0.5,
|
||||
scroll_bottom - blotchheight * 0.5,
|
||||
),
|
||||
size=(blotchwidth, blotchheight),
|
||||
color=(0.4, 0.37, 0.49),
|
||||
# color=(1, 0, 0),
|
||||
)
|
||||
bui.widget(edit=bimg, depth_range=(0.9, 1.0))
|
||||
|
||||
bui.containerwidget(
|
||||
edit=self._root_widget, cancel_button=self._back_button
|
||||
)
|
||||
|
|
@ -205,21 +196,32 @@ class AchievementsWindow(bui.MainWindow):
|
|||
sub_width = scroll_width - 25
|
||||
sub_height = 85 + len(achievements) * incr
|
||||
|
||||
# For fullscreen scrollable, account for toolbar.
|
||||
if uiscale is bui.UIScale.SMALL:
|
||||
sub_height += 30
|
||||
|
||||
eq_rsrc = 'coopSelectWindow.powerRankingPointsEqualsText'
|
||||
pts_rsrc = 'coopSelectWindow.powerRankingPointsText'
|
||||
|
||||
self._subcontainer = bui.containerwidget(
|
||||
parent=self._scrollwidget,
|
||||
id=f'{self.main_window_id_prefix}|subcontainer',
|
||||
size=(sub_width, sub_height),
|
||||
background=False,
|
||||
)
|
||||
|
||||
basey = sub_height
|
||||
|
||||
# For fullscreen scrollable, account for toolbar.
|
||||
if uiscale is bui.UIScale.SMALL:
|
||||
basey -= 30
|
||||
|
||||
total_pts = 0
|
||||
for i, ach in enumerate(achievements):
|
||||
complete = ach.complete
|
||||
bui.textwidget(
|
||||
parent=self._subcontainer,
|
||||
position=(sub_width * 0.08 - 5, sub_height - 20 - incr * i),
|
||||
position=(sub_width * 0.08 - 5, basey - 20 - incr * i),
|
||||
maxwidth=20,
|
||||
scale=0.5,
|
||||
color=(0.6, 0.6, 0.7) if complete else (0.6, 0.6, 0.7, 0.2),
|
||||
|
|
@ -234,9 +236,9 @@ class AchievementsWindow(bui.MainWindow):
|
|||
bui.imagewidget(
|
||||
parent=self._subcontainer,
|
||||
position=(
|
||||
(sub_width * 0.10 + 1, sub_height - 20 - incr * i - 9)
|
||||
(sub_width * 0.10 + 1, basey - 20 - incr * i - 9)
|
||||
if complete
|
||||
else (sub_width * 0.10 - 4, sub_height - 20 - incr * i - 14)
|
||||
else (sub_width * 0.10 - 4, basey - 20 - incr * i - 14)
|
||||
),
|
||||
size=(18, 18) if complete else (27, 27),
|
||||
opacity=1.0 if complete else 0.3,
|
||||
|
|
@ -248,7 +250,7 @@ class AchievementsWindow(bui.MainWindow):
|
|||
parent=self._subcontainer,
|
||||
position=(
|
||||
sub_width * 0.10 - 4,
|
||||
sub_height - 25 - incr * i - 9,
|
||||
basey - 25 - incr * i - 9,
|
||||
),
|
||||
size=(28, 28),
|
||||
color=(2, 1.4, 0),
|
||||
|
|
@ -256,7 +258,7 @@ class AchievementsWindow(bui.MainWindow):
|
|||
)
|
||||
bui.textwidget(
|
||||
parent=self._subcontainer,
|
||||
position=(sub_width * 0.19, sub_height - 19 - incr * i + 3),
|
||||
position=(sub_width * 0.19, basey - 19 - incr * i + 3),
|
||||
maxwidth=sub_width * 0.62,
|
||||
scale=0.6,
|
||||
flatness=1.0,
|
||||
|
|
@ -270,7 +272,7 @@ class AchievementsWindow(bui.MainWindow):
|
|||
|
||||
bui.textwidget(
|
||||
parent=self._subcontainer,
|
||||
position=(sub_width * 0.19, sub_height - 19 - incr * i - 10),
|
||||
position=(sub_width * 0.19, basey - 19 - incr * i - 10),
|
||||
maxwidth=sub_width * 0.62,
|
||||
scale=0.4,
|
||||
flatness=1.0,
|
||||
|
|
@ -295,7 +297,7 @@ class AchievementsWindow(bui.MainWindow):
|
|||
opacity=0.0 if complete else 1.0,
|
||||
position=(
|
||||
sub_width * 0.92 - 40.0 - chestsize * 0.5,
|
||||
sub_height - 20 - incr * i - chestsize * 0.5,
|
||||
basey - 20 - incr * i - chestsize * 0.5,
|
||||
),
|
||||
size=(chestsize, chestsize),
|
||||
color=chestdisplayinfo.color,
|
||||
|
|
@ -308,7 +310,7 @@ class AchievementsWindow(bui.MainWindow):
|
|||
pts = ach.power_ranking_value
|
||||
bui.textwidget(
|
||||
parent=self._subcontainer,
|
||||
position=(sub_width * 0.92, sub_height - 20 - incr * i),
|
||||
position=(sub_width * 0.92, basey - 20 - incr * i),
|
||||
maxwidth=sub_width * 0.15,
|
||||
color=(0.7, 0.8, 1.0) if complete else (0.9, 0.9, 1.0, 0.3),
|
||||
flatness=1.0,
|
||||
|
|
@ -328,7 +330,7 @@ class AchievementsWindow(bui.MainWindow):
|
|||
parent=self._subcontainer,
|
||||
position=(
|
||||
sub_width * 1.0,
|
||||
sub_height - 20 - incr * len(achievements),
|
||||
basey - 20 - incr * len(achievements),
|
||||
),
|
||||
maxwidth=sub_width * 0.5,
|
||||
scale=0.7,
|
||||
|
|
@ -362,3 +364,7 @@ class AchievementsWindow(bui.MainWindow):
|
|||
transition=transition, origin_widget=origin_widget
|
||||
)
|
||||
)
|
||||
|
||||
@override
|
||||
def main_window_should_preserve_selection(self) -> bool:
|
||||
return True
|
||||
|
|
|
|||
31
dist/ba_data/python/bauiv1lib/chest.py
vendored
31
dist/ba_data/python/bauiv1lib/chest.py
vendored
|
|
@ -33,6 +33,7 @@ class ChestWindow(bui.MainWindow):
|
|||
index: int,
|
||||
transition: str | None = 'in_right',
|
||||
origin_widget: bui.Widget | None = None,
|
||||
auxiliary_style: bool = True,
|
||||
):
|
||||
# pylint: disable=too-many-statements
|
||||
self._index = index
|
||||
|
|
@ -101,6 +102,9 @@ class ChestWindow(bui.MainWindow):
|
|||
root_widget=bui.containerwidget(
|
||||
size=(self._width, self._height),
|
||||
toolbar_visibility='menu_full',
|
||||
toolbar_cancel_button_style=(
|
||||
'close' if auxiliary_style else 'back'
|
||||
),
|
||||
scale=scale,
|
||||
),
|
||||
transition=transition,
|
||||
|
|
@ -136,10 +140,14 @@ class ChestWindow(bui.MainWindow):
|
|||
btn = bui.buttonwidget(
|
||||
parent=self._root_widget,
|
||||
position=(50, self._yoffs - 44),
|
||||
size=(60, 55),
|
||||
size=(60, 60),
|
||||
scale=0.8,
|
||||
label=bui.charstr(bui.SpecialChar.BACK),
|
||||
button_type='backSmall',
|
||||
label=bui.charstr(
|
||||
bui.SpecialChar.CLOSE
|
||||
if auxiliary_style
|
||||
else bui.SpecialChar.BACK
|
||||
),
|
||||
button_type=None if auxiliary_style else 'backSmall',
|
||||
extra_touch_border_scale=2.0,
|
||||
autoselect=True,
|
||||
on_activate_call=self.main_window_back,
|
||||
|
|
@ -210,6 +218,14 @@ class ChestWindow(bui.MainWindow):
|
|||
)
|
||||
)
|
||||
|
||||
@override
|
||||
def main_window_should_preserve_selection(self) -> bool:
|
||||
# This doesn't really benefit us since we do lots of widget
|
||||
# creates/destroys throughout our lifetime and also we're an
|
||||
# auxliary window so should never need to restore toolbar
|
||||
# selections.
|
||||
return False
|
||||
|
||||
def _update_time_display(self, unlock_time: datetime.datetime) -> None:
|
||||
# Once our target text widget disappears, kill our timer.
|
||||
if not self._time_string_text:
|
||||
|
|
@ -961,6 +977,7 @@ class ChestWindow(bui.MainWindow):
|
|||
self, response: bacommon.bs.ChestActionResponse
|
||||
) -> float:
|
||||
# pylint: disable=too-many-locals
|
||||
# pylint: disable=too-many-statements
|
||||
|
||||
from baclassic import show_display_item
|
||||
|
||||
|
|
@ -1101,9 +1118,10 @@ class ChestWindow(bui.MainWindow):
|
|||
# through highlighting our options and stop on the winner when
|
||||
# the chest opens. To do this, we start at the end at the prize
|
||||
# and work backwards setting timers.
|
||||
ease_out = False # Experimenting...
|
||||
if self._prizesets:
|
||||
toffs2 = toffsopen - 0.01
|
||||
amt = 0.02
|
||||
amt = 0.25 if ease_out else 0.02
|
||||
i = self._prizeindex
|
||||
while toffs2 > 0.0:
|
||||
bui.apptimer(
|
||||
|
|
@ -1111,7 +1129,10 @@ class ChestWindow(bui.MainWindow):
|
|||
bui.WeakCall(self._highlight_odds_row, i),
|
||||
)
|
||||
toffs2 -= amt
|
||||
amt *= 1.05 * random.uniform(0.9, 1.1)
|
||||
if ease_out:
|
||||
amt = max(0.032, amt * 0.75 * random.uniform(0.9, 1.1))
|
||||
else:
|
||||
amt *= 1.05 * random.uniform(0.9, 1.1)
|
||||
i = (i - 1) % len(self._prizesets)
|
||||
|
||||
# Let the caller know how long we'll take in case they want to
|
||||
|
|
|
|||
1
dist/ba_data/python/bauiv1lib/colorpicker.py
vendored
1
dist/ba_data/python/bauiv1lib/colorpicker.py
vendored
|
|
@ -336,7 +336,6 @@ class ColorPickerExact(PopupWindow):
|
|||
# Store the current text for our next comparison.
|
||||
self._hex_prev_text = hextext
|
||||
|
||||
# noinspection PyUnresolvedReferences
|
||||
def _update_for_color(self) -> None:
|
||||
if not self.root_widget:
|
||||
return
|
||||
|
|
|
|||
12
dist/ba_data/python/bauiv1lib/config.py
vendored
12
dist/ba_data/python/bauiv1lib/config.py
vendored
|
|
@ -34,6 +34,7 @@ class ConfigCheckBox:
|
|||
maxwidth: float | None = None,
|
||||
autoselect: bool = True,
|
||||
value_change_call: Callable[[Any], Any] | None = None,
|
||||
check_box_id: str | None = None,
|
||||
):
|
||||
if displayname is None:
|
||||
displayname = configkey
|
||||
|
|
@ -41,6 +42,7 @@ class ConfigCheckBox:
|
|||
self._configkey = configkey
|
||||
self.widget = bui.checkboxwidget(
|
||||
parent=parent,
|
||||
id=check_box_id,
|
||||
autoselect=autoselect,
|
||||
position=position,
|
||||
size=size,
|
||||
|
|
@ -51,8 +53,8 @@ class ConfigCheckBox:
|
|||
scale=scale,
|
||||
maxwidth=maxwidth,
|
||||
)
|
||||
# complain if we outlive our checkbox
|
||||
bui.uicleanupcheck(self, self.widget)
|
||||
# Complain if we outlive our checkbox.
|
||||
bui.app.ui_v1.add_ui_cleanup_check(self, self.widget)
|
||||
|
||||
def _value_changed(self, val: bool) -> None:
|
||||
cfg = bui.app.config
|
||||
|
|
@ -98,7 +100,9 @@ class ConfigNumberEdit:
|
|||
as_percent: bool = False,
|
||||
fallback_value: float = 0.0,
|
||||
f: int = 1,
|
||||
idprefix: str | None = None,
|
||||
):
|
||||
# pylint: disable=too-many-locals
|
||||
if displayname is None:
|
||||
displayname = configkey
|
||||
|
||||
|
|
@ -143,6 +147,7 @@ class ConfigNumberEdit:
|
|||
)
|
||||
self.minusbutton = bui.buttonwidget(
|
||||
parent=parent,
|
||||
id=None if idprefix is None else f'{idprefix}|minus',
|
||||
position=(position[0] + 230 + xoffset, position[1]),
|
||||
size=(28, 28),
|
||||
label='-',
|
||||
|
|
@ -153,6 +158,7 @@ class ConfigNumberEdit:
|
|||
)
|
||||
self.plusbutton = bui.buttonwidget(
|
||||
parent=parent,
|
||||
id=None if idprefix is None else f'{idprefix}|plus',
|
||||
position=(position[0] + 280 + xoffset, position[1]),
|
||||
size=(28, 28),
|
||||
label='+',
|
||||
|
|
@ -162,7 +168,7 @@ class ConfigNumberEdit:
|
|||
enable_sound=changesound,
|
||||
)
|
||||
# Complain if we outlive our widgets.
|
||||
bui.uicleanupcheck(self, self.nametext)
|
||||
bui.app.ui_v1.add_ui_cleanup_check(self, self.nametext)
|
||||
self._update_display()
|
||||
|
||||
def _up(self) -> None:
|
||||
|
|
|
|||
43
dist/ba_data/python/bauiv1lib/confirm.py
vendored
43
dist/ba_data/python/bauiv1lib/confirm.py
vendored
|
|
@ -5,7 +5,6 @@
|
|||
from __future__ import annotations
|
||||
|
||||
from typing import TYPE_CHECKING
|
||||
import logging
|
||||
|
||||
import bauiv1 as bui
|
||||
|
||||
|
|
@ -30,8 +29,15 @@ class ConfirmWindow:
|
|||
ok_text: str | bui.Lstr | None = None,
|
||||
cancel_text: str | bui.Lstr | None = None,
|
||||
origin_widget: bui.Widget | None = None,
|
||||
permanent_ok_fade: bool = False,
|
||||
):
|
||||
# pylint: disable=too-many-locals
|
||||
|
||||
ui = bui.app.ui_v1
|
||||
|
||||
# Make sure our widgets have globally unique ids.
|
||||
self._id_prefix = ui.new_id_prefix('confirm')
|
||||
|
||||
if text is None:
|
||||
text = bui.Lstr(resource='areYouSureText')
|
||||
if ok_text is None:
|
||||
|
|
@ -42,6 +48,8 @@ class ConfirmWindow:
|
|||
width = max(width, 360)
|
||||
self._action = action
|
||||
|
||||
self._permanent_ok_fade = permanent_ok_fade
|
||||
|
||||
# If they provided an origin-widget, scale up from that.
|
||||
self._transition_out: str | None
|
||||
scale_origin: tuple[float, float] | None
|
||||
|
|
@ -87,6 +95,7 @@ class ConfirmWindow:
|
|||
if cancel_button:
|
||||
cbtn = btn = bui.buttonwidget(
|
||||
parent=self.root_widget,
|
||||
id=f'{self._id_prefix}|cancel',
|
||||
autoselect=True,
|
||||
position=(20, 20),
|
||||
size=(150, 50),
|
||||
|
|
@ -103,6 +112,7 @@ class ConfirmWindow:
|
|||
cbtn = None
|
||||
btn = bui.buttonwidget(
|
||||
parent=self.root_widget,
|
||||
id=f'{self._id_prefix}|ok',
|
||||
autoselect=True,
|
||||
position=(ok_button_h, 20),
|
||||
size=(150, 50),
|
||||
|
|
@ -140,6 +150,7 @@ class ConfirmWindow:
|
|||
return
|
||||
bui.containerwidget(
|
||||
edit=self.root_widget,
|
||||
darken_behind_is_permanent=self._permanent_ok_fade,
|
||||
transition=(
|
||||
'out_left'
|
||||
if self._transition_out is None
|
||||
|
|
@ -159,10 +170,9 @@ class QuitWindow:
|
|||
swish: bool = False,
|
||||
origin_widget: bui.Widget | None = None,
|
||||
):
|
||||
classic = bui.app.classic
|
||||
assert classic is not None
|
||||
ui = bui.app.ui_v1
|
||||
app = bui.app
|
||||
platform = bui.app.env.platform
|
||||
|
||||
self._quit_type = quit_type
|
||||
|
||||
# If there's already one of us up somewhere, kill it.
|
||||
|
|
@ -172,18 +182,13 @@ class QuitWindow:
|
|||
if swish:
|
||||
bui.getsound('swish').play()
|
||||
|
||||
if app.classic is None:
|
||||
if bui.do_once():
|
||||
logging.warning(
|
||||
'QuitWindow needs to be updated to work without classic.'
|
||||
)
|
||||
quit_resource = 'exitGameText'
|
||||
else:
|
||||
quit_resource = (
|
||||
'quitGameText'
|
||||
if app.classic.platform == 'mac'
|
||||
else 'exitGameText'
|
||||
)
|
||||
# Generally Macs say Quit and other stuff says Exit
|
||||
quit_resource = (
|
||||
'quitGameText'
|
||||
if platform is type(platform).MACOS
|
||||
else 'exitGameText'
|
||||
)
|
||||
|
||||
self._root_widget = ui.quit_window = ConfirmWindow(
|
||||
bui.Lstr(
|
||||
resource=quit_resource,
|
||||
|
|
@ -195,4 +200,10 @@ class QuitWindow:
|
|||
else bui.quit(confirm=False)
|
||||
),
|
||||
origin_widget=origin_widget,
|
||||
# In situations where the quit action will *actually* kill
|
||||
# the process, tell the confirm to not fade back in when the
|
||||
# confirm button is pressed. It just looks a bit visually
|
||||
# odd if the confirm fades back in just before the app fades
|
||||
# out to quit.
|
||||
permanent_ok_fade=not bui.app.env.supports_soft_quit,
|
||||
).root_widget
|
||||
|
|
|
|||
|
|
@ -49,6 +49,7 @@ class WaitForConnectivityWindow(bui.Window):
|
|||
self._on_cancel = on_cancel
|
||||
self._width = 650
|
||||
self._height = 300
|
||||
self._idprefix = bui.app.ui_v1.new_id_prefix('connectivity')
|
||||
super().__init__(
|
||||
root_widget=bui.containerwidget(
|
||||
size=(self._width, self._height),
|
||||
|
|
@ -90,6 +91,7 @@ class WaitForConnectivityWindow(bui.Window):
|
|||
self._info_text_str = ''
|
||||
cancel_button = bui.buttonwidget(
|
||||
parent=self._root_widget,
|
||||
id=f'{self._idprefix}|cancel',
|
||||
autoselect=True,
|
||||
position=(50, 30),
|
||||
size=(150, 50),
|
||||
|
|
|
|||
184
dist/ba_data/python/bauiv1lib/coop/browser.py
vendored
184
dist/ba_data/python/bauiv1lib/coop/browser.py
vendored
|
|
@ -11,6 +11,7 @@ from typing import TYPE_CHECKING, override
|
|||
|
||||
|
||||
import bauiv1 as bui
|
||||
from bauiv1lib.utils import scroll_fade_top, scroll_fade_bottom
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from typing import Any
|
||||
|
|
@ -131,8 +132,14 @@ class CoopBrowserWindow(bui.MainWindow):
|
|||
yoffs = 0.5 * self._height + 0.5 * target_height + 30.0
|
||||
|
||||
self._scroll_width = target_width
|
||||
self._scroll_height = target_height - 40
|
||||
self._scroll_bottom = yoffs - 70 - self._scroll_height
|
||||
self._scroll_height = target_height - (
|
||||
-5 if uiscale is bui.UIScale.SMALL else 40
|
||||
)
|
||||
self._scroll_bottom = (
|
||||
yoffs
|
||||
- (27 if uiscale is bui.UIScale.SMALL else 70)
|
||||
- self._scroll_height
|
||||
)
|
||||
|
||||
super().__init__(
|
||||
root_widget=bui.containerwidget(
|
||||
|
|
@ -154,6 +161,7 @@ class CoopBrowserWindow(bui.MainWindow):
|
|||
else:
|
||||
self._back_button = bui.buttonwidget(
|
||||
parent=self._root_widget,
|
||||
id=f'{self.main_window_id_prefix}|back',
|
||||
position=(75, yoffs - 48.0),
|
||||
size=(60, 50),
|
||||
scale=1.2,
|
||||
|
|
@ -184,7 +192,50 @@ class CoopBrowserWindow(bui.MainWindow):
|
|||
|
||||
# Don't want initial construction affecting our last-selected.
|
||||
self._do_selection_callbacks = False
|
||||
bui.textwidget(
|
||||
|
||||
self._selected_row = cfg.get('Selected Coop Row', None)
|
||||
|
||||
self._subcontainerwidth = 800.0
|
||||
self._subcontainerheight = 1400.0
|
||||
|
||||
# Allow empty space at top when our toolbar overlaps scroll area.
|
||||
if uiscale is bui.UIScale.SMALL:
|
||||
self._subcontainerheight += 40
|
||||
|
||||
self._scrollwidget = bui.scrollwidget(
|
||||
parent=self._root_widget,
|
||||
highlight=False,
|
||||
size=(self._scroll_width, self._scroll_height),
|
||||
position=(
|
||||
self._width * 0.5 - self._scroll_width * 0.5,
|
||||
self._scroll_bottom,
|
||||
),
|
||||
simple_culling_v=10.0,
|
||||
claims_left_right=True,
|
||||
selection_loops_to_parent=True,
|
||||
border_opacity=0.4,
|
||||
)
|
||||
|
||||
# Splotches at the top to fade scrollable content as it hits
|
||||
# toolbars.
|
||||
if uiscale is bui.UIScale.SMALL and bool(True):
|
||||
scroll_fade_top(
|
||||
self._root_widget,
|
||||
self._width * 0.5 - self._scroll_width * 0.5,
|
||||
self._scroll_bottom,
|
||||
self._scroll_width,
|
||||
self._scroll_height,
|
||||
)
|
||||
scroll_fade_bottom(
|
||||
self._root_widget,
|
||||
self._width * 0.5 - self._scroll_width * 0.5,
|
||||
self._scroll_bottom,
|
||||
self._scroll_width,
|
||||
self._scroll_height,
|
||||
)
|
||||
|
||||
# Title.
|
||||
ttxt = bui.textwidget(
|
||||
parent=self._root_widget,
|
||||
position=(
|
||||
self._width * 0.5,
|
||||
|
|
@ -201,61 +252,7 @@ class CoopBrowserWindow(bui.MainWindow):
|
|||
maxwidth=tmaxw,
|
||||
v_align='center',
|
||||
)
|
||||
|
||||
self._selected_row = cfg.get('Selected Coop Row', None)
|
||||
|
||||
self._subcontainerwidth = 800.0
|
||||
self._subcontainerheight = 1400.0
|
||||
|
||||
self._scrollwidget = bui.scrollwidget(
|
||||
parent=self._root_widget,
|
||||
highlight=False,
|
||||
size=(self._scroll_width, self._scroll_height),
|
||||
position=(
|
||||
self._width * 0.5 - self._scroll_width * 0.5,
|
||||
self._scroll_bottom,
|
||||
),
|
||||
simple_culling_v=10.0,
|
||||
claims_left_right=True,
|
||||
selection_loops_to_parent=True,
|
||||
border_opacity=0.4,
|
||||
)
|
||||
|
||||
if uiscale is bui.UIScale.SMALL:
|
||||
blotchwidth = 500.0
|
||||
blotchheight = 200.0
|
||||
bimg = bui.imagewidget(
|
||||
parent=self._root_widget,
|
||||
texture=bui.gettexture('uiAtlas'),
|
||||
mesh_transparent=bui.getmesh('windowBGBlotch'),
|
||||
position=(
|
||||
self._width * 0.5
|
||||
- self._scroll_width * 0.5
|
||||
+ 60.0
|
||||
- blotchwidth * 0.5,
|
||||
self._scroll_bottom - blotchheight * 0.5,
|
||||
),
|
||||
size=(blotchwidth, blotchheight),
|
||||
color=(0.4, 0.37, 0.49),
|
||||
# color=(1, 0, 0),
|
||||
)
|
||||
bui.widget(edit=bimg, depth_range=(0.9, 1.0))
|
||||
bimg = bui.imagewidget(
|
||||
parent=self._root_widget,
|
||||
texture=bui.gettexture('uiAtlas'),
|
||||
mesh_transparent=bui.getmesh('windowBGBlotch'),
|
||||
position=(
|
||||
self._width * 0.5
|
||||
+ self._scroll_width * 0.5
|
||||
- 60.0
|
||||
- blotchwidth * 0.5,
|
||||
self._scroll_bottom - blotchheight * 0.5,
|
||||
),
|
||||
size=(blotchwidth, blotchheight),
|
||||
color=(0.4, 0.37, 0.49),
|
||||
# color=(1, 0, 0),
|
||||
)
|
||||
bui.widget(edit=bimg, depth_range=(0.9, 1.0))
|
||||
bui.widget(edit=ttxt, depth_range=(0.9, 1.0))
|
||||
|
||||
self._subcontainer: bui.Widget | None = None
|
||||
|
||||
|
|
@ -266,7 +263,6 @@ class CoopBrowserWindow(bui.MainWindow):
|
|||
self._fg_state = app.fg_state
|
||||
|
||||
self._refresh()
|
||||
self._restore_state()
|
||||
|
||||
# Even though we might display cached tournament data immediately, we
|
||||
# don't consider it valid until we've pinged.
|
||||
|
|
@ -307,6 +303,10 @@ class CoopBrowserWindow(bui.MainWindow):
|
|||
)
|
||||
)
|
||||
|
||||
@override
|
||||
def main_window_should_preserve_selection(self) -> bool:
|
||||
return True
|
||||
|
||||
@override
|
||||
def on_main_window_close(self) -> None:
|
||||
self._save_state()
|
||||
|
|
@ -532,6 +532,7 @@ class CoopBrowserWindow(bui.MainWindow):
|
|||
un_sel_textcolor = (0.6, 0.6, 0.6)
|
||||
self._easy_button = bui.buttonwidget(
|
||||
parent=parent_widget,
|
||||
id=f'{self.main_window_id_prefix}|easy',
|
||||
position=(h + 30, v2 + 105),
|
||||
size=(120, 70),
|
||||
label=bui.Lstr(resource='difficultyEasyText'),
|
||||
|
|
@ -562,6 +563,7 @@ class CoopBrowserWindow(bui.MainWindow):
|
|||
|
||||
self._hard_button = bui.buttonwidget(
|
||||
parent=parent_widget,
|
||||
id=f'{self.main_window_id_prefix}|hard',
|
||||
position=(h + 30, v2 + 32),
|
||||
size=(120, 70),
|
||||
label=bui.Lstr(resource='difficultyHardText'),
|
||||
|
|
@ -716,6 +718,11 @@ class CoopBrowserWindow(bui.MainWindow):
|
|||
|
||||
v = self._subcontainerheight - 90
|
||||
|
||||
# Move down past toolbar when it overlaps us.
|
||||
uiscale = bui.app.ui_v1.uiscale
|
||||
if uiscale is bui.UIScale.SMALL:
|
||||
v -= 40.0
|
||||
|
||||
self._campaign_percent_text = bui.textwidget(
|
||||
parent=w_parent,
|
||||
position=(h_base + 27, v + 30),
|
||||
|
|
@ -727,7 +734,10 @@ class CoopBrowserWindow(bui.MainWindow):
|
|||
scale=1.1,
|
||||
)
|
||||
|
||||
row_v_show_buffer = 80
|
||||
# Need a bit more show-buffer on top to account for our
|
||||
# non-selectable titles above our selectable button rows.
|
||||
row_v_show_buffer_top = 120
|
||||
row_v_show_buffer_bottom = 70
|
||||
v -= 198
|
||||
|
||||
h_scroll = bui.hscrollwidget(
|
||||
|
|
@ -743,8 +753,8 @@ class CoopBrowserWindow(bui.MainWindow):
|
|||
self._campaign_h_scroll = h_scroll
|
||||
bui.widget(
|
||||
edit=h_scroll,
|
||||
show_buffer_top=row_v_show_buffer,
|
||||
show_buffer_bottom=row_v_show_buffer,
|
||||
show_buffer_top=row_v_show_buffer_top,
|
||||
show_buffer_bottom=row_v_show_buffer_bottom,
|
||||
autoselect=True,
|
||||
)
|
||||
if self._selected_row == 'campaign':
|
||||
|
|
@ -778,6 +788,7 @@ class CoopBrowserWindow(bui.MainWindow):
|
|||
)
|
||||
self._tournament_info_button = bui.buttonwidget(
|
||||
parent=w_parent,
|
||||
id=f'{self.main_window_id_prefix}|tourneyinfo',
|
||||
label='?',
|
||||
size=(20, 20),
|
||||
text_scale=0.6,
|
||||
|
|
@ -837,8 +848,8 @@ class CoopBrowserWindow(bui.MainWindow):
|
|||
)
|
||||
bui.widget(
|
||||
edit=h_scroll,
|
||||
show_buffer_top=row_v_show_buffer,
|
||||
show_buffer_bottom=row_v_show_buffer,
|
||||
show_buffer_top=row_v_show_buffer_top,
|
||||
show_buffer_bottom=row_v_show_buffer_bottom,
|
||||
autoselect=True,
|
||||
)
|
||||
if self._selected_row == 'tournament' + str(i + 1):
|
||||
|
|
@ -920,8 +931,8 @@ class CoopBrowserWindow(bui.MainWindow):
|
|||
)
|
||||
bui.widget(
|
||||
edit=h_scroll,
|
||||
show_buffer_top=row_v_show_buffer,
|
||||
show_buffer_bottom=1.5 * row_v_show_buffer,
|
||||
show_buffer_top=row_v_show_buffer_top,
|
||||
show_buffer_bottom=1.5 * row_v_show_buffer_bottom,
|
||||
autoselect=True,
|
||||
)
|
||||
if self._selected_row == 'custom':
|
||||
|
|
@ -1168,18 +1179,6 @@ class CoopBrowserWindow(bui.MainWindow):
|
|||
)
|
||||
return
|
||||
|
||||
# assert required_purchases
|
||||
# if plus.get_v1_account_state() != 'signed_in':
|
||||
# show_sign_in_prompt()
|
||||
# else:
|
||||
# # Hmm; just show the first requirement. They can come
|
||||
# # back to see more after they purchase the first.
|
||||
# PurchaseWindow(
|
||||
# items=[required_purchases[0]],
|
||||
# origin_widget=tournament_button.button,
|
||||
# )
|
||||
# return
|
||||
|
||||
if tournament_button.time_remaining <= 0:
|
||||
bui.screenmessage(
|
||||
bui.Lstr(resource='tournamentEndedText'), color=(1, 0, 0)
|
||||
|
|
@ -1197,40 +1196,11 @@ class CoopBrowserWindow(bui.MainWindow):
|
|||
|
||||
def _save_state(self) -> None:
|
||||
cfg = bui.app.config
|
||||
try:
|
||||
sel = self._root_widget.get_selected_child()
|
||||
if sel == self._back_button:
|
||||
sel_name = 'Back'
|
||||
elif sel == self._scrollwidget:
|
||||
sel_name = 'Scroll'
|
||||
else:
|
||||
raise ValueError('unrecognized selection')
|
||||
assert bui.app.classic is not None
|
||||
bui.app.ui_v1.window_states[type(self)] = {'sel_name': sel_name}
|
||||
except Exception:
|
||||
logging.exception('Error saving state for %s.', self)
|
||||
|
||||
cfg['Selected Coop Row'] = self._selected_row
|
||||
cfg['Selected Coop Custom Level'] = self._selected_custom_level
|
||||
cfg['Selected Coop Campaign Level'] = self._selected_campaign_level
|
||||
cfg.commit()
|
||||
|
||||
def _restore_state(self) -> None:
|
||||
try:
|
||||
assert bui.app.classic is not None
|
||||
sel_name = bui.app.ui_v1.window_states.get(type(self), {}).get(
|
||||
'sel_name'
|
||||
)
|
||||
if sel_name == 'Back':
|
||||
sel = self._back_button
|
||||
elif sel_name == 'Scroll':
|
||||
sel = self._scrollwidget
|
||||
else:
|
||||
sel = self._scrollwidget
|
||||
bui.containerwidget(edit=self._root_widget, selected_child=sel)
|
||||
except Exception:
|
||||
logging.exception('Error restoring state for %s.', self)
|
||||
|
||||
def sel_change(self, row: str, game: str) -> None:
|
||||
"""(internal)"""
|
||||
if self._do_selection_callbacks:
|
||||
|
|
|
|||
|
|
@ -79,6 +79,8 @@ class GameButton:
|
|||
show_buffer_top=50,
|
||||
show_buffer_left=400,
|
||||
show_buffer_right=200,
|
||||
# We handle reselection manually for these so no ids.
|
||||
allow_preserve_selection=False,
|
||||
)
|
||||
if select:
|
||||
bui.containerwidget(
|
||||
|
|
|
|||
|
|
@ -60,6 +60,8 @@ class TournamentButton:
|
|||
show_buffer_top=50,
|
||||
show_buffer_left=400,
|
||||
show_buffer_right=200,
|
||||
# We handle reselection manually for these so no ids.
|
||||
allow_preserve_selection=False,
|
||||
)
|
||||
if select:
|
||||
bui.containerwidget(
|
||||
|
|
@ -331,6 +333,11 @@ class TournamentButton:
|
|||
color=value_color,
|
||||
flatness=1.0,
|
||||
)
|
||||
# We handle reselection manually for these so no ids.
|
||||
bui.widget(
|
||||
edit=self.current_leader_name_text, allow_preserve_selection=False
|
||||
)
|
||||
|
||||
self.current_leader_score_text = bui.textwidget(
|
||||
parent=parent,
|
||||
draw_controller=btn,
|
||||
|
|
@ -357,6 +364,8 @@ class TournamentButton:
|
|||
text_scale=0.6,
|
||||
on_activate_call=bui.WeakCall(self._show_scores),
|
||||
)
|
||||
# We handle reselection manually for these so no ids.
|
||||
bui.widget(edit=self.more_scores_button, allow_preserve_selection=False)
|
||||
bui.widget(
|
||||
edit=self.current_leader_name_text,
|
||||
down_widget=self.more_scores_button,
|
||||
|
|
@ -446,6 +455,10 @@ class TournamentButton:
|
|||
|
||||
def _update_lock_state(self) -> None:
|
||||
|
||||
# no-op if our widget is dead.
|
||||
if not self.button:
|
||||
return
|
||||
|
||||
if self.game is None:
|
||||
return
|
||||
|
||||
|
|
|
|||
17
dist/ba_data/python/bauiv1lib/credits.py
vendored
17
dist/ba_data/python/bauiv1lib/credits.py
vendored
|
|
@ -94,8 +94,9 @@ class CreditsWindow(bui.MainWindow):
|
|||
else:
|
||||
btn = bui.buttonwidget(
|
||||
parent=self._root_widget,
|
||||
id=f'{self.main_window_id_prefix}|back',
|
||||
position=(40, yoffs - 46),
|
||||
size=(60, 48),
|
||||
size=(60, 55),
|
||||
scale=0.8,
|
||||
label=bui.charstr(bui.SpecialChar.BACK),
|
||||
button_type='backSmall',
|
||||
|
|
@ -113,15 +114,16 @@ class CreditsWindow(bui.MainWindow):
|
|||
center_small_content_horizontally=True,
|
||||
)
|
||||
|
||||
bui.widget(
|
||||
edit=scroll,
|
||||
right_widget=bui.get_special_widget('squad_button'),
|
||||
)
|
||||
if uiscale is bui.UIScale.SMALL:
|
||||
bui.widget(
|
||||
edit=scroll,
|
||||
left_widget=bui.get_special_widget('back_button'),
|
||||
)
|
||||
bui.widget(
|
||||
edit=scroll,
|
||||
right_widget=bui.get_special_widget('squad_button'),
|
||||
)
|
||||
bui.containerwidget(edit=self._root_widget, selected_child=scroll)
|
||||
|
||||
def _format_names(names2: Sequence[str], inset: float) -> str:
|
||||
sval = ''
|
||||
|
|
@ -340,6 +342,7 @@ class CreditsWindow(bui.MainWindow):
|
|||
|
||||
container = self._subcontainer = bui.containerwidget(
|
||||
parent=scroll,
|
||||
id=f'{self.main_window_id_prefix}|sub',
|
||||
size=(self._sub_width, self._sub_height),
|
||||
background=False,
|
||||
claims_left_right=False,
|
||||
|
|
@ -396,3 +399,7 @@ class CreditsWindow(bui.MainWindow):
|
|||
transition=transition, origin_widget=origin_widget
|
||||
)
|
||||
)
|
||||
|
||||
@override
|
||||
def main_window_should_preserve_selection(self) -> bool:
|
||||
return True
|
||||
|
|
|
|||
|
|
@ -164,6 +164,11 @@ class FileSelectorWindow(bui.MainWindow):
|
|||
)
|
||||
)
|
||||
|
||||
@override
|
||||
def main_window_should_preserve_selection(self) -> bool:
|
||||
# TODO - wire this up.
|
||||
return False
|
||||
|
||||
def _on_up_press(self) -> None:
|
||||
self._on_entry_activated('..')
|
||||
|
||||
|
|
|
|||
387
dist/ba_data/python/bauiv1lib/gather/__init__.py
vendored
387
dist/ba_data/python/bauiv1lib/gather/__init__.py
vendored
|
|
@ -2,390 +2,7 @@
|
|||
#
|
||||
"""Provides UI for inviting/joining friends."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import weakref
|
||||
import logging
|
||||
from enum import Enum
|
||||
from typing import override, TYPE_CHECKING
|
||||
from bauiv1lib.gather._gather import GatherTab, GatherWindow
|
||||
|
||||
from bauiv1lib.tabs import TabRow
|
||||
import bauiv1 as bui
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from bauiv1lib.play import PlaylistSelectContext
|
||||
|
||||
|
||||
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 bui.NotFoundError("GatherTab's window no longer exists.")
|
||||
return window
|
||||
|
||||
def on_activate(
|
||||
self,
|
||||
parent_widget: bui.Widget,
|
||||
tab_button: bui.Widget,
|
||||
region_width: float,
|
||||
region_height: float,
|
||||
region_left: float,
|
||||
region_bottom: float,
|
||||
) -> bui.Widget:
|
||||
"""Called when the tab becomes the active one.
|
||||
|
||||
The tab should create and return a container widget covering the
|
||||
specified region.
|
||||
"""
|
||||
# pylint: disable=too-many-positional-arguments
|
||||
raise RuntimeError('Should not get here.')
|
||||
|
||||
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(bui.MainWindow):
|
||||
"""Window for joining/inviting friends."""
|
||||
|
||||
class TabID(Enum):
|
||||
"""Our available tab types."""
|
||||
|
||||
ABOUT = 'about'
|
||||
INTERNET = 'internet'
|
||||
PRIVATE = 'private'
|
||||
NEARBY = 'nearby'
|
||||
MANUAL = 'manual'
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
transition: str | None = 'in_right',
|
||||
origin_widget: bui.Widget | None = None,
|
||||
):
|
||||
# pylint: disable=too-many-locals
|
||||
# pylint: disable=cyclic-import
|
||||
from bauiv1lib.gather.abouttab import AboutGatherTab
|
||||
from bauiv1lib.gather.manualtab import ManualGatherTab
|
||||
from bauiv1lib.gather.privatetab import PrivateGatherTab
|
||||
from bauiv1lib.gather.publictab import PublicGatherTab
|
||||
from bauiv1lib.gather.nearbytab import NearbyGatherTab
|
||||
|
||||
plus = bui.app.plus
|
||||
assert plus is not None
|
||||
|
||||
bui.set_analytics_screen('Gather Window')
|
||||
uiscale = bui.app.ui_v1.uiscale
|
||||
self._width = (
|
||||
1640
|
||||
if uiscale is bui.UIScale.SMALL
|
||||
else 1100 if uiscale is bui.UIScale.MEDIUM else 1200
|
||||
)
|
||||
self._height = (
|
||||
1000
|
||||
if uiscale is bui.UIScale.SMALL
|
||||
else 730 if uiscale is bui.UIScale.MEDIUM else 900
|
||||
)
|
||||
self._current_tab: GatherWindow.TabID | None = None
|
||||
self._r = 'gatherWindow'
|
||||
|
||||
# Do some fancy math to fill all available screen area up to the
|
||||
# size of our backing container. This lets us fit to the exact
|
||||
# screen shape at small ui scale.
|
||||
screensize = bui.get_virtual_screen_size()
|
||||
scale = (
|
||||
1.4
|
||||
if uiscale is bui.UIScale.SMALL
|
||||
else 0.88 if uiscale is bui.UIScale.MEDIUM else 0.66
|
||||
)
|
||||
# Calc screen size in our local container space and clamp to a
|
||||
# bit smaller than our container size.
|
||||
target_width = min(self._width - 130, screensize[0] / scale)
|
||||
target_height = min(self._height - 130, screensize[1] / scale)
|
||||
|
||||
# To get top/left coords, go to the center of our window and
|
||||
# offset by half the width/height of our target area.
|
||||
yoffs = 0.5 * self._height + 0.5 * target_height + 30.0
|
||||
|
||||
self._scroll_width = target_width
|
||||
self._scroll_height = target_height - 65
|
||||
self._scroll_bottom = yoffs - 93 - self._scroll_height
|
||||
self._scroll_left = (self._width - self._scroll_width) * 0.5
|
||||
|
||||
super().__init__(
|
||||
root_widget=bui.containerwidget(
|
||||
size=(self._width, self._height),
|
||||
toolbar_visibility=(
|
||||
'menu_tokens'
|
||||
if uiscale is bui.UIScale.SMALL
|
||||
else 'menu_full'
|
||||
),
|
||||
scale=scale,
|
||||
),
|
||||
transition=transition,
|
||||
origin_widget=origin_widget,
|
||||
# We're affected by screen size only at small ui-scale.
|
||||
refresh_on_screen_size_changes=uiscale is bui.UIScale.SMALL,
|
||||
)
|
||||
|
||||
if uiscale is bui.UIScale.SMALL:
|
||||
bui.containerwidget(
|
||||
edit=self._root_widget, on_cancel_call=self.main_window_back
|
||||
)
|
||||
self._back_button = None
|
||||
else:
|
||||
self._back_button = btn = bui.buttonwidget(
|
||||
parent=self._root_widget,
|
||||
position=(70, yoffs - 43),
|
||||
size=(60, 60),
|
||||
scale=1.1,
|
||||
autoselect=True,
|
||||
label=bui.charstr(bui.SpecialChar.BACK),
|
||||
button_type='backSmall',
|
||||
on_activate_call=self.main_window_back,
|
||||
)
|
||||
bui.containerwidget(edit=self._root_widget, cancel_button=btn)
|
||||
|
||||
bui.textwidget(
|
||||
parent=self._root_widget,
|
||||
position=(
|
||||
(
|
||||
self._width * 0.5
|
||||
+ (
|
||||
(self._scroll_width * -0.5 + 170.0 - 70.0)
|
||||
if uiscale is bui.UIScale.SMALL
|
||||
else 0.0
|
||||
)
|
||||
),
|
||||
yoffs - (64 if uiscale is bui.UIScale.SMALL else 4),
|
||||
),
|
||||
size=(0, 0),
|
||||
color=bui.app.ui_v1.title_color,
|
||||
scale=1.3 if uiscale is bui.UIScale.SMALL else 1.0,
|
||||
h_align='left' if uiscale is bui.UIScale.SMALL else 'center',
|
||||
v_align='center',
|
||||
text=(bui.Lstr(resource=f'{self._r}.titleText')),
|
||||
maxwidth=135 if uiscale is bui.UIScale.SMALL else 320,
|
||||
)
|
||||
|
||||
# Build up the set of tabs we want.
|
||||
tabdefs: list[tuple[GatherWindow.TabID, bui.Lstr]] = [
|
||||
(self.TabID.ABOUT, bui.Lstr(resource=f'{self._r}.aboutText'))
|
||||
]
|
||||
if plus.get_v1_account_misc_read_val('enablePublicParties', True):
|
||||
tabdefs.append(
|
||||
(
|
||||
self.TabID.INTERNET,
|
||||
bui.Lstr(resource=f'{self._r}.publicText'),
|
||||
)
|
||||
)
|
||||
tabdefs.append(
|
||||
(self.TabID.PRIVATE, bui.Lstr(resource=f'{self._r}.privateText'))
|
||||
)
|
||||
tabdefs.append(
|
||||
(self.TabID.NEARBY, bui.Lstr(resource=f'{self._r}.nearbyText'))
|
||||
)
|
||||
tabdefs.append(
|
||||
(self.TabID.MANUAL, bui.Lstr(resource=f'{self._r}.manualText'))
|
||||
)
|
||||
|
||||
tab_inset = 250.0 if uiscale is bui.UIScale.SMALL else 100.0
|
||||
|
||||
self._tab_row = TabRow(
|
||||
self._root_widget,
|
||||
tabdefs,
|
||||
size=(self._scroll_width - 2.0 * tab_inset, 50),
|
||||
pos=(
|
||||
self._scroll_left + tab_inset,
|
||||
self._scroll_bottom + self._scroll_height - 4.0,
|
||||
),
|
||||
on_select_call=bui.WeakCall(self._set_tab),
|
||||
)
|
||||
|
||||
# Now instantiate handlers for these tabs.
|
||||
tabtypes: dict[GatherWindow.TabID, type[GatherTab]] = {
|
||||
self.TabID.ABOUT: AboutGatherTab,
|
||||
self.TabID.MANUAL: ManualGatherTab,
|
||||
self.TabID.PRIVATE: PrivateGatherTab,
|
||||
self.TabID.INTERNET: PublicGatherTab,
|
||||
self.TabID.NEARBY: NearbyGatherTab,
|
||||
}
|
||||
self._tabs: dict[GatherWindow.TabID, GatherTab] = {}
|
||||
for tab_id in self._tab_row.tabs:
|
||||
tabtype = tabtypes.get(tab_id)
|
||||
if tabtype is not None:
|
||||
self._tabs[tab_id] = tabtype(self)
|
||||
|
||||
# Eww; tokens meter may or may not be here; should be smarter
|
||||
# about this.
|
||||
bui.widget(
|
||||
edit=self._tab_row.tabs[tabdefs[-1][0]].button,
|
||||
right_widget=bui.get_special_widget('tokens_meter'),
|
||||
)
|
||||
if uiscale is bui.UIScale.SMALL:
|
||||
bui.widget(
|
||||
edit=self._tab_row.tabs[tabdefs[0][0]].button,
|
||||
left_widget=bui.get_special_widget('back_button'),
|
||||
up_widget=bui.get_special_widget('back_button'),
|
||||
)
|
||||
|
||||
# Not actually using a scroll widget anymore; just an image.
|
||||
bui.imagewidget(
|
||||
parent=self._root_widget,
|
||||
size=(self._scroll_width, self._scroll_height),
|
||||
position=(
|
||||
self._width * 0.5 - self._scroll_width * 0.5,
|
||||
self._scroll_bottom,
|
||||
),
|
||||
texture=bui.gettexture('scrollWidget'),
|
||||
mesh_transparent=bui.getmesh('softEdgeOutside'),
|
||||
opacity=0.4,
|
||||
)
|
||||
self._tab_container: bui.Widget | None = None
|
||||
|
||||
self._restore_state()
|
||||
|
||||
@override
|
||||
def get_main_window_state(self) -> bui.MainWindowState:
|
||||
# Support recreating our window for back/refresh purposes.
|
||||
cls = type(self)
|
||||
return bui.BasicMainWindowState(
|
||||
create_call=lambda transition, origin_widget: cls(
|
||||
transition=transition, origin_widget=origin_widget
|
||||
)
|
||||
)
|
||||
|
||||
@override
|
||||
def on_main_window_close(self) -> None:
|
||||
self._save_state()
|
||||
|
||||
def playlist_select(
|
||||
self,
|
||||
origin_widget: bui.Widget,
|
||||
context: PlaylistSelectContext,
|
||||
) -> None:
|
||||
"""Called by the private-hosting tab to select a playlist."""
|
||||
from bauiv1lib.play import PlayWindow
|
||||
|
||||
# Avoid redundant window spawns.
|
||||
if not self.main_window_has_control():
|
||||
return
|
||||
|
||||
playwindow = PlayWindow(
|
||||
origin_widget=origin_widget, playlist_select_context=context
|
||||
)
|
||||
self.main_window_replace(playwindow)
|
||||
|
||||
# Grab the newly-set main-window's back-state; that will lead us
|
||||
# back here once we're done going down our main-window
|
||||
# rabbit-hole for playlist selection.
|
||||
context.back_state = playwindow.main_window_back_state
|
||||
|
||||
def _set_tab(self, tab_id: TabID) -> None:
|
||||
if self._current_tab is tab_id:
|
||||
return
|
||||
prev_tab_id = self._current_tab
|
||||
self._current_tab = tab_id
|
||||
|
||||
# We wanna preserve our current tab between runs.
|
||||
cfg = bui.app.config
|
||||
cfg['Gather Tab'] = tab_id.value
|
||||
cfg.commit()
|
||||
|
||||
# Update tab colors based on which is selected.
|
||||
self._tab_row.update_appearance(tab_id)
|
||||
|
||||
if prev_tab_id is not None:
|
||||
prev_tab = self._tabs.get(prev_tab_id)
|
||||
if prev_tab is not None:
|
||||
prev_tab.on_deactivate()
|
||||
|
||||
# Clear up prev container if it hasn't been done.
|
||||
if self._tab_container:
|
||||
self._tab_container.delete()
|
||||
|
||||
tab = self._tabs.get(tab_id)
|
||||
if tab is not None:
|
||||
self._tab_container = tab.on_activate(
|
||||
self._root_widget,
|
||||
self._tab_row.tabs[tab_id].button,
|
||||
self._scroll_width,
|
||||
self._scroll_height,
|
||||
self._scroll_left,
|
||||
self._scroll_bottom,
|
||||
)
|
||||
return
|
||||
|
||||
def _save_state(self) -> None:
|
||||
try:
|
||||
for tab in self._tabs.values():
|
||||
tab.save_state()
|
||||
|
||||
sel = self._root_widget.get_selected_child()
|
||||
selected_tab_ids = [
|
||||
tab_id
|
||||
for tab_id, tab in self._tab_row.tabs.items()
|
||||
if sel == tab.button
|
||||
]
|
||||
if sel == self._back_button:
|
||||
sel_name = 'Back'
|
||||
elif selected_tab_ids:
|
||||
assert len(selected_tab_ids) == 1
|
||||
sel_name = f'Tab:{selected_tab_ids[0].value}'
|
||||
elif sel == self._tab_container:
|
||||
sel_name = 'TabContainer'
|
||||
else:
|
||||
raise ValueError(f'unrecognized selection: \'{sel}\'')
|
||||
assert bui.app.classic is not None
|
||||
bui.app.ui_v1.window_states[type(self)] = {
|
||||
'sel_name': sel_name,
|
||||
}
|
||||
except Exception:
|
||||
logging.exception('Error saving state for %s.', self)
|
||||
|
||||
def _restore_state(self) -> None:
|
||||
try:
|
||||
for tab in self._tabs.values():
|
||||
tab.restore_state()
|
||||
|
||||
sel: bui.Widget | None
|
||||
assert bui.app.classic is not None
|
||||
winstate = bui.app.ui_v1.window_states.get(type(self), {})
|
||||
sel_name = winstate.get('sel_name', None)
|
||||
assert isinstance(sel_name, (str, type(None)))
|
||||
current_tab = self.TabID.ABOUT
|
||||
gather_tab_val = bui.app.config.get('Gather Tab')
|
||||
try:
|
||||
stored_tab = self.TabID(gather_tab_val)
|
||||
if stored_tab in self._tab_row.tabs:
|
||||
current_tab = stored_tab
|
||||
except ValueError:
|
||||
pass
|
||||
self._set_tab(current_tab)
|
||||
if sel_name == 'Back':
|
||||
sel = self._back_button
|
||||
elif sel_name == 'TabContainer':
|
||||
sel = self._tab_container
|
||||
elif isinstance(sel_name, str) and sel_name.startswith('Tab:'):
|
||||
try:
|
||||
sel_tab_id = self.TabID(sel_name.split(':')[-1])
|
||||
except ValueError:
|
||||
sel_tab_id = self.TabID.ABOUT
|
||||
sel = self._tab_row.tabs[sel_tab_id].button
|
||||
else:
|
||||
sel = self._tab_row.tabs[current_tab].button
|
||||
bui.containerwidget(edit=self._root_widget, selected_child=sel)
|
||||
|
||||
except Exception:
|
||||
logging.exception('Error restoring state for %s.', self)
|
||||
__all__ = ['GatherTab', 'GatherWindow']
|
||||
|
|
|
|||
361
dist/ba_data/python/bauiv1lib/gather/_gather.py
vendored
Normal file
361
dist/ba_data/python/bauiv1lib/gather/_gather.py
vendored
Normal file
|
|
@ -0,0 +1,361 @@
|
|||
# Released under the MIT License. See LICENSE for details.
|
||||
#
|
||||
"""Provides UI for inviting/joining friends."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import weakref
|
||||
import logging
|
||||
from enum import Enum
|
||||
from typing import override, TYPE_CHECKING
|
||||
|
||||
from bauiv1lib.tabs import TabRow
|
||||
import bauiv1 as bui
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from bauiv1lib.play import PlaylistSelectContext
|
||||
|
||||
|
||||
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 bui.NotFoundError("GatherTab's window no longer exists.")
|
||||
return window
|
||||
|
||||
def on_activate(
|
||||
self,
|
||||
parent_widget: bui.Widget,
|
||||
tab_button: bui.Widget,
|
||||
region_width: float,
|
||||
region_height: float,
|
||||
region_left: float,
|
||||
region_bottom: float,
|
||||
) -> bui.Widget:
|
||||
"""Called when the tab becomes the active one.
|
||||
|
||||
The tab should create and return a container widget covering the
|
||||
specified region.
|
||||
"""
|
||||
# pylint: disable=too-many-positional-arguments
|
||||
raise RuntimeError('Should not get here.')
|
||||
|
||||
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(bui.MainWindow):
|
||||
"""Window for joining/inviting friends."""
|
||||
|
||||
class TabID(Enum):
|
||||
"""Our available tab types."""
|
||||
|
||||
ABOUT = 'about'
|
||||
INTERNET = 'internet'
|
||||
PRIVATE = 'private'
|
||||
NEARBY = 'nearby'
|
||||
MANUAL = 'manual'
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
transition: str | None = 'in_right',
|
||||
origin_widget: bui.Widget | None = None,
|
||||
):
|
||||
# pylint: disable=too-many-locals
|
||||
# pylint: disable=cyclic-import
|
||||
from bauiv1lib.gather.abouttab import AboutGatherTab
|
||||
from bauiv1lib.gather.manualtab import ManualGatherTab
|
||||
from bauiv1lib.gather.privatetab import PrivateGatherTab
|
||||
from bauiv1lib.gather.publictab import PublicGatherTab
|
||||
from bauiv1lib.gather.nearbytab import NearbyGatherTab
|
||||
|
||||
plus = bui.app.plus
|
||||
assert plus is not None
|
||||
|
||||
bui.set_analytics_screen('Gather Window')
|
||||
uiscale = bui.app.ui_v1.uiscale
|
||||
self._width = (
|
||||
1640
|
||||
if uiscale is bui.UIScale.SMALL
|
||||
else 1100 if uiscale is bui.UIScale.MEDIUM else 1200
|
||||
)
|
||||
self._height = (
|
||||
1000
|
||||
if uiscale is bui.UIScale.SMALL
|
||||
else 730 if uiscale is bui.UIScale.MEDIUM else 900
|
||||
)
|
||||
self._current_tab: GatherWindow.TabID | None = None
|
||||
self._r = 'gatherWindow'
|
||||
|
||||
# Do some fancy math to fill all available screen area up to the
|
||||
# size of our backing container. This lets us fit to the exact
|
||||
# screen shape at small ui scale.
|
||||
screensize = bui.get_virtual_screen_size()
|
||||
scale = (
|
||||
1.4
|
||||
if uiscale is bui.UIScale.SMALL
|
||||
else 0.88 if uiscale is bui.UIScale.MEDIUM else 0.66
|
||||
)
|
||||
# Calc screen size in our local container space and clamp to a
|
||||
# bit smaller than our container size.
|
||||
target_width = min(self._width - 130, screensize[0] / scale)
|
||||
target_height = min(self._height - 130, screensize[1] / scale)
|
||||
|
||||
# To get top/left coords, go to the center of our window and
|
||||
# offset by half the width/height of our target area.
|
||||
yoffs = 0.5 * self._height + 0.5 * target_height + 30.0
|
||||
|
||||
self._scroll_width = target_width
|
||||
self._scroll_height = target_height - 65
|
||||
self._scroll_bottom = yoffs - 93 - self._scroll_height
|
||||
self._scroll_left = (self._width - self._scroll_width) * 0.5
|
||||
|
||||
super().__init__(
|
||||
root_widget=bui.containerwidget(
|
||||
size=(self._width, self._height),
|
||||
toolbar_visibility=(
|
||||
'menu_tokens'
|
||||
if uiscale is bui.UIScale.SMALL
|
||||
else 'menu_full'
|
||||
),
|
||||
scale=scale,
|
||||
),
|
||||
transition=transition,
|
||||
origin_widget=origin_widget,
|
||||
# We're affected by screen size only at small ui-scale.
|
||||
refresh_on_screen_size_changes=uiscale is bui.UIScale.SMALL,
|
||||
)
|
||||
|
||||
if uiscale is bui.UIScale.SMALL:
|
||||
bui.containerwidget(
|
||||
edit=self._root_widget, on_cancel_call=self.main_window_back
|
||||
)
|
||||
self._back_button = None
|
||||
else:
|
||||
self._back_button = btn = bui.buttonwidget(
|
||||
parent=self._root_widget,
|
||||
id=f'{self.main_window_id_prefix}|back',
|
||||
position=(70, yoffs - 43),
|
||||
size=(60, 60),
|
||||
scale=1.1,
|
||||
autoselect=True,
|
||||
label=bui.charstr(bui.SpecialChar.BACK),
|
||||
button_type='backSmall',
|
||||
on_activate_call=self.main_window_back,
|
||||
)
|
||||
bui.containerwidget(edit=self._root_widget, cancel_button=btn)
|
||||
|
||||
bui.textwidget(
|
||||
parent=self._root_widget,
|
||||
position=(
|
||||
(
|
||||
self._width * 0.5
|
||||
+ (
|
||||
(self._scroll_width * -0.5 + 170.0 - 70.0)
|
||||
if uiscale is bui.UIScale.SMALL
|
||||
else 0.0
|
||||
)
|
||||
),
|
||||
yoffs - (64 if uiscale is bui.UIScale.SMALL else 4),
|
||||
),
|
||||
size=(0, 0),
|
||||
color=bui.app.ui_v1.title_color,
|
||||
scale=1.3 if uiscale is bui.UIScale.SMALL else 1.0,
|
||||
h_align='left' if uiscale is bui.UIScale.SMALL else 'center',
|
||||
v_align='center',
|
||||
text=(bui.Lstr(resource=f'{self._r}.titleText')),
|
||||
maxwidth=135 if uiscale is bui.UIScale.SMALL else 320,
|
||||
)
|
||||
|
||||
# Build up the set of tabs we want.
|
||||
tabdefs: list[tuple[GatherWindow.TabID, bui.Lstr]] = [
|
||||
(self.TabID.ABOUT, bui.Lstr(resource=f'{self._r}.aboutText'))
|
||||
]
|
||||
if plus.get_v1_account_misc_read_val('enablePublicParties', True):
|
||||
tabdefs.append(
|
||||
(
|
||||
self.TabID.INTERNET,
|
||||
bui.Lstr(resource=f'{self._r}.publicText'),
|
||||
)
|
||||
)
|
||||
tabdefs.append(
|
||||
(self.TabID.PRIVATE, bui.Lstr(resource=f'{self._r}.privateText'))
|
||||
)
|
||||
tabdefs.append(
|
||||
(self.TabID.NEARBY, bui.Lstr(resource=f'{self._r}.nearbyText'))
|
||||
)
|
||||
tabdefs.append(
|
||||
(self.TabID.MANUAL, bui.Lstr(resource=f'{self._r}.manualText'))
|
||||
)
|
||||
|
||||
tab_inset = 250.0 if uiscale is bui.UIScale.SMALL else 100.0
|
||||
|
||||
self._tab_row = TabRow(
|
||||
self._root_widget,
|
||||
tabdefs,
|
||||
idprefix=self.main_window_id_prefix,
|
||||
size=(self._scroll_width - 2.0 * tab_inset, 50),
|
||||
pos=(
|
||||
self._scroll_left + tab_inset,
|
||||
self._scroll_bottom + self._scroll_height - 4.0,
|
||||
),
|
||||
on_select_call=bui.WeakCall(self._set_tab),
|
||||
)
|
||||
|
||||
# Now instantiate handlers for these tabs.
|
||||
tabtypes: dict[GatherWindow.TabID, type[GatherTab]] = {
|
||||
self.TabID.ABOUT: AboutGatherTab,
|
||||
self.TabID.MANUAL: ManualGatherTab,
|
||||
self.TabID.PRIVATE: PrivateGatherTab,
|
||||
self.TabID.INTERNET: PublicGatherTab,
|
||||
self.TabID.NEARBY: NearbyGatherTab,
|
||||
}
|
||||
self._tabs: dict[GatherWindow.TabID, GatherTab] = {}
|
||||
for tab_id in self._tab_row.tabs:
|
||||
tabtype = tabtypes.get(tab_id)
|
||||
if tabtype is not None:
|
||||
self._tabs[tab_id] = tabtype(self)
|
||||
|
||||
# Eww; tokens meter may or may not be here; should be smarter
|
||||
# about this.
|
||||
bui.widget(
|
||||
edit=self._tab_row.tabs[tabdefs[-1][0]].button,
|
||||
right_widget=bui.get_special_widget('tokens_meter'),
|
||||
)
|
||||
if uiscale is bui.UIScale.SMALL:
|
||||
bui.widget(
|
||||
edit=self._tab_row.tabs[tabdefs[0][0]].button,
|
||||
left_widget=bui.get_special_widget('back_button'),
|
||||
up_widget=bui.get_special_widget('back_button'),
|
||||
)
|
||||
|
||||
# Not actually using a scroll widget anymore; just an image.
|
||||
bui.imagewidget(
|
||||
parent=self._root_widget,
|
||||
size=(self._scroll_width, self._scroll_height),
|
||||
position=(
|
||||
self._width * 0.5 - self._scroll_width * 0.5,
|
||||
self._scroll_bottom,
|
||||
),
|
||||
texture=bui.gettexture('scrollWidget'),
|
||||
mesh_transparent=bui.getmesh('softEdgeOutside'),
|
||||
opacity=0.4,
|
||||
)
|
||||
self._tab_container: bui.Widget | None = None
|
||||
|
||||
self._restore_state()
|
||||
|
||||
@override
|
||||
def get_main_window_state(self) -> bui.MainWindowState:
|
||||
# Support recreating our window for back/refresh purposes.
|
||||
cls = type(self)
|
||||
return bui.BasicMainWindowState(
|
||||
create_call=lambda transition, origin_widget: cls(
|
||||
transition=transition, origin_widget=origin_widget
|
||||
)
|
||||
)
|
||||
|
||||
@override
|
||||
def main_window_should_preserve_selection(self) -> bool:
|
||||
return True
|
||||
|
||||
@override
|
||||
def on_main_window_close(self) -> None:
|
||||
self._save_state()
|
||||
|
||||
def playlist_select(
|
||||
self,
|
||||
origin_widget: bui.Widget,
|
||||
context: PlaylistSelectContext,
|
||||
) -> None:
|
||||
"""Called by the private-hosting tab to select a playlist."""
|
||||
from bauiv1lib.play import PlayWindow
|
||||
|
||||
# Avoid redundant window spawns.
|
||||
if not self.main_window_has_control():
|
||||
return
|
||||
|
||||
new_window = self.main_window_replace(
|
||||
lambda: PlayWindow(
|
||||
origin_widget=origin_widget, playlist_select_context=context
|
||||
)
|
||||
)
|
||||
assert new_window is not None
|
||||
|
||||
# Grab the newly-set main-window's back-state; that will lead us
|
||||
# back here once we're done going down our main-window
|
||||
# rabbit-hole for playlist selection.
|
||||
context.back_state = new_window.main_window_back_state
|
||||
|
||||
def _set_tab(self, tab_id: TabID) -> None:
|
||||
if self._current_tab is tab_id:
|
||||
return
|
||||
prev_tab_id = self._current_tab
|
||||
self._current_tab = tab_id
|
||||
|
||||
# We wanna preserve our current tab between runs.
|
||||
cfg = bui.app.config
|
||||
cfg['Gather Tab'] = tab_id.value
|
||||
cfg.commit()
|
||||
|
||||
# Update tab colors based on which is selected.
|
||||
self._tab_row.update_appearance(tab_id)
|
||||
|
||||
if prev_tab_id is not None:
|
||||
prev_tab = self._tabs.get(prev_tab_id)
|
||||
if prev_tab is not None:
|
||||
prev_tab.on_deactivate()
|
||||
|
||||
# Clear up prev container if it hasn't been done.
|
||||
if self._tab_container:
|
||||
self._tab_container.delete()
|
||||
|
||||
tab = self._tabs.get(tab_id)
|
||||
if tab is not None:
|
||||
self._tab_container = tab.on_activate(
|
||||
self._root_widget,
|
||||
self._tab_row.tabs[tab_id].button,
|
||||
self._scroll_width,
|
||||
self._scroll_height,
|
||||
self._scroll_left,
|
||||
self._scroll_bottom,
|
||||
)
|
||||
return
|
||||
|
||||
def _save_state(self) -> None:
|
||||
try:
|
||||
for tab in self._tabs.values():
|
||||
tab.save_state()
|
||||
except Exception:
|
||||
logging.exception('Error saving state for %s.', self)
|
||||
|
||||
def _restore_state(self) -> None:
|
||||
try:
|
||||
for tab in self._tabs.values():
|
||||
tab.restore_state()
|
||||
|
||||
current_tab = self.TabID.ABOUT
|
||||
gather_tab_val = bui.app.config.get('Gather Tab')
|
||||
try:
|
||||
stored_tab = self.TabID(gather_tab_val)
|
||||
if stored_tab in self._tab_row.tabs:
|
||||
current_tab = stored_tab
|
||||
except ValueError:
|
||||
pass
|
||||
self._set_tab(current_tab)
|
||||
|
||||
except Exception:
|
||||
logging.exception('Error restoring state for %s.', self)
|
||||
|
|
@ -27,11 +27,14 @@ class AboutGatherTab(GatherTab):
|
|||
region_bottom: float,
|
||||
) -> bui.Widget:
|
||||
# pylint: disable=too-many-locals
|
||||
# pylint: disable=too-many-statements
|
||||
# pylint: disable=too-many-positional-arguments
|
||||
|
||||
plus = bui.app.plus
|
||||
assert plus is not None
|
||||
|
||||
idprefix = f'{self.window.main_window_id_prefix}|about'
|
||||
|
||||
try_tickets = plus.get_v1_account_misc_read_val(
|
||||
'friendTryTickets', None
|
||||
)
|
||||
|
|
@ -147,6 +150,7 @@ class AboutGatherTab(GatherTab):
|
|||
)
|
||||
invite_button = bui.buttonwidget(
|
||||
parent=container,
|
||||
id=f'{idprefix}|invitefriend',
|
||||
position=(region_width * 0.59, y - 25),
|
||||
size=(230, 50),
|
||||
color=(0.54, 0.42, 0.56),
|
||||
|
|
@ -179,6 +183,7 @@ class AboutGatherTab(GatherTab):
|
|||
)
|
||||
discord_button = bui.buttonwidget(
|
||||
parent=container,
|
||||
id=f'{idprefix}|discordjoin',
|
||||
position=(region_width * 0.59, y - 25),
|
||||
size=(230, 50),
|
||||
color=(0.54, 0.42, 0.56),
|
||||
|
|
|
|||
|
|
@ -81,6 +81,7 @@ class ManualGatherTab(GatherTab):
|
|||
|
||||
def __init__(self, window: GatherWindow) -> None:
|
||||
super().__init__(window)
|
||||
self._idprefix = f'{window.main_window_id_prefix}|manual'
|
||||
self._check_button: bui.Widget | None = None
|
||||
self._doing_access_check: bool | None = None
|
||||
self._access_check_count: int | None = None
|
||||
|
|
@ -135,6 +136,7 @@ class ManualGatherTab(GatherTab):
|
|||
v = c_height - 30
|
||||
self._join_by_address_text = bui.textwidget(
|
||||
parent=self._container,
|
||||
id=f'{self._idprefix}|joinbyaddress',
|
||||
position=(c_width * 0.5 - 245, v - 13),
|
||||
color=(0.6, 1.0, 0.6),
|
||||
scale=1.3,
|
||||
|
|
@ -156,6 +158,7 @@ class ManualGatherTab(GatherTab):
|
|||
)
|
||||
self._favorites_text = bui.textwidget(
|
||||
parent=self._container,
|
||||
id=f'{self._idprefix}|favorites',
|
||||
position=(c_width * 0.5 + 45, v - 13),
|
||||
color=(0.6, 1.0, 0.6),
|
||||
scale=1.3,
|
||||
|
|
@ -271,6 +274,7 @@ class ManualGatherTab(GatherTab):
|
|||
)
|
||||
txt = bui.textwidget(
|
||||
parent=self._container,
|
||||
id=f'{self._idprefix}|manualaddress',
|
||||
editable=True,
|
||||
description=bui.Lstr(resource='gatherWindow.' 'manualAddressText'),
|
||||
position=(c_width * 0.5 - 240 - 50, v - 30),
|
||||
|
|
@ -298,6 +302,7 @@ class ManualGatherTab(GatherTab):
|
|||
)
|
||||
txt2 = bui.textwidget(
|
||||
parent=self._container,
|
||||
id=f'{self._idprefix}|manualport',
|
||||
editable=True,
|
||||
description=bui.Lstr(resource='gatherWindow.' 'portText'),
|
||||
text=str(last_port),
|
||||
|
|
@ -313,6 +318,7 @@ class ManualGatherTab(GatherTab):
|
|||
|
||||
btn = bui.buttonwidget(
|
||||
parent=self._container,
|
||||
id=f'{self._idprefix}|manualconnect',
|
||||
size=(300, 70),
|
||||
label=bui.Lstr(resource='gatherWindow.' 'manualConnectText'),
|
||||
position=(c_width * 0.5 - 300, v),
|
||||
|
|
@ -321,6 +327,7 @@ class ManualGatherTab(GatherTab):
|
|||
)
|
||||
savebutton = bui.buttonwidget(
|
||||
parent=self._container,
|
||||
id=f'{self._idprefix}|savefavorite',
|
||||
size=(300, 70),
|
||||
label=bui.Lstr(resource='gatherWindow.favoritesSaveText'),
|
||||
position=(c_width * 0.5 - 240 + 490 - 200, v),
|
||||
|
|
@ -336,6 +343,7 @@ class ManualGatherTab(GatherTab):
|
|||
self._check_button = bui.textwidget(
|
||||
parent=self._container,
|
||||
size=(250, 60),
|
||||
id=f'{self._idprefix}|showmyaddress',
|
||||
text=bui.Lstr(resource='gatherWindow.showMyAddressText'),
|
||||
v_align='center',
|
||||
h_align='center',
|
||||
|
|
@ -408,6 +416,7 @@ class ManualGatherTab(GatherTab):
|
|||
|
||||
self._favorites_connect_button = btn1 = bui.buttonwidget(
|
||||
parent=self._container,
|
||||
id=f'{self._idprefix}|favoritesconnect',
|
||||
size=(b_width, b_height),
|
||||
position=(140 if uiscale is bui.UIScale.SMALL else 40, btnv),
|
||||
button_type='square',
|
||||
|
|
@ -426,6 +435,7 @@ class ManualGatherTab(GatherTab):
|
|||
btnv -= b_height + b_space_extra
|
||||
bui.buttonwidget(
|
||||
parent=self._container,
|
||||
id=f'{self._idprefix}|favoritesedit',
|
||||
size=(b_width, b_height),
|
||||
position=(140 if uiscale is bui.UIScale.SMALL else 40, btnv),
|
||||
button_type='square',
|
||||
|
|
@ -439,6 +449,7 @@ class ManualGatherTab(GatherTab):
|
|||
btnv -= b_height + b_space_extra
|
||||
bui.buttonwidget(
|
||||
parent=self._container,
|
||||
id=f'{self._idprefix}|favoritesdelete',
|
||||
size=(b_width, b_height),
|
||||
position=(140 if uiscale is bui.UIScale.SMALL else 40, btnv),
|
||||
button_type='square',
|
||||
|
|
@ -462,6 +473,7 @@ class ManualGatherTab(GatherTab):
|
|||
)
|
||||
self._columnwidget = bui.columnwidget(
|
||||
parent=scrlw,
|
||||
id=f'{self._idprefix}|favoritescolumn',
|
||||
left_border=10,
|
||||
border=2,
|
||||
margin=0,
|
||||
|
|
@ -517,6 +529,7 @@ class ManualGatherTab(GatherTab):
|
|||
assert bui.app.classic is not None
|
||||
uiscale = bui.app.ui_v1.uiscale
|
||||
self._favorite_edit_window = cnt = bui.containerwidget(
|
||||
parent=bui.get_special_widget('overlay_stack'),
|
||||
scale=(
|
||||
1.8
|
||||
if uiscale is bui.UIScale.SMALL
|
||||
|
|
@ -524,6 +537,7 @@ class ManualGatherTab(GatherTab):
|
|||
),
|
||||
size=(c_width, c_height),
|
||||
transition='in_scale',
|
||||
darken_behind=True,
|
||||
)
|
||||
|
||||
bui.textwidget(
|
||||
|
|
@ -693,8 +707,8 @@ class ManualGatherTab(GatherTab):
|
|||
],
|
||||
),
|
||||
self._delete_saved_party,
|
||||
450,
|
||||
150,
|
||||
width=450,
|
||||
height=150,
|
||||
)
|
||||
|
||||
def _delete_saved_party(self) -> None:
|
||||
|
|
@ -735,6 +749,7 @@ class ManualGatherTab(GatherTab):
|
|||
for i, server in enumerate(servers):
|
||||
txt = bui.textwidget(
|
||||
parent=self._columnwidget,
|
||||
id=f'{self._idprefix}|favorite{i}',
|
||||
size=(self._favorites_scroll_width / t_scale, 30),
|
||||
selectable=True,
|
||||
color=(1.0, 1, 0.4),
|
||||
|
|
|
|||
|
|
@ -23,16 +23,23 @@ class NetScanner:
|
|||
|
||||
def __init__(
|
||||
self,
|
||||
*,
|
||||
tab: GatherTab,
|
||||
scrollwidget: bui.Widget,
|
||||
tab_button: bui.Widget,
|
||||
width: float,
|
||||
idprefix: str,
|
||||
):
|
||||
self._idprefix = idprefix
|
||||
self._tab = weakref.ref(tab)
|
||||
self._scrollwidget = scrollwidget
|
||||
self._tab_button = tab_button
|
||||
self._columnwidget = bui.columnwidget(
|
||||
parent=self._scrollwidget, border=2, margin=0, left_border=10
|
||||
parent=self._scrollwidget,
|
||||
id=f'{self._idprefix}|col',
|
||||
border=2,
|
||||
margin=0,
|
||||
left_border=10,
|
||||
)
|
||||
bui.widget(edit=self._columnwidget, up_widget=tab_button)
|
||||
self._width = width
|
||||
|
|
@ -92,6 +99,11 @@ class NetScanner:
|
|||
corner_scale=t_scale,
|
||||
maxwidth=(self._width / t_scale) * 0.93,
|
||||
)
|
||||
# We don't give these ids since they pop in and out and it
|
||||
# doesn't make sense to save/restore selections for them.
|
||||
# But we need to suppress the warning from that.
|
||||
bui.widget(edit=txt3, allow_preserve_selection=False)
|
||||
|
||||
if host == last_selected_host:
|
||||
bui.containerwidget(
|
||||
edit=self._columnwidget,
|
||||
|
|
@ -107,6 +119,7 @@ class NearbyGatherTab(GatherTab):
|
|||
|
||||
def __init__(self, window: GatherWindow) -> None:
|
||||
super().__init__(window)
|
||||
self._idprefix = f'{window.main_window_id_prefix}|nearby'
|
||||
self._net_scanner: NetScanner | None = None
|
||||
self._container: bui.Widget | None = None
|
||||
|
||||
|
|
@ -158,7 +171,11 @@ class NearbyGatherTab(GatherTab):
|
|||
)
|
||||
|
||||
self._net_scanner = NetScanner(
|
||||
self, scrollw, tab_button, width=sub_scroll_width
|
||||
idprefix=self._idprefix,
|
||||
tab=self,
|
||||
scrollwidget=scrollw,
|
||||
tab_button=tab_button,
|
||||
width=sub_scroll_width,
|
||||
)
|
||||
|
||||
bui.widget(edit=scrollw, autoselect=True, up_widget=tab_button)
|
||||
|
|
|
|||
|
|
@ -58,6 +58,7 @@ class PrivateGatherTab(GatherTab):
|
|||
|
||||
def __init__(self, window: GatherWindow) -> None:
|
||||
super().__init__(window)
|
||||
self._idprefix = f'{window.main_window_id_prefix}|private'
|
||||
self._container: bui.Widget | None = None
|
||||
self._state: State = State()
|
||||
self._last_datacode_refresh_time: float | None = None
|
||||
|
|
@ -115,6 +116,7 @@ class PrivateGatherTab(GatherTab):
|
|||
v = self._c_height - 30.0
|
||||
self._join_sub_tab_text = bui.textwidget(
|
||||
parent=self._container,
|
||||
id=f'{self._idprefix}|jointab',
|
||||
position=(self._c_width * 0.5 - 245, v - 13),
|
||||
color=(0.6, 1.0, 0.6),
|
||||
scale=1.3,
|
||||
|
|
@ -134,6 +136,7 @@ class PrivateGatherTab(GatherTab):
|
|||
)
|
||||
self._host_sub_tab_text = bui.textwidget(
|
||||
parent=self._container,
|
||||
id=f'{self._idprefix}|hosttab',
|
||||
position=(self._c_width * 0.5 + 45, v - 13),
|
||||
color=(0.6, 1.0, 0.6),
|
||||
scale=1.3,
|
||||
|
|
@ -526,6 +529,7 @@ class PrivateGatherTab(GatherTab):
|
|||
|
||||
self._join_party_code_text = bui.textwidget(
|
||||
parent=self._container,
|
||||
id=f'{self._idprefix}|joinpartycode',
|
||||
position=(self._c_width * 0.5 - 150, self._c_height - 250),
|
||||
flatness=1.0,
|
||||
scale=1.5,
|
||||
|
|
@ -540,6 +544,7 @@ class PrivateGatherTab(GatherTab):
|
|||
)
|
||||
btn = bui.buttonwidget(
|
||||
parent=self._container,
|
||||
id=f'{self._idprefix}|connect',
|
||||
size=(300, 70),
|
||||
label=bui.Lstr(resource='gatherWindow.manualConnectText'),
|
||||
position=(self._c_width * 0.5 - 150, self._c_height - 350),
|
||||
|
|
@ -667,6 +672,7 @@ class PrivateGatherTab(GatherTab):
|
|||
)
|
||||
self._host_playlist_button = bui.buttonwidget(
|
||||
parent=self._container,
|
||||
id=f'{self._idprefix}|host',
|
||||
size=(400, 70),
|
||||
color=(0.6, 0.5, 0.6),
|
||||
textcolor=(0.8, 0.75, 0.8),
|
||||
|
|
@ -726,6 +732,7 @@ class PrivateGatherTab(GatherTab):
|
|||
cbtnoffs = 10
|
||||
self._host_copy_button = bui.buttonwidget(
|
||||
parent=self._container,
|
||||
id=f'{self._idprefix}|hostcopy',
|
||||
size=(140, 40),
|
||||
color=(0.6, 0.5, 0.6),
|
||||
textcolor=(0.8, 0.75, 0.8),
|
||||
|
|
@ -738,6 +745,7 @@ class PrivateGatherTab(GatherTab):
|
|||
cbtnoffs = -70
|
||||
self._host_connect_button = bui.buttonwidget(
|
||||
parent=self._container,
|
||||
id=f'{self._idprefix}|hostconnect',
|
||||
size=(140, 40),
|
||||
color=(0.6, 0.5, 0.6),
|
||||
textcolor=(0.8, 0.75, 0.8),
|
||||
|
|
@ -898,6 +906,7 @@ class PrivateGatherTab(GatherTab):
|
|||
waiting = self._waiting_for_start_stop_response
|
||||
self._host_start_stop_button = bui.buttonwidget(
|
||||
parent=self._container,
|
||||
id=f'{self._idprefix}|hoststartstop',
|
||||
size=(400, 80),
|
||||
color=(
|
||||
(0.6, 0.6, 0.6)
|
||||
|
|
|
|||
|
|
@ -130,6 +130,11 @@ class UIRow:
|
|||
h_align='left',
|
||||
v_align='center',
|
||||
)
|
||||
# These are popping in and out too chaotically to try and do
|
||||
# auto-select-save/restore, so we don't supply ids for them. We
|
||||
# need to suppress the warning that comes with that though.
|
||||
bui.widget(edit=self._name_widget, allow_preserve_selection=False)
|
||||
|
||||
bui.widget(
|
||||
edit=self._name_widget,
|
||||
left_widget=join_text,
|
||||
|
|
@ -164,6 +169,12 @@ class UIRow:
|
|||
position=(sub_scroll_width - 270.0, 1 + vpos),
|
||||
scale=0.9,
|
||||
)
|
||||
# These are popping in and out too chaotically to try and do
|
||||
# auto-select-save/restore, so we don't supply ids for them.
|
||||
# We need to suppress the warning that comes with that
|
||||
# though.
|
||||
bui.widget(edit=self._stats_button, allow_preserve_selection=False)
|
||||
|
||||
if existing_selection == Selection(
|
||||
party.get_key(), SelectionComponent.STATS_BUTTON
|
||||
):
|
||||
|
|
@ -356,6 +367,7 @@ class PublicGatherTab(GatherTab):
|
|||
|
||||
def __init__(self, window: GatherWindow) -> None:
|
||||
super().__init__(window)
|
||||
self._idprefix = f'{window.main_window_id_prefix}|public'
|
||||
self._container: bui.Widget | None = None
|
||||
self._join_text: bui.Widget | None = None
|
||||
self._host_text: bui.Widget | None = None
|
||||
|
|
@ -428,6 +440,7 @@ class PublicGatherTab(GatherTab):
|
|||
v = c_height - 30
|
||||
self._join_text = bui.textwidget(
|
||||
parent=self._container,
|
||||
id=f'{self._idprefix}|jointab',
|
||||
position=(c_width * 0.5 - 245, v - 13),
|
||||
color=(0.6, 1.0, 0.6),
|
||||
scale=1.3,
|
||||
|
|
@ -451,6 +464,7 @@ class PublicGatherTab(GatherTab):
|
|||
)
|
||||
self._host_text = bui.textwidget(
|
||||
parent=self._container,
|
||||
id=f'{self._idprefix}|hosttab',
|
||||
position=(c_width * 0.5 + 45, v - 13),
|
||||
color=(0.6, 1.0, 0.6),
|
||||
scale=1.3,
|
||||
|
|
@ -593,6 +607,7 @@ class PublicGatherTab(GatherTab):
|
|||
filter_txt = bui.Lstr(resource='filterText')
|
||||
self._filter_text = bui.textwidget(
|
||||
parent=self._container,
|
||||
id=f'{self._idprefix}|filter',
|
||||
text=self._filter_value,
|
||||
size=(350, 45),
|
||||
position=(c_width * 0.5 - 150, v - 10),
|
||||
|
|
@ -670,6 +685,7 @@ class PublicGatherTab(GatherTab):
|
|||
)
|
||||
self._join_list_column = bui.containerwidget(
|
||||
parent=scrollw,
|
||||
id=f'{self._idprefix}|joinlistcolumn',
|
||||
background=False,
|
||||
size=(400, 400),
|
||||
claims_left_right=True,
|
||||
|
|
@ -755,6 +771,7 @@ class PublicGatherTab(GatherTab):
|
|||
)
|
||||
self._host_name_text = bui.textwidget(
|
||||
parent=self._container,
|
||||
id=f'{self._idprefix}|hostingname',
|
||||
editable=True,
|
||||
size=(535, 40),
|
||||
position=(230 + xoffs, v - 30),
|
||||
|
|
@ -795,6 +812,7 @@ class PublicGatherTab(GatherTab):
|
|||
)
|
||||
btn1 = self._host_max_party_size_minus_button = bui.buttonwidget(
|
||||
parent=self._container,
|
||||
id=f'{self._idprefix}|maxsizeminus',
|
||||
size=(40, 40),
|
||||
on_activate_call=bui.WeakCall(
|
||||
self._on_max_public_party_size_minus_press
|
||||
|
|
@ -805,6 +823,7 @@ class PublicGatherTab(GatherTab):
|
|||
)
|
||||
btn2 = self._host_max_party_size_plus_button = bui.buttonwidget(
|
||||
parent=self._container,
|
||||
id=f'{self._idprefix}|maxsizeplus',
|
||||
size=(40, 40),
|
||||
on_activate_call=bui.WeakCall(
|
||||
self._on_max_public_party_size_plus_press
|
||||
|
|
@ -827,6 +846,7 @@ class PublicGatherTab(GatherTab):
|
|||
)
|
||||
self._host_toggle_button = bui.buttonwidget(
|
||||
parent=self._container,
|
||||
id=f'{self._idprefix}|hosttoggle',
|
||||
label=label,
|
||||
size=(400, 80),
|
||||
on_activate_call=(
|
||||
|
|
|
|||
4
dist/ba_data/python/bauiv1lib/getremote.py
vendored
4
dist/ba_data/python/bauiv1lib/getremote.py
vendored
|
|
@ -20,7 +20,7 @@ class GetBSRemoteWindow(PopupWindow):
|
|||
scale = (
|
||||
2.3
|
||||
if uiscale is bui.UIScale.SMALL
|
||||
else 1.65 if uiscale is bui.UIScale.MEDIUM else 1.23
|
||||
else 1.7 if uiscale is bui.UIScale.MEDIUM else 1.4
|
||||
)
|
||||
self._transitioning_out = False
|
||||
self._width = 570
|
||||
|
|
@ -54,7 +54,7 @@ class GetBSRemoteWindow(PopupWindow):
|
|||
size=(0, 0),
|
||||
h_align='center',
|
||||
v_align='center',
|
||||
maxwidth=self._width * 0.9,
|
||||
maxwidth=self._width * 0.8,
|
||||
position=(self._width * 0.5, 60),
|
||||
text=bui.Lstr(
|
||||
resource='remoteAppInfoShortText',
|
||||
|
|
|
|||
33
dist/ba_data/python/bauiv1lib/gettokens.py
vendored
33
dist/ba_data/python/bauiv1lib/gettokens.py
vendored
|
|
@ -357,6 +357,8 @@ class GetTokensWindow(bui.MainWindow):
|
|||
# We're affected by screen size only at small ui-scale.
|
||||
refresh_on_screen_size_changes=uiscale is bui.UIScale.SMALL,
|
||||
)
|
||||
# Am seeing preserve-selection try to restore this sometimes.
|
||||
bui.widget(edit=self._root_widget, allow_preserve_selection=False)
|
||||
|
||||
if uiscale is bui.UIScale.SMALL:
|
||||
bui.containerwidget(
|
||||
|
|
@ -366,6 +368,7 @@ class GetTokensWindow(bui.MainWindow):
|
|||
else:
|
||||
self._back_button = bui.buttonwidget(
|
||||
parent=self._root_widget,
|
||||
id=f'{self.main_window_id_prefix}|back',
|
||||
position=(60, self._yoffs - 90),
|
||||
size=((60, 60)),
|
||||
scale=1.0,
|
||||
|
|
@ -444,6 +447,10 @@ class GetTokensWindow(bui.MainWindow):
|
|||
)
|
||||
)
|
||||
|
||||
@override
|
||||
def main_window_should_preserve_selection(self) -> bool:
|
||||
return True
|
||||
|
||||
def _update(self) -> None:
|
||||
# No-op if our underlying widget is dead or on its way out.
|
||||
if not self._root_widget or self._root_widget.transitioning_out:
|
||||
|
|
@ -596,6 +603,7 @@ class GetTokensWindow(bui.MainWindow):
|
|||
)
|
||||
tinfobtn = bui.buttonwidget(
|
||||
parent=self._root_widget,
|
||||
id=f'{self.main_window_id_prefix}|learnmore',
|
||||
autoselect=True,
|
||||
label=bui.Lstr(resource='learnMoreText'),
|
||||
text_scale=0.7,
|
||||
|
|
@ -634,6 +642,7 @@ class GetTokensWindow(bui.MainWindow):
|
|||
btn = bui.buttonwidget(
|
||||
autoselect=True,
|
||||
label='',
|
||||
id=f'{self.main_window_id_prefix}|button{i}',
|
||||
color=buttondef.color,
|
||||
transition_delay=tdelay,
|
||||
up_widget=tinfobtn,
|
||||
|
|
@ -741,6 +750,14 @@ class GetTokensWindow(bui.MainWindow):
|
|||
text=bui.Lstr(resource='removeInGameAdsTokenPurchaseText'),
|
||||
)
|
||||
|
||||
# Most of our UI won't exist until this point so we need to
|
||||
# explicitly restore state for selection restore to work.
|
||||
#
|
||||
# Note to self: perhaps we should *not* do this if significant
|
||||
# time has passed since the window was made or if input commands
|
||||
# have happened.
|
||||
self.main_window_restore_shared_state()
|
||||
|
||||
def _purchase_press(self, itemid: str) -> None:
|
||||
plus = bui.app.plus
|
||||
|
||||
|
|
@ -802,8 +819,14 @@ def show_get_tokens_window(origin_widget: bui.Widget | None = None) -> None:
|
|||
|
||||
# NOTE TO USERS: The code below is not the proper way to do things;
|
||||
# whenever possible one should use a MainWindow's
|
||||
# main_window_replace() or main_window_back() methods. We just need
|
||||
# to do things a bit more manually in this particular case.
|
||||
# main_window_replace() or main_window_back() methods or
|
||||
# bauiv1.auxiliary_window_activate(). We just need to do things a
|
||||
# bit more manually in this particular case.
|
||||
|
||||
# Basically we want to pop up our auxiliary window but we don't want
|
||||
# to replace any existing auxiliary windows; we want our close
|
||||
# button to go back to whatever was there already, no matter whether
|
||||
# it was an auxiliary window or not.
|
||||
|
||||
prev_main_window = bui.app.ui_v1.get_main_window()
|
||||
|
||||
|
|
@ -811,10 +834,12 @@ def show_get_tokens_window(origin_widget: bui.Widget | None = None) -> None:
|
|||
if isinstance(prev_main_window, GetTokensWindow):
|
||||
return
|
||||
|
||||
ui = bui.app.ui_v1
|
||||
# Set our new main window.
|
||||
bui.app.ui_v1.set_main_window(
|
||||
ui.set_main_window(
|
||||
GetTokensWindow(origin_widget=origin_widget),
|
||||
from_window=False,
|
||||
from_window=False, # Don't check where we're coming from.
|
||||
back_state=ui.save_current_main_window_state(),
|
||||
is_auxiliary=True,
|
||||
suppress_warning=True,
|
||||
)
|
||||
|
|
|
|||
25
dist/ba_data/python/bauiv1lib/help.py
vendored
25
dist/ba_data/python/bauiv1lib/help.py
vendored
|
|
@ -91,6 +91,7 @@ class HelpWindow(bui.MainWindow):
|
|||
else:
|
||||
btn = bui.buttonwidget(
|
||||
parent=self._root_widget,
|
||||
id=f'{self.main_window_id_prefix}|back',
|
||||
position=(50, yoffs - 45),
|
||||
size=(60, 55),
|
||||
scale=0.8,
|
||||
|
|
@ -117,7 +118,6 @@ class HelpWindow(bui.MainWindow):
|
|||
edit=self._scrollwidget,
|
||||
left_widget=bui.get_special_widget('back_button'),
|
||||
)
|
||||
|
||||
bui.widget(
|
||||
edit=self._scrollwidget,
|
||||
right_widget=bui.get_special_widget('squad_button'),
|
||||
|
|
@ -143,6 +143,7 @@ class HelpWindow(bui.MainWindow):
|
|||
|
||||
self._subcontainer = bui.containerwidget(
|
||||
parent=self._scrollwidget,
|
||||
id=f'{self.main_window_id_prefix}|sub',
|
||||
size=(self._sub_width, self._sub_height),
|
||||
background=False,
|
||||
claims_left_right=False,
|
||||
|
|
@ -174,7 +175,8 @@ class HelpWindow(bui.MainWindow):
|
|||
)
|
||||
|
||||
spacing = 1.0
|
||||
h = self._sub_width * 0.5
|
||||
baseh = self._sub_width * 0.5
|
||||
h = baseh + 30
|
||||
v = self._sub_height - 55
|
||||
if uiscale is bui.UIScale.SMALL:
|
||||
v -= inline_title_height
|
||||
|
|
@ -217,6 +219,7 @@ class HelpWindow(bui.MainWindow):
|
|||
position=(hval2 - 0.5 * icon_size, v - 0.45 * icon_size),
|
||||
texture=logo_tex,
|
||||
)
|
||||
h = baseh
|
||||
|
||||
app = bui.app
|
||||
assert app.classic is not None
|
||||
|
|
@ -284,6 +287,8 @@ class HelpWindow(bui.MainWindow):
|
|||
flatness=1.0,
|
||||
)
|
||||
|
||||
h = baseh + 20
|
||||
|
||||
v -= spacing * 40.0
|
||||
txt_scale = 0.74
|
||||
txt = bui.Lstr(resource=f'{self._r}.friendsText').evaluate()
|
||||
|
|
@ -373,6 +378,8 @@ class HelpWindow(bui.MainWindow):
|
|||
|
||||
v -= spacing * 150.0
|
||||
|
||||
h = baseh + 30
|
||||
|
||||
txt = bui.Lstr(resource=f'{self._r}.controlsText').evaluate()
|
||||
txt_scale = 1.4
|
||||
txt_maxwidth = 480
|
||||
|
|
@ -405,6 +412,8 @@ class HelpWindow(bui.MainWindow):
|
|||
|
||||
v -= spacing * 45.0
|
||||
|
||||
h = baseh
|
||||
|
||||
txt_scale = 0.7
|
||||
txt = bui.Lstr(
|
||||
resource=f'{self._r}.controlsSubtitleText',
|
||||
|
|
@ -557,6 +566,8 @@ class HelpWindow(bui.MainWindow):
|
|||
|
||||
v -= spacing * 280.0
|
||||
|
||||
h = baseh + 30
|
||||
|
||||
txt = bui.Lstr(resource=f'{self._r}.powerupsText').evaluate()
|
||||
txt_scale = 1.4
|
||||
txt_maxwidth = 480
|
||||
|
|
@ -585,6 +596,8 @@ class HelpWindow(bui.MainWindow):
|
|||
texture=logo_tex,
|
||||
)
|
||||
|
||||
h = baseh + 20
|
||||
|
||||
v -= spacing * 50.0
|
||||
txt_scale = getres(f'{self._r}.powerupsSubtitleTextScale')
|
||||
txt = bui.Lstr(resource=f'{self._r}.powerupsSubtitleText').evaluate()
|
||||
|
|
@ -601,6 +614,8 @@ class HelpWindow(bui.MainWindow):
|
|||
flatness=1.0,
|
||||
)
|
||||
|
||||
h = baseh + 20
|
||||
|
||||
v -= spacing * 1.0
|
||||
|
||||
mm1 = -270
|
||||
|
|
@ -670,7 +685,7 @@ class HelpWindow(bui.MainWindow):
|
|||
position=(h + mm3, v),
|
||||
size=(0, 0),
|
||||
scale=txt_scale,
|
||||
maxwidth=300,
|
||||
maxwidth=290,
|
||||
flatness=1.0,
|
||||
text=txtl,
|
||||
h_align='left',
|
||||
|
|
@ -691,3 +706,7 @@ class HelpWindow(bui.MainWindow):
|
|||
transition=transition, origin_widget=origin_widget
|
||||
)
|
||||
)
|
||||
|
||||
@override
|
||||
def main_window_should_preserve_selection(self) -> bool:
|
||||
return True
|
||||
|
|
|
|||
175
dist/ba_data/python/bauiv1lib/inbox.py
vendored
175
dist/ba_data/python/bauiv1lib/inbox.py
vendored
|
|
@ -13,6 +13,7 @@ from typing import override, assert_never, TYPE_CHECKING
|
|||
from efro.util import strict_partial, pairs_from_flat
|
||||
from efro.error import CommunicationError
|
||||
import bacommon.bs
|
||||
from bauiv1lib.utils import scroll_fade_bottom, scroll_fade_top
|
||||
import bauiv1 as bui
|
||||
|
||||
if TYPE_CHECKING:
|
||||
|
|
@ -29,7 +30,7 @@ class _Section:
|
|||
"""Return rows of selectable controls."""
|
||||
return []
|
||||
|
||||
def emit(self, subcontainer: bui.Widget, y: float) -> None:
|
||||
def emit(self, subcontainer: bui.Widget, y: float, idprefix: str) -> None:
|
||||
"""Emit the section."""
|
||||
|
||||
|
||||
|
|
@ -75,7 +76,7 @@ class _TextSection(_Section):
|
|||
return self.full_height
|
||||
|
||||
@override
|
||||
def emit(self, subcontainer: bui.Widget, y: float) -> None:
|
||||
def emit(self, subcontainer: bui.Widget, y: float, idprefix: str) -> None:
|
||||
bui.textwidget(
|
||||
parent=subcontainer,
|
||||
position=(
|
||||
|
|
@ -132,9 +133,10 @@ class _ButtonSection(_Section):
|
|||
section_strong.call(section_strong)
|
||||
|
||||
@override
|
||||
def emit(self, subcontainer: bui.Widget, y: float) -> None:
|
||||
def emit(self, subcontainer: bui.Widget, y: float, idprefix: str) -> None:
|
||||
self.button = bui.buttonwidget(
|
||||
parent=subcontainer,
|
||||
id=f'{idprefix}|button',
|
||||
position=(
|
||||
self.sub_width * 0.5 - self.button_width * 0.5,
|
||||
y - self.spacing_top - self.button_height,
|
||||
|
|
@ -184,7 +186,7 @@ class _DisplayItemsSection(_Section):
|
|||
return self.full_height
|
||||
|
||||
@override
|
||||
def emit(self, subcontainer: bui.Widget, y: float) -> None:
|
||||
def emit(self, subcontainer: bui.Widget, y: float, idprefix: str) -> None:
|
||||
# pylint: disable=cyclic-import
|
||||
from baclassic import show_display_item
|
||||
|
||||
|
|
@ -267,7 +269,7 @@ class _ExpireTimeSection(_Section):
|
|||
bui.textwidget(edit=self._widget, text=val, color=color)
|
||||
|
||||
@override
|
||||
def emit(self, subcontainer: bui.Widget, y: float) -> None:
|
||||
def emit(self, subcontainer: bui.Widget, y: float, idprefix: str) -> None:
|
||||
self._widget = bui.textwidget(
|
||||
parent=subcontainer,
|
||||
position=(
|
||||
|
|
@ -290,9 +292,9 @@ class _ExpireTimeSection(_Section):
|
|||
|
||||
@dataclass
|
||||
class _EntryDisplay:
|
||||
interaction_style: bacommon.bs.BasicClientUI.InteractionStyle
|
||||
button_label_positive: bacommon.bs.BasicClientUI.ButtonLabel
|
||||
button_label_negative: bacommon.bs.BasicClientUI.ButtonLabel
|
||||
interaction_style: bacommon.bs.BasicCloudDialog.InteractionStyle
|
||||
button_label_positive: bacommon.bs.BasicCloudDialog.ButtonLabel
|
||||
button_label_negative: bacommon.bs.BasicCloudDialog.ButtonLabel
|
||||
sections: list[_Section]
|
||||
id: str
|
||||
total_height: float
|
||||
|
|
@ -351,10 +353,15 @@ class InboxWindow(bui.MainWindow):
|
|||
scroll_height = target_height - 31
|
||||
scroll_bottom = yoffs - 59 - scroll_height
|
||||
|
||||
# Go with full screen area scrollable on small ui.
|
||||
if uiscale is bui.UIScale.SMALL:
|
||||
scroll_height += 36
|
||||
scroll_bottom -= 4
|
||||
|
||||
super().__init__(
|
||||
root_widget=bui.containerwidget(
|
||||
size=(self._width, self._height),
|
||||
toolbar_visibility=('menu_full'),
|
||||
toolbar_visibility='menu_full',
|
||||
toolbar_cancel_button_style=(
|
||||
'close' if auxiliary_style else 'back'
|
||||
),
|
||||
|
|
@ -374,6 +381,7 @@ class InboxWindow(bui.MainWindow):
|
|||
else:
|
||||
self._back_button = bui.buttonwidget(
|
||||
parent=self._root_widget,
|
||||
id=f'{self.main_window_id_prefix}|back',
|
||||
autoselect=True,
|
||||
position=(50, yoffs - 48),
|
||||
size=(60, 60),
|
||||
|
|
@ -390,21 +398,6 @@ class InboxWindow(bui.MainWindow):
|
|||
edit=self._root_widget, cancel_button=self._back_button
|
||||
)
|
||||
|
||||
self._title_text = bui.textwidget(
|
||||
parent=self._root_widget,
|
||||
position=(
|
||||
self._width * 0.5,
|
||||
yoffs - (45 if uiscale is bui.UIScale.SMALL else 30),
|
||||
),
|
||||
size=(0, 0),
|
||||
h_align='center',
|
||||
v_align='center',
|
||||
scale=0.6 if uiscale is bui.UIScale.SMALL else 0.8,
|
||||
text=bui.Lstr(resource='inboxText'),
|
||||
maxwidth=200,
|
||||
color=bui.app.ui_v1.title_color,
|
||||
)
|
||||
|
||||
# Shows 'loading', 'no messages', etc.
|
||||
self._infotext = bui.textwidget(
|
||||
parent=self._root_widget,
|
||||
|
|
@ -427,6 +420,7 @@ class InboxWindow(bui.MainWindow):
|
|||
)
|
||||
self._scrollwidget = bui.scrollwidget(
|
||||
parent=self._root_widget,
|
||||
id=f'{self.main_window_id_prefix}|scroll',
|
||||
size=(scroll_width, scroll_height),
|
||||
position=(self._width * 0.5 - scroll_width * 0.5, scroll_bottom),
|
||||
capture_arrows=True,
|
||||
|
|
@ -446,12 +440,45 @@ class InboxWindow(bui.MainWindow):
|
|||
left_widget=bui.get_special_widget('back_button'),
|
||||
)
|
||||
|
||||
# When we're doing fullscreen scrolling, fade content around
|
||||
# toolbars.
|
||||
if uiscale is bui.UIScale.SMALL:
|
||||
scroll_fade_top(
|
||||
self._root_widget,
|
||||
self._width * 0.5 - scroll_width * 0.5,
|
||||
scroll_bottom,
|
||||
scroll_width,
|
||||
scroll_height,
|
||||
)
|
||||
scroll_fade_bottom(
|
||||
self._root_widget,
|
||||
self._width * 0.5 - scroll_width * 0.5,
|
||||
scroll_bottom,
|
||||
scroll_width,
|
||||
scroll_height,
|
||||
)
|
||||
|
||||
bui.containerwidget(
|
||||
edit=self._root_widget,
|
||||
cancel_button=self._back_button,
|
||||
single_depth=True,
|
||||
)
|
||||
|
||||
self._title_text = bui.textwidget(
|
||||
parent=self._root_widget,
|
||||
position=(
|
||||
self._width * 0.5,
|
||||
yoffs - (45 if uiscale is bui.UIScale.SMALL else 30),
|
||||
),
|
||||
size=(0, 0),
|
||||
h_align='center',
|
||||
v_align='center',
|
||||
scale=0.6 if uiscale is bui.UIScale.SMALL else 0.8,
|
||||
text=bui.Lstr(resource='inboxText'),
|
||||
maxwidth=200,
|
||||
color=bui.app.ui_v1.title_color,
|
||||
)
|
||||
|
||||
# Kick off request.
|
||||
plus = bui.app.plus
|
||||
if plus is None or plus.accounts.primary is None:
|
||||
|
|
@ -474,6 +501,10 @@ class InboxWindow(bui.MainWindow):
|
|||
)
|
||||
)
|
||||
|
||||
@override
|
||||
def main_window_should_preserve_selection(self) -> bool:
|
||||
return True
|
||||
|
||||
def _error(self, errmsg: bui.Lstr | str) -> None:
|
||||
"""Put ourself in a permanent error state."""
|
||||
bui.spinnerwidget(edit=self._loading_spinner, visible=False)
|
||||
|
|
@ -486,7 +517,7 @@ class InboxWindow(bui.MainWindow):
|
|||
def _on_entry_display_press(
|
||||
self,
|
||||
display_weak: weakref.ReferenceType[_EntryDisplay],
|
||||
action: bacommon.bs.ClientUIAction,
|
||||
action: bacommon.bs.CloudDialogAction,
|
||||
) -> None:
|
||||
display = display_weak()
|
||||
if display is None:
|
||||
|
|
@ -500,7 +531,7 @@ class InboxWindow(bui.MainWindow):
|
|||
# interaction types.
|
||||
if (
|
||||
display.interaction_style
|
||||
is bacommon.bs.BasicClientUI.InteractionStyle.UNKNOWN
|
||||
is bacommon.bs.BasicCloudDialog.InteractionStyle.UNKNOWN
|
||||
):
|
||||
display.processing_complete = True
|
||||
self._close_soon_if_all_processed()
|
||||
|
|
@ -523,7 +554,7 @@ class InboxWindow(bui.MainWindow):
|
|||
# Ask the master-server to run our action.
|
||||
with plus.accounts.primary:
|
||||
plus.cloud.send_message_cb(
|
||||
bacommon.bs.ClientUIActionMessage(display.id, action),
|
||||
bacommon.bs.CloudDialogActionMessage(display.id, action),
|
||||
on_response=bui.WeakCall(
|
||||
self._on_client_ui_action_response,
|
||||
display_weak,
|
||||
|
|
@ -534,12 +565,12 @@ class InboxWindow(bui.MainWindow):
|
|||
# Tweak the UI to show that things are in motion.
|
||||
button = (
|
||||
display.button_positive
|
||||
if action is bacommon.bs.ClientUIAction.BUTTON_PRESS_POSITIVE
|
||||
if action is bacommon.bs.CloudDialogAction.BUTTON_PRESS_POSITIVE
|
||||
else display.button_negative
|
||||
)
|
||||
button_spinner = (
|
||||
display.button_spinner_positive
|
||||
if action is bacommon.bs.ClientUIAction.BUTTON_PRESS_POSITIVE
|
||||
if action is bacommon.bs.CloudDialogAction.BUTTON_PRESS_POSITIVE
|
||||
else display.button_spinner_negative
|
||||
)
|
||||
if button is not None:
|
||||
|
|
@ -578,8 +609,8 @@ class InboxWindow(bui.MainWindow):
|
|||
def _on_client_ui_action_response(
|
||||
self,
|
||||
display_weak: weakref.ReferenceType[_EntryDisplay],
|
||||
action: bacommon.bs.ClientUIAction,
|
||||
response: bacommon.bs.ClientUIActionResponse | Exception,
|
||||
action: bacommon.bs.CloudDialogAction,
|
||||
response: bacommon.bs.CloudDialogActionResponse | Exception,
|
||||
) -> None:
|
||||
# pylint: disable=too-many-branches
|
||||
|
||||
|
|
@ -602,12 +633,12 @@ class InboxWindow(bui.MainWindow):
|
|||
# Tweak the button to show results.
|
||||
button = (
|
||||
display.button_positive
|
||||
if action is bacommon.bs.ClientUIAction.BUTTON_PRESS_POSITIVE
|
||||
if action is bacommon.bs.CloudDialogAction.BUTTON_PRESS_POSITIVE
|
||||
else display.button_negative
|
||||
)
|
||||
button_spinner = (
|
||||
display.button_spinner_positive
|
||||
if action is bacommon.bs.ClientUIAction.BUTTON_PRESS_POSITIVE
|
||||
if action is bacommon.bs.CloudDialogAction.BUTTON_PRESS_POSITIVE
|
||||
else display.button_spinner_negative
|
||||
)
|
||||
# Always hide spinner at this point.
|
||||
|
|
@ -717,6 +748,10 @@ class InboxWindow(bui.MainWindow):
|
|||
sub_width = 400.0
|
||||
sub_height = margin_top
|
||||
|
||||
# For fullscreen scrollable, account for toolbar.
|
||||
if uiscale is bui.UIScale.SMALL:
|
||||
sub_height += 36
|
||||
|
||||
# Construct entries for everything we'll display.
|
||||
for i, wrapper in enumerate(response.wrappers):
|
||||
|
||||
|
|
@ -724,9 +759,9 @@ class InboxWindow(bui.MainWindow):
|
|||
# textfin: str
|
||||
color: tuple[float, float, float]
|
||||
|
||||
interaction_style: bacommon.bs.BasicClientUI.InteractionStyle
|
||||
button_label_positive: bacommon.bs.BasicClientUI.ButtonLabel
|
||||
button_label_negative: bacommon.bs.BasicClientUI.ButtonLabel
|
||||
interaction_style: bacommon.bs.BasicCloudDialog.InteractionStyle
|
||||
button_label_positive: bacommon.bs.BasicCloudDialog.ButtonLabel
|
||||
button_label_negative: bacommon.bs.BasicCloudDialog.ButtonLabel
|
||||
|
||||
sections: list[_Section] = []
|
||||
total_height = 80.0
|
||||
|
|
@ -734,7 +769,7 @@ class InboxWindow(bui.MainWindow):
|
|||
# Display only entries where we recognize all style/label
|
||||
# values and ui component types.
|
||||
if (
|
||||
isinstance(wrapper.ui, bacommon.bs.BasicClientUI)
|
||||
isinstance(wrapper.ui, bacommon.bs.BasicCloudDialog)
|
||||
and not wrapper.ui.contains_unknown_elements()
|
||||
):
|
||||
color = (0.55, 0.5, 0.7)
|
||||
|
|
@ -742,14 +777,14 @@ class InboxWindow(bui.MainWindow):
|
|||
button_label_positive = wrapper.ui.button_label_positive
|
||||
button_label_negative = wrapper.ui.button_label_negative
|
||||
|
||||
idcls = bacommon.bs.BasicClientUIComponentTypeID
|
||||
idcls = bacommon.bs.BasicCloudDialogComponentTypeID
|
||||
for component in wrapper.ui.components:
|
||||
ctypeid = component.get_type_id()
|
||||
section: _Section
|
||||
|
||||
if ctypeid is idcls.TEXT:
|
||||
assert isinstance(
|
||||
component, bacommon.bs.BasicClientUIComponentText
|
||||
component, bacommon.bs.BasicCloudDialogComponentText
|
||||
)
|
||||
section = _TextSection(
|
||||
sub_width=sub_width,
|
||||
|
|
@ -767,7 +802,7 @@ class InboxWindow(bui.MainWindow):
|
|||
|
||||
elif ctypeid is idcls.LINK:
|
||||
assert isinstance(
|
||||
component, bacommon.bs.BasicClientUIComponentLink
|
||||
component, bacommon.bs.BasicCloudDialogComponentLink
|
||||
)
|
||||
|
||||
def _do_open_url(url: str, sec: _ButtonSection) -> None:
|
||||
|
|
@ -792,7 +827,7 @@ class InboxWindow(bui.MainWindow):
|
|||
elif ctypeid is idcls.DISPLAY_ITEMS:
|
||||
assert isinstance(
|
||||
component,
|
||||
bacommon.bs.BasicClientUIDisplayItems,
|
||||
bacommon.bs.BasicCloudDialogDisplayItems,
|
||||
)
|
||||
section = _DisplayItemsSection(
|
||||
sub_width=sub_width,
|
||||
|
|
@ -809,7 +844,7 @@ class InboxWindow(bui.MainWindow):
|
|||
|
||||
assert isinstance(
|
||||
component,
|
||||
bacommon.bs.BasicClientUIBsClassicTourneyResult,
|
||||
bacommon.bs.BasicCloudDialogBsClassicTourneyResult,
|
||||
)
|
||||
campaignname, levelname = component.game.split(':')
|
||||
assert bui.app.classic is not None
|
||||
|
|
@ -934,7 +969,7 @@ class InboxWindow(bui.MainWindow):
|
|||
|
||||
elif ctypeid is idcls.EXPIRE_TIME:
|
||||
assert isinstance(
|
||||
component, bacommon.bs.BasicClientUIExpireTime
|
||||
component, bacommon.bs.BasicCloudDialogExpireTime
|
||||
)
|
||||
section = _ExpireTimeSection(
|
||||
sub_width=sub_width,
|
||||
|
|
@ -957,11 +992,13 @@ class InboxWindow(bui.MainWindow):
|
|||
# 'upgrade your app to see this' message.
|
||||
color = (0.6, 0.6, 0.6)
|
||||
interaction_style = (
|
||||
bacommon.bs.BasicClientUI.InteractionStyle.UNKNOWN
|
||||
bacommon.bs.BasicCloudDialog.InteractionStyle.UNKNOWN
|
||||
)
|
||||
button_label_positive = (
|
||||
bacommon.bs.BasicCloudDialog.ButtonLabel.OK
|
||||
)
|
||||
button_label_positive = bacommon.bs.BasicClientUI.ButtonLabel.OK
|
||||
button_label_negative = (
|
||||
bacommon.bs.BasicClientUI.ButtonLabel.CANCEL
|
||||
bacommon.bs.BasicCloudDialog.ButtonLabel.CANCEL
|
||||
)
|
||||
|
||||
section = _TextSection(
|
||||
|
|
@ -989,7 +1026,7 @@ class InboxWindow(bui.MainWindow):
|
|||
sub_height += margin_bottom
|
||||
|
||||
subcontainer = bui.containerwidget(
|
||||
id='inboxsub',
|
||||
id=f'{self.main_window_id_prefix}|subc',
|
||||
parent=self._scrollwidget,
|
||||
size=(sub_width, sub_height),
|
||||
background=False,
|
||||
|
|
@ -1004,8 +1041,15 @@ class InboxWindow(bui.MainWindow):
|
|||
|
||||
buttonrows: list[list[bui.Widget]] = []
|
||||
y = sub_height - margin_top
|
||||
for i, _wrapper in enumerate(response.wrappers):
|
||||
entry_display = self._entry_displays[i]
|
||||
|
||||
# For fullscreen scrollable, account for toolbar.
|
||||
uiscale = bui.app.ui_v1.uiscale
|
||||
if uiscale is bui.UIScale.SMALL:
|
||||
y -= 36
|
||||
|
||||
# for i, _wrapper in enumerate(response.wrappers):
|
||||
for entry_display in self._entry_displays:
|
||||
# entry_display = self._entry_displays[i]
|
||||
entry_display_weak = weakref.ref(entry_display)
|
||||
bwidth = 140
|
||||
bheight = 40
|
||||
|
|
@ -1027,8 +1071,15 @@ class InboxWindow(bui.MainWindow):
|
|||
bui.widget(edit=img, depth_range=(0, 0.1))
|
||||
|
||||
# Section contents.
|
||||
for sec in entry_display.sections:
|
||||
sec.emit(subcontainer, ysection)
|
||||
for s, sec in enumerate(entry_display.sections):
|
||||
sec.emit(
|
||||
subcontainer,
|
||||
ysection,
|
||||
(
|
||||
f'{self.main_window_id_prefix}|entry_{entry_display.id}'
|
||||
f'|section{s}'
|
||||
),
|
||||
)
|
||||
# Wire up any widgets created by this section.
|
||||
sec_button_row = sec.get_button_row()
|
||||
if sec_button_row:
|
||||
|
|
@ -1039,7 +1090,7 @@ class InboxWindow(bui.MainWindow):
|
|||
have_negative_button = (
|
||||
entry_display.interaction_style
|
||||
is (
|
||||
bacommon.bs.BasicClientUI
|
||||
bacommon.bs.BasicCloudDialog
|
||||
).InteractionStyle.BUTTON_POSITIVE_NEGATIVE
|
||||
)
|
||||
|
||||
|
|
@ -1053,6 +1104,10 @@ class InboxWindow(bui.MainWindow):
|
|||
)
|
||||
entry_display.button_positive = btn = bui.buttonwidget(
|
||||
parent=subcontainer,
|
||||
id=(
|
||||
f'{self.main_window_id_prefix}|entry_{entry_display.id}'
|
||||
f'|buttonpositive'
|
||||
),
|
||||
position=bpos,
|
||||
autoselect=True,
|
||||
size=(bwidth, bheight),
|
||||
|
|
@ -1064,7 +1119,7 @@ class InboxWindow(bui.MainWindow):
|
|||
on_activate_call=bui.WeakCall(
|
||||
self._on_entry_display_press,
|
||||
entry_display_weak,
|
||||
bacommon.bs.ClientUIAction.BUTTON_PRESS_POSITIVE,
|
||||
bacommon.bs.CloudDialogAction.BUTTON_PRESS_POSITIVE,
|
||||
),
|
||||
enable_sound=False,
|
||||
)
|
||||
|
|
@ -1084,6 +1139,10 @@ class InboxWindow(bui.MainWindow):
|
|||
bpos = (25, y - entry_display.total_height + 15.0)
|
||||
entry_display.button_negative = btn2 = bui.buttonwidget(
|
||||
parent=subcontainer,
|
||||
id=(
|
||||
f'{self.main_window_id_prefix}'
|
||||
f'|entry_{entry_display.id}|buttonnegative'
|
||||
),
|
||||
position=bpos,
|
||||
autoselect=True,
|
||||
size=(bwidth, bheight),
|
||||
|
|
@ -1095,7 +1154,7 @@ class InboxWindow(bui.MainWindow):
|
|||
on_activate_call=bui.WeakCall(
|
||||
self._on_entry_display_press,
|
||||
entry_display_weak,
|
||||
(bacommon.bs.ClientUIAction).BUTTON_PRESS_NEGATIVE,
|
||||
(bacommon.bs.CloudDialogAction).BUTTON_PRESS_NEGATIVE,
|
||||
),
|
||||
enable_sound=False,
|
||||
)
|
||||
|
|
@ -1142,6 +1201,14 @@ class InboxWindow(bui.MainWindow):
|
|||
|
||||
above_widget = buttons[0]
|
||||
|
||||
# Most of our UI won't exist until this point so we need to
|
||||
# explicitly restore state for selection restore to work.
|
||||
#
|
||||
# Note to self: perhaps we should *not* do this if significant
|
||||
# time has passed since the window was made or if input commands
|
||||
# have happened.
|
||||
self.main_window_restore_shared_state()
|
||||
|
||||
|
||||
def _get_bs_classic_tourney_results_sections() -> list[_Section]:
|
||||
return []
|
||||
|
|
|
|||
17
dist/ba_data/python/bauiv1lib/ingamemenu.py
vendored
17
dist/ba_data/python/bauiv1lib/ingamemenu.py
vendored
|
|
@ -58,6 +58,10 @@ class InGameMenuWindow(bui.MainWindow):
|
|||
)
|
||||
)
|
||||
|
||||
@override
|
||||
def main_window_should_preserve_selection(self) -> bool:
|
||||
return True
|
||||
|
||||
def _refresh(self) -> None:
|
||||
|
||||
# Clear everything that was there.
|
||||
|
|
@ -91,6 +95,7 @@ class InGameMenuWindow(bui.MainWindow):
|
|||
if bs.is_in_replay():
|
||||
self._end_button = bui.buttonwidget(
|
||||
parent=self._root_widget,
|
||||
id=f'{self.main_window_id_prefix}|end',
|
||||
position=(h - self._button_width * 0.5 * scale, v),
|
||||
scale=scale,
|
||||
size=(self._button_width, self._button_height),
|
||||
|
|
@ -101,6 +106,7 @@ class InGameMenuWindow(bui.MainWindow):
|
|||
elif bs.get_foreground_host_session() is not None:
|
||||
self._end_button = bui.buttonwidget(
|
||||
parent=self._root_widget,
|
||||
id=f'{self.main_window_id_prefix}|end',
|
||||
position=(h - self._button_width * 0.5 * scale, v),
|
||||
scale=scale,
|
||||
size=(self._button_width, self._button_height),
|
||||
|
|
@ -124,6 +130,7 @@ class InGameMenuWindow(bui.MainWindow):
|
|||
# button.
|
||||
self._end_button = bui.buttonwidget(
|
||||
parent=self._root_widget,
|
||||
id=f'{self.main_window_id_prefix}|end',
|
||||
position=(h - self._button_width * 0.5 * scale, v),
|
||||
scale=scale,
|
||||
size=(self._button_width, self._button_height),
|
||||
|
|
@ -174,6 +181,7 @@ class InGameMenuWindow(bui.MainWindow):
|
|||
)
|
||||
btn = bui.buttonwidget(
|
||||
parent=self._root_widget,
|
||||
id=f'{self.main_window_id_prefix}|replayspeedminus',
|
||||
position=(
|
||||
h - b_size - b_buffer_1,
|
||||
v - b_size - b_buffer_2 + v_offs,
|
||||
|
|
@ -199,6 +207,7 @@ class InGameMenuWindow(bui.MainWindow):
|
|||
)
|
||||
btn = bui.buttonwidget(
|
||||
parent=self._root_widget,
|
||||
id=f'{self.main_window_id_prefix}|replayspeedplus',
|
||||
position=(h + b_buffer_1, v - b_size - b_buffer_2 + v_offs),
|
||||
button_type='square',
|
||||
size=(b_size, b_size),
|
||||
|
|
@ -221,6 +230,7 @@ class InGameMenuWindow(bui.MainWindow):
|
|||
)
|
||||
self._pause_resume_button = btn = bui.buttonwidget(
|
||||
parent=self._root_widget,
|
||||
id=f'{self.main_window_id_prefix}|pauseresume',
|
||||
position=(h - b_size * 0.5, v - b_size - b_buffer_2 + v_offs),
|
||||
button_type='square',
|
||||
size=(b_size, b_size),
|
||||
|
|
@ -234,6 +244,7 @@ class InGameMenuWindow(bui.MainWindow):
|
|||
)
|
||||
btn = bui.buttonwidget(
|
||||
parent=self._root_widget,
|
||||
id=f'{self.main_window_id_prefix}|rewind',
|
||||
position=(
|
||||
h - b_size * 1.5 - b_buffer_1 * 2,
|
||||
v - b_size - b_buffer_2 + v_offs,
|
||||
|
|
@ -260,6 +271,7 @@ class InGameMenuWindow(bui.MainWindow):
|
|||
)
|
||||
btn = bui.buttonwidget(
|
||||
parent=self._root_widget,
|
||||
id=f'{self.main_window_id_prefix}|forward',
|
||||
position=(
|
||||
h + b_size * 0.5 + b_buffer_1 * 2,
|
||||
v - b_size - b_buffer_2 + v_offs,
|
||||
|
|
@ -376,6 +388,7 @@ class InGameMenuWindow(bui.MainWindow):
|
|||
self._p_index += 1
|
||||
btn = bui.buttonwidget(
|
||||
parent=self._root_widget,
|
||||
id=f'{self.main_window_id_prefix}|resume',
|
||||
position=(h - self._button_width / 2, v),
|
||||
size=(self._button_width, self._button_height),
|
||||
scale=scale,
|
||||
|
|
@ -386,7 +399,7 @@ class InGameMenuWindow(bui.MainWindow):
|
|||
bui.containerwidget(edit=self._root_widget, cancel_button=btn)
|
||||
|
||||
# Add any custom options defined by the current game.
|
||||
for entry in custom_menu_entries:
|
||||
for i, entry in enumerate(custom_menu_entries):
|
||||
h, v, scale = positions[self._p_index]
|
||||
self._p_index += 1
|
||||
|
||||
|
|
@ -401,6 +414,7 @@ class InGameMenuWindow(bui.MainWindow):
|
|||
|
||||
bui.buttonwidget(
|
||||
parent=self._root_widget,
|
||||
id=f'{self.main_window_id_prefix}|custom{i}',
|
||||
position=(h - self._button_width / 2, v),
|
||||
size=(self._button_width, self._button_height),
|
||||
scale=scale,
|
||||
|
|
@ -417,6 +431,7 @@ class InGameMenuWindow(bui.MainWindow):
|
|||
self._p_index += 1
|
||||
btn = bui.buttonwidget(
|
||||
parent=self._root_widget,
|
||||
id=f'{self.main_window_id_prefix}|leave',
|
||||
position=(h - self._button_width / 2, v),
|
||||
size=(self._button_width, self._button_height),
|
||||
scale=scale,
|
||||
|
|
|
|||
19
dist/ba_data/python/bauiv1lib/inventory.py
vendored
19
dist/ba_data/python/bauiv1lib/inventory.py
vendored
|
|
@ -19,9 +19,6 @@ class InventoryWindow(bui.MainWindow):
|
|||
auxiliary_style: bool = True,
|
||||
):
|
||||
|
||||
bui.set_analytics_screen('Help Window')
|
||||
|
||||
assert bui.app.classic is not None
|
||||
uiscale = bui.app.ui_v1.uiscale
|
||||
self._width = 1400 if uiscale is bui.UIScale.SMALL else 750
|
||||
self._height = (
|
||||
|
|
@ -87,6 +84,7 @@ class InventoryWindow(bui.MainWindow):
|
|||
else:
|
||||
btn = bui.buttonwidget(
|
||||
parent=self._root_widget,
|
||||
id=f'{self.main_window_id_prefix}|back',
|
||||
scale=0.8,
|
||||
position=(50, yoffs - 50),
|
||||
size=(50, 50) if auxiliary_style else (60, 55),
|
||||
|
|
@ -108,6 +106,7 @@ class InventoryWindow(bui.MainWindow):
|
|||
button_width = 300
|
||||
self._player_profiles_button = btn = bui.buttonwidget(
|
||||
parent=self._root_widget,
|
||||
id=f'{self.main_window_id_prefix}|playerprofiles',
|
||||
position=(self._width * 0.5 - button_width * 0.5, yoffs - 200),
|
||||
autoselect=True,
|
||||
size=(button_width, 60),
|
||||
|
|
@ -117,6 +116,8 @@ class InventoryWindow(bui.MainWindow):
|
|||
textcolor=(0.75, 0.7, 0.8),
|
||||
on_activate_call=self._player_profiles_press,
|
||||
)
|
||||
# Select this by default.
|
||||
bui.containerwidget(edit=self._root_widget, selected_child=btn)
|
||||
bui.textwidget(
|
||||
parent=self._root_widget,
|
||||
position=(self._width * 0.5, yoffs - 250),
|
||||
|
|
@ -132,12 +133,10 @@ class InventoryWindow(bui.MainWindow):
|
|||
# pylint: disable=cyclic-import
|
||||
from bauiv1lib.profile.browser import ProfileBrowserWindow
|
||||
|
||||
# no-op if our underlying widget is dead or on its way out.
|
||||
if not self._root_widget or self._root_widget.transitioning_out:
|
||||
return
|
||||
|
||||
self.main_window_replace(
|
||||
ProfileBrowserWindow(origin_widget=self._player_profiles_button)
|
||||
lambda: ProfileBrowserWindow(
|
||||
origin_widget=self._player_profiles_button
|
||||
)
|
||||
)
|
||||
|
||||
@override
|
||||
|
|
@ -149,3 +148,7 @@ class InventoryWindow(bui.MainWindow):
|
|||
transition=transition, origin_widget=origin_widget
|
||||
)
|
||||
)
|
||||
|
||||
@override
|
||||
def main_window_should_preserve_selection(self) -> bool:
|
||||
return True
|
||||
|
|
|
|||
13
dist/ba_data/python/bauiv1lib/kiosk.py
vendored
13
dist/ba_data/python/bauiv1lib/kiosk.py
vendored
|
|
@ -374,6 +374,11 @@ class KioskWindow(bui.MainWindow):
|
|||
)
|
||||
)
|
||||
|
||||
@override
|
||||
def main_window_should_preserve_selection(self) -> bool:
|
||||
# TODO: Wire this up.
|
||||
return False
|
||||
|
||||
@override
|
||||
def on_main_window_close(self) -> None:
|
||||
self._save_state()
|
||||
|
|
@ -529,13 +534,15 @@ class KioskWindow(bui.MainWindow):
|
|||
# pylint: disable=cyclic-import
|
||||
from bauiv1lib.mainmenu import MainMenuWindow
|
||||
|
||||
# no-op if we're not in control.
|
||||
# No-op if we're not in control.
|
||||
if not self.main_window_has_control():
|
||||
return
|
||||
|
||||
assert bui.app.classic is not None
|
||||
|
||||
self._save_state()
|
||||
bui.app.classic.did_menu_intro = True # prevent delayed transition-in
|
||||
|
||||
self.main_window_replace(MainMenuWindow())
|
||||
# Prevent delayed transition-in.
|
||||
bui.app.classic.did_menu_intro = True
|
||||
|
||||
self.main_window_replace(MainMenuWindow)
|
||||
|
|
|
|||
108
dist/ba_data/python/bauiv1lib/league/rankwindow.py
vendored
108
dist/ba_data/python/bauiv1lib/league/rankwindow.py
vendored
|
|
@ -9,6 +9,7 @@ import copy
|
|||
import logging
|
||||
from typing import TYPE_CHECKING, override
|
||||
|
||||
from bauiv1lib.utils import scroll_fade_bottom, scroll_fade_top
|
||||
from bauiv1lib.popup import PopupMenu
|
||||
import bauiv1 as bui
|
||||
|
||||
|
|
@ -57,9 +58,6 @@ class LeagueRankWindow(bui.MainWindow):
|
|||
)
|
||||
self._r = 'coopSelectWindow'
|
||||
self._rdict = bui.app.lang.get_resource(self._r)
|
||||
# top_extra = 20 if uiscale is bui.UIScale.SMALL else 0
|
||||
|
||||
# self._xoffs = 80.0 if uiscale is bui.UIScale.SMALL else 0
|
||||
self._xoffs = 40
|
||||
|
||||
self._league_url_arg = ''
|
||||
|
|
@ -86,19 +84,19 @@ class LeagueRankWindow(bui.MainWindow):
|
|||
yoffs = 0.5 * self._height + 0.5 * target_height + 30.0
|
||||
|
||||
self._scroll_width = target_width
|
||||
self._scroll_height = target_height - 35
|
||||
self._scroll_height = target_height - 50
|
||||
scroll_bottom = yoffs - 80 - self._scroll_height
|
||||
|
||||
# Go with full-screen scrollable area in small ui.
|
||||
if uiscale is bui.UIScale.SMALL:
|
||||
self._scroll_height += 53
|
||||
scroll_bottom -= 1
|
||||
|
||||
super().__init__(
|
||||
root_widget=bui.containerwidget(
|
||||
size=(self._width, self._height),
|
||||
stack_offset=(
|
||||
(0, 0)
|
||||
if uiscale is bui.UIScale.SMALL
|
||||
else (0, 10) if uiscale is bui.UIScale.MEDIUM else (0, 0)
|
||||
),
|
||||
scale=scale,
|
||||
toolbar_visibility=('menu_full'),
|
||||
toolbar_visibility='menu_full',
|
||||
toolbar_cancel_button_style=(
|
||||
'close' if auxiliary_style else 'back'
|
||||
),
|
||||
|
|
@ -117,6 +115,7 @@ class LeagueRankWindow(bui.MainWindow):
|
|||
else:
|
||||
self._back_button = bui.buttonwidget(
|
||||
parent=self._root_widget,
|
||||
id=f'{self.main_window_id_prefix}|back',
|
||||
position=(75 + x_inset, yoffs - 60),
|
||||
size=(60, 55),
|
||||
scale=1.2,
|
||||
|
|
@ -135,6 +134,40 @@ class LeagueRankWindow(bui.MainWindow):
|
|||
selected_child=self._back_button,
|
||||
)
|
||||
|
||||
self._scrollwidget = bui.scrollwidget(
|
||||
parent=self._root_widget,
|
||||
highlight=False,
|
||||
size=(self._scroll_width, self._scroll_height),
|
||||
position=(
|
||||
self._width * 0.5 - self._scroll_width * 0.5,
|
||||
scroll_bottom,
|
||||
),
|
||||
center_small_content=True,
|
||||
center_small_content_horizontally=True,
|
||||
border_opacity=0.4,
|
||||
)
|
||||
bui.widget(edit=self._scrollwidget, autoselect=True)
|
||||
bui.containerwidget(edit=self._scrollwidget, claims_left_right=True)
|
||||
|
||||
# With full-screen scrolling, fade content as it approaches
|
||||
# toolbars.
|
||||
if uiscale is bui.UIScale.SMALL:
|
||||
scroll_fade_top(
|
||||
self._root_widget,
|
||||
self._width * 0.5 - self._scroll_width * 0.5,
|
||||
scroll_bottom,
|
||||
self._scroll_width,
|
||||
self._scroll_height,
|
||||
yscale=0.5,
|
||||
)
|
||||
scroll_fade_bottom(
|
||||
self._root_widget,
|
||||
self._width * 0.5 - self._scroll_width * 0.5,
|
||||
scroll_bottom,
|
||||
self._scroll_width,
|
||||
self._scroll_height,
|
||||
)
|
||||
|
||||
self._title_text = bui.textwidget(
|
||||
parent=self._root_widget,
|
||||
position=(
|
||||
|
|
@ -153,27 +186,17 @@ class LeagueRankWindow(bui.MainWindow):
|
|||
v_align='center',
|
||||
)
|
||||
|
||||
self._scrollwidget = bui.scrollwidget(
|
||||
parent=self._root_widget,
|
||||
highlight=False,
|
||||
size=(self._scroll_width, self._scroll_height),
|
||||
position=(
|
||||
self._width * 0.5 - self._scroll_width * 0.5,
|
||||
scroll_bottom,
|
||||
),
|
||||
center_small_content=True,
|
||||
center_small_content_horizontally=True,
|
||||
border_opacity=0.4,
|
||||
)
|
||||
bui.widget(edit=self._scrollwidget, autoselect=True)
|
||||
bui.containerwidget(edit=self._scrollwidget, claims_left_right=True)
|
||||
|
||||
self._last_power_ranking_query_time: float | None = None
|
||||
self._doing_power_ranking_query = False
|
||||
|
||||
self._subcontainer: bui.Widget | None = None
|
||||
self._subcontainerwidth = 1024
|
||||
self._subcontainerheight = 573
|
||||
|
||||
# For fullscreen scrollable, account for toolbar.
|
||||
if uiscale is bui.UIScale.SMALL:
|
||||
self._subcontainerheight += 53
|
||||
|
||||
self._power_ranking_score_widgets: list[bui.Widget] = []
|
||||
|
||||
self._season_popup_menu: PopupMenu | None = None
|
||||
|
|
@ -185,7 +208,6 @@ class LeagueRankWindow(bui.MainWindow):
|
|||
self._account_state = plus.get_v1_account_state()
|
||||
|
||||
self._refresh()
|
||||
self._restore_state()
|
||||
|
||||
# If we've got cached power-ranking data already, display it.
|
||||
assert bui.app.classic is not None
|
||||
|
|
@ -209,8 +231,8 @@ class LeagueRankWindow(bui.MainWindow):
|
|||
)
|
||||
|
||||
@override
|
||||
def on_main_window_close(self) -> None:
|
||||
self._save_state()
|
||||
def main_window_should_preserve_selection(self) -> bool:
|
||||
return True
|
||||
|
||||
def _on_achievements_press(self) -> None:
|
||||
from bauiv1lib.achievements import AchievementsWindow
|
||||
|
|
@ -221,7 +243,9 @@ class LeagueRankWindow(bui.MainWindow):
|
|||
if self._season == 'a' or self._is_current_season:
|
||||
prab = self._power_ranking_achievements_button
|
||||
assert prab is not None
|
||||
self.main_window_replace(AchievementsWindow(origin_widget=prab))
|
||||
self.main_window_replace(
|
||||
lambda: AchievementsWindow(origin_widget=prab)
|
||||
)
|
||||
else:
|
||||
bui.screenmessage(
|
||||
bui.Lstr(
|
||||
|
|
@ -317,9 +341,6 @@ class LeagueRankWindow(bui.MainWindow):
|
|||
self._league_rank_data = copy.deepcopy(data)
|
||||
self._update_for_league_rank_data(data)
|
||||
|
||||
def _restore_state(self) -> None:
|
||||
pass
|
||||
|
||||
def _update(self, show: bool = False) -> None:
|
||||
plus = bui.app.plus
|
||||
assert plus is not None
|
||||
|
|
@ -330,7 +351,6 @@ class LeagueRankWindow(bui.MainWindow):
|
|||
account_state = plus.get_v1_account_state()
|
||||
if account_state != self._account_state:
|
||||
self._account_state = account_state
|
||||
self._save_state()
|
||||
self._refresh()
|
||||
|
||||
# And power ranking too.
|
||||
|
|
@ -384,6 +404,11 @@ class LeagueRankWindow(bui.MainWindow):
|
|||
|
||||
v -= 0
|
||||
|
||||
# For fullscreen scrollable, account for toolbar.
|
||||
uiscale = bui.app.ui_v1.uiscale
|
||||
if uiscale is bui.UIScale.SMALL:
|
||||
v -= 52
|
||||
|
||||
h2 = 80
|
||||
v2 = v - 60
|
||||
worth_color = (0.6, 0.6, 0.65)
|
||||
|
|
@ -410,6 +435,7 @@ class LeagueRankWindow(bui.MainWindow):
|
|||
|
||||
self._power_ranking_achievements_button = bui.buttonwidget(
|
||||
parent=w_parent,
|
||||
id=f'{self.main_window_id_prefix}|ach',
|
||||
position=(self._xoffs + h2 - 60, v2 + 10),
|
||||
size=(200, 80),
|
||||
icon=bui.gettexture('achievementsIcon'),
|
||||
|
|
@ -440,6 +466,7 @@ class LeagueRankWindow(bui.MainWindow):
|
|||
|
||||
self._power_ranking_trophies_button = bui.buttonwidget(
|
||||
parent=w_parent,
|
||||
id=f'{self.main_window_id_prefix}|trophies',
|
||||
position=(self._xoffs + h2 - 60, v2 + 10),
|
||||
size=(200, 80),
|
||||
icon=bui.gettexture('medalSilver'),
|
||||
|
|
@ -484,6 +511,7 @@ class LeagueRankWindow(bui.MainWindow):
|
|||
if plus.get_v1_account_misc_read_val('act', False):
|
||||
self._activity_mult_button = bui.buttonwidget(
|
||||
parent=w_parent,
|
||||
id=f'{self.main_window_id_prefix}|amult',
|
||||
position=(self._xoffs + h2 - 60, v2 + 10),
|
||||
size=(200, 60),
|
||||
icon=bui.gettexture('heart'),
|
||||
|
|
@ -515,6 +543,7 @@ class LeagueRankWindow(bui.MainWindow):
|
|||
|
||||
self._up_to_date_bonus_button = bui.buttonwidget(
|
||||
parent=w_parent,
|
||||
id=f'{self.main_window_id_prefix}|uptodatebonus',
|
||||
position=(self._xoffs + h2 - 60, v2 + 10),
|
||||
size=(200, 60),
|
||||
icon=bui.gettexture('logo'),
|
||||
|
|
@ -712,6 +741,7 @@ class LeagueRankWindow(bui.MainWindow):
|
|||
|
||||
self._see_more_button = bui.buttonwidget(
|
||||
parent=w_parent,
|
||||
id=f'{self.main_window_id_prefix}|seemore',
|
||||
label=self._rdict.seeMoreText,
|
||||
position=(self._xoffs + h, v),
|
||||
color=(0.5, 0.5, 0.6),
|
||||
|
|
@ -811,6 +841,11 @@ class LeagueRankWindow(bui.MainWindow):
|
|||
self._season = data['s'] if data is not None else None
|
||||
|
||||
v = self._subcontainerheight - 20
|
||||
# For fullscreen scrollable, account for toolbar.
|
||||
uiscale = bui.app.ui_v1.uiscale
|
||||
if uiscale is bui.UIScale.SMALL:
|
||||
v -= 52
|
||||
|
||||
popup_was_selected = False
|
||||
if self._season_popup_menu is not None:
|
||||
btn = self._season_popup_menu.get_button()
|
||||
|
|
@ -853,6 +888,7 @@ class LeagueRankWindow(bui.MainWindow):
|
|||
assert self._subcontainer
|
||||
self._season_popup_menu = PopupMenu(
|
||||
parent=self._subcontainer,
|
||||
button_id=f'{self.main_window_id_prefix}|season',
|
||||
position=(self._xoffs + 390, v - 45),
|
||||
width=150,
|
||||
button_size=(200, 50),
|
||||
|
|
@ -1160,6 +1196,9 @@ class LeagueRankWindow(bui.MainWindow):
|
|||
v_align='center',
|
||||
scale=0.9,
|
||||
)
|
||||
# Giving these ids doesn't work cleanly with sel
|
||||
# save/restore due to refreshing, so let's just not for now.
|
||||
bui.widget(edit=txt, allow_preserve_selection=False)
|
||||
self._power_ranking_score_widgets.append(txt)
|
||||
bui.textwidget(
|
||||
edit=txt,
|
||||
|
|
@ -1187,6 +1226,3 @@ class LeagueRankWindow(bui.MainWindow):
|
|||
self._requested_season = value
|
||||
self._last_power_ranking_query_time = None # Update asap.
|
||||
self._update(show=True)
|
||||
|
||||
def _save_state(self) -> None:
|
||||
pass
|
||||
|
|
|
|||
142
dist/ba_data/python/bauiv1lib/mainmenu.py
vendored
142
dist/ba_data/python/bauiv1lib/mainmenu.py
vendored
|
|
@ -22,6 +22,7 @@ class MainMenuWindow(bui.MainWindow):
|
|||
transition: str | None = 'in_right',
|
||||
origin_widget: bui.Widget | None = None,
|
||||
):
|
||||
ui = bui.app.ui_v1
|
||||
|
||||
# Preload some modules we use in a background thread so we won't
|
||||
# have a visual hitch when the user taps them.
|
||||
|
|
@ -30,13 +31,13 @@ class MainMenuWindow(bui.MainWindow):
|
|||
bui.set_analytics_screen('Main Menu')
|
||||
self._show_remote_app_info_on_first_launch()
|
||||
|
||||
uiscale = bui.app.ui_v1.uiscale
|
||||
uiscale = ui.uiscale
|
||||
|
||||
# Make a vanilla container; we'll modify it to our needs in
|
||||
# refresh.
|
||||
super().__init__(
|
||||
root_widget=bui.containerwidget(
|
||||
toolbar_visibility=('menu_full_no_back')
|
||||
toolbar_visibility=('menu_full_no_back'),
|
||||
),
|
||||
transition=transition,
|
||||
origin_widget=origin_widget,
|
||||
|
|
@ -44,10 +45,6 @@ class MainMenuWindow(bui.MainWindow):
|
|||
refresh_on_screen_size_changes=uiscale is bui.UIScale.SMALL,
|
||||
)
|
||||
|
||||
# Grab this stuff in case it changes.
|
||||
# self._is_demo = bui.app.env.demo
|
||||
# self._is_arcade = bui.app.env.arcade
|
||||
|
||||
self._tdelay = 0.0
|
||||
self._t_delay_inc = 0.02
|
||||
self._t_delay_play = 1.7
|
||||
|
|
@ -65,22 +62,28 @@ class MainMenuWindow(bui.MainWindow):
|
|||
|
||||
self._refresh()
|
||||
|
||||
self._restore_state()
|
||||
|
||||
@override
|
||||
def on_main_window_close(self) -> None:
|
||||
self._save_state()
|
||||
|
||||
@override
|
||||
def get_main_window_state(self) -> bui.MainWindowState:
|
||||
# Support recreating our window for back/refresh purposes.
|
||||
cls = type(self)
|
||||
|
||||
# Pull values from self here; if we do it in the lambda we'll
|
||||
# keep self alive which we don't want.
|
||||
# id_prefix = self._id_prefix
|
||||
|
||||
return bui.BasicMainWindowState(
|
||||
create_call=lambda transition, origin_widget: cls(
|
||||
transition=transition, origin_widget=origin_widget
|
||||
)
|
||||
transition=transition,
|
||||
origin_widget=origin_widget,
|
||||
# id_prefix=id_prefix,
|
||||
),
|
||||
# restore_selection=True,
|
||||
)
|
||||
|
||||
@override
|
||||
def main_window_should_preserve_selection(self) -> bool:
|
||||
return True
|
||||
|
||||
@staticmethod
|
||||
def _preload_modules() -> None:
|
||||
"""Preload modules we use; avoids hitches (called in bg thread)."""
|
||||
|
|
@ -257,7 +260,6 @@ class MainMenuWindow(bui.MainWindow):
|
|||
),
|
||||
h_align='center',
|
||||
v_align='center',
|
||||
# transition_delay=self._t_delay_play,
|
||||
transition_delay=thistdelay,
|
||||
)
|
||||
|
||||
|
|
@ -267,20 +269,14 @@ class MainMenuWindow(bui.MainWindow):
|
|||
|
||||
# In kiosk mode, provide a button to get back to the kiosk menu.
|
||||
if arcade_or_demo:
|
||||
# h, v, scale = positions[self._p_index]
|
||||
h = self._width * 0.5
|
||||
v = button_y_offs
|
||||
scale = 1.0
|
||||
this_b_width = self._button_width * 0.4 * scale
|
||||
# demo_menu_delay = (
|
||||
# 0.0
|
||||
# if self._t_delay_play == 0.0
|
||||
# else max(0, self._t_delay_play + 0.1)
|
||||
# )
|
||||
demo_menu_delay = 0.0
|
||||
self._demo_menu_button = bui.buttonwidget(
|
||||
parent=self._root_widget,
|
||||
id='demo',
|
||||
id=f'{self.main_window_id_prefix}|demo',
|
||||
position=(self._width * 0.5 - this_b_width * 0.5, v + 90),
|
||||
size=(this_b_width, 45),
|
||||
autoselect=True,
|
||||
|
|
@ -312,6 +308,7 @@ class MainMenuWindow(bui.MainWindow):
|
|||
thistdelay = self._tdelay + td2 * self._t_delay_inc
|
||||
self._gather_button = bui.buttonwidget(
|
||||
parent=self._root_widget,
|
||||
id=f'{self.main_window_id_prefix}|gather',
|
||||
position=(h - side_button_width * side_button_scale * 0.5, v),
|
||||
size=(side_button_width, side_button_height),
|
||||
scale=side_button_scale,
|
||||
|
|
@ -359,7 +356,7 @@ class MainMenuWindow(bui.MainWindow):
|
|||
|
||||
self._how_to_play_button = bui.buttonwidget(
|
||||
parent=self._root_widget,
|
||||
id='howtoplay',
|
||||
id=f'{self.main_window_id_prefix}|howtoplay',
|
||||
position=(h, v),
|
||||
autoselect=self._use_autoselect,
|
||||
size=(side_button_2_width, side_button_2_height * 2.0),
|
||||
|
|
@ -380,8 +377,9 @@ class MainMenuWindow(bui.MainWindow):
|
|||
assert play_button_width is not None
|
||||
assert play_button_height is not None
|
||||
thistdelay = self._tdelay + td3 * self._t_delay_inc
|
||||
self._play_button = start_button = bui.buttonwidget(
|
||||
self._play_button = play_button = bui.buttonwidget(
|
||||
parent=self._root_widget,
|
||||
id=f'{self.main_window_id_prefix}|play',
|
||||
position=(h - play_button_width * 0.5 * play_button_scale, v),
|
||||
size=(play_button_width, play_button_height),
|
||||
autoselect=self._use_autoselect,
|
||||
|
|
@ -393,12 +391,10 @@ class MainMenuWindow(bui.MainWindow):
|
|||
)
|
||||
bui.containerwidget(
|
||||
edit=self._root_widget,
|
||||
start_button=start_button,
|
||||
selected_child=start_button,
|
||||
start_button=play_button,
|
||||
selected_child=play_button,
|
||||
)
|
||||
|
||||
# self._tdelay += self._t_delay_inc
|
||||
|
||||
h = (
|
||||
self._width * 0.5
|
||||
+ play_button_width * play_button_scale * 0.5
|
||||
|
|
@ -409,6 +405,7 @@ class MainMenuWindow(bui.MainWindow):
|
|||
thistdelay = self._tdelay + td4 * self._t_delay_inc
|
||||
self._watch_button = bui.buttonwidget(
|
||||
parent=self._root_widget,
|
||||
id=f'{self.main_window_id_prefix}|watch',
|
||||
position=(h - side_button_width * side_button_scale * 0.5, v),
|
||||
size=(side_button_width, side_button_height),
|
||||
scale=side_button_scale,
|
||||
|
|
@ -457,6 +454,7 @@ class MainMenuWindow(bui.MainWindow):
|
|||
|
||||
self._credits_button = bui.buttonwidget(
|
||||
parent=self._root_widget,
|
||||
id=f'{self.main_window_id_prefix}|credits',
|
||||
position=(h, v),
|
||||
button_type=None if self._have_quit_button else 'square',
|
||||
size=(
|
||||
|
|
@ -477,6 +475,7 @@ class MainMenuWindow(bui.MainWindow):
|
|||
# credits button to get to it.
|
||||
self._quit_button = quit_button = bui.buttonwidget(
|
||||
parent=self._root_widget,
|
||||
id=f'{self.main_window_id_prefix}|quit',
|
||||
autoselect=self._use_autoselect,
|
||||
position=(h + 4.0, v),
|
||||
size=(side_button_2_width, side_button_2_height),
|
||||
|
|
@ -534,111 +533,38 @@ class MainMenuWindow(bui.MainWindow):
|
|||
# pylint: disable=cyclic-import
|
||||
from bauiv1lib.credits import CreditsWindow
|
||||
|
||||
# no-op if we're not currently in control.
|
||||
if not self.main_window_has_control():
|
||||
return
|
||||
|
||||
self.main_window_replace(
|
||||
CreditsWindow(origin_widget=self._credits_button),
|
||||
lambda: CreditsWindow(origin_widget=self._credits_button),
|
||||
)
|
||||
|
||||
def _howtoplay(self) -> None:
|
||||
# pylint: disable=cyclic-import
|
||||
from bauiv1lib.help import HelpWindow
|
||||
|
||||
# no-op if we're not currently in control.
|
||||
if not self.main_window_has_control():
|
||||
return
|
||||
|
||||
self.main_window_replace(
|
||||
HelpWindow(origin_widget=self._how_to_play_button),
|
||||
lambda: HelpWindow(origin_widget=self._how_to_play_button),
|
||||
)
|
||||
|
||||
def _save_state(self) -> None:
|
||||
try:
|
||||
sel = self._root_widget.get_selected_child()
|
||||
if sel == self._play_button:
|
||||
sel_name = 'Start'
|
||||
elif sel == self._gather_button:
|
||||
sel_name = 'Gather'
|
||||
elif sel == self._watch_button:
|
||||
sel_name = 'Watch'
|
||||
elif sel == self._how_to_play_button:
|
||||
sel_name = 'HowToPlay'
|
||||
elif sel == self._credits_button:
|
||||
sel_name = 'Credits'
|
||||
elif sel == self._quit_button:
|
||||
sel_name = 'Quit'
|
||||
elif sel == self._demo_menu_button:
|
||||
sel_name = 'DemoMenu'
|
||||
else:
|
||||
print(f'Unknown widget in main menu selection: {sel}.')
|
||||
sel_name = 'Start'
|
||||
bui.app.ui_v1.window_states[type(self)] = {'sel_name': sel_name}
|
||||
except Exception:
|
||||
logging.exception('Error saving state for %s.', self)
|
||||
|
||||
def _restore_state(self) -> None:
|
||||
try:
|
||||
|
||||
sel: bui.Widget | None
|
||||
|
||||
sel_name = bui.app.ui_v1.window_states.get(type(self), {}).get(
|
||||
'sel_name'
|
||||
)
|
||||
assert isinstance(sel_name, (str, type(None)))
|
||||
if sel_name is None:
|
||||
sel_name = 'Start'
|
||||
if sel_name == 'HowToPlay':
|
||||
sel = self._how_to_play_button
|
||||
elif sel_name == 'Gather':
|
||||
sel = self._gather_button
|
||||
elif sel_name == 'Watch':
|
||||
sel = self._watch_button
|
||||
elif sel_name == 'Credits':
|
||||
sel = self._credits_button
|
||||
elif sel_name == 'Quit':
|
||||
sel = self._quit_button
|
||||
elif sel_name == 'DemoMenu':
|
||||
sel = self._demo_menu_button
|
||||
else:
|
||||
sel = self._play_button
|
||||
if sel is not None:
|
||||
bui.containerwidget(edit=self._root_widget, selected_child=sel)
|
||||
|
||||
except Exception:
|
||||
logging.exception('Error restoring state for %s.', self)
|
||||
|
||||
def _gather_press(self) -> None:
|
||||
# pylint: disable=cyclic-import
|
||||
from bauiv1lib.gather import GatherWindow
|
||||
|
||||
# no-op if we're not currently in control.
|
||||
if not self.main_window_has_control():
|
||||
return
|
||||
|
||||
self.main_window_replace(
|
||||
GatherWindow(origin_widget=self._gather_button)
|
||||
lambda: GatherWindow(origin_widget=self._gather_button)
|
||||
)
|
||||
|
||||
def _watch_press(self) -> None:
|
||||
# pylint: disable=cyclic-import
|
||||
from bauiv1lib.watch import WatchWindow
|
||||
|
||||
# no-op if we're not currently in control.
|
||||
if not self.main_window_has_control():
|
||||
return
|
||||
|
||||
self.main_window_replace(
|
||||
WatchWindow(origin_widget=self._watch_button),
|
||||
lambda: WatchWindow(origin_widget=self._watch_button),
|
||||
)
|
||||
|
||||
def _play_press(self) -> None:
|
||||
# pylint: disable=cyclic-import
|
||||
from bauiv1lib.play import PlayWindow
|
||||
|
||||
# no-op if we're not currently in control.
|
||||
if not self.main_window_has_control():
|
||||
return
|
||||
|
||||
self.main_window_replace(PlayWindow(origin_widget=self._play_button))
|
||||
self.main_window_replace(
|
||||
lambda: PlayWindow(origin_widget=self._play_button)
|
||||
)
|
||||
|
|
|
|||
12
dist/ba_data/python/bauiv1lib/party.py
vendored
12
dist/ba_data/python/bauiv1lib/party.py
vendored
|
|
@ -38,6 +38,7 @@ class PartyWindow(bui.Window):
|
|||
if uiscale is bui.UIScale.SMALL
|
||||
else 480 if uiscale is bui.UIScale.MEDIUM else 600
|
||||
)
|
||||
self._idprefix = bui.app.ui_v1.new_id_prefix('party')
|
||||
self._display_old_msgs = True
|
||||
super().__init__(
|
||||
root_widget=bui.containerwidget(
|
||||
|
|
@ -68,6 +69,7 @@ class PartyWindow(bui.Window):
|
|||
|
||||
self._cancel_button = bui.buttonwidget(
|
||||
parent=self._root_widget,
|
||||
id=f'{self._idprefix}|cancel',
|
||||
scale=0.7,
|
||||
position=(30, self._height - 47),
|
||||
size=(50, 50),
|
||||
|
|
@ -82,6 +84,7 @@ class PartyWindow(bui.Window):
|
|||
|
||||
self._menu_button = bui.buttonwidget(
|
||||
parent=self._root_widget,
|
||||
id=f'{self._idprefix}|menu',
|
||||
scale=0.7,
|
||||
position=(self._width - 60, self._height - 47),
|
||||
size=(50, 50),
|
||||
|
|
@ -138,13 +141,18 @@ class PartyWindow(bui.Window):
|
|||
self._scroll_width = self._width - 50
|
||||
self._scrollwidget = bui.scrollwidget(
|
||||
parent=self._root_widget,
|
||||
id=f'{self._idprefix}|scroll',
|
||||
size=(self._scroll_width, self._height - 200),
|
||||
position=(30, 80),
|
||||
color=(0.4, 0.6, 0.3),
|
||||
border_opacity=0.6,
|
||||
)
|
||||
self._columnwidget = bui.columnwidget(
|
||||
parent=self._scrollwidget, border=2, left_border=-200, margin=0
|
||||
parent=self._scrollwidget,
|
||||
id=f'{self._idprefix}|column',
|
||||
border=2,
|
||||
left_border=-200,
|
||||
margin=0,
|
||||
)
|
||||
bui.widget(edit=self._menu_button, down_widget=self._columnwidget)
|
||||
|
||||
|
|
@ -160,6 +168,7 @@ class PartyWindow(bui.Window):
|
|||
|
||||
self._text_field = txt = bui.textwidget(
|
||||
parent=self._root_widget,
|
||||
id=f'{self._idprefix}|messagetext',
|
||||
editable=True,
|
||||
size=(530, 40),
|
||||
position=(44, 39),
|
||||
|
|
@ -190,6 +199,7 @@ class PartyWindow(bui.Window):
|
|||
|
||||
btn = bui.buttonwidget(
|
||||
parent=self._root_widget,
|
||||
id=f'{self._idprefix}|send',
|
||||
size=(50, 35),
|
||||
label=bui.Lstr(resource=f'{self._r}.sendText'),
|
||||
button_type='square',
|
||||
|
|
|
|||
75
dist/ba_data/python/bauiv1lib/play.py
vendored
75
dist/ba_data/python/bauiv1lib/play.py
vendored
|
|
@ -4,7 +4,7 @@
|
|||
|
||||
from __future__ import annotations
|
||||
|
||||
import logging
|
||||
# import logging
|
||||
from typing import override, TYPE_CHECKING
|
||||
|
||||
import bascenev1 as bs
|
||||
|
|
@ -34,6 +34,8 @@ class PlayWindow(bui.MainWindow):
|
|||
|
||||
import bacommon.cloud
|
||||
|
||||
ui = bui.app.ui_v1
|
||||
|
||||
# TEMP TESTING
|
||||
if bool(False):
|
||||
print('HELLO FROM TEST')
|
||||
|
|
@ -59,7 +61,7 @@ class PlayWindow(bui.MainWindow):
|
|||
|
||||
self._playlist_select_context = playlist_select_context
|
||||
|
||||
uiscale = bui.app.ui_v1.uiscale
|
||||
uiscale = ui.uiscale
|
||||
width = 1300 if uiscale is bui.UIScale.SMALL else 1000
|
||||
height = 1000 if uiscale is bui.UIScale.SMALL else 550
|
||||
|
||||
|
|
@ -127,6 +129,7 @@ class PlayWindow(bui.MainWindow):
|
|||
else:
|
||||
self._back_button = bui.buttonwidget(
|
||||
parent=self._root_widget,
|
||||
id=f'{self.main_window_id_prefix}|back',
|
||||
position=(50, yoffs - 100),
|
||||
size=(60, 60),
|
||||
scale=1.1,
|
||||
|
|
@ -158,7 +161,7 @@ class PlayWindow(bui.MainWindow):
|
|||
scale=1.2 if uiscale is bui.UIScale.SMALL else 1.7,
|
||||
res_scale=2.0,
|
||||
maxwidth=250,
|
||||
color=bui.app.ui_v1.heading_color,
|
||||
color=ui.heading_color,
|
||||
h_align='center',
|
||||
v_align='center',
|
||||
)
|
||||
|
|
@ -203,6 +206,7 @@ class PlayWindow(bui.MainWindow):
|
|||
if self._playlist_select_context is None:
|
||||
self._coop_button = btn = bui.buttonwidget(
|
||||
parent=self._root_widget,
|
||||
id=f'{self.main_window_id_prefix}|coop',
|
||||
position=(hoffs, v),
|
||||
size=(
|
||||
scl * button_width,
|
||||
|
|
@ -303,6 +307,7 @@ class PlayWindow(bui.MainWindow):
|
|||
|
||||
self._teams_button = btn = bui.buttonwidget(
|
||||
parent=self._root_widget,
|
||||
id=f'{self.main_window_id_prefix}|teams',
|
||||
position=(hoffs, v),
|
||||
size=(
|
||||
scl * button_width,
|
||||
|
|
@ -424,6 +429,7 @@ class PlayWindow(bui.MainWindow):
|
|||
hoffs += scl * button_width + button_spacing
|
||||
self._free_for_all_button = btn = bui.buttonwidget(
|
||||
parent=self._root_widget,
|
||||
id=f'{self.main_window_id_prefix}|ffa',
|
||||
position=(hoffs, v),
|
||||
size=(scl * button_width, scl * button_height),
|
||||
extra_touch_border_scale=0.1,
|
||||
|
|
@ -555,8 +561,6 @@ class PlayWindow(bui.MainWindow):
|
|||
),
|
||||
)
|
||||
|
||||
self._restore_state()
|
||||
|
||||
@override
|
||||
def get_main_window_state(self) -> bui.MainWindowState:
|
||||
# Support recreating our window for back/refresh purposes.
|
||||
|
|
@ -565,17 +569,18 @@ class PlayWindow(bui.MainWindow):
|
|||
# Pull any values out of self here; if we do it in the lambda
|
||||
# we'll keep our window alive inadvertantly.
|
||||
playlist_select_context = self._playlist_select_context
|
||||
|
||||
return bui.BasicMainWindowState(
|
||||
create_call=lambda transition, origin_widget: cls(
|
||||
transition=transition,
|
||||
origin_widget=origin_widget,
|
||||
playlist_select_context=playlist_select_context,
|
||||
)
|
||||
),
|
||||
)
|
||||
|
||||
@override
|
||||
def on_main_window_close(self) -> None:
|
||||
self._save_state()
|
||||
def main_window_should_preserve_selection(self) -> bool:
|
||||
return True
|
||||
|
||||
@staticmethod
|
||||
def _preload_modules() -> None:
|
||||
|
|
@ -602,19 +607,15 @@ class PlayWindow(bui.MainWindow):
|
|||
return
|
||||
|
||||
self.main_window_replace(
|
||||
CoopBrowserWindow(origin_widget=self._coop_button)
|
||||
lambda: CoopBrowserWindow(origin_widget=self._coop_button)
|
||||
)
|
||||
|
||||
def _team_tourney(self) -> None:
|
||||
# pylint: disable=cyclic-import
|
||||
from bauiv1lib.playlist.browser import PlaylistBrowserWindow
|
||||
|
||||
# no-op if we're not currently in control.
|
||||
if not self.main_window_has_control():
|
||||
return
|
||||
|
||||
self.main_window_replace(
|
||||
PlaylistBrowserWindow(
|
||||
lambda: PlaylistBrowserWindow(
|
||||
origin_widget=self._teams_button,
|
||||
sessiontype=bs.DualTeamSession,
|
||||
playlist_select_context=self._playlist_select_context,
|
||||
|
|
@ -625,12 +626,8 @@ class PlayWindow(bui.MainWindow):
|
|||
# pylint: disable=cyclic-import
|
||||
from bauiv1lib.playlist.browser import PlaylistBrowserWindow
|
||||
|
||||
# no-op if we're not currently in control.
|
||||
if not self.main_window_has_control():
|
||||
return
|
||||
|
||||
self.main_window_replace(
|
||||
PlaylistBrowserWindow(
|
||||
lambda: PlaylistBrowserWindow(
|
||||
origin_widget=self._free_for_all_button,
|
||||
sessiontype=bs.FreeForAllSession,
|
||||
playlist_select_context=self._playlist_select_context,
|
||||
|
|
@ -755,43 +752,3 @@ class PlayWindow(bui.MainWindow):
|
|||
color=eye_color,
|
||||
mesh_transparent=self._eyes_mesh,
|
||||
)
|
||||
|
||||
def _save_state(self) -> None:
|
||||
try:
|
||||
sel = self._root_widget.get_selected_child()
|
||||
if sel == self._teams_button:
|
||||
sel_name = 'Team Games'
|
||||
elif self._coop_button is not None and sel == self._coop_button:
|
||||
sel_name = 'Co-op Games'
|
||||
elif sel == self._free_for_all_button:
|
||||
sel_name = 'Free-for-All Games'
|
||||
elif sel == self._back_button:
|
||||
sel_name = 'Back'
|
||||
else:
|
||||
raise ValueError(f'unrecognized selection {sel}')
|
||||
assert bui.app.classic is not None
|
||||
bui.app.ui_v1.window_states[type(self)] = sel_name
|
||||
except Exception:
|
||||
logging.exception('Error saving state for %s.', self)
|
||||
|
||||
def _restore_state(self) -> None:
|
||||
try:
|
||||
assert bui.app.classic is not None
|
||||
sel_name = bui.app.ui_v1.window_states.get(type(self))
|
||||
if sel_name == 'Team Games':
|
||||
sel = self._teams_button
|
||||
elif sel_name == 'Co-op Games' and self._coop_button is not None:
|
||||
sel = self._coop_button
|
||||
elif sel_name == 'Free-for-All Games':
|
||||
sel = self._free_for_all_button
|
||||
elif sel_name == 'Back' and self._back_button is not None:
|
||||
sel = self._back_button
|
||||
else:
|
||||
sel = (
|
||||
self._coop_button
|
||||
if self._coop_button is not None
|
||||
else self._teams_button
|
||||
)
|
||||
bui.containerwidget(edit=self._root_widget, selected_child=sel)
|
||||
except Exception:
|
||||
logging.exception('Error restoring state for %s.', self)
|
||||
|
|
|
|||
|
|
@ -202,6 +202,10 @@ class PlaylistAddGameWindow(bui.MainWindow):
|
|||
)
|
||||
)
|
||||
|
||||
@override
|
||||
def main_window_should_preserve_selection(self) -> bool:
|
||||
return False
|
||||
|
||||
def _on_game_types_loaded(
|
||||
self, gametypes: list[type[bs.GameActivity]]
|
||||
) -> None:
|
||||
|
|
@ -288,7 +292,7 @@ class PlaylistAddGameWindow(bui.MainWindow):
|
|||
return
|
||||
|
||||
self.main_window_replace(
|
||||
StoreBrowserWindow(
|
||||
lambda: StoreBrowserWindow(
|
||||
show_tab=StoreBrowserWindow.TabID.MINIGAMES,
|
||||
origin_widget=self._get_more_games_button,
|
||||
minimal_toolbars=True,
|
||||
|
|
|
|||
174
dist/ba_data/python/bauiv1lib/playlist/browser.py
vendored
174
dist/ba_data/python/bauiv1lib/playlist/browser.py
vendored
|
|
@ -10,6 +10,7 @@ import logging
|
|||
from typing import override, TYPE_CHECKING
|
||||
|
||||
import bascenev1 as bs
|
||||
from bauiv1lib.utils import scroll_fade_bottom, scroll_fade_top
|
||||
import bauiv1 as bui
|
||||
|
||||
if TYPE_CHECKING:
|
||||
|
|
@ -27,7 +28,7 @@ class PlaylistBrowserWindow(bui.MainWindow):
|
|||
playlist_select_context: PlaylistSelectContext | None = None,
|
||||
):
|
||||
# pylint: disable=cyclic-import
|
||||
# pylint: disable=too-many-locals
|
||||
# pylint: disable=too-many-statements
|
||||
from bauiv1lib.playlist import PlaylistTypeVars
|
||||
|
||||
# Store state for when we exit the next game.
|
||||
|
|
@ -87,6 +88,11 @@ class PlaylistBrowserWindow(bui.MainWindow):
|
|||
self._scroll_height = target_height - 31
|
||||
scroll_bottom = yoffs - 60 - self._scroll_height
|
||||
|
||||
# Go with full-screen scrollable area in small ui.
|
||||
if uiscale is bui.UIScale.SMALL:
|
||||
self._scroll_height += 35
|
||||
scroll_bottom -= 2
|
||||
|
||||
super().__init__(
|
||||
root_widget=bui.containerwidget(
|
||||
size=(self._width, self._height),
|
||||
|
|
@ -112,6 +118,7 @@ class PlaylistBrowserWindow(bui.MainWindow):
|
|||
else:
|
||||
self._back_button = bui.buttonwidget(
|
||||
parent=self._root_widget,
|
||||
id=f'{self.main_window_id_prefix}|back',
|
||||
position=(59, yoffs - 45),
|
||||
size=(60, 54),
|
||||
scale=1.0,
|
||||
|
|
@ -124,21 +131,6 @@ class PlaylistBrowserWindow(bui.MainWindow):
|
|||
edit=self._root_widget, cancel_button=self._back_button
|
||||
)
|
||||
|
||||
self._title_text = bui.textwidget(
|
||||
parent=self._root_widget,
|
||||
position=(
|
||||
self._width * 0.5,
|
||||
yoffs - (45 if uiscale is bui.UIScale.SMALL else 20),
|
||||
),
|
||||
size=(0, 0),
|
||||
text=self._pvars.window_title_name,
|
||||
scale=(0.8 if uiscale is bui.UIScale.SMALL else 1.3),
|
||||
res_scale=1.5,
|
||||
color=bui.app.ui_v1.heading_color,
|
||||
h_align='center',
|
||||
v_align='center',
|
||||
)
|
||||
|
||||
self._scrollwidget = bui.scrollwidget(
|
||||
parent=self._root_widget,
|
||||
highlight=False,
|
||||
|
|
@ -155,43 +147,44 @@ class PlaylistBrowserWindow(bui.MainWindow):
|
|||
self._config_name_full = self._pvars.config_name + ' Playlists'
|
||||
self._last_config = None
|
||||
|
||||
# Add some blotches so our contents fades out as it approaches
|
||||
# the bottom toolbar.
|
||||
if uiscale is bui.UIScale.SMALL and playlist_select_context is None:
|
||||
blotchwidth = 500.0
|
||||
blotchheight = 200.0
|
||||
bimg = bui.imagewidget(
|
||||
parent=self._root_widget,
|
||||
texture=bui.gettexture('uiAtlas'),
|
||||
mesh_transparent=bui.getmesh('windowBGBlotch'),
|
||||
position=(
|
||||
self._width * 0.5
|
||||
- self._scroll_width * 0.5
|
||||
+ 60.0
|
||||
- blotchwidth * 0.5,
|
||||
scroll_bottom - blotchheight * 0.5,
|
||||
),
|
||||
size=(blotchwidth, blotchheight),
|
||||
color=(0.4, 0.37, 0.49),
|
||||
# color=(1, 0, 0),
|
||||
# With full-screen scrolling, fade content as it approaches
|
||||
# toolbars.
|
||||
if uiscale is bui.UIScale.SMALL and bool(True):
|
||||
scroll_fade_top(
|
||||
self._root_widget,
|
||||
self._width * 0.5 - self._scroll_width * 0.5,
|
||||
scroll_bottom,
|
||||
self._scroll_width,
|
||||
self._scroll_height,
|
||||
)
|
||||
bui.widget(edit=bimg, depth_range=(0.9, 1.0))
|
||||
bimg = bui.imagewidget(
|
||||
parent=self._root_widget,
|
||||
texture=bui.gettexture('uiAtlas'),
|
||||
mesh_transparent=bui.getmesh('windowBGBlotch'),
|
||||
position=(
|
||||
self._width * 0.5
|
||||
+ self._scroll_width * 0.5
|
||||
- 60.0
|
||||
- blotchwidth * 0.5,
|
||||
scroll_bottom - blotchheight * 0.5,
|
||||
),
|
||||
size=(blotchwidth, blotchheight),
|
||||
color=(0.4, 0.37, 0.49),
|
||||
# color=(1, 0, 0),
|
||||
)
|
||||
bui.widget(edit=bimg, depth_range=(0.9, 1.0))
|
||||
if playlist_select_context is None:
|
||||
scroll_fade_bottom(
|
||||
self._root_widget,
|
||||
self._width * 0.5 - self._scroll_width * 0.5,
|
||||
scroll_bottom,
|
||||
self._scroll_width,
|
||||
self._scroll_height,
|
||||
)
|
||||
|
||||
self._title_text = bui.textwidget(
|
||||
parent=self._root_widget,
|
||||
position=(
|
||||
self._width * 0.5,
|
||||
yoffs - (45 if uiscale is bui.UIScale.SMALL else 20),
|
||||
),
|
||||
size=(0, 0),
|
||||
text=self._pvars.window_title_name,
|
||||
scale=(0.8 if uiscale is bui.UIScale.SMALL else 1.3),
|
||||
res_scale=1.5,
|
||||
color=bui.app.ui_v1.heading_color,
|
||||
h_align='center',
|
||||
v_align='center',
|
||||
)
|
||||
|
||||
# By default, start with the scroll-widget selected.
|
||||
bui.containerwidget(
|
||||
edit=self._root_widget, selected_child=self._scrollwidget
|
||||
)
|
||||
|
||||
# Update now and once per second (this should do our initial
|
||||
# refresh).
|
||||
|
|
@ -223,8 +216,8 @@ class PlaylistBrowserWindow(bui.MainWindow):
|
|||
)
|
||||
|
||||
@override
|
||||
def on_main_window_close(self) -> None:
|
||||
self._save_state()
|
||||
def main_window_should_preserve_selection(self) -> bool:
|
||||
return True
|
||||
|
||||
def _ensure_standard_playlists_exist(self) -> None:
|
||||
plus = bui.app.plus
|
||||
|
|
@ -396,7 +389,6 @@ class PlaylistBrowserWindow(bui.MainWindow):
|
|||
if not self._root_widget:
|
||||
return
|
||||
if self._subcontainer is not None:
|
||||
self._save_state()
|
||||
self._subcontainer.delete()
|
||||
|
||||
# Make sure config exists.
|
||||
|
|
@ -438,6 +430,12 @@ class PlaylistBrowserWindow(bui.MainWindow):
|
|||
+ 90
|
||||
+ extra_bottom_buffer
|
||||
)
|
||||
|
||||
# For fullscreen scrollable, account for toolbar.
|
||||
uiscale = bui.app.ui_v1.uiscale
|
||||
if uiscale is bui.UIScale.SMALL:
|
||||
self._sub_height += 35
|
||||
|
||||
assert self._sub_width is not None
|
||||
assert self._sub_height is not None
|
||||
self._subcontainer = bui.containerwidget(
|
||||
|
|
@ -459,11 +457,16 @@ class PlaylistBrowserWindow(bui.MainWindow):
|
|||
40 if uiscale is bui.UIScale.SMALL and screensize[0] < 1400 else 0
|
||||
)
|
||||
|
||||
# For fullscreen scrollable, account for toolbar.
|
||||
yoffs = 0
|
||||
if uiscale is bui.UIScale.SMALL:
|
||||
yoffs -= 35
|
||||
|
||||
assert bui.app.classic is not None
|
||||
bui.textwidget(
|
||||
parent=self._subcontainer,
|
||||
text=bui.Lstr(resource='playlistsText'),
|
||||
position=(40 + xoffs, self._sub_height - 26),
|
||||
position=(40 + xoffs, self._sub_height + yoffs - 26),
|
||||
size=(0, 0),
|
||||
scale=1.0,
|
||||
maxwidth=400,
|
||||
|
|
@ -494,6 +497,7 @@ class PlaylistBrowserWindow(bui.MainWindow):
|
|||
+ 8
|
||||
+ h_offs,
|
||||
self._sub_height
|
||||
+ yoffs
|
||||
- 47
|
||||
- (y + 1) * (button_height + 2 * button_buffer_v),
|
||||
)
|
||||
|
|
@ -505,6 +509,11 @@ class PlaylistBrowserWindow(bui.MainWindow):
|
|||
label='',
|
||||
position=pos,
|
||||
)
|
||||
# We handle reselecting these manually below so don't
|
||||
# provide them proper ids for auto-reselection. Let's
|
||||
# suppress the warnings that usually happen in that
|
||||
# case.
|
||||
bui.widget(edit=btn, allow_preserve_selection=False)
|
||||
|
||||
if x == 0 and uiscale is bui.UIScale.SMALL:
|
||||
bui.widget(
|
||||
|
|
@ -527,7 +536,7 @@ class PlaylistBrowserWindow(bui.MainWindow):
|
|||
# Top row biases things up more to show header above it.
|
||||
if y == 0:
|
||||
bui.widget(
|
||||
edit=btn, show_buffer_top=60, show_buffer_bottom=5
|
||||
edit=btn, show_buffer_top=80, show_buffer_bottom=5
|
||||
)
|
||||
else:
|
||||
bui.widget(
|
||||
|
|
@ -710,6 +719,7 @@ class PlaylistBrowserWindow(bui.MainWindow):
|
|||
break
|
||||
self._customize_button = btn = bui.buttonwidget(
|
||||
parent=self._subcontainer,
|
||||
id=f'{self.main_window_id_prefix}|customize',
|
||||
size=(100, 30),
|
||||
position=(34 + h_offs_bottom, 50 + extra_bottom_buffer),
|
||||
text_scale=0.6,
|
||||
|
|
@ -720,7 +730,6 @@ class PlaylistBrowserWindow(bui.MainWindow):
|
|||
autoselect=True,
|
||||
)
|
||||
bui.widget(edit=btn, show_buffer_top=22, show_buffer_bottom=60)
|
||||
self._restore_state()
|
||||
|
||||
def on_play_options_window_run_game(self) -> None:
|
||||
"""(internal)"""
|
||||
|
|
@ -775,7 +784,6 @@ class PlaylistBrowserWindow(bui.MainWindow):
|
|||
if not exists:
|
||||
return
|
||||
|
||||
self._save_state()
|
||||
PlayOptionsWindow(
|
||||
sessiontype=self._sessiontype,
|
||||
scale_origin=button.get_screen_space_center(),
|
||||
|
|
@ -790,20 +798,14 @@ class PlaylistBrowserWindow(bui.MainWindow):
|
|||
PlaylistCustomizeBrowserWindow,
|
||||
)
|
||||
|
||||
# no-op if we're not in control.
|
||||
if not self.main_window_has_control():
|
||||
return
|
||||
|
||||
self.main_window_replace(
|
||||
PlaylistCustomizeBrowserWindow(
|
||||
lambda: PlaylistCustomizeBrowserWindow(
|
||||
origin_widget=self._customize_button,
|
||||
sessiontype=self._sessiontype,
|
||||
)
|
||||
)
|
||||
|
||||
def _on_back_press(self) -> None:
|
||||
# pylint: disable=cyclic-import
|
||||
# from bauiv1lib.play import PlayWindow
|
||||
|
||||
# no-op if we're not in control.
|
||||
if not self.main_window_has_control():
|
||||
|
|
@ -822,43 +824,3 @@ class PlaylistBrowserWindow(bui.MainWindow):
|
|||
cfg.commit()
|
||||
|
||||
self.main_window_back()
|
||||
|
||||
def _save_state(self) -> None:
|
||||
try:
|
||||
sel = self._root_widget.get_selected_child()
|
||||
if sel == self._back_button:
|
||||
sel_name = 'Back'
|
||||
elif sel == self._scrollwidget:
|
||||
assert self._subcontainer is not None
|
||||
subsel = self._subcontainer.get_selected_child()
|
||||
if subsel == self._customize_button:
|
||||
sel_name = 'Customize'
|
||||
else:
|
||||
sel_name = 'Scroll'
|
||||
else:
|
||||
raise RuntimeError('Unrecognized selected widget.')
|
||||
assert bui.app.classic is not None
|
||||
bui.app.ui_v1.window_states[type(self)] = sel_name
|
||||
except Exception:
|
||||
logging.exception('Error saving state for %s.', self)
|
||||
|
||||
def _restore_state(self) -> None:
|
||||
try:
|
||||
assert bui.app.classic is not None
|
||||
sel_name = bui.app.ui_v1.window_states.get(type(self))
|
||||
if sel_name == 'Back':
|
||||
sel = self._back_button
|
||||
elif sel_name == 'Scroll':
|
||||
sel = self._scrollwidget
|
||||
elif sel_name == 'Customize':
|
||||
sel = self._scrollwidget
|
||||
bui.containerwidget(
|
||||
edit=self._subcontainer,
|
||||
selected_child=self._customize_button,
|
||||
visible_child=self._customize_button,
|
||||
)
|
||||
else:
|
||||
sel = self._scrollwidget
|
||||
bui.containerwidget(edit=self._root_widget, selected_child=sel)
|
||||
except Exception:
|
||||
logging.exception('Error restoring state for %s.', self)
|
||||
|
|
|
|||
|
|
@ -103,6 +103,7 @@ class PlaylistCustomizeBrowserWindow(bui.MainWindow):
|
|||
else:
|
||||
self._back_button = bui.buttonwidget(
|
||||
parent=self._root_widget,
|
||||
id=f'{self.main_window_id_prefix}|back',
|
||||
position=(43, yoffs - 87),
|
||||
size=(60, 60),
|
||||
scale=0.77,
|
||||
|
|
@ -134,11 +135,12 @@ class PlaylistCustomizeBrowserWindow(bui.MainWindow):
|
|||
ymargin = 0.05
|
||||
|
||||
def _make_button(
|
||||
i: int, label: bui.Lstr, call: Callable[[], None]
|
||||
i: int, button_id: str, label: bui.Lstr, call: Callable[[], None]
|
||||
) -> bui.Widget:
|
||||
v = self._scroll_bottom + self._button_height * i
|
||||
return bui.buttonwidget(
|
||||
parent=self._root_widget,
|
||||
id=button_id,
|
||||
position=(
|
||||
h + xmargin * self._button_width,
|
||||
v + ymargin * self._button_height,
|
||||
|
|
@ -158,6 +160,7 @@ class PlaylistCustomizeBrowserWindow(bui.MainWindow):
|
|||
|
||||
new_button = _make_button(
|
||||
5,
|
||||
f'{self.main_window_id_prefix}|new',
|
||||
bui.Lstr(
|
||||
resource='newText', fallback_resource=f'{self._r}.newText'
|
||||
),
|
||||
|
|
@ -165,6 +168,7 @@ class PlaylistCustomizeBrowserWindow(bui.MainWindow):
|
|||
)
|
||||
self._edit_button = _make_button(
|
||||
4,
|
||||
f'{self.main_window_id_prefix}|edit',
|
||||
bui.Lstr(
|
||||
resource='editText',
|
||||
fallback_resource=f'{self._r}.editText',
|
||||
|
|
@ -174,6 +178,7 @@ class PlaylistCustomizeBrowserWindow(bui.MainWindow):
|
|||
|
||||
duplicate_button = _make_button(
|
||||
3,
|
||||
f'{self.main_window_id_prefix}|duplicate',
|
||||
bui.Lstr(
|
||||
resource='duplicateText',
|
||||
fallback_resource=f'{self._r}.duplicateText',
|
||||
|
|
@ -183,6 +188,7 @@ class PlaylistCustomizeBrowserWindow(bui.MainWindow):
|
|||
|
||||
delete_button = _make_button(
|
||||
2,
|
||||
f'{self.main_window_id_prefix}|delete',
|
||||
bui.Lstr(
|
||||
resource='deleteText', fallback_resource=f'{self._r}.deleteText'
|
||||
),
|
||||
|
|
@ -190,11 +196,17 @@ class PlaylistCustomizeBrowserWindow(bui.MainWindow):
|
|||
)
|
||||
|
||||
self._import_button = _make_button(
|
||||
1, bui.Lstr(resource='importText'), self._import_playlist
|
||||
1,
|
||||
f'{self.main_window_id_prefix}|import',
|
||||
bui.Lstr(resource='importText'),
|
||||
self._import_playlist,
|
||||
)
|
||||
|
||||
share_button = _make_button(
|
||||
0, bui.Lstr(resource='shareText'), self._share_playlist
|
||||
0,
|
||||
f'{self.main_window_id_prefix}|share',
|
||||
bui.Lstr(resource='shareText'),
|
||||
self._share_playlist,
|
||||
)
|
||||
|
||||
scrollwidget = bui.scrollwidget(
|
||||
|
|
@ -284,6 +296,10 @@ class PlaylistCustomizeBrowserWindow(bui.MainWindow):
|
|||
)
|
||||
)
|
||||
|
||||
@override
|
||||
def main_window_should_preserve_selection(self) -> bool:
|
||||
return True
|
||||
|
||||
@override
|
||||
def on_main_window_close(self) -> None:
|
||||
if self._selected_playlist_name is not None:
|
||||
|
|
@ -353,7 +369,15 @@ class PlaylistCustomizeBrowserWindow(bui.MainWindow):
|
|||
on_activate_call=bui.Call(self._edit_button.activate),
|
||||
selectable=True,
|
||||
)
|
||||
bui.widget(edit=txtw, show_buffer_top=50, show_buffer_bottom=50)
|
||||
# We don't give these widgets ids because we handle
|
||||
# re-selecting them ourself, but we need to suppress the
|
||||
# warning this usually causes.
|
||||
bui.widget(
|
||||
edit=txtw,
|
||||
show_buffer_top=50,
|
||||
show_buffer_bottom=50,
|
||||
allow_preserve_selection=False,
|
||||
)
|
||||
|
||||
# Hitting up from top widget should jump to 'back'.
|
||||
if index == 0:
|
||||
|
|
@ -593,11 +617,11 @@ class PlaylistCustomizeBrowserWindow(bui.MainWindow):
|
|||
subs=[('${LIST}', self._selected_playlist_name)],
|
||||
),
|
||||
self._do_delete_playlist,
|
||||
450,
|
||||
150,
|
||||
width=450,
|
||||
height=150,
|
||||
)
|
||||
|
||||
def _get_playlist_display_name(self, playlist: str) -> bui.Lstr:
|
||||
def _get_playlist_display_name(self, playlist: str | bui.Lstr) -> bui.Lstr:
|
||||
if playlist == '__default__':
|
||||
return self._pvars.default_list_name
|
||||
return (
|
||||
|
|
|
|||
|
|
@ -289,6 +289,10 @@ class PlaylistEditWindow(bui.MainWindow):
|
|||
)
|
||||
)
|
||||
|
||||
@override
|
||||
def main_window_should_preserve_selection(self) -> bool:
|
||||
return False
|
||||
|
||||
def _set_ui_selection(self, selection: str) -> None:
|
||||
self._editcontroller.set_edit_ui_selection(selection)
|
||||
|
||||
|
|
|
|||
|
|
@ -92,8 +92,10 @@ class PlaylistEditController:
|
|||
# and that's all they can do.
|
||||
self._edit_ui_selection = 'add_button'
|
||||
|
||||
editwindow = PlaylistEditWindow(editcontroller=self)
|
||||
from_window.main_window_replace(editwindow)
|
||||
editwindow = from_window.main_window_replace(
|
||||
lambda: PlaylistEditWindow(editcontroller=self)
|
||||
)
|
||||
assert editwindow is not None
|
||||
|
||||
# Once we've set our start window, store the back state. We'll
|
||||
# skip back to there once we're fully done.
|
||||
|
|
@ -151,17 +153,17 @@ class PlaylistEditController:
|
|||
"""(internal)"""
|
||||
from bauiv1lib.playlist.addgame import PlaylistAddGameWindow
|
||||
|
||||
# assert bui.app.classic is not None
|
||||
|
||||
# No op if we're not in control.
|
||||
if not from_window.main_window_has_control():
|
||||
return
|
||||
|
||||
addwindow = PlaylistAddGameWindow(editcontroller=self)
|
||||
from_window.main_window_replace(addwindow)
|
||||
addwindow = from_window.main_window_replace(
|
||||
lambda: PlaylistAddGameWindow(editcontroller=self)
|
||||
)
|
||||
assert addwindow is not None
|
||||
|
||||
# Once we're there, store the back state. We'll use that to jump
|
||||
# back to our current location once the edit is done.
|
||||
# back out to our current location once the edit is done.
|
||||
assert self._pre_game_add_state is None
|
||||
self._pre_game_add_state = addwindow.main_window_back_state
|
||||
|
||||
|
|
@ -197,16 +199,18 @@ class PlaylistEditController:
|
|||
assert self._sessiontype is not None
|
||||
|
||||
# Jump into an edit window.
|
||||
editwindow = PlaylistEditGameWindow(
|
||||
gametype,
|
||||
self._sessiontype,
|
||||
copy.deepcopy(settings),
|
||||
completion_call=self._edit_game_done,
|
||||
editwindow = from_window.main_window_replace(
|
||||
lambda: PlaylistEditGameWindow(
|
||||
gametype,
|
||||
self._sessiontype,
|
||||
copy.deepcopy(settings),
|
||||
completion_call=self._edit_game_done,
|
||||
)
|
||||
)
|
||||
from_window.main_window_replace(editwindow)
|
||||
assert editwindow is not None
|
||||
|
||||
# Once we're there, store the back state. We'll use that to jump
|
||||
# back to our current location once the edit is done.
|
||||
# back out to our current location once the edit is done.
|
||||
assert self._pre_game_edit_state is None
|
||||
self._pre_game_edit_state = editwindow.main_window_back_state
|
||||
|
||||
|
|
|
|||
|
|
@ -541,6 +541,10 @@ class PlaylistEditGameWindow(bui.MainWindow):
|
|||
)
|
||||
)
|
||||
|
||||
@override
|
||||
def main_window_should_preserve_selection(self) -> bool:
|
||||
return False
|
||||
|
||||
def _get_localized_setting_name(self, name: str) -> bui.Lstr:
|
||||
return bui.Lstr(translate=('settingNames', name))
|
||||
|
||||
|
|
@ -552,14 +556,14 @@ class PlaylistEditGameWindow(bui.MainWindow):
|
|||
if not self.main_window_has_control():
|
||||
return
|
||||
|
||||
self._config = self._getconfig()
|
||||
config = self._config = self._getconfig()
|
||||
|
||||
# Replace ourself with the map-select UI.
|
||||
self.main_window_replace(
|
||||
PlaylistMapSelectWindow(
|
||||
lambda: PlaylistMapSelectWindow(
|
||||
self._gametype,
|
||||
self._sessiontype,
|
||||
self._config,
|
||||
config,
|
||||
self._edit_info,
|
||||
self._completion_call,
|
||||
)
|
||||
|
|
|
|||
|
|
@ -148,6 +148,10 @@ class PlaylistMapSelectWindow(bui.MainWindow):
|
|||
)
|
||||
)
|
||||
|
||||
@override
|
||||
def main_window_should_preserve_selection(self) -> bool:
|
||||
return False
|
||||
|
||||
def _refresh(self, select_get_more_maps_button: bool = False) -> None:
|
||||
# pylint: disable=too-many-statements
|
||||
# pylint: disable=too-many-branches
|
||||
|
|
@ -301,7 +305,7 @@ class PlaylistMapSelectWindow(bui.MainWindow):
|
|||
self._selected_get_more_maps = True
|
||||
|
||||
self.main_window_replace(
|
||||
StoreBrowserWindow(
|
||||
lambda: StoreBrowserWindow(
|
||||
show_tab=StoreBrowserWindow.TabID.MAPS,
|
||||
origin_widget=self._get_more_maps_button,
|
||||
minimal_toolbars=True,
|
||||
|
|
|
|||
15
dist/ba_data/python/bauiv1lib/playoptions.py
vendored
15
dist/ba_data/python/bauiv1lib/playoptions.py
vendored
|
|
@ -40,6 +40,8 @@ class PlayOptionsWindow(PopupWindow):
|
|||
from bauiv1lib.playlist import PlaylistTypeVars
|
||||
from bauiv1lib.config import ConfigNumberEdit
|
||||
|
||||
ui = bui.app.ui_v1
|
||||
|
||||
self._r = 'gameListWindow'
|
||||
self._delegate = delegate
|
||||
self._pvars = PlaylistTypeVars(sessiontype)
|
||||
|
|
@ -145,7 +147,7 @@ class PlayOptionsWindow(PopupWindow):
|
|||
if show_shuffle_check_box:
|
||||
self._height += 40
|
||||
|
||||
uiscale = bui.app.ui_v1.uiscale
|
||||
uiscale = ui.uiscale
|
||||
scale = (
|
||||
1.69
|
||||
if uiscale is bui.UIScale.SMALL
|
||||
|
|
@ -184,6 +186,7 @@ class PlayOptionsWindow(PopupWindow):
|
|||
on_activate_call=self._on_cancel_press,
|
||||
autoselect=True,
|
||||
)
|
||||
bui.widget(edit=self._cancel_button, allow_preserve_selection=False)
|
||||
|
||||
h_offs_img = self._width * 0.5 - c_width_total * 0.5
|
||||
v_offs_img = self._height - 118 - scl * 125.0 + 50
|
||||
|
|
@ -247,6 +250,8 @@ class PlayOptionsWindow(PopupWindow):
|
|||
mesh_transparent=mesh_transparent if owned else None,
|
||||
mask_texture=mask_tex if owned else None,
|
||||
)
|
||||
bui.widget(edit=btn, allow_preserve_selection=False)
|
||||
|
||||
if row == 0 and col == 0:
|
||||
bui.widget(edit=self._cancel_button, down_widget=btn)
|
||||
if row == rows - 1:
|
||||
|
|
@ -316,6 +321,11 @@ class PlayOptionsWindow(PopupWindow):
|
|||
textcolor=(0.8, 0.8, 0.8),
|
||||
label=bui.Lstr(resource='teamNamesColorText'),
|
||||
)
|
||||
bui.widget(
|
||||
edit=self._custom_colors_names_button,
|
||||
allow_preserve_selection=False,
|
||||
)
|
||||
|
||||
assert bui.app.classic is not None
|
||||
if REQUIRE_PRO and not bui.app.classic.accounts.have_pro():
|
||||
bui.imagewidget(
|
||||
|
|
@ -414,6 +424,7 @@ class PlayOptionsWindow(PopupWindow):
|
|||
)
|
||||
),
|
||||
)
|
||||
bui.widget(edit=self._ok_button, allow_preserve_selection=False)
|
||||
|
||||
bui.widget(
|
||||
edit=self._ok_button, up_widget=self._show_tutorial_check_box
|
||||
|
|
@ -505,8 +516,6 @@ class PlayOptionsWindow(PopupWindow):
|
|||
# Head back to the gather window in playlist-select mode or
|
||||
# start the game in regular mode.
|
||||
if self._playlist_select_context is not None:
|
||||
# from bauiv1lib.gather import GatherWindow
|
||||
|
||||
if self._sessiontype is bs.FreeForAllSession:
|
||||
typename = 'ffa'
|
||||
elif self._sessiontype is bs.DualTeamSession:
|
||||
|
|
|
|||
10
dist/ba_data/python/bauiv1lib/popup.py
vendored
10
dist/ba_data/python/bauiv1lib/popup.py
vendored
|
|
@ -102,8 +102,8 @@ class PopupWindow:
|
|||
on_cancel_call=self.on_popup_cancel,
|
||||
darken_behind=darken_behind,
|
||||
)
|
||||
# complain if we outlive our root widget
|
||||
bui.uicleanupcheck(self, self.root_widget)
|
||||
# Complain if we outlive our root widget.
|
||||
bui.app.ui_v1.add_ui_cleanup_check(self, self.root_widget)
|
||||
|
||||
def on_popup_cancel(self) -> None:
|
||||
"""Called when the popup is canceled.
|
||||
|
|
@ -253,6 +253,8 @@ class PopupMenuWindow(PopupWindow):
|
|||
selectable=(not inactive),
|
||||
glow_type='uniform',
|
||||
)
|
||||
bui.widget(edit=wdg, allow_preserve_selection=False)
|
||||
|
||||
if choice == self._current_choice:
|
||||
bui.containerwidget(
|
||||
edit=self._columnwidget,
|
||||
|
|
@ -312,6 +314,7 @@ class PopupMenu:
|
|||
position: tuple[float, float],
|
||||
choices: Sequence[str],
|
||||
*,
|
||||
button_id: str | None = None,
|
||||
current_choice: str | None = None,
|
||||
on_value_change_call: Callable[[str], Any] | None = None,
|
||||
opening_call: Callable[[], Any] | None = None,
|
||||
|
|
@ -359,6 +362,7 @@ class PopupMenu:
|
|||
|
||||
self._button = bui.buttonwidget(
|
||||
parent=self._parent,
|
||||
id=button_id,
|
||||
position=(self._position[0], self._position[1]),
|
||||
autoselect=autoselect,
|
||||
size=self._button_size,
|
||||
|
|
@ -375,7 +379,7 @@ class PopupMenu:
|
|||
self._window_widget: bui.Widget | None = None
|
||||
|
||||
# Complain if we outlive our button.
|
||||
bui.uicleanupcheck(self, self._button)
|
||||
bui.app.ui_v1.add_ui_cleanup_check(self, self._button)
|
||||
|
||||
def _make_popup(self) -> None:
|
||||
if not self._button:
|
||||
|
|
|
|||
112
dist/ba_data/python/bauiv1lib/profile/browser.py
vendored
112
dist/ba_data/python/bauiv1lib/profile/browser.py
vendored
|
|
@ -4,23 +4,24 @@
|
|||
|
||||
from __future__ import annotations
|
||||
|
||||
import logging
|
||||
from typing import TYPE_CHECKING, override
|
||||
|
||||
import bauiv1 as bui
|
||||
import bascenev1 as bs
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from typing import Any
|
||||
from typing import Any, ClassVar
|
||||
|
||||
|
||||
class ProfileBrowserWindow(bui.MainWindow):
|
||||
"""Window for browsing player profiles."""
|
||||
|
||||
# Keep track of this at the class level to share between instances.
|
||||
selected_profile: ClassVar[str | None] = None
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
transition: str | None = 'in_right',
|
||||
# in_main_menu: bool = True,
|
||||
selected_profile: str | None = None,
|
||||
origin_widget: bui.Widget | None = None,
|
||||
minimal_toolbar: bool = False,
|
||||
|
|
@ -80,6 +81,7 @@ class ProfileBrowserWindow(bui.MainWindow):
|
|||
else:
|
||||
self._back_button = btn = bui.buttonwidget(
|
||||
parent=self._root_widget,
|
||||
id=f'{self.main_window_id_prefix}|back',
|
||||
position=(40 + x_inset, self._height - 59),
|
||||
size=(120, 60),
|
||||
scale=0.8,
|
||||
|
|
@ -122,6 +124,7 @@ class ProfileBrowserWindow(bui.MainWindow):
|
|||
v -= 70.0 * scl
|
||||
self._new_button = bui.buttonwidget(
|
||||
parent=self._root_widget,
|
||||
id=f'{self.main_window_id_prefix}|new',
|
||||
position=(h, v),
|
||||
size=(80, 66.0 * scl),
|
||||
on_activate_call=self._new_profile,
|
||||
|
|
@ -135,6 +138,7 @@ class ProfileBrowserWindow(bui.MainWindow):
|
|||
v -= 70.0 * scl
|
||||
self._edit_button = bui.buttonwidget(
|
||||
parent=self._root_widget,
|
||||
id=f'{self.main_window_id_prefix}|edit',
|
||||
position=(h, v),
|
||||
size=(80, 66.0 * scl),
|
||||
on_activate_call=self._edit_profile,
|
||||
|
|
@ -148,6 +152,7 @@ class ProfileBrowserWindow(bui.MainWindow):
|
|||
v -= 70.0 * scl
|
||||
self._delete_button = bui.buttonwidget(
|
||||
parent=self._root_widget,
|
||||
id=f'{self.main_window_id_prefix}|delete',
|
||||
position=(h, v),
|
||||
size=(80, 66.0 * scl),
|
||||
on_activate_call=self._delete_profile,
|
||||
|
|
@ -194,10 +199,10 @@ class ProfileBrowserWindow(bui.MainWindow):
|
|||
)
|
||||
v -= 255
|
||||
self._profiles: dict[str, dict[str, Any]] | None = None
|
||||
self._selected_profile = selected_profile
|
||||
if selected_profile is not None:
|
||||
type(self).selected_profile = selected_profile
|
||||
self._profile_widgets: list[bui.Widget] = []
|
||||
self._refresh()
|
||||
self._restore_state()
|
||||
|
||||
@override
|
||||
def get_main_window_state(self) -> bui.MainWindowState:
|
||||
|
|
@ -215,8 +220,19 @@ class ProfileBrowserWindow(bui.MainWindow):
|
|||
)
|
||||
|
||||
@override
|
||||
def on_main_window_close(self) -> None:
|
||||
self._save_state()
|
||||
def main_window_should_preserve_selection(self) -> bool:
|
||||
return True
|
||||
|
||||
# @override
|
||||
# def main_window_do_save_shared_state(self, state: dict) -> None:
|
||||
# state['selected_profile'] = self._selected_profile
|
||||
|
||||
# @override
|
||||
# def main_window_do_restore_shared_state(self, state: dict) -> None:
|
||||
# pval = state.get('selected_profile')
|
||||
# if isinstance(pval, str | None):
|
||||
# print('RESTORING', pval)
|
||||
# self._selected_profile = pval
|
||||
|
||||
def _new_profile(self) -> None:
|
||||
# pylint: disable=cyclic-import
|
||||
|
|
@ -263,19 +279,21 @@ class ProfileBrowserWindow(bui.MainWindow):
|
|||
bui.getsound('error').play()
|
||||
return
|
||||
|
||||
self.main_window_replace(EditProfileWindow(existing_profile=None))
|
||||
self.main_window_replace(
|
||||
lambda: EditProfileWindow(existing_profile=None)
|
||||
)
|
||||
|
||||
def _delete_profile(self) -> None:
|
||||
# pylint: disable=cyclic-import
|
||||
from bauiv1lib import confirm
|
||||
|
||||
if self._selected_profile is None:
|
||||
if self.selected_profile is None:
|
||||
bui.getsound('error').play()
|
||||
bui.screenmessage(
|
||||
bui.Lstr(resource='nothingIsSelectedErrorText'), color=(1, 0, 0)
|
||||
)
|
||||
return
|
||||
if self._selected_profile == '__account__':
|
||||
if self.selected_profile == '__account__':
|
||||
bui.getsound('error').play()
|
||||
bui.screenmessage(
|
||||
bui.Lstr(resource=f'{self._r}.cantDeleteAccountProfileText'),
|
||||
|
|
@ -285,18 +303,21 @@ class ProfileBrowserWindow(bui.MainWindow):
|
|||
confirm.ConfirmWindow(
|
||||
bui.Lstr(
|
||||
resource=f'{self._r}.deleteConfirmText',
|
||||
subs=[('${PROFILE}', self._selected_profile)],
|
||||
subs=[('${PROFILE}', self.selected_profile)],
|
||||
),
|
||||
self._do_delete_profile,
|
||||
350,
|
||||
width=350,
|
||||
)
|
||||
|
||||
def _do_delete_profile(self) -> None:
|
||||
plus = bui.app.plus
|
||||
assert plus is not None
|
||||
|
||||
# Go back to default selection.
|
||||
type(self).selected_profile = None
|
||||
|
||||
plus.add_v1_account_transaction(
|
||||
{'type': 'REMOVE_PLAYER_PROFILE', 'name': self._selected_profile}
|
||||
{'type': 'REMOVE_PLAYER_PROFILE', 'name': self.selected_profile}
|
||||
)
|
||||
plus.run_v1_account_transactions()
|
||||
bui.getsound('shieldDown').play()
|
||||
|
|
@ -315,18 +336,20 @@ class ProfileBrowserWindow(bui.MainWindow):
|
|||
if not self.main_window_has_control():
|
||||
return
|
||||
|
||||
if self._selected_profile is None:
|
||||
if self.selected_profile is None:
|
||||
bui.getsound('error').play()
|
||||
bui.screenmessage(
|
||||
bui.Lstr(resource='nothingIsSelectedErrorText'), color=(1, 0, 0)
|
||||
)
|
||||
return
|
||||
|
||||
self.main_window_replace(EditProfileWindow(self._selected_profile))
|
||||
self.main_window_replace(
|
||||
lambda: EditProfileWindow(self.selected_profile)
|
||||
)
|
||||
|
||||
def _select(self, name: str, index: int) -> None:
|
||||
del index # Unused.
|
||||
self._selected_profile = name
|
||||
type(self).selected_profile = name
|
||||
|
||||
def _refresh(self) -> None:
|
||||
# pylint: disable=too-many-locals
|
||||
|
|
@ -340,7 +363,7 @@ class ProfileBrowserWindow(bui.MainWindow):
|
|||
plus = bui.app.plus
|
||||
assert plus is not None
|
||||
|
||||
old_selection = self._selected_profile
|
||||
old_selection = self.selected_profile
|
||||
|
||||
# Delete old.
|
||||
while self._profile_widgets:
|
||||
|
|
@ -390,6 +413,7 @@ class ProfileBrowserWindow(bui.MainWindow):
|
|||
assert isinstance(tval, str)
|
||||
txtw = bui.textwidget(
|
||||
parent=self._subcontainer,
|
||||
id=f'{self.main_window_id_prefix}|profile{index}',
|
||||
position=(5, y_val),
|
||||
size=((self._width - 210) / scl, 28),
|
||||
text=bui.Lstr(value=f' {tval}'),
|
||||
|
|
@ -403,6 +427,9 @@ class ProfileBrowserWindow(bui.MainWindow):
|
|||
on_activate_call=bui.Call(self._edit_button.activate),
|
||||
selectable=True,
|
||||
)
|
||||
# We handle reselection of these manually; no need for ids.
|
||||
bui.widget(edit=txtw, allow_preserve_selection=False)
|
||||
|
||||
character = bui.imagewidget(
|
||||
parent=self._subcontainer,
|
||||
position=(0, y_val),
|
||||
|
|
@ -416,16 +443,16 @@ class ProfileBrowserWindow(bui.MainWindow):
|
|||
)
|
||||
if index == 0:
|
||||
bui.widget(edit=txtw, up_widget=self._back_button)
|
||||
if self._selected_profile is None:
|
||||
self._selected_profile = p_name
|
||||
if self.selected_profile is None:
|
||||
type(self).selected_profile = p_name
|
||||
bui.widget(edit=txtw, show_buffer_top=40, show_buffer_bottom=40)
|
||||
self._profile_widgets.append(txtw)
|
||||
self._profile_widgets.append(character)
|
||||
|
||||
# Select/show this one if it was previously selected
|
||||
# Select/show this one if it was previously selected.
|
||||
# (but defer till after this loop since our height is
|
||||
# still changing).
|
||||
if p_name == old_selection:
|
||||
if p_name == old_selection or widget_to_select is None:
|
||||
widget_to_select = txtw
|
||||
|
||||
index += 1
|
||||
|
|
@ -447,46 +474,3 @@ class ProfileBrowserWindow(bui.MainWindow):
|
|||
session = bs.get_foreground_host_session()
|
||||
if session is not None:
|
||||
session.handlemessage(PlayerProfilesChangedMessage())
|
||||
|
||||
def _save_state(self) -> None:
|
||||
try:
|
||||
sel = self._root_widget.get_selected_child()
|
||||
if sel == self._new_button:
|
||||
sel_name = 'New'
|
||||
elif sel == self._edit_button:
|
||||
sel_name = 'Edit'
|
||||
elif sel == self._delete_button:
|
||||
sel_name = 'Delete'
|
||||
elif sel == self._scrollwidget:
|
||||
sel_name = 'Scroll'
|
||||
else:
|
||||
sel_name = 'Back'
|
||||
assert bui.app.classic is not None
|
||||
bui.app.ui_v1.window_states[type(self)] = sel_name
|
||||
except Exception:
|
||||
logging.exception('Error saving state for %s.', self)
|
||||
|
||||
def _restore_state(self) -> None:
|
||||
try:
|
||||
assert bui.app.classic is not None
|
||||
sel_name = bui.app.ui_v1.window_states.get(type(self))
|
||||
if sel_name == 'Scroll':
|
||||
sel = self._scrollwidget
|
||||
elif sel_name == 'New':
|
||||
sel = self._new_button
|
||||
elif sel_name == 'Delete':
|
||||
sel = self._delete_button
|
||||
elif sel_name == 'Edit':
|
||||
sel = self._edit_button
|
||||
elif sel_name == 'Back':
|
||||
sel = self._back_button
|
||||
else:
|
||||
# By default we select our scroll widget if we have profiles;
|
||||
# otherwise our new widget.
|
||||
if not self._profile_widgets:
|
||||
sel = self._new_button
|
||||
else:
|
||||
sel = self._scrollwidget
|
||||
bui.containerwidget(edit=self._root_widget, selected_child=sel)
|
||||
except Exception:
|
||||
logging.exception('Error restoring state for %s.', self)
|
||||
|
|
|
|||
30
dist/ba_data/python/bauiv1lib/profile/edit.py
vendored
30
dist/ba_data/python/bauiv1lib/profile/edit.py
vendored
|
|
@ -22,20 +22,13 @@ class EditProfileWindow(
|
|||
def reload_window(self) -> None:
|
||||
"""Transitions out and recreates ourself."""
|
||||
|
||||
# no-op if we're not in control.
|
||||
if not self.main_window_has_control():
|
||||
return
|
||||
|
||||
# Replace ourself with ourself, but keep the same back location.
|
||||
assert self.main_window_back_state is not None
|
||||
self.main_window_replace(
|
||||
EditProfileWindow(self.getname()),
|
||||
lambda: EditProfileWindow(self.getname()),
|
||||
back_state=self.main_window_back_state,
|
||||
)
|
||||
|
||||
# def __del__(self) -> None:
|
||||
# print(f'~EditProfileWindow({id(self)})')
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
existing_profile: str | None,
|
||||
|
|
@ -535,6 +528,11 @@ class EditProfileWindow(
|
|||
)
|
||||
)
|
||||
|
||||
@override
|
||||
def main_window_should_preserve_selection(self) -> bool:
|
||||
# Not bothering with this for now.
|
||||
return False
|
||||
|
||||
def assign_random_name(self) -> None:
|
||||
"""Assigning a random name to the player."""
|
||||
names = bs.get_random_names()
|
||||
|
|
@ -652,11 +650,8 @@ class EditProfileWindow(
|
|||
"""User wants to get more icons."""
|
||||
from bauiv1lib.store.browser import StoreBrowserWindow
|
||||
|
||||
if not self.main_window_has_control():
|
||||
return
|
||||
|
||||
self.main_window_replace(
|
||||
StoreBrowserWindow(
|
||||
lambda: StoreBrowserWindow(
|
||||
minimal_toolbars=True,
|
||||
show_tab=StoreBrowserWindow.TabID.ICONS,
|
||||
)
|
||||
|
|
@ -680,11 +675,8 @@ class EditProfileWindow(
|
|||
def on_character_picker_get_more_press(self) -> None:
|
||||
from bauiv1lib.store.browser import StoreBrowserWindow
|
||||
|
||||
if not self.main_window_has_control():
|
||||
return
|
||||
|
||||
self.main_window_replace(
|
||||
StoreBrowserWindow(
|
||||
lambda: StoreBrowserWindow(
|
||||
minimal_toolbars=True,
|
||||
show_tab=StoreBrowserWindow.TabID.CHARACTERS,
|
||||
)
|
||||
|
|
@ -832,6 +824,9 @@ class EditProfileWindow(
|
|||
|
||||
def save(self, transition_out: bool = True) -> bool:
|
||||
"""Save has been selected."""
|
||||
# pylint: disable=cyclic-import
|
||||
|
||||
from bauiv1lib.profile.browser import ProfileBrowserWindow
|
||||
|
||||
# no-op if our underlying widget is dead or on its way out.
|
||||
if not self._root_widget or self._root_widget.transitioning_out:
|
||||
|
|
@ -856,6 +851,9 @@ class EditProfileWindow(
|
|||
bui.getsound('error').play()
|
||||
return False
|
||||
|
||||
# Set the profile-browser to have this one selected by default.
|
||||
ProfileBrowserWindow.selected_profile = new_name
|
||||
|
||||
if transition_out:
|
||||
bui.getsound('gunCocking').play()
|
||||
|
||||
|
|
|
|||
3
dist/ba_data/python/bauiv1lib/purchase.py
vendored
3
dist/ba_data/python/bauiv1lib/purchase.py
vendored
|
|
@ -36,6 +36,8 @@ class PurchaseWindow(bui.Window):
|
|||
)
|
||||
if len(items) != 1:
|
||||
raise ValueError('expected exactly 1 item')
|
||||
|
||||
self._idprefix = bui.app.ui_v1.new_id_prefix('purchase')
|
||||
self._items = list(items)
|
||||
self._width = 580
|
||||
self._height = 520
|
||||
|
|
@ -81,6 +83,7 @@ class PurchaseWindow(bui.Window):
|
|||
instantiate_store_item_display(
|
||||
items[0],
|
||||
display,
|
||||
idprefix=self._idprefix,
|
||||
parent_widget=self._root_widget,
|
||||
b_pos=(
|
||||
self._width * 0.5
|
||||
|
|
|
|||
|
|
@ -32,6 +32,7 @@ class ResourceTypeInfoWindow(PopupWindow):
|
|||
self._width = 570
|
||||
self._height = 400
|
||||
self._get_tokens_button: bui.Widget | None = None
|
||||
self._idprefix = bui.app.ui_v1.new_id_prefix('resourcetypeinfo')
|
||||
bg_color = (0.5, 0.4, 0.6)
|
||||
super().__init__(
|
||||
size=(self._width, self._height),
|
||||
|
|
@ -43,17 +44,15 @@ class ResourceTypeInfoWindow(PopupWindow):
|
|||
)
|
||||
self._cancel_button = bui.buttonwidget(
|
||||
parent=self.root_widget,
|
||||
id=f'{self._idprefix}|cancel',
|
||||
position=(40, self._height - 40),
|
||||
size=(50, 50),
|
||||
scale=0.7,
|
||||
# label='',
|
||||
color=bg_color,
|
||||
on_activate_call=self._on_cancel_press,
|
||||
autoselect=True,
|
||||
label=bui.charstr(bui.SpecialChar.CLOSE),
|
||||
textcolor=(1, 1, 1),
|
||||
# icon=bui.gettexture('crossOut'),
|
||||
# iconscale=1.2,
|
||||
)
|
||||
|
||||
yoffs = self._height - 145
|
||||
|
|
@ -78,6 +77,7 @@ class ResourceTypeInfoWindow(PopupWindow):
|
|||
if not bui.app.classic.gold_pass:
|
||||
self._get_tokens_button = bui.buttonwidget(
|
||||
parent=self.root_widget,
|
||||
id=f'{self._idprefix}|gettokens',
|
||||
position=(
|
||||
self._width * 0.5 - bwidth * 0.5,
|
||||
yoffs - 15.0 - bheight - max_rdesc_height,
|
||||
|
|
|
|||
9
dist/ba_data/python/bauiv1lib/sendinfo.py
vendored
9
dist/ba_data/python/bauiv1lib/sendinfo.py
vendored
|
|
@ -72,6 +72,7 @@ class SendInfoWindow(bui.MainWindow):
|
|||
if uiscale is not bui.UIScale.SMALL:
|
||||
close_button = bui.buttonwidget(
|
||||
parent=self._root_widget,
|
||||
id=f'{self.main_window_id_prefix}|close',
|
||||
position=(25, yoffs - 35),
|
||||
size=(60, 60),
|
||||
scale=0.7,
|
||||
|
|
@ -114,6 +115,7 @@ class SendInfoWindow(bui.MainWindow):
|
|||
|
||||
self._text_field = bui.textwidget(
|
||||
parent=self._root_widget,
|
||||
id=f'{self.main_window_id_prefix}|desc',
|
||||
position=(width * 0.5 + txoffs + 125, v),
|
||||
size=(380, 46),
|
||||
text='',
|
||||
|
|
@ -134,6 +136,7 @@ class SendInfoWindow(bui.MainWindow):
|
|||
b_width = 200
|
||||
self._enter_button = btn2 = bui.buttonwidget(
|
||||
parent=self._root_widget,
|
||||
id=f'{self.main_window_id_prefix}|enter',
|
||||
position=(width * 0.5 - b_width * 0.5, v),
|
||||
size=(b_width, 60),
|
||||
scale=1.0,
|
||||
|
|
@ -170,12 +173,14 @@ class SendInfoWindow(bui.MainWindow):
|
|||
)
|
||||
)
|
||||
|
||||
@override
|
||||
def main_window_should_preserve_selection(self) -> bool:
|
||||
return True
|
||||
|
||||
def _activate_enter_button(self) -> None:
|
||||
self._enter_button.activate()
|
||||
|
||||
def _do_enter(self) -> None:
|
||||
# pylint: disable=cyclic-import
|
||||
# from bauiv1lib.settings.advanced import AdvancedSettingsWindow
|
||||
|
||||
plus = bui.app.plus
|
||||
assert plus is not None
|
||||
|
|
|
|||
348
dist/ba_data/python/bauiv1lib/settings/advanced.py
vendored
348
dist/ba_data/python/bauiv1lib/settings/advanced.py
vendored
|
|
@ -1,6 +1,5 @@
|
|||
# Released under the MIT License. See LICENSE for details.
|
||||
#
|
||||
# pylint: disable=too-many-lines
|
||||
|
||||
"""UI functionality for advanced settings."""
|
||||
|
||||
|
|
@ -12,6 +11,7 @@ from typing import TYPE_CHECKING, override
|
|||
|
||||
from bacommon.locale import LocaleResolved
|
||||
from bauiv1lib.popup import PopupMenu
|
||||
from bauiv1lib.utils import scroll_fade_bottom, scroll_fade_top
|
||||
import bauiv1 as bui
|
||||
|
||||
if TYPE_CHECKING:
|
||||
|
|
@ -73,6 +73,11 @@ class AdvancedSettingsWindow(bui.MainWindow):
|
|||
self._scroll_height = target_height - 25
|
||||
scroll_bottom = yoffs - 56 - self._scroll_height
|
||||
|
||||
# Go with full-screen scrollable area in small ui.
|
||||
if uiscale is bui.UIScale.SMALL:
|
||||
self._scroll_height += 27
|
||||
scroll_bottom += 1
|
||||
|
||||
super().__init__(
|
||||
root_widget=bui.containerwidget(
|
||||
size=(self._width, self._height),
|
||||
|
|
@ -100,6 +105,10 @@ class AdvancedSettingsWindow(bui.MainWindow):
|
|||
self._sub_width = min(550, self._scroll_width * 0.95)
|
||||
self._sub_height = 920.0
|
||||
|
||||
# For fullscreen scrollable, account for toolbar.
|
||||
if uiscale is bui.UIScale.SMALL:
|
||||
self._sub_height += 27
|
||||
|
||||
if self._show_always_use_internal_keyboard:
|
||||
self._sub_height += 62
|
||||
|
||||
|
|
@ -132,6 +141,7 @@ class AdvancedSettingsWindow(bui.MainWindow):
|
|||
else:
|
||||
self._back_button = bui.buttonwidget(
|
||||
parent=self._root_widget,
|
||||
id=f'{self.main_window_id_prefix}|back',
|
||||
position=(50, yoffs - 48),
|
||||
size=(60, 60),
|
||||
scale=0.8,
|
||||
|
|
@ -144,6 +154,50 @@ class AdvancedSettingsWindow(bui.MainWindow):
|
|||
edit=self._root_widget, cancel_button=self._back_button
|
||||
)
|
||||
|
||||
self._scrollwidget = bui.scrollwidget(
|
||||
parent=self._root_widget,
|
||||
size=(self._scroll_width, self._scroll_height),
|
||||
position=(
|
||||
self._width * 0.5 - self._scroll_width * 0.5,
|
||||
scroll_bottom,
|
||||
),
|
||||
simple_culling_v=20.0,
|
||||
highlight=False,
|
||||
center_small_content_horizontally=True,
|
||||
selection_loops_to_parent=True,
|
||||
border_opacity=0.4,
|
||||
)
|
||||
bui.containerwidget(
|
||||
edit=self._root_widget, selected_child=self._scrollwidget
|
||||
)
|
||||
bui.widget(edit=self._scrollwidget, right_widget=self._scrollwidget)
|
||||
|
||||
self._subcontainer = bui.containerwidget(
|
||||
parent=self._scrollwidget,
|
||||
size=(self._sub_width, self._sub_height),
|
||||
background=False,
|
||||
selection_loops_to_parent=True,
|
||||
)
|
||||
|
||||
# With full-screen scrolling, fade content as it approaches
|
||||
# toolbars. (but only in the main menu where we're showing said
|
||||
# toolbars).
|
||||
if uiscale is bui.UIScale.SMALL and bui.in_main_menu():
|
||||
scroll_fade_top(
|
||||
self._root_widget,
|
||||
self._width * 0.5 - self._scroll_width * 0.5,
|
||||
scroll_bottom,
|
||||
self._scroll_width,
|
||||
self._scroll_height,
|
||||
)
|
||||
scroll_fade_bottom(
|
||||
self._root_widget,
|
||||
self._width * 0.5 - self._scroll_width * 0.5,
|
||||
scroll_bottom,
|
||||
self._scroll_width,
|
||||
self._scroll_height,
|
||||
)
|
||||
|
||||
self._title_text = bui.textwidget(
|
||||
parent=self._root_widget,
|
||||
position=(
|
||||
|
|
@ -158,66 +212,6 @@ class AdvancedSettingsWindow(bui.MainWindow):
|
|||
v_align='center',
|
||||
)
|
||||
|
||||
self._scrollwidget = bui.scrollwidget(
|
||||
parent=self._root_widget,
|
||||
size=(self._scroll_width, self._scroll_height),
|
||||
position=(
|
||||
self._width * 0.5 - self._scroll_width * 0.5,
|
||||
scroll_bottom,
|
||||
),
|
||||
simple_culling_v=20.0,
|
||||
highlight=False,
|
||||
center_small_content_horizontally=True,
|
||||
selection_loops_to_parent=True,
|
||||
border_opacity=0.4,
|
||||
)
|
||||
bui.widget(edit=self._scrollwidget, right_widget=self._scrollwidget)
|
||||
self._subcontainer = bui.containerwidget(
|
||||
parent=self._scrollwidget,
|
||||
size=(self._sub_width, self._sub_height),
|
||||
background=False,
|
||||
selection_loops_to_parent=True,
|
||||
)
|
||||
|
||||
# Add some blotches so our contents fades out as it approaches
|
||||
# the bottom toolbar (but only in the main menu when there's
|
||||
# something down there).
|
||||
if uiscale is bui.UIScale.SMALL and bui.in_main_menu():
|
||||
blotchwidth = 500.0
|
||||
blotchheight = 200.0
|
||||
bimg = bui.imagewidget(
|
||||
parent=self._root_widget,
|
||||
texture=bui.gettexture('uiAtlas'),
|
||||
mesh_transparent=bui.getmesh('windowBGBlotch'),
|
||||
position=(
|
||||
self._width * 0.5
|
||||
- self._scroll_width * 0.5
|
||||
+ 60.0
|
||||
- blotchwidth * 0.5,
|
||||
scroll_bottom - blotchheight * 0.5,
|
||||
),
|
||||
size=(blotchwidth, blotchheight),
|
||||
color=(0.4, 0.37, 0.49),
|
||||
# color=(1, 0, 0),
|
||||
)
|
||||
bui.widget(edit=bimg, depth_range=(0.9, 1.0))
|
||||
bimg = bui.imagewidget(
|
||||
parent=self._root_widget,
|
||||
texture=bui.gettexture('uiAtlas'),
|
||||
mesh_transparent=bui.getmesh('windowBGBlotch'),
|
||||
position=(
|
||||
self._width * 0.5
|
||||
+ self._scroll_width * 0.5
|
||||
- 60.0
|
||||
- blotchwidth * 0.5,
|
||||
scroll_bottom - blotchheight * 0.5,
|
||||
),
|
||||
size=(blotchwidth, blotchheight),
|
||||
color=(0.4, 0.37, 0.49),
|
||||
# color=(1, 0, 0),
|
||||
)
|
||||
bui.widget(edit=bimg, depth_range=(0.9, 1.0))
|
||||
|
||||
self._rebuild()
|
||||
|
||||
# Rebuild periodically to pick up language changes/additions/etc.
|
||||
|
|
@ -243,8 +237,8 @@ class AdvancedSettingsWindow(bui.MainWindow):
|
|||
)
|
||||
|
||||
@override
|
||||
def on_main_window_close(self) -> None:
|
||||
self._save_state()
|
||||
def main_window_should_preserve_selection(self) -> bool:
|
||||
return True
|
||||
|
||||
@staticmethod
|
||||
def _preload_modules() -> None:
|
||||
|
|
@ -342,6 +336,11 @@ class AdvancedSettingsWindow(bui.MainWindow):
|
|||
|
||||
v = self._sub_height - 35
|
||||
|
||||
# For fullscreen scrollable, account for toolbar.
|
||||
uiscale = bui.app.ui_v1.uiscale
|
||||
if uiscale is bui.UIScale.SMALL:
|
||||
v -= 27
|
||||
|
||||
v -= self._spacing * 1.2
|
||||
|
||||
# Update our existing back button and title.
|
||||
|
|
@ -412,6 +411,7 @@ class AdvancedSettingsWindow(bui.MainWindow):
|
|||
|
||||
self._language_popup = PopupMenu(
|
||||
parent=self._subcontainer,
|
||||
button_id=f'{self.main_window_id_prefix}|language',
|
||||
position=(210, v - 19),
|
||||
width=250,
|
||||
opening_call=bui.WeakCall(self._on_menu_open),
|
||||
|
|
@ -440,6 +440,7 @@ class AdvancedSettingsWindow(bui.MainWindow):
|
|||
),
|
||||
current_choice=cur_lang,
|
||||
)
|
||||
|
||||
if self._back_button is not None:
|
||||
bui.widget(
|
||||
edit=self._language_popup.get_button(),
|
||||
|
|
@ -469,6 +470,7 @@ class AdvancedSettingsWindow(bui.MainWindow):
|
|||
this_button_width = 410
|
||||
self._translation_editor_button = bui.buttonwidget(
|
||||
parent=self._subcontainer,
|
||||
id=f'{self.main_window_id_prefix}|translationedit',
|
||||
position=(self._sub_width / 2 - this_button_width / 2, v - 24),
|
||||
size=(this_button_width, 60),
|
||||
label=bui.Lstr(
|
||||
|
|
@ -499,6 +501,7 @@ class AdvancedSettingsWindow(bui.MainWindow):
|
|||
|
||||
self._language_inform_checkbox = cbw = bui.checkboxwidget(
|
||||
parent=self._subcontainer,
|
||||
id=f'{self.main_window_id_prefix}|langinform',
|
||||
position=(50, v - 50),
|
||||
size=(self._sub_width - 100, 30),
|
||||
autoselect=True,
|
||||
|
|
@ -521,6 +524,7 @@ class AdvancedSettingsWindow(bui.MainWindow):
|
|||
|
||||
self._kick_idle_players_check_box = ConfigCheckBox(
|
||||
parent=self._subcontainer,
|
||||
check_box_id=f'{self.main_window_id_prefix}|kickidleplayers',
|
||||
position=(50, v),
|
||||
size=(self._sub_width - 100, 30),
|
||||
configkey='Kick Idle Players',
|
||||
|
|
@ -532,6 +536,7 @@ class AdvancedSettingsWindow(bui.MainWindow):
|
|||
v -= 42
|
||||
self._show_game_ping_check_box = ConfigCheckBox(
|
||||
parent=self._subcontainer,
|
||||
check_box_id=f'{self.main_window_id_prefix}|showping',
|
||||
position=(50, v),
|
||||
size=(self._sub_width - 100, 30),
|
||||
configkey='Show Ping',
|
||||
|
|
@ -543,6 +548,7 @@ class AdvancedSettingsWindow(bui.MainWindow):
|
|||
v -= 42
|
||||
self._show_demos_when_idle_check_box = ConfigCheckBox(
|
||||
parent=self._subcontainer,
|
||||
check_box_id=f'{self.main_window_id_prefix}|showdemoswhenidle',
|
||||
position=(50, v),
|
||||
size=(self._sub_width - 100, 30),
|
||||
configkey='Show Demos When Idle',
|
||||
|
|
@ -554,6 +560,9 @@ class AdvancedSettingsWindow(bui.MainWindow):
|
|||
v -= 42
|
||||
self._show_deprecated_login_types_check_box = ConfigCheckBox(
|
||||
parent=self._subcontainer,
|
||||
check_box_id=(
|
||||
f'{self.main_window_id_prefix}|showdeprecatedlogintypes'
|
||||
),
|
||||
position=(50, v),
|
||||
size=(self._sub_width - 100, 30),
|
||||
configkey='Show Deprecated Login Types',
|
||||
|
|
@ -567,6 +576,7 @@ class AdvancedSettingsWindow(bui.MainWindow):
|
|||
v -= 42
|
||||
self._disable_camera_shake_check_box = ConfigCheckBox(
|
||||
parent=self._subcontainer,
|
||||
check_box_id=f'{self.main_window_id_prefix}|disablecamerashake',
|
||||
position=(50, v),
|
||||
size=(self._sub_width - 100, 30),
|
||||
configkey='Disable Camera Shake',
|
||||
|
|
@ -580,6 +590,7 @@ class AdvancedSettingsWindow(bui.MainWindow):
|
|||
v -= 42
|
||||
self._disable_gyro_check_box = ConfigCheckBox(
|
||||
parent=self._subcontainer,
|
||||
check_box_id=f'{self.main_window_id_prefix}|disablegyro',
|
||||
position=(50, v),
|
||||
size=(self._sub_width - 100, 30),
|
||||
configkey='Disable Camera Gyro',
|
||||
|
|
@ -595,17 +606,16 @@ class AdvancedSettingsWindow(bui.MainWindow):
|
|||
v -= 42
|
||||
self._use_insecure_connections_check_box = ConfigCheckBox(
|
||||
parent=self._subcontainer,
|
||||
check_box_id=(
|
||||
f'{self.main_window_id_prefix}|useinsecureconnections'
|
||||
),
|
||||
position=(50, v),
|
||||
size=(self._sub_width - 100, 30),
|
||||
configkey='Use Insecure Connections',
|
||||
autoselect=True,
|
||||
# displayname='USE INSECURE CONNECTIONS',
|
||||
displayname=bui.Lstr(
|
||||
resource=(f'{self._r}.insecureConnectionsText')
|
||||
),
|
||||
# displayname=bui.Lstr(
|
||||
# resource=f'{self._r}.alwaysUseInternalKeyboardText'
|
||||
# ),
|
||||
scale=1.0,
|
||||
maxwidth=430,
|
||||
)
|
||||
|
|
@ -613,10 +623,6 @@ class AdvancedSettingsWindow(bui.MainWindow):
|
|||
parent=self._subcontainer,
|
||||
position=(90, v - 20),
|
||||
size=(0, 0),
|
||||
# text=(
|
||||
# 'not recommended, but may allow online play\n'
|
||||
# 'from restricted countries or networks'
|
||||
# ),
|
||||
text=bui.Lstr(
|
||||
resource=(f'{self._r}.insecureConnectionsDescriptionText')
|
||||
),
|
||||
|
|
@ -636,6 +642,9 @@ class AdvancedSettingsWindow(bui.MainWindow):
|
|||
v -= 42
|
||||
self._always_use_internal_keyboard_check_box = ConfigCheckBox(
|
||||
parent=self._subcontainer,
|
||||
check_box_id=(
|
||||
f'{self.main_window_id_prefix}|alwaysuseinternalkb'
|
||||
),
|
||||
position=(50, v),
|
||||
size=(self._sub_width - 100, 30),
|
||||
configkey='Always Use Internal Keyboard',
|
||||
|
|
@ -671,6 +680,7 @@ class AdvancedSettingsWindow(bui.MainWindow):
|
|||
this_button_width = 410
|
||||
self._modding_guide_button = bui.buttonwidget(
|
||||
parent=self._subcontainer,
|
||||
id=f'{self.main_window_id_prefix}|moddingguide',
|
||||
position=(self._sub_width / 2 - this_button_width / 2, v - 10),
|
||||
size=(this_button_width, 60),
|
||||
autoselect=True,
|
||||
|
|
@ -685,6 +695,7 @@ class AdvancedSettingsWindow(bui.MainWindow):
|
|||
|
||||
self._dev_tools_button = bui.buttonwidget(
|
||||
parent=self._subcontainer,
|
||||
id=f'{self.main_window_id_prefix}|devtools',
|
||||
position=(self._sub_width / 2 - this_button_width / 2, v - 10),
|
||||
size=(this_button_width, 60),
|
||||
autoselect=True,
|
||||
|
|
@ -723,6 +734,7 @@ class AdvancedSettingsWindow(bui.MainWindow):
|
|||
|
||||
self._show_user_mods_button = bui.buttonwidget(
|
||||
parent=self._subcontainer,
|
||||
id=f'{self.main_window_id_prefix}|showusermods',
|
||||
position=(self._sub_width / 2 - this_button_width / 2, v - 10),
|
||||
size=(this_button_width, 60),
|
||||
autoselect=True,
|
||||
|
|
@ -735,6 +747,7 @@ class AdvancedSettingsWindow(bui.MainWindow):
|
|||
|
||||
self._plugins_button = bui.buttonwidget(
|
||||
parent=self._subcontainer,
|
||||
id=f'{self.main_window_id_prefix}|plugins',
|
||||
position=(self._sub_width / 2 - this_button_width / 2, v - 10),
|
||||
size=(this_button_width, 60),
|
||||
autoselect=True,
|
||||
|
|
@ -750,6 +763,7 @@ class AdvancedSettingsWindow(bui.MainWindow):
|
|||
v -= self._extra_button_spacing
|
||||
self._vr_test_button = bui.buttonwidget(
|
||||
parent=self._subcontainer,
|
||||
id=f'{self.main_window_id_prefix}|vrtest',
|
||||
position=(self._sub_width / 2 - this_button_width / 2, v - 14),
|
||||
size=(this_button_width, 60),
|
||||
autoselect=True,
|
||||
|
|
@ -765,6 +779,7 @@ class AdvancedSettingsWindow(bui.MainWindow):
|
|||
v -= self._extra_button_spacing
|
||||
self._net_test_button = bui.buttonwidget(
|
||||
parent=self._subcontainer,
|
||||
id=f'{self.main_window_id_prefix}|nettest',
|
||||
position=(self._sub_width / 2 - this_button_width / 2, v - 14),
|
||||
size=(this_button_width, 60),
|
||||
autoselect=True,
|
||||
|
|
@ -778,6 +793,7 @@ class AdvancedSettingsWindow(bui.MainWindow):
|
|||
v -= 70
|
||||
self._benchmarks_button = bui.buttonwidget(
|
||||
parent=self._subcontainer,
|
||||
id=f'{self.main_window_id_prefix}|benchmarks',
|
||||
position=(self._sub_width / 2 - this_button_width / 2, v - 14),
|
||||
size=(this_button_width, 60),
|
||||
autoselect=True,
|
||||
|
|
@ -789,6 +805,7 @@ class AdvancedSettingsWindow(bui.MainWindow):
|
|||
v -= 100
|
||||
self._send_info_button = bui.buttonwidget(
|
||||
parent=self._subcontainer,
|
||||
id=f'{self.main_window_id_prefix}|sendinfo',
|
||||
position=(self._sub_width / 2 - this_button_width / 2, v - 14),
|
||||
size=(this_button_width, 60),
|
||||
autoselect=True,
|
||||
|
|
@ -798,7 +815,7 @@ class AdvancedSettingsWindow(bui.MainWindow):
|
|||
)
|
||||
|
||||
for child in self._subcontainer.get_children():
|
||||
bui.widget(edit=child, show_buffer_bottom=60, show_buffer_top=20)
|
||||
bui.widget(edit=child, show_buffer_bottom=80, show_buffer_top=80)
|
||||
|
||||
pbtn = bui.get_special_widget('squad_button')
|
||||
bui.widget(edit=self._scrollwidget, right_widget=pbtn)
|
||||
|
|
@ -808,8 +825,6 @@ class AdvancedSettingsWindow(bui.MainWindow):
|
|||
left_widget=bui.get_special_widget('back_button'),
|
||||
)
|
||||
|
||||
self._restore_state()
|
||||
|
||||
def _show_restart_needed(self, value: Any) -> None:
|
||||
del value # Unused.
|
||||
bui.screenmessage(
|
||||
|
|
@ -827,20 +842,14 @@ class AdvancedSettingsWindow(bui.MainWindow):
|
|||
def _on_vr_test_press(self) -> None:
|
||||
from bauiv1lib.settings.vrtesting import VRTestingWindow
|
||||
|
||||
# no-op if we're not in control.
|
||||
if not self.main_window_has_control():
|
||||
return
|
||||
|
||||
self.main_window_replace(VRTestingWindow(transition='in_right'))
|
||||
self.main_window_replace(lambda: VRTestingWindow(transition='in_right'))
|
||||
|
||||
def _on_net_test_press(self) -> None:
|
||||
from bauiv1lib.settings.nettesting import NetTestingWindow
|
||||
|
||||
# no-op if we're not in control.
|
||||
if not self.main_window_has_control():
|
||||
return
|
||||
|
||||
self.main_window_replace(NetTestingWindow(transition='in_right'))
|
||||
self.main_window_replace(
|
||||
lambda: NetTestingWindow(transition='in_right')
|
||||
)
|
||||
|
||||
def _on_friend_promo_code_press(self) -> None:
|
||||
from bauiv1lib import appinvite
|
||||
|
|
@ -857,192 +866,32 @@ class AdvancedSettingsWindow(bui.MainWindow):
|
|||
def _on_plugins_button_press(self) -> None:
|
||||
from bauiv1lib.settings.plugins import PluginWindow
|
||||
|
||||
# no-op if we're not in control.
|
||||
if not self.main_window_has_control():
|
||||
return
|
||||
|
||||
self.main_window_replace(
|
||||
PluginWindow(origin_widget=self._plugins_button)
|
||||
lambda: PluginWindow(origin_widget=self._plugins_button)
|
||||
)
|
||||
|
||||
def _on_dev_tools_button_press(self) -> None:
|
||||
# pylint: disable=cyclic-import
|
||||
from bauiv1lib.settings.devtools import DevToolsWindow
|
||||
|
||||
# no-op if we're not in control.
|
||||
if not self.main_window_has_control():
|
||||
return
|
||||
|
||||
self.main_window_replace(
|
||||
DevToolsWindow(origin_widget=self._dev_tools_button)
|
||||
lambda: DevToolsWindow(origin_widget=self._dev_tools_button)
|
||||
)
|
||||
|
||||
def _on_send_info_press(self) -> None:
|
||||
from bauiv1lib.sendinfo import SendInfoWindow
|
||||
|
||||
# no-op if we're not in control.
|
||||
if not self.main_window_has_control():
|
||||
return
|
||||
|
||||
self.main_window_replace(
|
||||
SendInfoWindow(origin_widget=self._send_info_button)
|
||||
lambda: SendInfoWindow(origin_widget=self._send_info_button)
|
||||
)
|
||||
|
||||
def _on_benchmark_press(self) -> None:
|
||||
from bauiv1lib.settings.benchmarks import BenchmarksAndStressTestsWindow
|
||||
|
||||
# no-op if we're not in control.
|
||||
if not self.main_window_has_control():
|
||||
return
|
||||
|
||||
self.main_window_replace(
|
||||
BenchmarksAndStressTestsWindow(transition='in_right')
|
||||
lambda: BenchmarksAndStressTestsWindow(transition='in_right')
|
||||
)
|
||||
|
||||
def _save_state(self) -> None:
|
||||
# pylint: disable=too-many-branches
|
||||
# pylint: disable=too-many-statements
|
||||
try:
|
||||
sel = self._root_widget.get_selected_child()
|
||||
if sel == self._scrollwidget:
|
||||
sel = self._subcontainer.get_selected_child()
|
||||
if sel == self._vr_test_button:
|
||||
sel_name = 'VRTest'
|
||||
elif sel == self._net_test_button:
|
||||
sel_name = 'NetTest'
|
||||
elif sel == self._send_info_button:
|
||||
sel_name = 'SendInfo'
|
||||
elif sel == self._benchmarks_button:
|
||||
sel_name = 'Benchmarks'
|
||||
elif sel == self._kick_idle_players_check_box.widget:
|
||||
sel_name = 'KickIdlePlayers'
|
||||
elif sel == self._show_demos_when_idle_check_box.widget:
|
||||
sel_name = 'ShowDemosWhenIdle'
|
||||
elif sel == self._show_deprecated_login_types_check_box.widget:
|
||||
sel_name = 'ShowDeprecatedLoginTypes'
|
||||
elif sel == self._show_game_ping_check_box.widget:
|
||||
sel_name = 'ShowPing'
|
||||
elif sel == self._disable_camera_shake_check_box.widget:
|
||||
sel_name = 'DisableCameraShake'
|
||||
elif (
|
||||
self._always_use_internal_keyboard_check_box is not None
|
||||
and sel
|
||||
== self._always_use_internal_keyboard_check_box.widget
|
||||
):
|
||||
sel_name = 'AlwaysUseInternalKeyboard'
|
||||
elif (
|
||||
self._use_insecure_connections_check_box is not None
|
||||
and sel == self._use_insecure_connections_check_box.widget
|
||||
):
|
||||
sel_name = 'UseInsecureConnections'
|
||||
elif (
|
||||
self._disable_gyro_check_box is not None
|
||||
and sel == self._disable_gyro_check_box.widget
|
||||
):
|
||||
sel_name = 'DisableGyro'
|
||||
elif (
|
||||
self._language_popup is not None
|
||||
and sel == self._language_popup.get_button()
|
||||
):
|
||||
sel_name = 'Languages'
|
||||
elif sel == self._translation_editor_button:
|
||||
sel_name = 'TranslationEditor'
|
||||
elif sel == self._show_user_mods_button:
|
||||
sel_name = 'ShowUserMods'
|
||||
elif sel == self._plugins_button:
|
||||
sel_name = 'Plugins'
|
||||
elif sel == self._dev_tools_button:
|
||||
sel_name = 'DevTools'
|
||||
elif sel == self._modding_guide_button:
|
||||
sel_name = 'ModdingGuide'
|
||||
elif sel == self._language_inform_checkbox:
|
||||
sel_name = 'LangInform'
|
||||
else:
|
||||
raise ValueError(f'unrecognized selection \'{sel}\'')
|
||||
elif sel == self._back_button:
|
||||
sel_name = 'Back'
|
||||
else:
|
||||
raise ValueError(f'unrecognized selection \'{sel}\'')
|
||||
assert bui.app.classic is not None
|
||||
bui.app.ui_v1.window_states[type(self)] = {'sel_name': sel_name}
|
||||
|
||||
except Exception:
|
||||
logging.exception('Error saving state for %s.', self)
|
||||
|
||||
def _restore_state(self) -> None:
|
||||
# pylint: disable=too-many-branches
|
||||
# pylint: disable=too-many-statements
|
||||
try:
|
||||
assert bui.app.classic is not None
|
||||
sel_name = bui.app.ui_v1.window_states.get(type(self), {}).get(
|
||||
'sel_name'
|
||||
)
|
||||
if sel_name == 'Back':
|
||||
sel = self._back_button
|
||||
else:
|
||||
bui.containerwidget(
|
||||
edit=self._root_widget, selected_child=self._scrollwidget
|
||||
)
|
||||
if sel_name == 'VRTest':
|
||||
sel = self._vr_test_button
|
||||
elif sel_name == 'NetTest':
|
||||
sel = self._net_test_button
|
||||
elif sel_name == 'SendInfo':
|
||||
sel = self._send_info_button
|
||||
elif sel_name == 'Benchmarks':
|
||||
sel = self._benchmarks_button
|
||||
elif sel_name == 'KickIdlePlayers':
|
||||
sel = self._kick_idle_players_check_box.widget
|
||||
elif sel_name == 'ShowDemosWhenIdle':
|
||||
sel = self._show_demos_when_idle_check_box.widget
|
||||
elif sel_name == 'ShowDeprecatedLoginTypes':
|
||||
sel = self._show_deprecated_login_types_check_box.widget
|
||||
elif sel_name == 'ShowPing':
|
||||
sel = self._show_game_ping_check_box.widget
|
||||
elif sel_name == 'DisableCameraShake':
|
||||
sel = self._disable_camera_shake_check_box.widget
|
||||
elif (
|
||||
sel_name == 'AlwaysUseInternalKeyboard'
|
||||
and self._always_use_internal_keyboard_check_box is not None
|
||||
):
|
||||
sel = self._always_use_internal_keyboard_check_box.widget
|
||||
elif (
|
||||
sel_name == 'UseInsecureConnections'
|
||||
and self._use_insecure_connections_check_box is not None
|
||||
):
|
||||
sel = self._use_insecure_connections_check_box.widget
|
||||
elif (
|
||||
sel_name == 'DisableGyro'
|
||||
and self._disable_gyro_check_box is not None
|
||||
):
|
||||
sel = self._disable_gyro_check_box.widget
|
||||
elif (
|
||||
sel_name == 'Languages' and self._language_popup is not None
|
||||
):
|
||||
sel = self._language_popup.get_button()
|
||||
elif sel_name == 'TranslationEditor':
|
||||
sel = self._translation_editor_button
|
||||
elif sel_name == 'ShowUserMods':
|
||||
sel = self._show_user_mods_button
|
||||
elif sel_name == 'Plugins':
|
||||
sel = self._plugins_button
|
||||
elif sel_name == 'DevTools':
|
||||
sel = self._dev_tools_button
|
||||
elif sel_name == 'ModdingGuide':
|
||||
sel = self._modding_guide_button
|
||||
elif sel_name == 'LangInform':
|
||||
sel = self._language_inform_checkbox
|
||||
else:
|
||||
sel = None
|
||||
if sel is not None:
|
||||
bui.containerwidget(
|
||||
edit=self._subcontainer,
|
||||
selected_child=sel,
|
||||
visible_child=sel,
|
||||
)
|
||||
except Exception:
|
||||
logging.exception('Error restoring state for %s.', self)
|
||||
|
||||
def _on_menu_open(self) -> None:
|
||||
self._menu_open = True
|
||||
|
||||
|
|
@ -1062,9 +911,8 @@ class AdvancedSettingsWindow(bui.MainWindow):
|
|||
|
||||
cfg.apply_and_commit()
|
||||
|
||||
self._save_state()
|
||||
self.main_window_save_shared_state()
|
||||
|
||||
# bui.app.lang.setlanguage(None if choice == 'Auto' else choice)
|
||||
bui.apptimer(0.1, bui.WeakCall(self._rebuild))
|
||||
|
||||
def _completed_langs_cb(self, results: dict[str, Any] | None) -> None:
|
||||
|
|
|
|||
|
|
@ -5,7 +5,6 @@
|
|||
from __future__ import annotations
|
||||
|
||||
from typing import TYPE_CHECKING, override
|
||||
import logging
|
||||
|
||||
import bauiv1 as bui
|
||||
|
||||
|
|
@ -61,10 +60,6 @@ class AllSettingsWindow(bui.MainWindow):
|
|||
# offset by half the width/height of our target area.
|
||||
yoffs = 0.5 * height + 0.5 * target_height + 30.0
|
||||
|
||||
# scroll_width = target_width
|
||||
# scroll_height = target_height - 25
|
||||
# scroll_bottom = yoffs - 54 - scroll_height
|
||||
|
||||
super().__init__(
|
||||
root_widget=bui.containerwidget(
|
||||
size=(width, height),
|
||||
|
|
@ -90,6 +85,7 @@ class AllSettingsWindow(bui.MainWindow):
|
|||
else:
|
||||
self._back_button = btn = bui.buttonwidget(
|
||||
parent=self._root_widget,
|
||||
id=f'{self.main_window_id_prefix}|back',
|
||||
autoselect=True,
|
||||
position=(50, yoffs - 80.0),
|
||||
size=(70, 70),
|
||||
|
|
@ -134,18 +130,20 @@ class AllSettingsWindow(bui.MainWindow):
|
|||
y = height * 0.5 - bheight * 0.5 + ynudge
|
||||
|
||||
def _button(
|
||||
*,
|
||||
widgetid: str,
|
||||
position: tuple[float, float],
|
||||
label: bui.Lstr,
|
||||
call: Callable[[], None],
|
||||
texture: bui.Texture,
|
||||
imgsize: float,
|
||||
*,
|
||||
color: tuple[float, float, float] = (1.0, 1.0, 1.0),
|
||||
imgoffs: tuple[float, float] = (0.0, 0.0),
|
||||
) -> bui.Widget:
|
||||
x, y = position
|
||||
btn = bui.buttonwidget(
|
||||
parent=self._root_widget,
|
||||
id=widgetid,
|
||||
autoselect=True,
|
||||
position=(x, y),
|
||||
size=(bwidth, bheight),
|
||||
|
|
@ -178,6 +176,7 @@ class AllSettingsWindow(bui.MainWindow):
|
|||
return btn
|
||||
|
||||
self._controllers_button = _button(
|
||||
widgetid=f'{self.main_window_id_prefix}|controllers',
|
||||
position=(x, y),
|
||||
label=bui.Lstr(resource=f'{self._r}.controllersText'),
|
||||
call=self._do_controllers,
|
||||
|
|
@ -188,6 +187,7 @@ class AllSettingsWindow(bui.MainWindow):
|
|||
x += bwidth + margin
|
||||
|
||||
self._graphics_button = _button(
|
||||
widgetid=f'{self.main_window_id_prefix}|graphics',
|
||||
position=(x, y),
|
||||
label=bui.Lstr(resource=f'{self._r}.graphicsText'),
|
||||
call=self._do_graphics,
|
||||
|
|
@ -198,6 +198,7 @@ class AllSettingsWindow(bui.MainWindow):
|
|||
x += bwidth + margin
|
||||
|
||||
self._audio_button = _button(
|
||||
widgetid=f'{self.main_window_id_prefix}|audio',
|
||||
position=(x, y),
|
||||
label=bui.Lstr(resource=f'{self._r}.audioText'),
|
||||
call=self._do_audio,
|
||||
|
|
@ -208,6 +209,7 @@ class AllSettingsWindow(bui.MainWindow):
|
|||
x += bwidth + margin
|
||||
|
||||
self._advanced_button = _button(
|
||||
widgetid=f'{self.main_window_id_prefix}|advanced',
|
||||
position=(x, y),
|
||||
label=bui.Lstr(resource=f'{self._r}.advancedText'),
|
||||
call=self._do_advanced,
|
||||
|
|
@ -217,6 +219,11 @@ class AllSettingsWindow(bui.MainWindow):
|
|||
imgoffs=(0, 5.0),
|
||||
)
|
||||
|
||||
# Select controllers by default.
|
||||
bui.containerwidget(
|
||||
edit=self._root_widget, selected_child=self._controllers_button
|
||||
)
|
||||
|
||||
# Hmm; we're now wide enough that being limited to pressing up
|
||||
# might be ok.
|
||||
if bool(True):
|
||||
|
|
@ -231,8 +238,6 @@ class AllSettingsWindow(bui.MainWindow):
|
|||
right_widget=bui.get_special_widget('squad_button'),
|
||||
)
|
||||
|
||||
self._restore_state()
|
||||
|
||||
@override
|
||||
def get_main_window_state(self) -> bui.MainWindowState:
|
||||
# Support recreating our window for back/refresh purposes.
|
||||
|
|
@ -244,8 +249,8 @@ class AllSettingsWindow(bui.MainWindow):
|
|||
)
|
||||
|
||||
@override
|
||||
def on_main_window_close(self) -> None:
|
||||
self._save_state()
|
||||
def main_window_should_preserve_selection(self) -> bool:
|
||||
return True
|
||||
|
||||
@staticmethod
|
||||
def _preload_modules() -> None:
|
||||
|
|
@ -260,90 +265,32 @@ class AllSettingsWindow(bui.MainWindow):
|
|||
# pylint: disable=cyclic-import
|
||||
from bauiv1lib.settings.controls import ControlsSettingsWindow
|
||||
|
||||
# no-op if we're not in control.
|
||||
if not self.main_window_has_control():
|
||||
return
|
||||
|
||||
self.main_window_replace(
|
||||
ControlsSettingsWindow(origin_widget=self._controllers_button)
|
||||
lambda: ControlsSettingsWindow(
|
||||
origin_widget=self._controllers_button
|
||||
)
|
||||
)
|
||||
|
||||
def _do_graphics(self) -> None:
|
||||
# pylint: disable=cyclic-import
|
||||
from bauiv1lib.settings.graphics import GraphicsSettingsWindow
|
||||
|
||||
# no-op if we're not in control.
|
||||
if not self.main_window_has_control():
|
||||
return
|
||||
|
||||
self.main_window_replace(
|
||||
GraphicsSettingsWindow(origin_widget=self._graphics_button)
|
||||
lambda: GraphicsSettingsWindow(origin_widget=self._graphics_button)
|
||||
)
|
||||
|
||||
def _do_audio(self) -> None:
|
||||
# pylint: disable=cyclic-import
|
||||
from bauiv1lib.settings.audio import AudioSettingsWindow
|
||||
|
||||
# no-op if we're not in control.
|
||||
if not self.main_window_has_control():
|
||||
return
|
||||
|
||||
self.main_window_replace(
|
||||
AudioSettingsWindow(origin_widget=self._audio_button)
|
||||
lambda: AudioSettingsWindow(origin_widget=self._audio_button)
|
||||
)
|
||||
|
||||
def _do_advanced(self) -> None:
|
||||
# pylint: disable=cyclic-import
|
||||
from bauiv1lib.settings.advanced import AdvancedSettingsWindow
|
||||
|
||||
# no-op if we're not in control.
|
||||
if not self.main_window_has_control():
|
||||
return
|
||||
|
||||
self.main_window_replace(
|
||||
AdvancedSettingsWindow(origin_widget=self._advanced_button)
|
||||
lambda: AdvancedSettingsWindow(origin_widget=self._advanced_button)
|
||||
)
|
||||
|
||||
def _save_state(self) -> None:
|
||||
try:
|
||||
sel = self._root_widget.get_selected_child()
|
||||
if sel == self._controllers_button:
|
||||
sel_name = 'Controllers'
|
||||
elif sel == self._graphics_button:
|
||||
sel_name = 'Graphics'
|
||||
elif sel == self._audio_button:
|
||||
sel_name = 'Audio'
|
||||
elif sel == self._advanced_button:
|
||||
sel_name = 'Advanced'
|
||||
elif sel == self._back_button:
|
||||
sel_name = 'Back'
|
||||
else:
|
||||
raise ValueError(f'unrecognized selection \'{sel}\'')
|
||||
assert bui.app.classic is not None
|
||||
bui.app.ui_v1.window_states[type(self)] = {'sel_name': sel_name}
|
||||
except Exception:
|
||||
logging.exception('Error saving state for %s.', self)
|
||||
|
||||
def _restore_state(self) -> None:
|
||||
try:
|
||||
assert bui.app.classic is not None
|
||||
sel_name = bui.app.ui_v1.window_states.get(type(self), {}).get(
|
||||
'sel_name'
|
||||
)
|
||||
sel: bui.Widget | None
|
||||
if sel_name == 'Controllers':
|
||||
sel = self._controllers_button
|
||||
elif sel_name == 'Graphics':
|
||||
sel = self._graphics_button
|
||||
elif sel_name == 'Audio':
|
||||
sel = self._audio_button
|
||||
elif sel_name == 'Advanced':
|
||||
sel = self._advanced_button
|
||||
elif sel_name == 'Back':
|
||||
sel = self._back_button
|
||||
else:
|
||||
sel = self._controllers_button
|
||||
if sel is not None:
|
||||
bui.containerwidget(edit=self._root_widget, selected_child=sel)
|
||||
except Exception:
|
||||
logging.exception('Error restoring state for %s.', self)
|
||||
|
|
|
|||
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Add a link
Reference in a new issue