Merge branch 'efroemling:main' into main

This commit is contained in:
Vishal 2024-09-04 18:02:41 +05:30 committed by GitHub
commit a9efb4a0ca
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
33 changed files with 770 additions and 736 deletions

56
.efrocachemap generated
View File

@ -4096,26 +4096,26 @@
"build/assets/windows/Win32/ucrtbased.dll": "2def5335207d41b21b9823f6805997f1", "build/assets/windows/Win32/ucrtbased.dll": "2def5335207d41b21b9823f6805997f1",
"build/assets/windows/Win32/vc_redist.x86.exe": "b08a55e2e77623fe657bea24f223a3ae", "build/assets/windows/Win32/vc_redist.x86.exe": "b08a55e2e77623fe657bea24f223a3ae",
"build/assets/windows/Win32/vcruntime140d.dll": "865b2af4d1e26a1a8073c89acb06e599", "build/assets/windows/Win32/vcruntime140d.dll": "865b2af4d1e26a1a8073c89acb06e599",
"build/prefab/full/linux_arm64_gui/debug/ballisticakit": "d6246c930e7e2d2d9a6aff6788f33b69", "build/prefab/full/linux_arm64_gui/debug/ballisticakit": "a0e1890fc3d156a1d480e7cbba7295bd",
"build/prefab/full/linux_arm64_gui/release/ballisticakit": "001c67c4d4d33e20755399e0b2ed1593", "build/prefab/full/linux_arm64_gui/release/ballisticakit": "2ea3ddfd5280b5fa721a82286767208a",
"build/prefab/full/linux_arm64_server/debug/dist/ballisticakit_headless": "6f6bfaf19daf6e866f4fecbc889b8854", "build/prefab/full/linux_arm64_server/debug/dist/ballisticakit_headless": "f9860dbec27801cf8b24c3b42e0b62bd",
"build/prefab/full/linux_arm64_server/release/dist/ballisticakit_headless": "ebc7449903d7868c631c504aed10f371", "build/prefab/full/linux_arm64_server/release/dist/ballisticakit_headless": "a53de05a4a3c3c2e766f488b0b53230d",
"build/prefab/full/linux_x86_64_gui/debug/ballisticakit": "91f03e7dbfc0d7eb75568704f681fba5", "build/prefab/full/linux_x86_64_gui/debug/ballisticakit": "43485a1c27605267ee83d27961b61017",
"build/prefab/full/linux_x86_64_gui/release/ballisticakit": "0b32b6eb05df0a7e23e55ff2e7235a8f", "build/prefab/full/linux_x86_64_gui/release/ballisticakit": "65fe6d346dfd844925a07278a73bbb39",
"build/prefab/full/linux_x86_64_server/debug/dist/ballisticakit_headless": "4ed4b63cc506815f759412a295bfe088", "build/prefab/full/linux_x86_64_server/debug/dist/ballisticakit_headless": "bc277e964bc08d0bd086f836732901f8",
"build/prefab/full/linux_x86_64_server/release/dist/ballisticakit_headless": "1890731fb8a2fb43c4cde72af9d7a4e6", "build/prefab/full/linux_x86_64_server/release/dist/ballisticakit_headless": "738e76cf0dc643a016f16f94503767db",
"build/prefab/full/mac_arm64_gui/debug/ballisticakit": "1a4764a504dcb20591ac3472c48db8d9", "build/prefab/full/mac_arm64_gui/debug/ballisticakit": "949fd54340bc597c19ada580f59c30a9",
"build/prefab/full/mac_arm64_gui/release/ballisticakit": "9bfd6234aac4ffd7d7601a40017a73be", "build/prefab/full/mac_arm64_gui/release/ballisticakit": "f0f1037044f841e6817e970ba67f3c02",
"build/prefab/full/mac_arm64_server/debug/dist/ballisticakit_headless": "941f8fb79d54522ca5df97a735c3babe", "build/prefab/full/mac_arm64_server/debug/dist/ballisticakit_headless": "3990893e788968fe74e98aa419f28397",
"build/prefab/full/mac_arm64_server/release/dist/ballisticakit_headless": "290a3d9840efc5e88532e13b76b3ae6b", "build/prefab/full/mac_arm64_server/release/dist/ballisticakit_headless": "0c5fbd4cd45f944f02d7482e08d4b5d5",
"build/prefab/full/mac_x86_64_gui/debug/ballisticakit": "621a6684b54b0a9101f808209bcca1ee", "build/prefab/full/mac_x86_64_gui/debug/ballisticakit": "c39bcf72ef41865429f3df1d3ad586d0",
"build/prefab/full/mac_x86_64_gui/release/ballisticakit": "43c8b0b29e5fb257e4cde0f7fce1c680", "build/prefab/full/mac_x86_64_gui/release/ballisticakit": "37cb193fec5c61443fafa24d3ae96447",
"build/prefab/full/mac_x86_64_server/debug/dist/ballisticakit_headless": "9a30c6b88ffc11bdf6765780616d8ba1", "build/prefab/full/mac_x86_64_server/debug/dist/ballisticakit_headless": "e454f30500a3ef2c0e4248388338db89",
"build/prefab/full/mac_x86_64_server/release/dist/ballisticakit_headless": "8a8f63850fc296b060a994f4e001d74f", "build/prefab/full/mac_x86_64_server/release/dist/ballisticakit_headless": "ddb81916ad653ccb38be9ffc9f98e7f3",
"build/prefab/full/windows_x86_gui/debug/BallisticaKit.exe": "2864e9ae1d2d566def85f1cdf6e863fe", "build/prefab/full/windows_x86_gui/debug/BallisticaKit.exe": "18766baaa9e1a23e785aceb9f6be0d3a",
"build/prefab/full/windows_x86_gui/release/BallisticaKit.exe": "dc0db7ca99661a896634fc05187a5c75", "build/prefab/full/windows_x86_gui/release/BallisticaKit.exe": "b687f66795a831f0dfd7e15aa7a908b2",
"build/prefab/full/windows_x86_server/debug/dist/BallisticaKitHeadless.exe": "f56bb0fafe0a45cecdcd06a10a6924c9", "build/prefab/full/windows_x86_server/debug/dist/BallisticaKitHeadless.exe": "146168838c8e9cdbd5c016772b68e91a",
"build/prefab/full/windows_x86_server/release/dist/BallisticaKitHeadless.exe": "e3a58b09fea193d78187f97cf922717d", "build/prefab/full/windows_x86_server/release/dist/BallisticaKitHeadless.exe": "f0ee92768afe3211aeab356539d58876",
"build/prefab/lib/linux_arm64_gui/debug/libballisticaplus.a": "73ad3303fe1a82005918fbc5dae3446c", "build/prefab/lib/linux_arm64_gui/debug/libballisticaplus.a": "73ad3303fe1a82005918fbc5dae3446c",
"build/prefab/lib/linux_arm64_gui/release/libballisticaplus.a": "fa659b5d6119acba6570c92ce4d35ae2", "build/prefab/lib/linux_arm64_gui/release/libballisticaplus.a": "fa659b5d6119acba6570c92ce4d35ae2",
"build/prefab/lib/linux_arm64_server/debug/libballisticaplus.a": "73ad3303fe1a82005918fbc5dae3446c", "build/prefab/lib/linux_arm64_server/debug/libballisticaplus.a": "73ad3303fe1a82005918fbc5dae3446c",
@ -4132,14 +4132,14 @@
"build/prefab/lib/mac_x86_64_gui/release/libballisticaplus.a": "3e5c5fd0a09f55ba7b05ce1e2ec7171e", "build/prefab/lib/mac_x86_64_gui/release/libballisticaplus.a": "3e5c5fd0a09f55ba7b05ce1e2ec7171e",
"build/prefab/lib/mac_x86_64_server/debug/libballisticaplus.a": "1659535e95e3047fda529543e265ac97", "build/prefab/lib/mac_x86_64_server/debug/libballisticaplus.a": "1659535e95e3047fda529543e265ac97",
"build/prefab/lib/mac_x86_64_server/release/libballisticaplus.a": "3e5c5fd0a09f55ba7b05ce1e2ec7171e", "build/prefab/lib/mac_x86_64_server/release/libballisticaplus.a": "3e5c5fd0a09f55ba7b05ce1e2ec7171e",
"build/prefab/lib/windows/Debug_Win32/BallisticaKitGenericPlus.lib": "6b6aae30362b1e9aaf3f1a8daae0b0b4", "build/prefab/lib/windows/Debug_Win32/BallisticaKitGenericPlus.lib": "07c51c6015b17d6ad2e32ac6bad9aaee",
"build/prefab/lib/windows/Debug_Win32/BallisticaKitGenericPlus.pdb": "25db08ee2b4c79ca631e517b622c741d", "build/prefab/lib/windows/Debug_Win32/BallisticaKitGenericPlus.pdb": "d9dc89c65a1f5ae3a0a4e25636129bb7",
"build/prefab/lib/windows/Debug_Win32/BallisticaKitHeadlessPlus.lib": "2ea419644782c4c52d1fa784d0ced86d", "build/prefab/lib/windows/Debug_Win32/BallisticaKitHeadlessPlus.lib": "8afac2e5edb1586137c60ce1d4829d0a",
"build/prefab/lib/windows/Debug_Win32/BallisticaKitHeadlessPlus.pdb": "c40627a46ee127997153df041e47eda5", "build/prefab/lib/windows/Debug_Win32/BallisticaKitHeadlessPlus.pdb": "c9bfc854e52b6d2270d748ce80f45342",
"build/prefab/lib/windows/Release_Win32/BallisticaKitGenericPlus.lib": "fff81b29e2d86031ec25c4a672a243e7", "build/prefab/lib/windows/Release_Win32/BallisticaKitGenericPlus.lib": "ec65affda58721c395d5188810195443",
"build/prefab/lib/windows/Release_Win32/BallisticaKitGenericPlus.pdb": "2a87e2f53c658d0be3918476cd8a04ee", "build/prefab/lib/windows/Release_Win32/BallisticaKitGenericPlus.pdb": "e16447cfaffecc545ae970aff2f09d16",
"build/prefab/lib/windows/Release_Win32/BallisticaKitHeadlessPlus.lib": "1e91536979b318b9a8325ab0e871ecec", "build/prefab/lib/windows/Release_Win32/BallisticaKitHeadlessPlus.lib": "1f5ba81d75d56eb680bb7f3aefaae5b6",
"build/prefab/lib/windows/Release_Win32/BallisticaKitHeadlessPlus.pdb": "c6506399a93ef93e8322425f78e2e6b0", "build/prefab/lib/windows/Release_Win32/BallisticaKitHeadlessPlus.pdb": "44bc0d1aab93081db335ebd837f44632",
"src/assets/ba_data/python/babase/_mgen/__init__.py": "f885fed7f2ed98ff2ba271f9dbe3391c", "src/assets/ba_data/python/babase/_mgen/__init__.py": "f885fed7f2ed98ff2ba271f9dbe3391c",
"src/assets/ba_data/python/babase/_mgen/enums.py": "cb299985623bbcc86015cb103a424ae6", "src/assets/ba_data/python/babase/_mgen/enums.py": "cb299985623bbcc86015cb103a424ae6",
"src/ballistica/base/mgen/pyembed/binding_base.inc": "efa61468cf098f77cc6a234461d8b86d", "src/ballistica/base/mgen/pyembed/binding_base.inc": "efa61468cf098f77cc6a234461d8b86d",

View File

@ -1,4 +1,4 @@
### 1.7.37 (build 21977, api 9, 2024-08-30) ### 1.7.37 (build 21985, api 9, 2024-09-03)
- Bumping api version to 9. As you'll see below, there's some UI changes that - Bumping api version to 9. As you'll see below, there's some UI changes that
will require a bit of work for any UI mods to adapt to. If your mods don't will require a bit of work for any UI mods to adapt to. If your mods don't
touch UI stuff at all you can simply bump your api version and call it a day. touch UI stuff at all you can simply bump your api version and call it a day.

View File

@ -6,8 +6,8 @@ mypy==1.11.2
pbxproj==4.2.1 pbxproj==4.2.1
pdoc==14.6.1 pdoc==14.6.1
pur==7.3.2 pur==7.3.2
pylint==3.2.6 pylint==3.2.7
pylsp-mypy==0.6.8 pylsp-mypy==0.6.9
pytest==8.3.2 pytest==8.3.2
python-daemon==3.0.1 python-daemon==3.0.1
python-lsp-black==2.0.0 python-lsp-black==2.0.0

View File

@ -112,10 +112,6 @@ class ClassicAppSubsystem(babase.AppSubsystem):
self.invite_confirm_windows: list[Any] = [] # FIXME: Don't use Any. self.invite_confirm_windows: list[Any] = [] # FIXME: Don't use Any.
self.party_window: weakref.ref[PartyWindow] | None = None self.party_window: weakref.ref[PartyWindow] | None = None
self.main_menu_resume_callbacks: list = [] self.main_menu_resume_callbacks: list = []
# Switch our overall game selection UI flow between Play and
# Private-party playlist selection modes; should do this in
# a more elegant way once we revamp high level UI stuff a bit.
self.selecting_private_party_playlist: bool = False
# Store. # Store.
self.store_layout: dict[str, list[dict[str, Any]]] | None = None self.store_layout: dict[str, list[dict[str, Any]]] | None = None

View File

@ -52,7 +52,7 @@ if TYPE_CHECKING:
# Build number and version of the ballistica binary we expect to be # Build number and version of the ballistica binary we expect to be
# using. # using.
TARGET_BALLISTICA_BUILD = 21977 TARGET_BALLISTICA_BUILD = 21985
TARGET_BALLISTICA_VERSION = '1.7.37' TARGET_BALLISTICA_VERSION = '1.7.37'

View File

@ -64,43 +64,43 @@ class GameActivity(Activity[PlayerT, TeamT]):
# (unless overridden by the map). # (unless overridden by the map).
default_music: bascenev1.MusicType | None = None default_music: bascenev1.MusicType | None = None
@classmethod # @classmethod
def create_settings_ui( # def create_settings_ui(
cls, # cls,
sessiontype: type[bascenev1.Session], # sessiontype: type[bascenev1.Session],
settings: dict | None, # settings: dict | None,
completion_call: Callable[[dict | None], None], # completion_call: Callable[[dict | None], None],
) -> None: # ) -> None:
"""Launch an in-game UI to configure settings for a game type. # """Launch an in-game UI to configure settings for a game type.
'sessiontype' should be the bascenev1.Session class the game will # 'sessiontype' should be the bascenev1.Session class the game will
be used in. # be used in.
'settings' should be an existing settings dict (implies 'edit' # 'settings' should be an existing settings dict (implies 'edit'
ui mode) or None (implies 'add' ui mode). # ui mode) or None (implies 'add' ui mode).
'completion_call' will be called with a filled-out settings dict on # 'completion_call' will be called with a filled-out settings dict on
success or None on cancel. # success or None on cancel.
Generally subclasses don't need to override this; if they override # Generally subclasses don't need to override this; if they override
bascenev1.GameActivity.get_available_settings() and # bascenev1.GameActivity.get_available_settings() and
bascenev1.GameActivity.get_supported_maps() they can just rely on # bascenev1.GameActivity.get_supported_maps() they can just rely on
the default implementation here which calls those methods. # the default implementation here which calls those methods.
""" # """
# pylint: disable=cyclic-import # # pylint: disable=cyclic-import
from bauiv1lib.playlist.editgame import PlaylistEditGameWindow # from bauiv1lib.playlist.editgame import PlaylistEditGameWindow
assert babase.app.classic is not None # assert babase.app.classic is not None
babase.app.ui_v1.clear_main_window() # babase.app.ui_v1.clear_main_window()
babase.app.ui_v1.set_main_window( # babase.app.ui_v1.set_main_window(
PlaylistEditGameWindow( # PlaylistEditGameWindow(
cls, # cls,
sessiontype, # sessiontype,
settings, # settings,
completion_call=completion_call, # completion_call=completion_call,
), # ),
from_window=False, # Disable check since we don't know. # from_window=False, # Disable check since we don't know.
) # )
@classmethod @classmethod
def getscoreconfig(cls) -> bascenev1.ScoreConfig: def getscoreconfig(cls) -> bascenev1.ScoreConfig:

View File

@ -68,7 +68,6 @@ class UIV1AppSubsystem(babase.AppSubsystem):
# another MainWindow and we complain if they don't. # another MainWindow and we complain if they don't.
self._main_window = empty_weakref(MainWindow) self._main_window = empty_weakref(MainWindow)
self._main_window_widget: bauiv1.Widget | None = None self._main_window_widget: bauiv1.Widget | None = None
self.main_window_group_id: str | None = None
self.quit_window: bauiv1.Widget | None = None self.quit_window: bauiv1.Widget | None = None
@ -123,7 +122,6 @@ class UIV1AppSubsystem(babase.AppSubsystem):
self.root_ui_calls.clear() self.root_ui_calls.clear()
self._main_window = empty_weakref(MainWindow) self._main_window = empty_weakref(MainWindow)
self._main_window_widget = None self._main_window_widget = None
self.main_window_group_id = None
@property @property
def uiscale(self) -> babase.UIScale: def uiscale(self) -> babase.UIScale:
@ -164,7 +162,7 @@ class UIV1AppSubsystem(babase.AppSubsystem):
# FIXME: Can probably kill this if we do immediate UI death checks. # FIXME: Can probably kill this if we do immediate UI death checks.
self.upkeeptimer = babase.AppTimer(2.6543, ui_upkeep, repeat=True) self.upkeeptimer = babase.AppTimer(2.6543, ui_upkeep, repeat=True)
def do_main_window_back(self, window: MainWindow) -> None: def do_main_window_back(self, from_window: MainWindow) -> None:
"""Sets the main menu window automatically from a parent WindowState.""" """Sets the main menu window automatically from a parent WindowState."""
main_window = self._main_window() main_window = self._main_window()
@ -178,7 +176,7 @@ class UIV1AppSubsystem(babase.AppSubsystem):
) )
backwin = back_state.create_window(transition='in_left') backwin = back_state.create_window(transition='in_left')
backwin.main_window_back_state = back_state.parent backwin.main_window_back_state = back_state.parent
self.set_main_window(backwin, from_window=window, is_back=True) self.set_main_window(backwin, from_window=from_window, is_back=True)
def get_main_window(self) -> bauiv1.MainWindow | None: def get_main_window(self) -> bauiv1.MainWindow | None:
"""Return main window, if any.""" """Return main window, if any."""
@ -189,30 +187,14 @@ class UIV1AppSubsystem(babase.AppSubsystem):
window: bauiv1.MainWindow, window: bauiv1.MainWindow,
from_window: bauiv1.MainWindow | None | bool = True, from_window: bauiv1.MainWindow | None | bool = True,
is_back: bool = False, is_back: bool = False,
group_id: str | None = None,
is_top_level: bool = False, is_top_level: bool = False,
back_state: MainWindowState | None = None, back_state: MainWindowState | None = None,
) -> None: ) -> None:
"""Set the current 'main' window, replacing any existing. """Set the current 'main' window, replacing any existing.
If 'from_window' is passed as a bauiv1.Widget or bauiv1.Window Generally this should not be called directly; The high level
or None, a warning will be issued if it that value does not MainWindow methods main_window_replace() and main_window_back()
match the current main window. This can help identify flawed should be used when possible for navigation.
code that can lead to bad UI states. A value of False will
disable the check, which is necessary in some cases when the
current main window is not known.
When navigating somewhere from a cancel or back-button, pass
is_back=True; this will prevent the new main window from itself
being registered as a new location on the stack that can be
returned to.
If a 'group_id' string is provided and the window being replaced
has the same group-id, the WindowState stack is left unchanged,
effectively replacing the previous window with the new one in
the stack. This can be useful in cases where tab-bar-like UIs
allow flipping between sibling windows with the back button
always leading to a shared parent.
""" """
# pylint: disable=too-many-locals # pylint: disable=too-many-locals
# pylint: disable=too-many-branches # pylint: disable=too-many-branches
@ -222,13 +204,24 @@ class UIV1AppSubsystem(babase.AppSubsystem):
from_window_widget: bauiv1.Widget | None from_window_widget: bauiv1.Widget | None
# We used to accept Widgets but now want MainWindows. # We used to accept Widgets but now want MainWindows.
assert isinstance(window, MainWindow) if not isinstance(window, MainWindow):
raise RuntimeError(
f'set_main_window() now takes a MainWindow as its "window" arg.'
f' You passed a {type(window)}.',
)
window_weakref = weakref.ref(window) window_weakref = weakref.ref(window)
window_widget = window.get_root_widget() window_widget = window.get_root_widget()
if isinstance(from_window, MainWindow): if isinstance(from_window, MainWindow):
from_window_widget = from_window.get_root_widget() from_window_widget = from_window.get_root_widget()
else: else:
if from_window is not None and not isinstance(from_window, bool):
raise RuntimeError(
f'set_main_window() now takes a MainWindow or bool or None'
f'as its "from_window" arg.'
f' You passed a {type(from_window)}.',
)
from_window_widget = None from_window_widget = None
existing = self._main_window_widget existing = self._main_window_widget
@ -317,13 +310,15 @@ class UIV1AppSubsystem(babase.AppSubsystem):
else: else:
# When navigating forward, generate a back-window-state from # When navigating forward, generate a back-window-state from
# the outgoing window. # the outgoing window.
if is_top_level:
# Exception is when we were passed a group and it matches # Top level windows don't have or expect anywhere to
# the existing group; in that case we just keep the existing # go back to.
# back-state. #
if group_id is not None and group_id == self.main_window_group_id: # self._main_window_back_state = None
assert not is_top_level window.main_window_back_state = None
print(f'GOT GROUP ID MATCH {group_id}; KEEPING BACK STATE.') elif back_state is not None:
window.main_window_back_state = back_state
else:
oldwin = self._main_window() oldwin = self._main_window()
if oldwin is None: if oldwin is None:
# We currenty only hold weak refs to windows so # We currenty only hold weak refs to windows so
@ -332,60 +327,32 @@ class UIV1AppSubsystem(babase.AppSubsystem):
# alive as long as its the main one. Holler if # alive as long as its the main one. Holler if
# that seems to not be happening. # that seems to not be happening.
logging.warning( logging.warning(
'set_main_window: no existing MainWindow found' 'set_main_window: No old MainWindow found'
' (and is_top_level is False); should not happen.' ' and is_top_level is False;'
' a MainWindow should keep itself alive as long' ' this should not happen.'
' as it is main.'
) )
window.main_window_back_state = None window.main_window_back_state = None
else: else:
window.main_window_back_state = ( oldwinstate = oldwin.get_main_window_state()
oldwin.main_window_back_state
)
else:
if is_top_level:
# Top level windows don't have or expect anywhere to go
# back to.
# self._main_window_back_state = None
window.main_window_back_state = None
elif back_state is not None:
window.main_window_back_state = back_state
else:
oldwin = self._main_window()
if oldwin is None:
# We currenty only hold weak refs to windows so
# that they are free to die on their own, but we
# expect the main menu window to keep itself
# alive as long as its the main one. Holler if
# that seems to not be happening.
logging.warning(
'set_main_window: No old MainWindow found'
' and is_top_level is False;'
' this should not happen.'
)
window.main_window_back_state = None
else:
oldwinstate = oldwin.get_main_window_state()
# Store our previous back state on this new one. # Store our previous back state on this new one.
oldwinstate.parent = oldwin.main_window_back_state oldwinstate.parent = oldwin.main_window_back_state
window.main_window_back_state = oldwinstate window.main_window_back_state = oldwinstate
self._main_window = window_weakref self._main_window = window_weakref
self._main_window_widget = window_widget self._main_window_widget = window_widget
self.main_window_group_id = group_id
def has_main_window(self) -> bool: def has_main_window(self) -> bool:
"""Return whether a main menu window is present.""" """Return whether a main menu window is present."""
return bool(self._main_window_widget) return bool(self._main_window_widget)
def clear_main_window(self) -> None: def clear_main_window(self, transition: str | None = None) -> None:
"""Clear any existing main window.""" """Clear any existing main window."""
from bauiv1._uitypes import MainWindow from bauiv1._uitypes import MainWindow
main_window = self._main_window() main_window = self._main_window()
if main_window: if main_window:
main_window.main_window_close() main_window.main_window_close(transition=transition)
else: else:
# Fallback; if we have a widget but no window, nuke the widget. # Fallback; if we have a widget but no window, nuke the widget.
if self._main_window_widget: if self._main_window_widget:
@ -397,4 +364,3 @@ class UIV1AppSubsystem(babase.AppSubsystem):
self._main_window = empty_weakref(MainWindow) self._main_window = empty_weakref(MainWindow)
self._main_window_widget = None self._main_window_widget = None
self.main_window_group_id = None

View File

@ -60,7 +60,6 @@ class MainWindow(Window):
Automatically handles in and out transitions on the provided widget, Automatically handles in and out transitions on the provided widget,
so there is no need to set transitions when creating it. so there is no need to set transitions when creating it.
""" """
# TODO - move to MainWindow
# A back-state supplied by the ui system. # A back-state supplied by the ui system.
self.main_window_back_state: MainWindowState | None = None self.main_window_back_state: MainWindowState | None = None
@ -82,7 +81,7 @@ class MainWindow(Window):
scale_origin_stack_offset=scale_origin, scale_origin_stack_offset=scale_origin,
) )
def main_window_close(self) -> None: def main_window_close(self, transition: str | None = None) -> None:
"""Get window transitioning out if still alive.""" """Get window transitioning out if still alive."""
# no-op if our underlying widget is dead or on its way out. # no-op if our underlying widget is dead or on its way out.
@ -96,16 +95,21 @@ class MainWindow(Window):
logging.exception('Error in on_main_window_close() for %s.', self) logging.exception('Error in on_main_window_close() for %s.', self)
_bauiv1.containerwidget( _bauiv1.containerwidget(
edit=self._root_widget, transition=self._main_window_transition_out edit=self._root_widget,
transition=(
self._main_window_transition_out
if transition is None
else transition
),
) )
def main_window_has_control(self) -> bool: def main_window_has_control(self) -> bool:
"""Is this MainWindow allowed to change the global main window? """Is this MainWindow allowed to change the global main window?
It is a good idea to make sure this is True before calling It is a good idea to make sure this is True before calling
either main_window_back() or main_window_replace(). This main_window_replace(). This prevents fluke UI breakage such as
prevents fluke UI breakage such as multiple simultaneous events multiple simultaneous events causing a MainWindow to spawn
causing a MainWindow to spawn multiple replacements for itself. multiple replacements for itself.
""" """
# We are allowed to change main windows if we are the current one # We are allowed to change main windows if we are the current one
# AND our underlying widget is still alive and not transitioning out. # AND our underlying widget is still alive and not transitioning out.
@ -116,16 +120,16 @@ class MainWindow(Window):
) )
def main_window_back(self) -> None: def main_window_back(self) -> None:
"""Move back in the main window stack.""" """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 # Users should always check main_window_has_control() before
# calling us. Error if it seems they did not. # calling us. Error if it seems they did not.
if not self.main_window_has_control(): if not self.main_window_has_control():
raise RuntimeError( return
'main_window_back() should only be called'
' if main_window_has_control() returns True'
' (it currently is False).'
)
# Get the 'back' window coming in. # Get the 'back' window coming in.
babase.app.ui_v1.do_main_window_back(self) babase.app.ui_v1.do_main_window_back(self)
@ -133,31 +137,24 @@ class MainWindow(Window):
self.main_window_close() self.main_window_close()
def main_window_replace( def main_window_replace(
self, new_window: MainWindow, group_id: str | None = None self, new_window: MainWindow, back_state: MainWindowState | None = None
) -> None: ) -> None:
"""Replace ourself with a new MainWindow.""" """Replace ourself with a new MainWindow."""
# Users should always check main_window_has_control() before # Users should always check main_window_has_control() *before*
# creating new MainWindows and passing them in here. Error if it # creating new MainWindows and passing them in here. Kill the
# seems they did not. # passed window and Error if it seems they did not.
if not self.main_window_has_control(): if not self.main_window_has_control():
new_window.get_root_widget().delete()
raise RuntimeError( raise RuntimeError(
'main_window_replace() should only be called' f'main_window_replace() called on a not-in-control window'
' if main_window_has_control() returns True' f' ({self}); always check main_window_has_control() before'
' (it currently is False).' f' calling main_window_replace().'
) )
# If we're navigating within a group, we want it to look like we're # Just shove the old out the left to give the feel that we're
# backing out of the old one and going into the new one. # adding to the nav stack.
if ( transition = 'out_left'
group_id is not None
and babase.app.ui_v1.main_window_group_id == group_id
):
transition = self._main_window_transition_out
else:
# Otherwise just shove the old out the left to give the feel
# that we're adding to the nav stack.
transition = 'out_left'
# Transition ourself out. # Transition ourself out.
try: try:
@ -167,7 +164,7 @@ class MainWindow(Window):
_bauiv1.containerwidget(edit=self._root_widget, transition=transition) _bauiv1.containerwidget(edit=self._root_widget, transition=transition)
babase.app.ui_v1.set_main_window( babase.app.ui_v1.set_main_window(
new_window, from_window=self, group_id=group_id new_window, from_window=self, back_state=back_state
) )
def on_main_window_close(self) -> None: def on_main_window_close(self) -> None:

View File

@ -181,7 +181,8 @@ class CharacterPicker(PopupWindow):
def _on_store_press(self) -> None: def _on_store_press(self) -> None:
from bauiv1lib.account import show_sign_in_prompt from bauiv1lib.account import show_sign_in_prompt
from bauiv1lib.store.browser import StoreBrowserWindow
# from bauiv1lib.store.browser import StoreBrowserWindow
plus = bui.app.plus plus = bui.app.plus
assert plus is not None assert plus is not None
@ -189,12 +190,16 @@ class CharacterPicker(PopupWindow):
if plus.get_v1_account_state() != 'signed_in': if plus.get_v1_account_state() != 'signed_in':
show_sign_in_prompt() show_sign_in_prompt()
return return
self._transition_out()
StoreBrowserWindow( bui.screenmessage('UNDER CONSTRUCTION')
modal=True, return
show_tab=StoreBrowserWindow.TabID.CHARACTERS,
origin_widget=self._get_more_characters_button, # self._transition_out()
) # StoreBrowserWindow(
# modal=True,
# show_tab=StoreBrowserWindow.TabID.CHARACTERS,
# origin_widget=self._get_more_characters_button,
# )
def _select_character(self, character: str) -> None: def _select_character(self, character: str) -> None:
if self._delegate is not None: if self._delegate is not None:

View File

@ -7,11 +7,14 @@ from __future__ import annotations
import weakref import weakref
import logging import logging
from enum import Enum from enum import Enum
from typing import override from typing import override, TYPE_CHECKING
from bauiv1lib.tabs import TabRow from bauiv1lib.tabs import TabRow
import bauiv1 as bui import bauiv1 as bui
if TYPE_CHECKING:
from bauiv1lib.play import PlaylistSelectContext
class GatherTab: class GatherTab:
"""Defines a tab for use in the gather UI.""" """Defines a tab for use in the gather UI."""
@ -264,23 +267,27 @@ class GatherWindow(bui.MainWindow):
def on_main_window_close(self) -> None: def on_main_window_close(self) -> None:
self._save_state() self._save_state()
def playlist_select(self, origin_widget: bui.Widget) -> None: def playlist_select(
self,
origin_widget: bui.Widget,
context: PlaylistSelectContext,
) -> None:
"""Called by the private-hosting tab to select a playlist.""" """Called by the private-hosting tab to select a playlist."""
from bauiv1lib.play import PlayWindow from bauiv1lib.play import PlayWindow
classic = bui.app.classic # Avoid redundant window spawns.
assert classic is not None if not self.main_window_has_control():
# 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 return
self._save_state() playwindow = PlayWindow(
bui.containerwidget(edit=self._root_widget, transition='out_left') origin_widget=origin_widget, playlist_select_context=context
classic.selecting_private_party_playlist = True
bui.app.ui_v1.set_main_window(
PlayWindow(origin_widget=origin_widget), from_window=self
) )
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: def _set_tab(self, tab_id: TabID) -> None:
if self._current_tab is tab_id: if self._current_tab is tab_id:

View File

@ -22,6 +22,7 @@ from bacommon.net import (
PrivatePartyConnectResult, PrivatePartyConnectResult,
) )
from bauiv1lib.gather import GatherTab from bauiv1lib.gather import GatherTab
from bauiv1lib.play import PlaylistSelectContext
from bauiv1lib.gettokens import GetTokensWindow, show_get_tokens_prompt from bauiv1lib.gettokens import GetTokensWindow, show_get_tokens_prompt
import bascenev1 as bs import bascenev1 as bs
@ -49,6 +50,7 @@ class State:
"""Our core state that persists while the app is running.""" """Our core state that persists while the app is running."""
sub_tab: SubTabType = SubTabType.JOIN sub_tab: SubTabType = SubTabType.JOIN
playlist_select_context: PlaylistSelectContext | None = None
class PrivateGatherTab(GatherTab): class PrivateGatherTab(GatherTab):
@ -685,12 +687,13 @@ class PrivateGatherTab(GatherTab):
# If it appears we're coming back from playlist selection, # If it appears we're coming back from playlist selection,
# re-select our playlist button. # re-select our playlist button.
if classic.selecting_private_party_playlist: if self._state.playlist_select_context is not None:
self._state.playlist_select_context = None
bui.containerwidget( bui.containerwidget(
edit=self._container, edit=self._container,
selected_child=self._host_playlist_button, selected_child=self._host_playlist_button,
) )
classic.selecting_private_party_playlist = False
else: else:
# We've got a current party; show its info. # We've got a current party; show its info.
bui.textwidget( bui.textwidget(
@ -918,11 +921,14 @@ class PrivateGatherTab(GatherTab):
) )
def _playlist_press(self) -> None: def _playlist_press(self) -> None:
if bool(True):
bui.screenmessage('UNDER CONSTRUCTION')
return
assert self._host_playlist_button is not None assert self._host_playlist_button is not None
self.window.playlist_select(origin_widget=self._host_playlist_button)
self._state.playlist_select_context = PlaylistSelectContext()
self.window.playlist_select(
origin_widget=self._host_playlist_button,
context=self._state.playlist_select_context,
)
def _host_copy_press(self) -> None: def _host_copy_press(self) -> None:
assert self._hostingstate.party_code is not None assert self._hostingstate.party_code is not None

View File

@ -159,7 +159,8 @@ class IconPicker(PopupWindow):
def _on_store_press(self) -> None: def _on_store_press(self) -> None:
from bauiv1lib.account import show_sign_in_prompt from bauiv1lib.account import show_sign_in_prompt
from bauiv1lib.store.browser import StoreBrowserWindow
# from bauiv1lib.store.browser import StoreBrowserWindow
plus = bui.app.plus plus = bui.app.plus
assert plus is not None assert plus is not None
@ -167,12 +168,16 @@ class IconPicker(PopupWindow):
if plus.get_v1_account_state() != 'signed_in': if plus.get_v1_account_state() != 'signed_in':
show_sign_in_prompt() show_sign_in_prompt()
return return
self._transition_out() # self._transition_out()
StoreBrowserWindow(
modal=True, bui.screenmessage('UNDER CONSTRUCTION')
show_tab=StoreBrowserWindow.TabID.ICONS, return
origin_widget=self._get_more_icons_button,
) # StoreBrowserWindow(
# modal=True,
# show_tab=StoreBrowserWindow.TabID.ICONS,
# origin_widget=self._get_more_icons_button,
# )
def _select_icon(self, icon: str) -> None: def _select_icon(self, icon: str) -> None:
if self._delegate is not None: if self._delegate is not None:

View File

@ -505,7 +505,6 @@ class MainMenuWindow(bui.MainWindow):
self.main_window_replace( self.main_window_replace(
CreditsWindow(origin_widget=self._credits_button), CreditsWindow(origin_widget=self._credits_button),
# group_id='mainmenutop',
) )
def _howtoplay(self) -> None: def _howtoplay(self) -> None:
@ -607,8 +606,4 @@ class MainMenuWindow(bui.MainWindow):
if not self.main_window_has_control(): if not self.main_window_has_control():
return return
classic = bui.app.classic
if classic is not None:
classic.selecting_private_party_playlist = False
self.main_window_replace(PlayWindow(origin_widget=self._play_button)) self.main_window_replace(PlayWindow(origin_widget=self._play_button))

View File

@ -5,11 +5,20 @@
from __future__ import annotations from __future__ import annotations
import logging import logging
from typing import override from typing import override, TYPE_CHECKING
import bascenev1 as bs import bascenev1 as bs
import bauiv1 as bui import bauiv1 as bui
if TYPE_CHECKING:
from bauiv1 import MainWindowState
class PlaylistSelectContext:
"""For using PlayWindow to select a playlist instead of running game."""
back_state: MainWindowState | None = None
class PlayWindow(bui.MainWindow): class PlayWindow(bui.MainWindow):
"""Window for selecting overall play type.""" """Window for selecting overall play type."""
@ -18,6 +27,7 @@ class PlayWindow(bui.MainWindow):
self, self,
transition: str | None = 'in_right', transition: str | None = 'in_right',
origin_widget: bui.Widget | None = None, origin_widget: bui.Widget | None = None,
playlist_select_context: PlaylistSelectContext | None = None,
): ):
# pylint: disable=too-many-statements # pylint: disable=too-many-statements
# pylint: disable=too-many-locals # pylint: disable=too-many-locals
@ -29,11 +39,7 @@ class PlayWindow(bui.MainWindow):
classic = bui.app.classic classic = bui.app.classic
assert classic is not None assert classic is not None
# We can currently be used either for main window duty or self._playlist_select_context = playlist_select_context
# modally for selecting playlists. Ideally we should clean
# things up and make playlist selection go through the
# main-window mechanism.
self._is_main_menu = not classic.selecting_private_party_playlist
uiscale = bui.app.ui_v1.uiscale uiscale = bui.app.ui_v1.uiscale
width = 1100 if uiscale is bui.UIScale.SMALL else 800 width = 1100 if uiscale is bui.UIScale.SMALL else 800
@ -54,7 +60,11 @@ class PlayWindow(bui.MainWindow):
super().__init__( super().__init__(
root_widget=bui.containerwidget( root_widget=bui.containerwidget(
size=(width, height), size=(width, height),
toolbar_visibility='menu_full', toolbar_visibility=(
'menu_full'
if playlist_select_context is None
else 'menu_minimal'
),
scale=( scale=(
1.35 1.35
if uiscale is bui.UIScale.SMALL if uiscale is bui.UIScale.SMALL
@ -88,7 +98,7 @@ class PlayWindow(bui.MainWindow):
text=bui.Lstr( text=bui.Lstr(
resource=( resource=(
(f'{self._r}.titleText') (f'{self._r}.titleText')
if self._is_main_menu if self._playlist_select_context is None
else 'playlistsText' else 'playlistsText'
) )
), ),
@ -109,13 +119,17 @@ class PlayWindow(bui.MainWindow):
if uiscale is bui.UIScale.SMALL: if uiscale is bui.UIScale.SMALL:
bui.textwidget(edit=txt, text='') bui.textwidget(edit=txt, text='')
v = height - (110 if self._is_main_menu else 90) v = height - (110 if self._playlist_select_context is None else 90)
v -= 100 v -= 100
clr = (0.6, 0.7, 0.6, 1.0) clr = (0.6, 0.7, 0.6, 1.0)
v -= 280 if self._is_main_menu else 180 v -= 280 if self._playlist_select_context is None else 180
v += 30 if uiscale is bui.UIScale.SMALL else 0 v += 30 if uiscale is bui.UIScale.SMALL else 0
hoffs = x_offs + 80 if self._is_main_menu else x_offs - 100 hoffs = (
scl = 1.13 if self._is_main_menu else 0.68 x_offs + 80
if self._playlist_select_context is None
else x_offs - 100
)
scl = 1.13 if self._playlist_select_context is None else 0.68
self._lineup_tex = bui.gettexture('playerLineup') self._lineup_tex = bui.gettexture('playerLineup')
angry_computer_transparent_mesh = bui.getmesh( angry_computer_transparent_mesh = bui.getmesh(
@ -137,8 +151,8 @@ class PlayWindow(bui.MainWindow):
self._coop_button: bui.Widget | None = None self._coop_button: bui.Widget | None = None
# Only show coop button in main-menu variant. # Only show coop button in regular variant.
if self._is_main_menu: if self._playlist_select_context is None:
self._coop_button = btn = bui.buttonwidget( self._coop_button = btn = bui.buttonwidget(
parent=self._root_widget, parent=self._root_widget,
position=(hoffs, v + (scl * 15)), position=(hoffs, v + (scl * 15)),
@ -238,16 +252,19 @@ class PlayWindow(bui.MainWindow):
color=clr, color=clr,
) )
scl = 0.5 if self._is_main_menu else 0.68 scl = 0.5 if self._playlist_select_context is None else 0.68
hoffs += 440 if self._is_main_menu else 216 hoffs += 440 if self._playlist_select_context is None else 216
v += 180 if self._is_main_menu else -68 v += 180 if self._playlist_select_context is None else -68
self._teams_button = btn = bui.buttonwidget( self._teams_button = btn = bui.buttonwidget(
parent=self._root_widget, parent=self._root_widget,
position=(hoffs, v + (scl * 15 if self._is_main_menu else 0)), position=(
hoffs,
v + (scl * 15 if self._playlist_select_context is None else 0),
),
size=( size=(
scl * button_width, scl * button_width,
scl * (300 if self._is_main_menu else 360), scl * (300 if self._playlist_select_context is None else 360),
), ),
extra_touch_border_scale=0.1, extra_touch_border_scale=0.1,
autoselect=True, autoselect=True,
@ -369,14 +386,17 @@ class PlayWindow(bui.MainWindow):
color=clr, color=clr,
) )
hoffs += 0 if self._is_main_menu else 300 hoffs += 0 if self._playlist_select_context is None else 300
v -= 155 if self._is_main_menu else 0 v -= 155 if self._playlist_select_context is None else 0
self._free_for_all_button = btn = bui.buttonwidget( self._free_for_all_button = btn = bui.buttonwidget(
parent=self._root_widget, parent=self._root_widget,
position=(hoffs, v + (scl * 15 if self._is_main_menu else 0)), position=(
hoffs,
v + (scl * 15 if self._playlist_select_context is None else 0),
),
size=( size=(
scl * button_width, scl * button_width,
scl * (300 if self._is_main_menu else 360), scl * (300 if self._playlist_select_context is None else 360),
), ),
extra_touch_border_scale=0.1, extra_touch_border_scale=0.1,
autoselect=True, autoselect=True,
@ -493,22 +513,24 @@ class PlayWindow(bui.MainWindow):
back_button.delete() back_button.delete()
bui.containerwidget( bui.containerwidget(
edit=self._root_widget, edit=self._root_widget,
on_cancel_call=self._back, on_cancel_call=self.main_window_back,
# cancel_button=bui.get_special_widget('back_button'), # cancel_button=bui.get_special_widget('back_button'),
selected_child=( selected_child=(
self._coop_button self._coop_button
if self._is_main_menu if self._playlist_select_context is None
else self._teams_button else self._teams_button
), ),
) )
else: else:
bui.buttonwidget(edit=back_button, on_activate_call=self._back) bui.buttonwidget(
edit=back_button, on_activate_call=self.main_window_back
)
bui.containerwidget( bui.containerwidget(
edit=self._root_widget, edit=self._root_widget,
cancel_button=back_button, cancel_button=back_button,
selected_child=( selected_child=(
self._coop_button self._coop_button
if self._is_main_menu if self._playlist_select_context is None
else self._teams_button else self._teams_button
), ),
) )
@ -519,9 +541,15 @@ class PlayWindow(bui.MainWindow):
def get_main_window_state(self) -> bui.MainWindowState: def get_main_window_state(self) -> bui.MainWindowState:
# Support recreating our window for back/refresh purposes. # Support recreating our window for back/refresh purposes.
cls = type(self) cls = type(self)
# 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( return bui.BasicMainWindowState(
create_call=lambda transition, origin_widget: cls( create_call=lambda transition, origin_widget: cls(
transition=transition, origin_widget=origin_widget transition=transition,
origin_widget=origin_widget,
playlist_select_context=playlist_select_context,
) )
) )
@ -537,30 +565,6 @@ class PlayWindow(bui.MainWindow):
import bauiv1lib.coop.browser as _unused3 import bauiv1lib.coop.browser as _unused3
import bauiv1lib.playlist.browser as _unused4 import bauiv1lib.playlist.browser as _unused4
def _back(self) -> None:
# pylint: disable=cyclic-import
# no-op if we're not currently in control.
if not self.main_window_has_control():
return
if self._is_main_menu:
self.main_window_back()
else:
from bauiv1lib.gather import GatherWindow
assert bui.app.classic is not None
self._save_state()
bui.app.ui_v1.set_main_window(
GatherWindow(transition='in_left'),
from_window=self,
is_back=True,
)
bui.containerwidget(
edit=self._root_widget, transition=self._transition_out
)
def _coop(self) -> None: def _coop(self) -> None:
# pylint: disable=cyclic-import # pylint: disable=cyclic-import
from bauiv1lib.account import show_sign_in_prompt from bauiv1lib.account import show_sign_in_prompt
@ -595,7 +599,9 @@ class PlayWindow(bui.MainWindow):
self.main_window_replace( self.main_window_replace(
PlaylistBrowserWindow( PlaylistBrowserWindow(
origin_widget=self._teams_button, sessiontype=bs.DualTeamSession origin_widget=self._teams_button,
sessiontype=bs.DualTeamSession,
playlist_select_context=self._playlist_select_context,
) )
) )
@ -614,6 +620,7 @@ class PlayWindow(bui.MainWindow):
PlaylistBrowserWindow( PlaylistBrowserWindow(
origin_widget=self._free_for_all_button, origin_widget=self._free_for_all_button,
sessiontype=bs.FreeForAllSession, sessiontype=bs.FreeForAllSession,
playlist_select_context=self._playlist_select_context,
), ),
from_window=self, from_window=self,
) )

View File

@ -57,12 +57,10 @@ class PlaylistAddGameWindow(bui.MainWindow):
self._back_button = bui.buttonwidget( self._back_button = bui.buttonwidget(
parent=self._root_widget, parent=self._root_widget,
position=(58 + x_inset, self._height - 53), position=(58 + x_inset, self._height - 53),
size=(165, 70), size=(60, 48),
scale=0.75, label=bui.charstr(bui.SpecialChar.BACK),
text_scale=1.2,
label=bui.Lstr(resource='backText'),
autoselect=True, autoselect=True,
button_type='back', button_type='backSmall',
on_activate_call=self.main_window_back, on_activate_call=self.main_window_back,
) )
self._select_button = select_button = bui.buttonwidget( self._select_button = select_button = bui.buttonwidget(
@ -257,27 +255,37 @@ class PlaylistAddGameWindow(bui.MainWindow):
from bauiv1lib.account import show_sign_in_prompt from bauiv1lib.account import show_sign_in_prompt
from bauiv1lib.store.browser import StoreBrowserWindow from bauiv1lib.store.browser import StoreBrowserWindow
# No-op if we're not in control.
if self.main_window_has_control():
return
plus = bui.app.plus plus = bui.app.plus
assert plus is not None assert plus is not None
if plus.get_v1_account_state() != 'signed_in': if plus.get_v1_account_state() != 'signed_in':
show_sign_in_prompt() show_sign_in_prompt()
return return
StoreBrowserWindow(
modal=True, self.main_window_replace(
show_tab=StoreBrowserWindow.TabID.MINIGAMES, StoreBrowserWindow(
on_close_call=self._on_store_close, # modal=True,
origin_widget=self._get_more_games_button, show_tab=StoreBrowserWindow.TabID.MINIGAMES,
# on_close_call=self._on_store_close,
origin_widget=self._get_more_games_button,
minimal_toolbars=True,
)
) )
def _on_store_close(self) -> None: # def _on_store_close(self) -> None:
self._refresh(select_get_more_games_button=True) # self._refresh(select_get_more_games_button=True)
def _add(self) -> None: def _add(self) -> None:
bui.lock_all_input() # Make sure no more commands happen. bui.lock_all_input() # Make sure no more commands happen.
bui.apptimer(0.1, bui.unlock_all_input) bui.apptimer(0.1, bui.unlock_all_input)
assert self._selected_game_type is not None assert self._selected_game_type is not None
self._editcontroller.add_game_type_selected(self._selected_game_type) self._editcontroller.add_game_type_selected(
self._selected_game_type, from_window=self
)
def _set_selected_game_type(self, gametype: type[bs.GameActivity]) -> None: def _set_selected_game_type(self, gametype: type[bs.GameActivity]) -> None:
self._selected_game_type = gametype self._selected_game_type = gametype
@ -290,6 +298,3 @@ class PlaylistAddGameWindow(bui.MainWindow):
self._editcontroller.get_session_type() self._editcontroller.get_session_type()
), ),
) )
# def _back(self) -> None:
# self._editcontroller.add_game_cancelled()

View File

@ -7,11 +7,14 @@ from __future__ import annotations
import copy import copy
import math import math
import logging import logging
from typing import override from typing import override, TYPE_CHECKING
import bascenev1 as bs import bascenev1 as bs
import bauiv1 as bui import bauiv1 as bui
if TYPE_CHECKING:
from bauiv1lib.play import PlaylistSelectContext
class PlaylistBrowserWindow(bui.MainWindow): class PlaylistBrowserWindow(bui.MainWindow):
"""Window for starting teams games.""" """Window for starting teams games."""
@ -21,6 +24,7 @@ class PlaylistBrowserWindow(bui.MainWindow):
sessiontype: type[bs.Session], sessiontype: type[bs.Session],
transition: str | None = 'in_right', transition: str | None = 'in_right',
origin_widget: bui.Widget | None = None, origin_widget: bui.Widget | None = None,
playlist_select_context: PlaylistSelectContext | None = None,
): ):
# pylint: disable=cyclic-import # pylint: disable=cyclic-import
from bauiv1lib.playlist import PlaylistTypeVars from bauiv1lib.playlist import PlaylistTypeVars
@ -39,6 +43,7 @@ class PlaylistBrowserWindow(bui.MainWindow):
self._customize_button: bui.Widget | None = None self._customize_button: bui.Widget | None = None
self._sub_width: float | None = None self._sub_width: float | None = None
self._sub_height: float | None = None self._sub_height: float | None = None
self._playlist_select_context = playlist_select_context
self._ensure_standard_playlists_exist() self._ensure_standard_playlists_exist()
@ -63,7 +68,10 @@ class PlaylistBrowserWindow(bui.MainWindow):
size=(self._width, self._height + top_extra), size=(self._width, self._height + top_extra),
toolbar_visibility=( toolbar_visibility=(
'menu_minimal' 'menu_minimal'
if uiscale is bui.UIScale.SMALL if (
uiscale is bui.UIScale.SMALL
or playlist_select_context is not None
)
else 'menu_full' else 'menu_full'
), ),
scale=( scale=(
@ -106,8 +114,6 @@ class PlaylistBrowserWindow(bui.MainWindow):
h_align='center', h_align='center',
v_align='center', v_align='center',
) )
# if uiscale is bui.UIScale.SMALL and bui.app.ui_v1.use_toolbars:
# bui.textwidget(edit=txt, text='')
bui.buttonwidget( bui.buttonwidget(
edit=self._back_button, edit=self._back_button,
@ -144,8 +150,8 @@ class PlaylistBrowserWindow(bui.MainWindow):
self._config_name_full = self._pvars.config_name + ' Playlists' self._config_name_full = self._pvars.config_name + ' Playlists'
self._last_config = None self._last_config = None
# Update now and once per second. # Update now and once per second (this should do our initial
# (this should do our initial refresh) # refresh).
self._update() self._update()
self._update_timer = bui.AppTimer( self._update_timer = bui.AppTimer(
1.0, bui.WeakCall(self._update), repeat=True 1.0, bui.WeakCall(self._update), repeat=True
@ -160,11 +166,16 @@ class PlaylistBrowserWindow(bui.MainWindow):
# then we keep self alive. # then we keep self alive.
sessiontype = self._sessiontype sessiontype = self._sessiontype
# Pull anything out of self here; if we do it in the lambda
# we'll inadvertanly keep self alive.
playlist_select_context = self._playlist_select_context
return bui.BasicMainWindowState( return bui.BasicMainWindowState(
create_call=lambda transition, origin_widget: cls( create_call=lambda transition, origin_widget: cls(
transition=transition, transition=transition,
origin_widget=origin_widget, origin_widget=origin_widget,
sessiontype=sessiontype, sessiontype=sessiontype,
playlist_select_context=playlist_select_context,
) )
) )
@ -639,15 +650,35 @@ class PlaylistBrowserWindow(bui.MainWindow):
def on_play_options_window_run_game(self) -> None: def on_play_options_window_run_game(self) -> None:
"""(internal)""" """(internal)"""
if not self._root_widget:
# No-op if we're not in control.
if not self.main_window_has_control():
# if not self._root_widget:
return return
bui.containerwidget(edit=self._root_widget, transition='out_left')
if self._playlist_select_context is not None:
# Done doing a playlist selection; now back all the way out
# of our selection windows to our stored starting point.
if self._playlist_select_context.back_state is None:
logging.error(
'No back state found'
' after playlist select context completion.'
)
else:
self.main_window_back_state = (
self._playlist_select_context.back_state
)
self.main_window_back()
else:
# Launching a regular game session; simply get our window
# transitioning out.
self.main_window_close(transition='out_left')
def _on_playlist_select(self, playlist_name: str) -> None: def _on_playlist_select(self, playlist_name: str) -> None:
self._selected_playlist = playlist_name self._selected_playlist = playlist_name
def _update(self) -> None: def _update(self) -> None:
# make sure config exists # Make sure config exists.
if self._config_name_full not in bui.app.config: if self._config_name_full not in bui.app.config:
bui.app.config[self._config_name_full] = {} bui.app.config[self._config_name_full] = {}
@ -676,6 +707,7 @@ class PlaylistBrowserWindow(bui.MainWindow):
scale_origin=button.get_screen_space_center(), scale_origin=button.get_screen_space_center(),
playlist=playlist_name, playlist=playlist_name,
delegate=self, delegate=self,
playlist_select_context=self._playlist_select_context,
) )
def _on_customize_press(self) -> None: def _on_customize_press(self) -> None:
@ -690,7 +722,6 @@ class PlaylistBrowserWindow(bui.MainWindow):
self._save_state() self._save_state()
bui.containerwidget(edit=self._root_widget, transition='out_left') bui.containerwidget(edit=self._root_widget, transition='out_left')
assert bui.app.classic is not None
bui.app.ui_v1.set_main_window( bui.app.ui_v1.set_main_window(
PlaylistCustomizeBrowserWindow( PlaylistCustomizeBrowserWindow(
origin_widget=self._customize_button, origin_widget=self._customize_button,
@ -721,15 +752,6 @@ class PlaylistBrowserWindow(bui.MainWindow):
self.main_window_back() self.main_window_back()
# self._save_state()
# bui.containerwidget(
# edit=self._root_widget, transition=self._transition_out
# )
# assert bui.app.classic is not None
# bui.app.ui_v1.set_main_window(
# PlayWindow(transition='in_left'), from_window=self, is_back=True
# )
def _save_state(self) -> None: def _save_state(self) -> None:
try: try:
sel = self._root_widget.get_selected_child() sel = self._root_widget.get_selected_child()

View File

@ -6,19 +6,19 @@ from __future__ import annotations
import copy import copy
import time import time
import logging
# import logging
from typing import TYPE_CHECKING, override from typing import TYPE_CHECKING, override
import bascenev1 as bs # import bascenev1 as bs
import bauiv1 as bui import bauiv1 as bui
if TYPE_CHECKING: if TYPE_CHECKING:
from typing import Any from typing import Any
REQUIRE_PRO = False import bascenev1 as bs
# TEMP REQUIRE_PRO = False
UNDER_CONSTRUCTION = True
class PlaylistCustomizeBrowserWindow(bui.MainWindow): class PlaylistCustomizeBrowserWindow(bui.MainWindow):
@ -77,7 +77,7 @@ class PlaylistCustomizeBrowserWindow(bui.MainWindow):
if uiscale is bui.UIScale.SMALL: if uiscale is bui.UIScale.SMALL:
self._back_button = None self._back_button = None
bui.containerwidget( bui.containerwidget(
edit=self._root_widget, on_cancel_call=self._back edit=self._root_widget, on_cancel_call=self.main_window_back
) )
else: else:
self._back_button = bui.buttonwidget( self._back_button = bui.buttonwidget(
@ -291,8 +291,8 @@ class PlaylistCustomizeBrowserWindow(bui.MainWindow):
right_widget=bui.get_special_widget('squad_button'), right_widget=bui.get_special_widget('squad_button'),
) )
# make sure config exists # Make sure config exists.
self._config_name_full = self._pvars.config_name + ' Playlists' self._config_name_full = f'{self._pvars.config_name} Playlists'
if self._config_name_full not in bui.app.config: if self._config_name_full not in bui.app.config:
bui.app.config[self._config_name_full] = {} bui.app.config[self._config_name_full] = {}
@ -305,7 +305,7 @@ class PlaylistCustomizeBrowserWindow(bui.MainWindow):
if self._back_button is not None: if self._back_button is not None:
bui.buttonwidget( bui.buttonwidget(
edit=self._back_button, on_activate_call=self._back edit=self._back_button, on_activate_call=self.main_window_back
) )
bui.containerwidget( bui.containerwidget(
edit=self._root_widget, cancel_button=self._back_button edit=self._root_widget, cancel_button=self._back_button
@ -336,9 +336,14 @@ class PlaylistCustomizeBrowserWindow(bui.MainWindow):
) )
) )
# @override @override
# def on_main_window_close(self) -> None: def on_main_window_close(self) -> None:
# self._save_state() if self._selected_playlist_name is not None:
cfg = bui.app.config
cfg[f'{self._pvars.config_name} Playlist Selection'] = (
self._selected_playlist_name
)
cfg.commit()
def _update(self) -> None: def _update(self) -> None:
assert bui.app.classic is not None assert bui.app.classic is not None
@ -348,59 +353,10 @@ class PlaylistCustomizeBrowserWindow(bui.MainWindow):
edit=lock, opacity=0.0 if (have or not REQUIRE_PRO) else 1.0 edit=lock, opacity=0.0 if (have or not REQUIRE_PRO) else 1.0
) )
def _back(self) -> None:
# pylint: disable=cyclic-import
# from bauiv1lib.playlist import browser
# no-op if our underlying widget is dead or on its way out.
if not self._root_widget or self._root_widget.transitioning_out:
return
if self._selected_playlist_name is not None:
cfg = bui.app.config
cfg[self._pvars.config_name + ' Playlist Selection'] = (
self._selected_playlist_name
)
cfg.commit()
self.main_window_back()
# bui.containerwidget(
# edit=self._root_widget, transition=self._transition_out
# )
# assert bui.app.classic is not None
# bui.app.ui_v1.set_main_window(
# browser.PlaylistBrowserWindow(
# transition='in_left', sessiontype=self._sessiontype
# ),
# from_window=self,
# is_back=True,
# )
def _select(self, name: str, index: int) -> None: def _select(self, name: str, index: int) -> None:
self._selected_playlist_name = name self._selected_playlist_name = name
self._selected_playlist_index = index self._selected_playlist_index = index
def _run_selected_playlist(self) -> None:
# pylint: disable=cyclic-import
bui.unlock_all_input()
try:
bs.new_host_session(self._sessiontype)
except Exception:
from bascenev1lib import mainmenu
logging.exception('Error running session %s.', self._sessiontype)
# Drop back into a main menu session.
bs.new_host_session(mainmenu.MainMenuSession)
def _choose_playlist(self) -> None:
if self._selected_playlist_name is None:
return
self._save_playlist_selection()
bui.containerwidget(edit=self._root_widget, transition='out_left')
bui.fade_screen(False, endcall=self._run_selected_playlist)
bui.lock_all_input()
def _refresh(self, select_playlist: str | None = None) -> None: def _refresh(self, select_playlist: str | None = None) -> None:
from efro.util import asserttype from efro.util import asserttype
@ -451,7 +407,7 @@ class PlaylistCustomizeBrowserWindow(bui.MainWindow):
) )
bui.widget(edit=txtw, show_buffer_top=50, show_buffer_bottom=50) bui.widget(edit=txtw, show_buffer_top=50, show_buffer_bottom=50)
# Hitting up from top widget should jump to 'back' # Hitting up from top widget should jump to 'back'.
if index == 0: if index == 0:
bui.widget( bui.widget(
edit=txtw, edit=txtw,
@ -473,8 +429,8 @@ class PlaylistCustomizeBrowserWindow(bui.MainWindow):
visible_child=txtw, visible_child=txtw,
) )
else: else:
# Select this one if it was previously selected. # Select this one if it was previously selected. Go by
# Go by index if there's one. # index if there's one.
if old_selection_index is not None: if old_selection_index is not None:
if index == old_selection_index: if index == old_selection_index:
bui.columnwidget( bui.columnwidget(
@ -493,10 +449,10 @@ class PlaylistCustomizeBrowserWindow(bui.MainWindow):
index += 1 index += 1
def _save_playlist_selection(self) -> None: def _save_playlist_selection(self) -> None:
# Store the selected playlist in prefs. # Store the selected playlist in prefs. This serves dual
# This serves dual purposes of letting us re-select it next time # purposes of letting us re-select it next time if we want and
# if we want and also lets us pass it to the game (since we reset # also lets us pass it to the game (since we reset the whole
# the whole python environment that's not actually easy). # python environment that's not actually easy).
cfg = bui.app.config cfg = bui.app.config
cfg[self._pvars.config_name + ' Playlist Selection'] = ( cfg[self._pvars.config_name + ' Playlist Selection'] = (
self._selected_playlist_name self._selected_playlist_name
@ -511,8 +467,8 @@ class PlaylistCustomizeBrowserWindow(bui.MainWindow):
from bauiv1lib.playlist.editcontroller import PlaylistEditController from bauiv1lib.playlist.editcontroller import PlaylistEditController
from bauiv1lib.purchase import PurchaseWindow from bauiv1lib.purchase import PurchaseWindow
if UNDER_CONSTRUCTION: # No-op if we're not in control.
bui.screenmessage('UNDER CONSTRUCTION') if not self.main_window_has_control():
return return
assert bui.app.classic is not None assert bui.app.classic is not None
@ -538,18 +494,13 @@ class PlaylistCustomizeBrowserWindow(bui.MainWindow):
self._save_playlist_selection() self._save_playlist_selection()
# Kick off the edit UI. # Kick off the edit UI.
PlaylistEditController(sessiontype=self._sessiontype) PlaylistEditController(sessiontype=self._sessiontype, from_window=self)
bui.containerwidget(edit=self._root_widget, transition='out_left')
def _edit_playlist(self) -> None: def _edit_playlist(self) -> None:
# pylint: disable=cyclic-import # pylint: disable=cyclic-import
from bauiv1lib.playlist.editcontroller import PlaylistEditController from bauiv1lib.playlist.editcontroller import PlaylistEditController
from bauiv1lib.purchase import PurchaseWindow from bauiv1lib.purchase import PurchaseWindow
if UNDER_CONSTRUCTION:
bui.screenmessage('UNDER CONSTRUCTION')
return
assert bui.app.classic is not None assert bui.app.classic is not None
if REQUIRE_PRO and not bui.app.classic.accounts.have_pro_options(): if REQUIRE_PRO and not bui.app.classic.accounts.have_pro_options():
PurchaseWindow(items=['pro']) PurchaseWindow(items=['pro'])
@ -566,8 +517,8 @@ class PlaylistCustomizeBrowserWindow(bui.MainWindow):
PlaylistEditController( PlaylistEditController(
existing_playlist_name=self._selected_playlist_name, existing_playlist_name=self._selected_playlist_name,
sessiontype=self._sessiontype, sessiontype=self._sessiontype,
from_window=self,
) )
bui.containerwidget(edit=self._root_widget, transition='out_left')
def _do_delete_playlist(self) -> None: def _do_delete_playlist(self) -> None:
plus = bui.app.plus plus = bui.app.plus
@ -732,7 +683,7 @@ class PlaylistCustomizeBrowserWindow(bui.MainWindow):
bui.getsound('error').play() bui.getsound('error').play()
return return
# clamp at our max playlist number # Clamp at our max playlist number.
if len(bui.app.config[self._config_name_full]) > self._max_playlists: if len(bui.app.config[self._config_name_full]) > self._max_playlists:
bui.screenmessage( bui.screenmessage(
bui.Lstr( bui.Lstr(
@ -747,10 +698,11 @@ class PlaylistCustomizeBrowserWindow(bui.MainWindow):
return return
copy_text = bui.Lstr(resource='copyOfText').evaluate() copy_text = bui.Lstr(resource='copyOfText').evaluate()
# get just 'Copy' or whatnot
copy_word = copy_text.replace('${NAME}', '').strip()
# find a valid dup name that doesn't exist
# Get just 'Copy' or whatnot.
copy_word = copy_text.replace('${NAME}', '').strip()
# Find a valid dup name that doesn't exist.
test_index = 1 test_index = 1
base_name = self._get_playlist_display_name( base_name = self._get_playlist_display_name(
self._selected_playlist_name self._selected_playlist_name

View File

@ -290,9 +290,6 @@ class PlaylistEditWindow(bui.MainWindow):
self._editcontroller.set_edit_ui_selection(selection) self._editcontroller.set_edit_ui_selection(selection)
def _cancel(self) -> None: def _cancel(self) -> None:
# from bauiv1lib.playlist.customizebrowser import (
# PlaylistCustomizeBrowserWindow,
# )
# no-op if our underlying widget is dead or on its way out. # no-op if our underlying widget is dead or on its way out.
if not self._root_widget or self._root_widget.transitioning_out: if not self._root_widget or self._root_widget.transitioning_out:
@ -301,38 +298,25 @@ class PlaylistEditWindow(bui.MainWindow):
bui.getsound('powerdown01').play() bui.getsound('powerdown01').play()
self.main_window_back() self.main_window_back()
# bui.containerwidget(edit=self._root_widget, transition='out_right')
# assert bui.app.classic is not None
# bui.app.ui_v1.set_main_window(
# PlaylistCustomizeBrowserWindow(
# transition='in_left',
# sessiontype=self._editcontroller.get_session_type(),
# select_playlist=(
# self._editcontroller.get_existing_playlist_name()
# ),
# ),
# from_window=self,
# is_back=True,
# )
def _add(self) -> None: def _add(self) -> None:
# Store list name then tell the session to perform an add. # Store list name then tell the session to perform an add.
self._editcontroller.setname( self._editcontroller.setname(
cast(str, bui.textwidget(query=self._text_field)) cast(str, bui.textwidget(query=self._text_field))
) )
self._editcontroller.add_game_pressed() self._editcontroller.add_game_pressed(from_window=self)
def _edit(self) -> None: def _edit(self) -> None:
# Store list name then tell the session to perform an add. # Store list name then tell the session to perform an add.
self._editcontroller.setname( self._editcontroller.setname(
cast(str, bui.textwidget(query=self._text_field)) cast(str, bui.textwidget(query=self._text_field))
) )
self._editcontroller.edit_game_pressed() self._editcontroller.edit_game_pressed(from_window=self)
def _save_press(self) -> None: def _save_press(self) -> None:
from bauiv1lib.playlist.customizebrowser import (
PlaylistCustomizeBrowserWindow, # No-op if we're not in control.
) if not self.main_window_has_control():
return
# no-op if our underlying widget is dead or on its way out. # no-op if our underlying widget is dead or on its way out.
if not self._root_widget or self._root_widget.transitioning_out: if not self._root_widget or self._root_widget.transitioning_out:
@ -395,18 +379,9 @@ class PlaylistEditWindow(bui.MainWindow):
) )
plus.run_v1_account_transactions() plus.run_v1_account_transactions()
bui.containerwidget(edit=self._root_widget, transition='out_right')
bui.getsound('gunCocking').play() bui.getsound('gunCocking').play()
assert bui.app.classic is not None
bui.app.ui_v1.set_main_window( self.main_window_back()
PlaylistCustomizeBrowserWindow(
transition='in_left',
sessiontype=self._editcontroller.get_session_type(),
select_playlist=new_name,
),
from_window=self,
is_back=True,
)
def _save_press_with_sound(self) -> None: def _save_press_with_sound(self) -> None:
bui.getsound('swish').play() bui.getsound('swish').play()

View File

@ -11,7 +11,7 @@ import bascenev1 as bs
import bauiv1 as bui import bauiv1 as bui
if TYPE_CHECKING: if TYPE_CHECKING:
from typing import Any from typing import Any, Callable
class PlaylistEditController: class PlaylistEditController:
@ -20,8 +20,8 @@ class PlaylistEditController:
def __init__( def __init__(
self, self,
sessiontype: type[bs.Session], sessiontype: type[bs.Session],
from_window: bui.MainWindow,
existing_playlist_name: str | None = None, existing_playlist_name: str | None = None,
transition: str = 'in_right',
playlist: list[dict[str, Any]] | None = None, playlist: list[dict[str, Any]] | None = None,
playlist_name: str | None = None, playlist_name: str | None = None,
): ):
@ -44,6 +44,9 @@ class PlaylistEditController:
self._existing_playlist_name = existing_playlist_name self._existing_playlist_name = existing_playlist_name
self._config_name_full = self._pvars.config_name + ' Playlists' self._config_name_full = self._pvars.config_name + ' Playlists'
self._pre_game_add_state: bui.MainWindowState | None = None
self._pre_game_edit_state: bui.MainWindowState | None = None
# Make sure config exists. # Make sure config exists.
if self._config_name_full not in appconfig: if self._config_name_full not in appconfig:
appconfig[self._config_name_full] = {} appconfig[self._config_name_full] = {}
@ -88,11 +91,12 @@ class PlaylistEditController:
# and that's all they can do. # and that's all they can do.
self._edit_ui_selection = 'add_button' self._edit_ui_selection = 'add_button'
assert bui.app.classic is not None editwindow = PlaylistEditWindow(editcontroller=self)
bui.app.ui_v1.set_main_window( from_window.main_window_replace(editwindow)
PlaylistEditWindow(editcontroller=self, transition=transition),
from_window=False, # Disable this check. # Once we've set our start window, store the back state. We'll
) # skip back to there once we're fully done.
self._back_state = editwindow.main_window_back_state
def get_config_name(self) -> str: def get_config_name(self) -> str:
"""(internal)""" """(internal)"""
@ -142,83 +146,88 @@ class PlaylistEditController:
"""Sets the selected playlist index.""" """Sets the selected playlist index."""
self._selected_index = index self._selected_index = index
def add_game_pressed(self) -> None: def add_game_pressed(self, from_window: bui.MainWindow) -> None:
"""(internal)""" """(internal)"""
from bauiv1lib.playlist.addgame import PlaylistAddGameWindow from bauiv1lib.playlist.addgame import PlaylistAddGameWindow
assert bui.app.classic is not None # assert bui.app.classic is not None
bui.app.ui_v1.clear_main_window()
bui.app.ui_v1.set_main_window(
PlaylistAddGameWindow(editcontroller=self), from_window=None
)
def edit_game_pressed(self) -> None: # 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)
# Once we're there, store the back state. We'll use that to jump
# back 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
def edit_game_pressed(self, from_window: bui.MainWindow) -> None:
"""Should be called by supplemental UIs when a game is to be edited.""" """Should be called by supplemental UIs when a game is to be edited."""
if not self._playlist: if not self._playlist:
return return
self._show_edit_ui( self._show_edit_ui(
gametype=bui.getclass( gametype=bui.getclass(
self._playlist[self._selected_index]['type'], self._playlist[self._selected_index]['type'],
subclassof=bs.GameActivity, subclassof=bs.GameActivity,
), ),
settings=self._playlist[self._selected_index], settings=self._playlist[self._selected_index],
from_window=from_window,
) )
# def add_game_cancelled(self) -> None:
# """(internal)"""
# from bauiv1lib.playlist.edit import PlaylistEditWindow
# assert bui.app.classic is not None
# bui.app.ui_v1.clear_main_window(transition='out_right')
# bui.app.ui_v1.set_main_window(
# PlaylistEditWindow(editcontroller=self, transition='in_left'),
# from_window=None,
# is_back=True,
# )
def _show_edit_ui( def _show_edit_ui(
self, gametype: type[bs.GameActivity], settings: dict[str, Any] | None self,
gametype: type[bs.GameActivity],
settings: dict[str, Any] | None,
from_window: bui.MainWindow,
) -> None: ) -> None:
# pylint: disable=cyclic-import
from bauiv1lib.playlist.editgame import PlaylistEditGameWindow
if not from_window.main_window_has_control():
return
self._editing_game = settings is not None self._editing_game = settings is not None
self._editing_game_type = gametype self._editing_game_type = gametype
assert self._sessiontype is not None assert self._sessiontype is not None
gametype.create_settings_ui(
self._sessiontype, copy.deepcopy(settings), self._edit_game_done # Jump into an edit window.
editwindow = PlaylistEditGameWindow(
gametype,
self._sessiontype,
copy.deepcopy(settings),
completion_call=self._edit_game_done,
)
from_window.main_window_replace(editwindow)
# Once we're there, store the back state. We'll use that to jump
# back 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
def add_game_type_selected(
self, gametype: type[bs.GameActivity], from_window: bui.MainWindow
) -> None:
"""(internal)"""
self._show_edit_ui(
gametype=gametype, settings=None, from_window=from_window
) )
def add_game_type_selected(self, gametype: type[bs.GameActivity]) -> None: def _edit_game_done(
"""(internal)""" self, config: dict[str, Any] | None, from_window: bui.MainWindow
self._show_edit_ui(gametype=gametype, settings=None) ) -> None:
def _edit_game_done(self, config: dict[str, Any] | None) -> None: # No-op if provided window isn't in charge.
from bauiv1lib.playlist.edit import PlaylistEditWindow if not from_window.main_window_has_control():
from bauiv1lib.playlist.addgame import PlaylistAddGameWindow return
assert bui.app.classic is not None assert bui.app.classic is not None
if config is None: if config is None:
# If we were editing, go back to our list. bui.getsound('powerdown01').play()
if self._editing_game:
bui.getsound('powerdown01').play()
bui.app.ui_v1.clear_main_window()
bui.app.ui_v1.set_main_window(
PlaylistEditWindow(
editcontroller=self, transition='in_left'
),
from_window=None,
is_back=True,
)
# Otherwise we were adding; go back to the add type choice list.
else:
bui.app.ui_v1.clear_main_window()
bui.app.ui_v1.set_main_window(
PlaylistAddGameWindow(
editcontroller=self, transition='in_left'
),
from_window=None,
is_back=True,
)
else: else:
# Make sure type is in there. # Make sure type is in there.
assert self._editing_game_type is not None assert self._editing_game_type is not None
@ -235,9 +244,17 @@ class PlaylistEditController:
self._selected_index = insert_index self._selected_index = insert_index
bui.getsound('gunCocking').play() bui.getsound('gunCocking').play()
bui.app.ui_v1.clear_main_window()
bui.app.ui_v1.set_main_window( # If we're adding, jump to before the add started.
PlaylistEditWindow(editcontroller=self, transition='in_left'), # Otherwise jump to before the edit started.
from_window=None, assert (
is_back=True, self._pre_game_edit_state is not None
) or self._pre_game_add_state is not None
)
if self._pre_game_add_state is not None:
from_window.main_window_back_state = self._pre_game_add_state
elif self._pre_game_edit_state is not None:
from_window.main_window_back_state = self._pre_game_edit_state
from_window.main_window_back()
self._pre_game_edit_state = None
self._pre_game_add_state = None

View File

@ -24,7 +24,7 @@ class PlaylistEditGameWindow(bui.MainWindow):
gametype: type[bs.GameActivity], gametype: type[bs.GameActivity],
sessiontype: type[bs.Session], sessiontype: type[bs.Session],
config: dict[str, Any] | None, config: dict[str, Any] | None,
completion_call: Callable[[dict[str, Any] | None], Any], completion_call: Callable[[dict[str, Any] | None, bui.MainWindow], Any],
default_selection: str | None = None, default_selection: str | None = None,
transition: str | None = 'in_right', transition: str | None = 'in_right',
origin_widget: bui.Widget | None = None, origin_widget: bui.Widget | None = None,
@ -141,15 +141,15 @@ class PlaylistEditGameWindow(bui.MainWindow):
btn = bui.buttonwidget( btn = bui.buttonwidget(
parent=self._root_widget, parent=self._root_widget,
position=(45 + x_inset, height - 82 + y_extra2), position=(45 + x_inset, height - 82 + y_extra2),
size=(180, 70) if is_add else (180, 65), size=(60, 48) if is_add else (180, 65),
label=( label=(
bui.Lstr(resource='backText') bui.charstr(bui.SpecialChar.BACK)
if is_add if is_add
else bui.Lstr(resource='cancelText') else bui.Lstr(resource='cancelText')
), ),
button_type='back' if is_add else None, button_type='backSmall' if is_add else None,
autoselect=True, autoselect=True,
scale=0.75, scale=1.0 if is_add else 0.75,
text_scale=1.3, text_scale=1.3,
on_activate_call=bui.Call(self._cancel), on_activate_call=bui.Call(self._cancel),
) )
@ -541,24 +541,41 @@ class PlaylistEditGameWindow(bui.MainWindow):
# pylint: disable=cyclic-import # pylint: disable=cyclic-import
from bauiv1lib.playlist.mapselect import PlaylistMapSelectWindow from bauiv1lib.playlist.mapselect import PlaylistMapSelectWindow
# no-op if our underlying widget is dead or on its way out. # No-op if we're not in control.
if not self._root_widget or self._root_widget.transitioning_out: if not self.main_window_has_control():
return return
# no-op if our underlying widget is dead or on its way out.
# if not self._root_widget or self._root_widget.transitioning_out:
# return
self._config = self._getconfig()
# Replace ourself with the map-select UI. # Replace ourself with the map-select UI.
bui.containerwidget(edit=self._root_widget, transition='out_left') self.main_window_replace(
assert bui.app.classic is not None
bui.app.ui_v1.set_main_window(
PlaylistMapSelectWindow( PlaylistMapSelectWindow(
self._gametype, self._gametype,
self._sessiontype, self._sessiontype,
copy.deepcopy(self._getconfig()), # copy.deepcopy(self._getconfig()),
self._config,
self._edit_info, self._edit_info,
self._completion_call, self._completion_call,
), )
from_window=self,
) )
# bui.containerwidget(edit=self._root_widget, transition='out_left')
# assert bui.app.classic is not None
# bui.app.ui_v1.set_main_window(
# PlaylistMapSelectWindow(
# self._gametype,
# self._sessiontype,
# copy.deepcopy(self._getconfig()),
# self._edit_info,
# self._completion_call,
# ),
# from_window=self,
# )
def _choice_inc( def _choice_inc(
self, self,
setting_name: str, setting_name: str,
@ -586,7 +603,7 @@ class PlaylistEditGameWindow(bui.MainWindow):
][1] ][1]
def _cancel(self) -> None: def _cancel(self) -> None:
self._completion_call(None) self._completion_call(None, self)
def _check_value_change( def _check_value_change(
self, setting_name: str, widget: bui.Widget, value: int self, setting_name: str, widget: bui.Widget, value: int
@ -607,7 +624,7 @@ class PlaylistEditGameWindow(bui.MainWindow):
return {'settings': settings} return {'settings': settings}
def _add(self) -> None: def _add(self) -> None:
self._completion_call(copy.deepcopy(self._getconfig())) self._completion_call(self._getconfig(), self)
def _inc( def _inc(
self, self,

View File

@ -5,7 +5,7 @@
from __future__ import annotations from __future__ import annotations
import math import math
from typing import TYPE_CHECKING from typing import TYPE_CHECKING, override
import bauiv1 as bui import bauiv1 as bui
@ -24,9 +24,10 @@ class PlaylistMapSelectWindow(bui.MainWindow):
sessiontype: type[bs.Session], sessiontype: type[bs.Session],
config: dict[str, Any], config: dict[str, Any],
edit_info: dict[str, Any], edit_info: dict[str, Any],
completion_call: Callable[[dict[str, Any] | None], Any], completion_call: Callable[[dict[str, Any] | None, bui.MainWindow], Any],
transition: str | None = 'in_right', transition: str | None = 'in_right',
origin_widget: bui.Widget | None = None, origin_widget: bui.Widget | None = None,
select_get_more_maps_button: bool = False,
): ):
# pylint: disable=too-many-locals # pylint: disable=too-many-locals
@ -38,6 +39,7 @@ class PlaylistMapSelectWindow(bui.MainWindow):
self._completion_call = completion_call self._completion_call = completion_call
self._edit_info = edit_info self._edit_info = edit_info
self._maps: list[tuple[str, bui.Texture]] = [] self._maps: list[tuple[str, bui.Texture]] = []
self._selected_get_more_maps = False
try: try:
self._previous_map = get_filtered_map_name( self._previous_map = get_filtered_map_name(
config['settings']['map'] config['settings']['map']
@ -114,7 +116,34 @@ class PlaylistMapSelectWindow(bui.MainWindow):
bui.containerwidget(edit=self._scrollwidget, claims_left_right=True) bui.containerwidget(edit=self._scrollwidget, claims_left_right=True)
self._subcontainer: bui.Widget | None = None self._subcontainer: bui.Widget | None = None
self._refresh() self._refresh(select_get_more_maps_button=select_get_more_maps_button)
@override
def get_main_window_state(self) -> bui.MainWindowState:
# Support recreating our window for back/refresh purposes.
cls = type(self)
# Pull things out of self here; if we do it in the lambda we'll
# keep ourself alive.
gametype = self._gametype
sessiontype = self._sessiontype
config = self._config
edit_info = self._edit_info
completion_call = self._completion_call
select_get_more_maps = self._selected_get_more_maps
return bui.BasicMainWindowState(
create_call=lambda transition, origin_widget: cls(
transition=transition,
origin_widget=origin_widget,
gametype=gametype,
sessiontype=sessiontype,
config=config,
edit_info=edit_info,
completion_call=completion_call,
select_get_more_maps_button=select_get_more_maps,
)
)
def _refresh(self, select_get_more_maps_button: bool = False) -> None: def _refresh(self, select_get_more_maps_button: bool = False) -> None:
# pylint: disable=too-many-statements # pylint: disable=too-many-statements
@ -255,21 +284,32 @@ class PlaylistMapSelectWindow(bui.MainWindow):
from bauiv1lib import account from bauiv1lib import account
from bauiv1lib.store.browser import StoreBrowserWindow from bauiv1lib.store.browser import StoreBrowserWindow
# No-op if we're not in control.
if not self.main_window_has_control():
return
plus = bui.app.plus plus = bui.app.plus
assert plus is not None assert plus is not None
if plus.get_v1_account_state() != 'signed_in': if plus.get_v1_account_state() != 'signed_in':
account.show_sign_in_prompt() account.show_sign_in_prompt()
return return
StoreBrowserWindow(
modal=True, self._selected_get_more_maps = True
show_tab=StoreBrowserWindow.TabID.MAPS,
on_close_call=self._on_store_close, self.main_window_replace(
origin_widget=self._get_more_maps_button, StoreBrowserWindow(
# modal=True,
show_tab=StoreBrowserWindow.TabID.MAPS,
# on_close_call=self._on_store_close,
origin_widget=self._get_more_maps_button,
minimal_toolbars=True,
)
) )
def _on_store_close(self) -> None: # def _on_store_close(self) -> None:
self._refresh(select_get_more_maps_button=True) # pass
# self._refresh(select_get_more_maps_button=True)
def _select(self, map_name: str) -> None: def _select(self, map_name: str) -> None:
# from bauiv1lib.playlist.editgame import PlaylistEditGameWindow # from bauiv1lib.playlist.editgame import PlaylistEditGameWindow

View File

@ -15,6 +15,8 @@ from bauiv1lib.popup import PopupWindow
if TYPE_CHECKING: if TYPE_CHECKING:
from typing import Any from typing import Any
from bauiv1lib.play import PlaylistSelectContext
class PlayOptionsWindow(PopupWindow): class PlayOptionsWindow(PopupWindow):
"""A popup window for configuring play options.""" """A popup window for configuring play options."""
@ -25,6 +27,7 @@ class PlayOptionsWindow(PopupWindow):
playlist: str, playlist: str,
scale_origin: tuple[float, float], scale_origin: tuple[float, float],
delegate: Any = None, delegate: Any = None,
playlist_select_context: PlaylistSelectContext | None = None,
): ):
# FIXME: Tidy this up. # FIXME: Tidy this up.
# pylint: disable=too-many-branches # pylint: disable=too-many-branches
@ -39,11 +42,7 @@ class PlayOptionsWindow(PopupWindow):
self._pvars = PlaylistTypeVars(sessiontype) self._pvars = PlaylistTypeVars(sessiontype)
self._transitioning_out = False self._transitioning_out = False
# We behave differently if we're being used for playlist selection self._playlist_select_context = playlist_select_context
# vs starting a game directly (should make this more elegant).
classic = bui.app.classic
assert classic is not None
self._selecting_mode = classic.selecting_private_party_playlist
self._do_randomize_val = bui.app.config.get( self._do_randomize_val = bui.app.config.get(
self._pvars.config_name + ' Playlist Randomize', 0 self._pvars.config_name + ' Playlist Randomize', 0
@ -66,7 +65,8 @@ class PlayOptionsWindow(PopupWindow):
mesh_transparent = bui.getmesh('level_select_button_transparent') mesh_transparent = bui.getmesh('level_select_button_transparent')
mask_tex = bui.gettexture('mapPreviewMask') mask_tex = bui.gettexture('mapPreviewMask')
# Poke into this playlist and see if we can display some of its maps. # Poke into this playlist and see if we can display some of its
# maps.
map_textures = [] map_textures = []
map_texture_entries = [] map_texture_entries = []
rows = 0 rows = 0
@ -405,7 +405,11 @@ class PlayOptionsWindow(PopupWindow):
on_activate_call=self._on_ok_press, on_activate_call=self._on_ok_press,
autoselect=True, autoselect=True,
label=bui.Lstr( label=bui.Lstr(
resource='okText' if self._selecting_mode else 'playText' resource=(
'okText'
if self._playlist_select_context is not None
else 'playText'
)
), ),
) )
@ -498,8 +502,8 @@ class PlayOptionsWindow(PopupWindow):
# Head back to the gather window in playlist-select mode # Head back to the gather window in playlist-select mode
# or start the game in regular mode. # or start the game in regular mode.
if self._selecting_mode: if self._playlist_select_context is not None:
from bauiv1lib.gather import GatherWindow # from bauiv1lib.gather import GatherWindow
if self._sessiontype is bs.FreeForAllSession: if self._sessiontype is bs.FreeForAllSession:
typename = 'ffa' typename = 'ffa'
@ -509,14 +513,15 @@ class PlayOptionsWindow(PopupWindow):
raise RuntimeError('Only teams and ffa currently supported') raise RuntimeError('Only teams and ffa currently supported')
cfg['Private Party Host Session Type'] = typename cfg['Private Party Host Session Type'] = typename
bui.getsound('gunCocking').play() bui.getsound('gunCocking').play()
assert bui.app.classic is not None
# Note: this is a wonky situation where we aren't actually # assert bui.app.classic is not None
# the main window but we set it on behalf of the main window # # Note: this is a wonky situation where we aren't actually
# that popped us up. # # the main window but we set it on behalf of the main window
bui.app.ui_v1.set_main_window( # # that popped us up.
GatherWindow(transition='in_right'), # bui.app.ui_v1.set_main_window(
from_window=False, # Disable this test. # GatherWindow(transition='in_right'),
) # from_window=False, # Disable this test.
# )
self._transition_out(transition='out_left') self._transition_out(transition='out_left')
if self._delegate is not None: if self._delegate is not None:
self._delegate.on_play_options_window_run_game() self._delegate.on_play_options_window_run_game()

View File

@ -105,7 +105,7 @@ class ControlsSettingsWindow(bui.MainWindow):
height += spacing height += spacing
assert bui.app.classic is not None assert bui.app.classic is not None
smallscale = 1.7 if show_keyboard else 2.2 smallscale = 1.7
super().__init__( super().__init__(
root_widget=bui.containerwidget( root_widget=bui.containerwidget(
size=(width, height), size=(width, height),

View File

@ -24,20 +24,20 @@ class GamepadSettingsWindow(bui.MainWindow):
def __init__( def __init__(
self, self,
gamepad: bs.InputDevice, inputdevice: bs.InputDevice,
is_main_menu: bool = True, modal: bool = False,
transition: str = 'in_right', transition: str = 'in_right',
transition_out: str = 'out_right', transition_out: str = 'out_right',
origin_widget: bui.Widget | None = None, origin_widget: bui.Widget | None = None,
settings: dict | None = None, settings: dict | None = None,
): ):
self._input = gamepad self._inputdevice = inputdevice
# If our input-device went away, just return an empty zombie. # If our input-device went away, just return an empty zombie.
if not self._input: if not self._inputdevice:
return return
self._name = self._input.name self._name = self._inputdevice.name
self._r = 'configGamepadWindow' self._r = 'configGamepadWindow'
self._transition_out = transition_out self._transition_out = transition_out
@ -45,7 +45,7 @@ class GamepadSettingsWindow(bui.MainWindow):
# We're a secondary gamepad if supplied with settings. # We're a secondary gamepad if supplied with settings.
self._is_secondary = settings is not None self._is_secondary = settings is not None
self._ext = '_B' if self._is_secondary else '' self._ext = '_B' if self._is_secondary else ''
self._is_main_menu = is_main_menu self._modal = modal
self._displayname = self._name self._displayname = self._name
self._width = 700 if self._is_secondary else 730 self._width = 700 if self._is_secondary else 730
self._height = 440 if self._is_secondary else 450 self._height = 440 if self._is_secondary else 450
@ -56,14 +56,13 @@ class GamepadSettingsWindow(bui.MainWindow):
root_widget=bui.containerwidget( root_widget=bui.containerwidget(
size=(self._width, self._height), size=(self._width, self._height),
scale=( scale=(
1.63 1.4
if uiscale is bui.UIScale.SMALL if uiscale is bui.UIScale.SMALL
else 1.35 if uiscale is bui.UIScale.MEDIUM else 1.0 else 1.3 if uiscale is bui.UIScale.MEDIUM else 1.0
), ),
stack_offset=( stack_offset=(
(-20, -16) if uiscale is bui.UIScale.SMALL else (0, 0) (0, -10) if uiscale is bui.UIScale.SMALL else (0, 0)
), ),
transition=transition,
), ),
transition=transition, transition=transition,
origin_widget=origin_widget, origin_widget=origin_widget,
@ -72,6 +71,7 @@ class GamepadSettingsWindow(bui.MainWindow):
self._settings: dict[str, int] = {} self._settings: dict[str, int] = {}
if not self._is_secondary: if not self._is_secondary:
self._get_config_mapping() self._get_config_mapping()
# Don't ask to config joysticks while we're in here. # Don't ask to config joysticks while we're in here.
self._rebuild_ui() self._rebuild_ui()
@ -137,7 +137,7 @@ class GamepadSettingsWindow(bui.MainWindow):
]: ]:
assert bui.app.classic is not None assert bui.app.classic is not None
val = bui.app.classic.get_input_device_mapped_value( val = bui.app.classic.get_input_device_mapped_value(
self._input, button, default self._inputdevice, button, default
) )
if val != -1: if val != -1:
self._settings[button] = val self._settings[button] = val
@ -419,7 +419,7 @@ class GamepadSettingsWindow(bui.MainWindow):
def get_input(self) -> bs.InputDevice: def get_input(self) -> bs.InputDevice:
"""(internal)""" """(internal)"""
return self._input return self._inputdevice
def _do_advanced(self) -> None: def _do_advanced(self) -> None:
# pylint: disable=cyclic-import # pylint: disable=cyclic-import
@ -535,8 +535,8 @@ class GamepadSettingsWindow(bui.MainWindow):
def show_secondary_editor(self) -> None: def show_secondary_editor(self) -> None:
"""(internal)""" """(internal)"""
GamepadSettingsWindow( GamepadSettingsWindow(
self._input, self._inputdevice,
is_main_menu=False, modal=True,
settings=self._settings, settings=self._settings,
transition='in_scale', transition='in_scale',
transition_out='out_scale', transition_out='out_scale',
@ -562,16 +562,16 @@ class GamepadSettingsWindow(bui.MainWindow):
assert isinstance(sval2, (int, type(None))) assert isinstance(sval2, (int, type(None)))
if sval1 is not None and sval2 is not None: if sval1 is not None and sval2 is not None:
return ( return (
self._input.get_axis_name(sval1) self._inputdevice.get_axis_name(sval1)
+ ' / ' + ' / '
+ self._input.get_axis_name(sval2) + self._inputdevice.get_axis_name(sval2)
) )
return bui.Lstr(resource=f'{self._r}.unsetText') return bui.Lstr(resource=f'{self._r}.unsetText')
# If they're looking for triggers. # If they're looking for triggers.
if control in ['triggerRun1' + self._ext, 'triggerRun2' + self._ext]: if control in ['triggerRun1' + self._ext, 'triggerRun2' + self._ext]:
if control in self._settings: if control in self._settings:
return self._input.get_axis_name(self._settings[control]) return self._inputdevice.get_axis_name(self._settings[control])
return bui.Lstr(resource=f'{self._r}.unsetText') return bui.Lstr(resource=f'{self._r}.unsetText')
# Dead-zone. # Dead-zone.
@ -592,7 +592,9 @@ class GamepadSettingsWindow(bui.MainWindow):
# If *any* dpad buttons are assigned, show only button assignments. # If *any* dpad buttons are assigned, show only button assignments.
if any(b in self._settings for b in dpad_buttons): if any(b in self._settings for b in dpad_buttons):
if control in self._settings: if control in self._settings:
return self._input.get_button_name(self._settings[control]) return self._inputdevice.get_button_name(
self._settings[control]
)
return bui.Lstr(resource=f'{self._r}.unsetText') return bui.Lstr(resource=f'{self._r}.unsetText')
# No dpad buttons - show the dpad number for all 4. # No dpad buttons - show the dpad number for all 4.
@ -617,7 +619,7 @@ class GamepadSettingsWindow(bui.MainWindow):
# Other buttons. # Other buttons.
if control in self._settings: if control in self._settings:
return self._input.get_button_name(self._settings[control]) return self._inputdevice.get_button_name(self._settings[control])
return bui.Lstr(resource=f'{self._r}.unsetText') return bui.Lstr(resource=f'{self._r}.unsetText')
def _gamepad_event( def _gamepad_event(
@ -694,7 +696,7 @@ class GamepadSettingsWindow(bui.MainWindow):
# Now launch the up/down listener. # Now launch the up/down listener.
AwaitGamepadInputWindow( AwaitGamepadInputWindow(
self._input, self._inputdevice,
'analogStickUD' + ext, 'analogStickUD' + ext,
self._gamepad_event, self._gamepad_event,
bui.Lstr(resource=f'{self._r}.pressUpDownText'), bui.Lstr(resource=f'{self._r}.pressUpDownText'),
@ -783,7 +785,7 @@ class GamepadSettingsWindow(bui.MainWindow):
edit=btn, edit=btn,
on_activate_call=bui.Call( on_activate_call=bui.Call(
AwaitGamepadInputWindow, AwaitGamepadInputWindow,
self._input, self._inputdevice,
button, button,
self._gamepad_event, self._gamepad_event,
message, message,
@ -795,16 +797,16 @@ class GamepadSettingsWindow(bui.MainWindow):
return btn return btn
def _cancel(self) -> None: def _cancel(self) -> None:
# from bauiv1lib.settings.controls import ControlsSettingsWindow
# no-op if our underlying widget is dead or on its way out. # no-op if our underlying widget is dead or on its way out.
if not self._root_widget or self._root_widget.transitioning_out: if not self._root_widget or self._root_widget.transitioning_out:
return return
bui.containerwidget( if self._modal:
edit=self._root_widget, transition=self._transition_out bui.containerwidget(
) edit=self._root_widget, transition=self._transition_out
if self._is_main_menu: )
else:
self.main_window_back() self.main_window_back()
# assert bui.app.classic is not None # assert bui.app.classic is not None
# bui.app.ui_v1.set_main_window( # bui.app.ui_v1.set_main_window(
@ -899,18 +901,20 @@ class GamepadSettingsWindow(bui.MainWindow):
if not self._root_widget or self._root_widget.transitioning_out: if not self._root_widget or self._root_widget.transitioning_out:
return return
bui.containerwidget(
edit=self._root_widget, transition=self._transition_out
)
# If we're a secondary editor we just go away (we were editing our # If we're a secondary editor we just go away (we were editing our
# parent's settings dict). # parent's settings dict).
if self._is_secondary: if self._is_secondary:
assert self._modal
bui.containerwidget(
edit=self._root_widget, transition=self._transition_out
)
return return
assert self._settings is not None assert self._settings is not None
if self._input: if self._inputdevice:
dst = classic.get_input_device_config(self._input, default=True) dst = classic.get_input_device_config(
self._inputdevice, default=True
)
dst2: dict[str, Any] = dst[0][dst[1]] dst2: dict[str, Any] = dst[0][dst[1]]
dst2.clear() dst2.clear()
@ -921,7 +925,7 @@ class GamepadSettingsWindow(bui.MainWindow):
# If we're allowed to phone home, send this config so we can # If we're allowed to phone home, send this config so we can
# generate more defaults in the future. # generate more defaults in the future.
inputhash = classic.get_input_device_map_hash(self._input) inputhash = classic.get_input_device_map_hash(self._inputdevice)
classic.master_server_v1_post( classic.master_server_v1_post(
'controllerConfig', 'controllerConfig',
{ {
@ -938,15 +942,13 @@ class GamepadSettingsWindow(bui.MainWindow):
else: else:
bui.getsound('error').play() bui.getsound('error').play()
if self._is_main_menu: if self._modal:
from bauiv1lib.settings.controls import ControlsSettingsWindow bui.containerwidget(
edit=self._root_widget, transition=self._transition_out
assert bui.app.classic is not None
bui.app.ui_v1.set_main_window(
ControlsSettingsWindow(transition='in_left'),
from_window=self,
is_back=True,
) )
else:
assert self.main_window_has_control()
self.main_window_back()
class AwaitGamepadInputWindow(bui.Window): class AwaitGamepadInputWindow(bui.Window):

View File

@ -40,7 +40,7 @@ class GamepadAdvancedSettingsWindow(bui.Window):
size=(self._width, self._height), size=(self._width, self._height),
scale=1.06 scale=1.06
* ( * (
1.85 1.6
if uiscale is bui.UIScale.SMALL if uiscale is bui.UIScale.SMALL
else 1.35 if uiscale is bui.UIScale.MEDIUM else 1.0 else 1.35 if uiscale is bui.UIScale.MEDIUM else 1.0
), ),

View File

@ -4,7 +4,6 @@
from __future__ import annotations from __future__ import annotations
import logging
from typing import TYPE_CHECKING, override from typing import TYPE_CHECKING, override
import bascenev1 as bs import bascenev1 as bs
@ -14,107 +13,6 @@ if TYPE_CHECKING:
from typing import Any from typing import Any
def gamepad_configure_callback(event: dict[str, Any]) -> None:
"""Respond to a gamepad button press during config selection."""
from bauiv1lib.settings.gamepad import GamepadSettingsWindow
# Ignore all but button-presses.
if event['type'] not in ['BUTTONDOWN', 'HATMOTION']:
return
bs.release_gamepad_input()
if bool(True):
bui.screenmessage('UNDER CONSTRUCTION')
return
assert bui.app.classic is not None
try:
bui.app.ui_v1.clear_main_window()
except Exception:
logging.exception('Error transitioning out main_menu_window.')
bui.getsound('activateBeep').play()
bui.getsound('swish').play()
device = event['input_device']
assert isinstance(device, bs.InputDevice)
if device.allows_configuring:
bui.app.ui_v1.set_main_window(
GamepadSettingsWindow(device), from_window=None
)
else:
width = 700
height = 200
button_width = 80
uiscale = bui.app.ui_v1.uiscale
class _Window(bui.MainWindow):
def __init__(self) -> None:
super().__init__(
root_widget=bui.containerwidget(
scale=(
1.7
if uiscale is bui.UIScale.SMALL
else 1.4 if uiscale is bui.UIScale.MEDIUM else 1.0
),
size=(width, height),
),
transition='in_right',
origin_widget=None,
)
win = _Window()
dlg = win.get_root_widget()
bui.app.ui_v1.set_main_window(win, from_window=None)
if device.allows_configuring_in_system_settings:
msg = bui.Lstr(
resource='configureDeviceInSystemSettingsText',
subs=[('${DEVICE}', device.name)],
)
elif device.is_controller_app:
msg = bui.Lstr(
resource='bsRemoteConfigureInAppText',
subs=[('${REMOTE_APP_NAME}', bui.get_remote_app_name())],
)
else:
msg = bui.Lstr(
resource='cantConfigureDeviceText',
subs=[('${DEVICE}', device.name)],
)
bui.textwidget(
parent=dlg,
position=(0, height - 80),
size=(width, 25),
text=msg,
scale=0.8,
h_align='center',
v_align='top',
)
def _ok() -> None:
from bauiv1lib.settings import controls
# no-op if our underlying widget is dead or on its way out.
if not dlg or dlg.transitioning_out:
return
bui.containerwidget(edit=dlg, transition='out_right')
assert bui.app.classic is not None
bui.app.ui_v1.set_main_window(
controls.ControlsSettingsWindow(transition='in_left'),
from_window=win,
is_back=True,
)
bui.buttonwidget(
parent=dlg,
position=((width - button_width) / 2, 20),
size=(button_width, 60),
label=bui.Lstr(resource='okText'),
on_activate_call=_ok,
)
class GamepadSelectWindow(bui.MainWindow): class GamepadSelectWindow(bui.MainWindow):
"""Window for selecting a gamepad to configure.""" """Window for selecting a gamepad to configure."""
@ -155,8 +53,8 @@ class GamepadSelectWindow(bui.MainWindow):
on_activate_call=self.main_window_back, on_activate_call=self.main_window_back,
) )
# Let's not have anything selected by default; its misleading looking # Let's not have anything selected by default; its misleading
# for the controller getting configured. # looking for the controller getting configured.
bui.containerwidget( bui.containerwidget(
edit=self._root_widget, edit=self._root_widget,
cancel_button=btn, cancel_button=btn,
@ -207,7 +105,10 @@ class GamepadSelectWindow(bui.MainWindow):
v_align='top', v_align='top',
) )
bs.capture_gamepad_input(gamepad_configure_callback) bs.capture_gamepad_input(bui.WeakCall(self.gamepad_configure_callback))
def __del__(self) -> None:
bs.release_gamepad_input()
@override @override
def get_main_window_state(self) -> bui.MainWindowState: def get_main_window_state(self) -> bui.MainWindowState:
@ -219,6 +120,102 @@ class GamepadSelectWindow(bui.MainWindow):
) )
) )
@override def gamepad_configure_callback(self, event: dict[str, Any]) -> None:
def on_main_window_close(self) -> None: """Respond to a gamepad button press during config selection."""
from bauiv1lib.settings.gamepad import GamepadSettingsWindow
if not self.main_window_has_control():
return
# Ignore all but button-presses.
if event['type'] not in ['BUTTONDOWN', 'HATMOTION']:
return
bs.release_gamepad_input() bs.release_gamepad_input()
assert bui.app.classic is not None
bui.getsound('activateBeep').play()
bui.getsound('swish').play()
device = event['input_device']
assert isinstance(device, bs.InputDevice)
# No matter where we redirect to, we want their back
# functionality to skip over us and go to our parent.
assert self.main_window_back_state is not None
back_state = self.main_window_back_state
if device.allows_configuring:
self.main_window_replace(
GamepadSettingsWindow(device), back_state=back_state
)
else:
self.main_window_replace(
_NotConfigurableWindow(device), back_state=back_state
)
class _NotConfigurableWindow(bui.MainWindow):
def __init__(self, device: bs.InputDevice) -> None:
width = 700
height = 200
button_width = 80
uiscale = bui.app.ui_v1.uiscale
super().__init__(
root_widget=bui.containerwidget(
scale=(
1.7
if uiscale is bui.UIScale.SMALL
else (1.4 if uiscale is bui.UIScale.MEDIUM else 1.0)
),
size=(width, height),
),
transition='in_right',
origin_widget=None,
)
if device.allows_configuring_in_system_settings:
msg = bui.Lstr(
resource='configureDeviceInSystemSettingsText',
subs=[('${DEVICE}', device.name)],
)
elif device.is_controller_app:
msg = bui.Lstr(
resource='bsRemoteConfigureInAppText',
subs=[
(
'${REMOTE_APP_NAME}',
bui.get_remote_app_name(),
)
],
)
else:
msg = bui.Lstr(
resource='cantConfigureDeviceText',
subs=[('${DEVICE}', device.name)],
)
bui.textwidget(
parent=self._root_widget,
position=(0, height - 80),
size=(width, 25),
text=msg,
scale=0.8,
h_align='center',
v_align='top',
)
btn = bui.buttonwidget(
parent=self._root_widget,
position=((width - button_width) / 2, 20),
size=(button_width, 60),
label=bui.Lstr(resource='okText'),
on_activate_call=self.main_window_back,
)
bui.containerwidget(edit=self._root_widget, cancel_button=btn)
# def _ok(self) -> None:
# # Back would take us to the gamepad-select window. We want to go
# # past that.
# assert self.main_window_back_state is not None
# self.main_window_back_state = self.main_window_back_state.parent
# self.main_window_back()

View File

@ -393,8 +393,7 @@ class SoundtrackEditWindow(bui.MainWindow):
bui.Call(self._restore_editor, state, song_type), bui.Call(self._restore_editor, state, song_type),
entry, entry,
selection_target_name, selection_target_name,
), )
group_id='soundtrackentryselect',
) )
# bui.containerwidget(edit=self._root_widget, transition='out_left') # bui.containerwidget(edit=self._root_widget, transition='out_left')
# bui.app.ui_v1.set_main_window( # bui.app.ui_v1.set_main_window(

View File

@ -209,8 +209,7 @@ class SoundtrackEntryTypeSelectWindow(bui.MainWindow):
self.main_window_replace( self.main_window_replace(
MacMusicAppPlaylistSelectWindow( MacMusicAppPlaylistSelectWindow(
self._callback, current_playlist_entry, self._current_entry self._callback, current_playlist_entry, self._current_entry
), )
group_id='soundtrackentryselect',
) )
# MacMusicAppPlaylistSelectWindow( # MacMusicAppPlaylistSelectWindow(
# self._callback, current_playlist_entry, self._current_entry # self._callback, current_playlist_entry, self._current_entry
@ -241,7 +240,6 @@ class SoundtrackEntryTypeSelectWindow(bui.MainWindow):
), ),
allow_folders=False, allow_folders=False,
), ),
group_id='soundtrackentryselect',
) )
# bui.app.ui_v1.set_main_window( # bui.app.ui_v1.set_main_window(
# FileSelectorWindow( # FileSelectorWindow(
@ -276,7 +274,6 @@ class SoundtrackEntryTypeSelectWindow(bui.MainWindow):
valid_file_extensions=[], valid_file_extensions=[],
allow_folders=True, allow_folders=True,
), ),
group_id='soundtrackentryselect',
) )
# bui.app.ui_v1.set_main_window( # bui.app.ui_v1.set_main_window(
# FileSelectorWindow( # FileSelectorWindow(

View File

@ -41,11 +41,12 @@ class StoreBrowserWindow(bui.MainWindow):
def __init__( def __init__(
self, self,
transition: str | None = 'in_right', transition: str | None = 'in_right',
modal: bool = False, # modal: bool = False,
show_tab: StoreBrowserWindow.TabID | None = None, show_tab: StoreBrowserWindow.TabID | None = None,
on_close_call: Callable[[], Any] | None = None, # on_close_call: Callable[[], Any] | None = None,
back_location: str | None = None, # back_location: str | None = None,
origin_widget: bui.Widget | None = None, origin_widget: bui.Widget | None = None,
minimal_toolbars: bool = False,
): ):
# pylint: disable=too-many-statements # pylint: disable=too-many-statements
# pylint: disable=too-many-locals # pylint: disable=too-many-locals
@ -59,19 +60,19 @@ class StoreBrowserWindow(bui.MainWindow):
bui.set_analytics_screen('Store Window') bui.set_analytics_screen('Store Window')
# Need to store this ourself for modal mode. # Need to store this ourself for modal mode.
if origin_widget is not None: # if origin_widget is not None:
self._transition_out = 'out_scale' # self._transition_out = 'out_scale'
else: # else:
self._transition_out = 'out_right' # self._transition_out = 'out_right'
self.button_infos: dict[str, dict[str, Any]] | None = None self.button_infos: dict[str, dict[str, Any]] | None = None
self.update_buttons_timer: bui.AppTimer | None = None self.update_buttons_timer: bui.AppTimer | None = None
self._status_textwidget_update_timer = None self._status_textwidget_update_timer = None
self._back_location = back_location # self._back_location = back_location
self._on_close_call = on_close_call # self._on_close_call = on_close_call
self._show_tab = show_tab self._show_tab = show_tab
self._modal = modal # self._modal = modal
self._width = 1670 if uiscale is bui.UIScale.SMALL else 1040 self._width = 1670 if uiscale is bui.UIScale.SMALL else 1040
self._x_inset = x_inset = 310 if uiscale is bui.UIScale.SMALL else 0 self._x_inset = x_inset = 310 if uiscale is bui.UIScale.SMALL else 0
self._height = ( self._height = (
@ -91,7 +92,7 @@ class StoreBrowserWindow(bui.MainWindow):
size=(self._width, self._height + extra_top), size=(self._width, self._height + extra_top),
toolbar_visibility=( toolbar_visibility=(
'menu_store' 'menu_store'
if uiscale is bui.UIScale.SMALL if (uiscale is bui.UIScale.SMALL or minimal_toolbars)
else 'menu_full' else 'menu_full'
), ),
scale=( scale=(
@ -115,15 +116,15 @@ class StoreBrowserWindow(bui.MainWindow):
size=(140, 60), size=(140, 60),
scale=1.1, scale=1.1,
autoselect=True, autoselect=True,
label=bui.Lstr(resource='doneText' if self._modal else 'backText'), label=bui.Lstr(resource='backText'),
button_type=None if self._modal else 'back', button_type='back',
on_activate_call=self._back, on_activate_call=self.main_window_back,
) )
if uiscale is bui.UIScale.SMALL: if uiscale is bui.UIScale.SMALL:
self._back_button.delete() self._back_button.delete()
bui.containerwidget( bui.containerwidget(
edit=self._root_widget, on_cancel_call=self._back edit=self._root_widget, on_cancel_call=self.main_window_back
) )
backbuttonspecial = True backbuttonspecial = True
else: else:
@ -163,7 +164,7 @@ class StoreBrowserWindow(bui.MainWindow):
maxwidth=290, maxwidth=290,
) )
if not self._modal and not backbuttonspecial: if not backbuttonspecial:
bui.buttonwidget( bui.buttonwidget(
edit=self._back_button, edit=self._back_button,
button_type='backSmall', button_type='backSmall',
@ -1251,23 +1252,23 @@ class StoreBrowserWindow(bui.MainWindow):
except Exception: except Exception:
logging.exception('Error restoring state for %s.', self) logging.exception('Error restoring state for %s.', self)
def _back(self) -> None: # def _back(self) -> None:
if self._modal: # # if self._modal:
# no-op if our underlying widget is dead or on its way out. # # # no-op if our underlying widget is dead or on its way out.
if not self._root_widget or self._root_widget.transitioning_out: # # if not self._root_widget or self._root_widget.transitioning_out:
return # # return
self._save_state() # # self._save_state()
bui.containerwidget( # # bui.containerwidget(
edit=self._root_widget, transition=self._transition_out # # edit=self._root_widget, transition=self._transition_out
) # # )
else: # # else:
# no-op if we're not currently in control. # # no-op if we're not currently in control.
if not self.main_window_has_control(): # if not self.main_window_has_control():
return # return
self.main_window_back() # self.main_window_back()
if self._on_close_call is not None: # # if self._on_close_call is not None:
self._on_close_call() # # self._on_close_call()
def _check_merch_availability_in_bg_thread() -> None: def _check_merch_availability_in_bg_thread() -> None:

View File

@ -229,14 +229,18 @@ class StoreButton:
def _default_on_activate_call(self) -> None: def _default_on_activate_call(self) -> None:
# pylint: disable=cyclic-import # pylint: disable=cyclic-import
from bauiv1lib.account import show_sign_in_prompt from bauiv1lib.account import show_sign_in_prompt
from bauiv1lib.store.browser import StoreBrowserWindow
# from bauiv1lib.store.browser import StoreBrowserWindow
plus = bui.app.plus plus = bui.app.plus
assert plus is not None assert plus is not None
if plus.get_v1_account_state() != 'signed_in': if plus.get_v1_account_state() != 'signed_in':
show_sign_in_prompt() show_sign_in_prompt()
return return
StoreBrowserWindow(modal=True, origin_widget=self._button)
raise RuntimeError('no longer wired up')
# StoreBrowserWindow(modal=True, origin_widget=self._button)
def get_button(self) -> bui.Widget: def get_button(self) -> bui.Widget:
"""Return the underlying button widget.""" """Return the underlying button widget."""

View File

@ -585,6 +585,17 @@ void AppAdapterSDL::RemoveSDLInputDevice_(int index) {
assert(g_core->InMainThread()); assert(g_core->InMainThread());
assert(index >= 0); assert(index >= 0);
JoystickInput* j = GetSDLJoystickInput_(index); JoystickInput* j = GetSDLJoystickInput_(index);
// Note: am running into this with a PS5 controller on macOS Sequoia beta.
if (!j) {
Log(LogLevel::kError,
"GetSDLJoystickInput_() returned nullptr on RemoveSDLInputDevice_();"
" joysticks size is "
+ std::to_string(sdl_joysticks_.size()) + "; index is "
+ std::to_string(index));
return;
}
assert(j); assert(j);
if (static_cast_check_fit<int>(sdl_joysticks_.size()) > index) { if (static_cast_check_fit<int>(sdl_joysticks_.size()) > index) {
sdl_joysticks_[index] = nullptr; sdl_joysticks_[index] = nullptr;

View File

@ -39,7 +39,7 @@ auto main(int argc, char** argv) -> int {
namespace ballistica { namespace ballistica {
// These are set automatically via script; don't modify them here. // These are set automatically via script; don't modify them here.
const int kEngineBuildNumber = 21977; const int kEngineBuildNumber = 21985;
const char* kEngineVersion = "1.7.37"; const char* kEngineVersion = "1.7.37";
const int kEngineApiVersion = 9; const int kEngineApiVersion = 9;

View File

@ -967,28 +967,31 @@ void RootWidget::Setup() {
// AddCover(0.5f, VAlign::kBottom, 0.0f, -180.0f, 600.0f, 550.0f, // AddCover(0.5f, VAlign::kBottom, 0.0f, -180.0f, 600.0f, 550.0f,
// 0.35f); // 0.35f);
float backingR = 0.44f; float backing_r = 0.43f;
float backingG = 0.41f; float backing_g = 0.40f;
float backingB = 0.56f; float backing_b = 0.53f;
float backingCoverR = backingR; // float backingR = 0.44f;
float backingCoverG = backingG; // float backingG = 0.41f;
float backingCoverB = backingB; // float backingB = 0.56f;
float backing_cover_r = backing_r;
float backing_cover_g = backing_g;
float backing_cover_b = backing_b;
float backingA = 1.0f; float backingA = 1.0f;
if (g_base->ui->scale() != UIScale::kSmall) { if (g_base->ui->scale() != UIScale::kSmall) {
backingR *= TOOLBAR_COLOR_R * TOOLBAR_BACK_COLOR_R; backing_r *= TOOLBAR_COLOR_R * TOOLBAR_BACK_COLOR_R;
backingG *= TOOLBAR_COLOR_G * TOOLBAR_BACK_COLOR_G; backing_g *= TOOLBAR_COLOR_G * TOOLBAR_BACK_COLOR_G;
backingB *= TOOLBAR_COLOR_B * TOOLBAR_BACK_COLOR_B; backing_b *= TOOLBAR_COLOR_B * TOOLBAR_BACK_COLOR_B;
backingCoverR *= TOOLBAR_COLOR_R; backing_cover_r *= TOOLBAR_COLOR_R;
backingCoverG *= TOOLBAR_COLOR_G; backing_cover_g *= TOOLBAR_COLOR_G;
backingCoverB *= TOOLBAR_COLOR_B; backing_cover_b *= TOOLBAR_COLOR_B;
backingA *= TOOLBAR_OPACITY; backingA *= TOOLBAR_OPACITY;
} else { } else {
backingR *= 1.1f; backing_r *= 1.1f;
backingG *= 1.1f; backing_g *= 1.1f;
backingB *= 1.1f; backing_b *= 1.1f;
backingCoverR *= 1.1f; backing_cover_r *= 1.1f;
backingCoverG *= 1.1f; backing_cover_g *= 1.1f;
backingCoverB *= 1.1f; backing_cover_b *= 1.1f;
backingA *= TOOLBAR_OPACITY_2; backingA *= TOOLBAR_OPACITY_2;
} }
@ -997,16 +1000,16 @@ void RootWidget::Setup() {
ButtonDef bd; ButtonDef bd;
bd.h_align = 0.5f; bd.h_align = 0.5f;
bd.v_align = VAlign::kBottom; bd.v_align = VAlign::kBottom;
bd.width = 550.0f; bd.width = 500.0f;
bd.height = 110.0f; bd.height = 100.0f;
bd.x = 0.0f; bd.x = 0.0f;
bd.y = 41.0f; bd.y = 41.0f;
bd.img = "uiAtlas2"; bd.img = "uiAtlas2";
bd.mesh_transparent = "toolbarBackingBottom2"; bd.mesh_transparent = "toolbarBackingBottom2";
bd.selectable = false; bd.selectable = false;
bd.color_r = backingR; bd.color_r = backing_r;
bd.color_g = backingG; bd.color_g = backing_g;
bd.color_b = backingB; bd.color_b = backing_b;
bd.opacity = backingA; bd.opacity = backingA;
bd.depth_min = 0.2f; bd.depth_min = 0.2f;
@ -1023,7 +1026,7 @@ void RootWidget::Setup() {
ButtonDef b; ButtonDef b;
b.h_align = 0.5f; b.h_align = 0.5f;
b.v_align = VAlign::kBottom; b.v_align = VAlign::kBottom;
b.width = b.height = 120.0f; b.width = b.height = 110.0f;
b.x = 0.0f; b.x = 0.0f;
b.y = b.height * 0.4f; b.y = b.height * 0.4f;
b.img = "chestIcon"; b.img = "chestIcon";
@ -1032,28 +1035,31 @@ void RootWidget::Setup() {
(static_cast<uint32_t>(Widget::ToolbarVisibility::kMenuFull) (static_cast<uint32_t>(Widget::ToolbarVisibility::kMenuFull)
| static_cast<uint32_t>(Widget::ToolbarVisibility::kMenuFullNoBack) | static_cast<uint32_t>(Widget::ToolbarVisibility::kMenuFullNoBack)
| static_cast<uint32_t>(Widget::ToolbarVisibility::kMenuFullRoot)); | static_cast<uint32_t>(Widget::ToolbarVisibility::kMenuFullRoot));
float spacing = 130.0f; float spacing = 120.0f;
b.x = -1.5f * spacing; b.x = -1.5f * spacing;
b.call = UIV1Python::ObjID::kRootUIChestSlot1PressCall; b.call = UIV1Python::ObjID::kRootUIChestSlot1PressCall;
b.allow_in_game = false; b.allow_in_game = false;
AddButton_(b); AddButton_(b);
b.x = -0.5f * spacing; // b.x = -0.5f * spacing;
b.img = "chestOpenIcon"; // b.img = "chestOpenIcon";
b.y = b.height * 0.5f; // b.y = b.height * 0.5f;
b.call = UIV1Python::ObjID::kRootUIChestSlot2PressCall; // b.call = UIV1Python::ObjID::kRootUIChestSlot2PressCall;
AddButton_(b); // AddButton_(b);
// test - empty icons // test - empty icons
b.y = b.height * 0.4f; b.y = b.height * 0.4f;
b.x = 0.5f * spacing; b.x = 0.5f * spacing;
b.img = "chestIconEmpty"; b.img = "chestIconEmpty";
b.width = b.height = 80.0f; b.width = b.height = 80.0f;
b.color_r = backingCoverR; b.color_r = backing_cover_r;
b.color_g = backingCoverG; b.color_g = backing_cover_g;
b.color_b = backingCoverB; b.color_b = backing_cover_b;
b.opacity = 1.0f; b.opacity = 1.0f;
b.call = UIV1Python::ObjID::kRootUIChestSlot3PressCall; b.call = UIV1Python::ObjID::kRootUIChestSlot3PressCall;
b.x = -0.5f * spacing;
AddButton_(b);
b.x = 0.5f * spacing;
AddButton_(b); AddButton_(b);
b.x = 1.5f * spacing; b.x = 1.5f * spacing;
b.call = UIV1Python::ObjID::kRootUIChestSlot4PressCall; b.call = UIV1Python::ObjID::kRootUIChestSlot4PressCall;