diff --git a/.efrocachemap b/.efrocachemap index b5f38fa3..c57ef2ad 100644 --- a/.efrocachemap +++ b/.efrocachemap @@ -4096,26 +4096,26 @@ "build/assets/windows/Win32/ucrtbased.dll": "2def5335207d41b21b9823f6805997f1", "build/assets/windows/Win32/vc_redist.x86.exe": "b08a55e2e77623fe657bea24f223a3ae", "build/assets/windows/Win32/vcruntime140d.dll": "865b2af4d1e26a1a8073c89acb06e599", - "build/prefab/full/linux_arm64_gui/debug/ballisticakit": "d6246c930e7e2d2d9a6aff6788f33b69", - "build/prefab/full/linux_arm64_gui/release/ballisticakit": "001c67c4d4d33e20755399e0b2ed1593", - "build/prefab/full/linux_arm64_server/debug/dist/ballisticakit_headless": "6f6bfaf19daf6e866f4fecbc889b8854", - "build/prefab/full/linux_arm64_server/release/dist/ballisticakit_headless": "ebc7449903d7868c631c504aed10f371", - "build/prefab/full/linux_x86_64_gui/debug/ballisticakit": "91f03e7dbfc0d7eb75568704f681fba5", - "build/prefab/full/linux_x86_64_gui/release/ballisticakit": "0b32b6eb05df0a7e23e55ff2e7235a8f", - "build/prefab/full/linux_x86_64_server/debug/dist/ballisticakit_headless": "4ed4b63cc506815f759412a295bfe088", - "build/prefab/full/linux_x86_64_server/release/dist/ballisticakit_headless": "1890731fb8a2fb43c4cde72af9d7a4e6", - "build/prefab/full/mac_arm64_gui/debug/ballisticakit": "1a4764a504dcb20591ac3472c48db8d9", - "build/prefab/full/mac_arm64_gui/release/ballisticakit": "9bfd6234aac4ffd7d7601a40017a73be", - "build/prefab/full/mac_arm64_server/debug/dist/ballisticakit_headless": "941f8fb79d54522ca5df97a735c3babe", - "build/prefab/full/mac_arm64_server/release/dist/ballisticakit_headless": "290a3d9840efc5e88532e13b76b3ae6b", - "build/prefab/full/mac_x86_64_gui/debug/ballisticakit": "621a6684b54b0a9101f808209bcca1ee", - "build/prefab/full/mac_x86_64_gui/release/ballisticakit": "43c8b0b29e5fb257e4cde0f7fce1c680", - "build/prefab/full/mac_x86_64_server/debug/dist/ballisticakit_headless": "9a30c6b88ffc11bdf6765780616d8ba1", - "build/prefab/full/mac_x86_64_server/release/dist/ballisticakit_headless": "8a8f63850fc296b060a994f4e001d74f", - "build/prefab/full/windows_x86_gui/debug/BallisticaKit.exe": "2864e9ae1d2d566def85f1cdf6e863fe", - "build/prefab/full/windows_x86_gui/release/BallisticaKit.exe": "dc0db7ca99661a896634fc05187a5c75", - "build/prefab/full/windows_x86_server/debug/dist/BallisticaKitHeadless.exe": "f56bb0fafe0a45cecdcd06a10a6924c9", - "build/prefab/full/windows_x86_server/release/dist/BallisticaKitHeadless.exe": "e3a58b09fea193d78187f97cf922717d", + "build/prefab/full/linux_arm64_gui/debug/ballisticakit": "6f45d8d041a5c44d44e6da00ddd68983", + "build/prefab/full/linux_arm64_gui/release/ballisticakit": "fc5c2514f1773c91159fc8014e5f3e93", + "build/prefab/full/linux_arm64_server/debug/dist/ballisticakit_headless": "a8679f4c2a071af5f024c04fb2de5685", + "build/prefab/full/linux_arm64_server/release/dist/ballisticakit_headless": "2d32e5d05331df1b4b6923ee241b630b", + "build/prefab/full/linux_x86_64_gui/debug/ballisticakit": "8d9d7a041a1d3e56aec39682821927f6", + "build/prefab/full/linux_x86_64_gui/release/ballisticakit": "0d0405cb2e05629b0a23d6b8eca203ad", + "build/prefab/full/linux_x86_64_server/debug/dist/ballisticakit_headless": "338528869c6fd8797b96cb875e3820cf", + "build/prefab/full/linux_x86_64_server/release/dist/ballisticakit_headless": "4428797051af1f7fde71fb638db52b17", + "build/prefab/full/mac_arm64_gui/debug/ballisticakit": "c202858e8db64aebb88be04828ddfaa9", + "build/prefab/full/mac_arm64_gui/release/ballisticakit": "bd13e0e3edf0aac64609fb764921d7b1", + "build/prefab/full/mac_arm64_server/debug/dist/ballisticakit_headless": "f183757c9541262774adb1e0599f2001", + "build/prefab/full/mac_arm64_server/release/dist/ballisticakit_headless": "85791e6585f4858517330e23834fc7b8", + "build/prefab/full/mac_x86_64_gui/debug/ballisticakit": "90b0d97ebaf7756824493b2fcee1e97e", + "build/prefab/full/mac_x86_64_gui/release/ballisticakit": "955eef9f918e24c37e279db75ec6fca3", + "build/prefab/full/mac_x86_64_server/debug/dist/ballisticakit_headless": "b42ae8f5896f850e72fc4c30b2b8c52b", + "build/prefab/full/mac_x86_64_server/release/dist/ballisticakit_headless": "dda8f5a5099d9b695c9ab5a148f5ff10", + "build/prefab/full/windows_x86_gui/debug/BallisticaKit.exe": "8389b61cf60dd2f4ebad8b0e2c376605", + "build/prefab/full/windows_x86_gui/release/BallisticaKit.exe": "cce612fb88788b32b303a956cd6fd6f5", + "build/prefab/full/windows_x86_server/debug/dist/BallisticaKitHeadless.exe": "d54a451817bee9d4f8debd1fecf84ae9", + "build/prefab/full/windows_x86_server/release/dist/BallisticaKitHeadless.exe": "e41c4795f5b6f7dd0cf61286e62e91ac", "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_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_server/debug/libballisticaplus.a": "1659535e95e3047fda529543e265ac97", "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.pdb": "25db08ee2b4c79ca631e517b622c741d", - "build/prefab/lib/windows/Debug_Win32/BallisticaKitHeadlessPlus.lib": "2ea419644782c4c52d1fa784d0ced86d", - "build/prefab/lib/windows/Debug_Win32/BallisticaKitHeadlessPlus.pdb": "c40627a46ee127997153df041e47eda5", - "build/prefab/lib/windows/Release_Win32/BallisticaKitGenericPlus.lib": "fff81b29e2d86031ec25c4a672a243e7", - "build/prefab/lib/windows/Release_Win32/BallisticaKitGenericPlus.pdb": "2a87e2f53c658d0be3918476cd8a04ee", - "build/prefab/lib/windows/Release_Win32/BallisticaKitHeadlessPlus.lib": "1e91536979b318b9a8325ab0e871ecec", - "build/prefab/lib/windows/Release_Win32/BallisticaKitHeadlessPlus.pdb": "c6506399a93ef93e8322425f78e2e6b0", + "build/prefab/lib/windows/Debug_Win32/BallisticaKitGenericPlus.lib": "ccc8a569891e0e5541706ca21aa91a3e", + "build/prefab/lib/windows/Debug_Win32/BallisticaKitGenericPlus.pdb": "f39ae9be9fe75e16f45ea78f2bd3147a", + "build/prefab/lib/windows/Debug_Win32/BallisticaKitHeadlessPlus.lib": "5b1eb9ecc0eaaa22fb0b838479515deb", + "build/prefab/lib/windows/Debug_Win32/BallisticaKitHeadlessPlus.pdb": "41977e7f7bbd1baa587661e9c0daee85", + "build/prefab/lib/windows/Release_Win32/BallisticaKitGenericPlus.lib": "92451c795510652611fdac035839f0eb", + "build/prefab/lib/windows/Release_Win32/BallisticaKitGenericPlus.pdb": "6b3215ddfb8bb3ff8149b380f829806a", + "build/prefab/lib/windows/Release_Win32/BallisticaKitHeadlessPlus.lib": "1980c181a06f7355770d31499b7f9245", + "build/prefab/lib/windows/Release_Win32/BallisticaKitHeadlessPlus.pdb": "ce4592ab12f3954f62140365211c8eb4", "src/assets/ba_data/python/babase/_mgen/__init__.py": "f885fed7f2ed98ff2ba271f9dbe3391c", "src/assets/ba_data/python/babase/_mgen/enums.py": "cb299985623bbcc86015cb103a424ae6", "src/ballistica/base/mgen/pyembed/binding_base.inc": "efa61468cf098f77cc6a234461d8b86d", diff --git a/CHANGELOG.md b/CHANGELOG.md index 514fe060..9f9072a8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,4 +1,4 @@ -### 1.7.37 (build 21977, api 9, 2024-08-30) +### 1.7.37 (build 21978, api 9, 2024-09-03) - 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 touch UI stuff at all you can simply bump your api version and call it a day. diff --git a/config/requirements.txt b/config/requirements.txt index 95c86a2f..42cf3454 100644 --- a/config/requirements.txt +++ b/config/requirements.txt @@ -6,8 +6,8 @@ mypy==1.11.2 pbxproj==4.2.1 pdoc==14.6.1 pur==7.3.2 -pylint==3.2.6 -pylsp-mypy==0.6.8 +pylint==3.2.7 +pylsp-mypy==0.6.9 pytest==8.3.2 python-daemon==3.0.1 python-lsp-black==2.0.0 diff --git a/src/assets/ba_data/python/baclassic/_appsubsystem.py b/src/assets/ba_data/python/baclassic/_appsubsystem.py index 99559170..4fe3637c 100644 --- a/src/assets/ba_data/python/baclassic/_appsubsystem.py +++ b/src/assets/ba_data/python/baclassic/_appsubsystem.py @@ -112,10 +112,6 @@ class ClassicAppSubsystem(babase.AppSubsystem): self.invite_confirm_windows: list[Any] = [] # FIXME: Don't use Any. self.party_window: weakref.ref[PartyWindow] | None = None 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. self.store_layout: dict[str, list[dict[str, Any]]] | None = None diff --git a/src/assets/ba_data/python/baenv.py b/src/assets/ba_data/python/baenv.py index eb72351a..dbd337c6 100644 --- a/src/assets/ba_data/python/baenv.py +++ b/src/assets/ba_data/python/baenv.py @@ -52,7 +52,7 @@ if TYPE_CHECKING: # Build number and version of the ballistica binary we expect to be # using. -TARGET_BALLISTICA_BUILD = 21977 +TARGET_BALLISTICA_BUILD = 21978 TARGET_BALLISTICA_VERSION = '1.7.37' diff --git a/src/assets/ba_data/python/bauiv1/_appsubsystem.py b/src/assets/ba_data/python/bauiv1/_appsubsystem.py index c58b44ba..d9fc8d41 100644 --- a/src/assets/ba_data/python/bauiv1/_appsubsystem.py +++ b/src/assets/ba_data/python/bauiv1/_appsubsystem.py @@ -68,7 +68,6 @@ class UIV1AppSubsystem(babase.AppSubsystem): # another MainWindow and we complain if they don't. self._main_window = empty_weakref(MainWindow) self._main_window_widget: bauiv1.Widget | None = None - self.main_window_group_id: str | None = None self.quit_window: bauiv1.Widget | None = None @@ -123,7 +122,6 @@ class UIV1AppSubsystem(babase.AppSubsystem): self.root_ui_calls.clear() self._main_window = empty_weakref(MainWindow) self._main_window_widget = None - self.main_window_group_id = None @property 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. 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.""" main_window = self._main_window() @@ -178,7 +176,7 @@ class UIV1AppSubsystem(babase.AppSubsystem): ) backwin = back_state.create_window(transition='in_left') 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: """Return main window, if any.""" @@ -189,30 +187,14 @@ class UIV1AppSubsystem(babase.AppSubsystem): window: bauiv1.MainWindow, from_window: bauiv1.MainWindow | None | bool = True, is_back: bool = False, - group_id: str | None = None, is_top_level: bool = False, back_state: MainWindowState | None = None, ) -> None: """Set the current 'main' window, replacing any existing. - If 'from_window' is passed as a bauiv1.Widget or bauiv1.Window - or None, a warning will be issued if it that value does not - match the current main window. This can help identify flawed - 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. + Generally this should not be called directly; The high level + MainWindow methods main_window_replace() and main_window_back() + should be used when possible for navigation. """ # pylint: disable=too-many-locals # pylint: disable=too-many-branches @@ -222,13 +204,24 @@ class UIV1AppSubsystem(babase.AppSubsystem): from_window_widget: bauiv1.Widget | None # 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_widget = window.get_root_widget() if isinstance(from_window, MainWindow): from_window_widget = from_window.get_root_widget() 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 existing = self._main_window_widget @@ -317,13 +310,15 @@ class UIV1AppSubsystem(babase.AppSubsystem): else: # When navigating forward, generate a back-window-state from # the outgoing window. - - # Exception is when we were passed a group and it matches - # the existing group; in that case we just keep the existing - # back-state. - if group_id is not None and group_id == self.main_window_group_id: - assert not is_top_level - print(f'GOT GROUP ID MATCH {group_id}; KEEPING BACK STATE.') + 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 @@ -332,48 +327,20 @@ class UIV1AppSubsystem(babase.AppSubsystem): # alive as long as its the main one. Holler if # that seems to not be happening. logging.warning( - 'set_main_window: no existing MainWindow found' - ' (and is_top_level is False); should not happen.' - ' a MainWindow should keep itself alive as long' - ' as it is main.' + 'set_main_window: No old MainWindow found' + ' and is_top_level is False;' + ' this should not happen.' ) window.main_window_back_state = None else: - window.main_window_back_state = ( - 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() + oldwinstate = oldwin.get_main_window_state() - # Store our previous back state on this new one. - oldwinstate.parent = oldwin.main_window_back_state - window.main_window_back_state = oldwinstate + # Store our previous back state on this new one. + oldwinstate.parent = oldwin.main_window_back_state + window.main_window_back_state = oldwinstate self._main_window = window_weakref self._main_window_widget = window_widget - self.main_window_group_id = group_id def has_main_window(self) -> bool: """Return whether a main menu window is present.""" @@ -397,4 +364,3 @@ class UIV1AppSubsystem(babase.AppSubsystem): self._main_window = empty_weakref(MainWindow) self._main_window_widget = None - self.main_window_group_id = None diff --git a/src/assets/ba_data/python/bauiv1/_uitypes.py b/src/assets/ba_data/python/bauiv1/_uitypes.py index 496d32fc..2743bde3 100644 --- a/src/assets/ba_data/python/bauiv1/_uitypes.py +++ b/src/assets/ba_data/python/bauiv1/_uitypes.py @@ -60,7 +60,6 @@ class MainWindow(Window): Automatically handles in and out transitions on the provided widget, so there is no need to set transitions when creating it. """ - # TODO - move to MainWindow # A back-state supplied by the ui system. self.main_window_back_state: MainWindowState | None = None @@ -82,7 +81,7 @@ class MainWindow(Window): 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.""" # 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) _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: """Is this MainWindow allowed to change the global main window? It is a good idea to make sure this is True before calling - either main_window_back() or main_window_replace(). This - prevents fluke UI breakage such as multiple simultaneous events - causing a MainWindow to spawn multiple replacements for itself. + main_window_replace(). This prevents fluke UI breakage such as + multiple simultaneous events causing a MainWindow to spawn + multiple replacements for itself. """ # We are allowed to change main windows if we are the current one # AND our underlying widget is still alive and not transitioning out. @@ -116,48 +120,39 @@ class MainWindow(Window): ) 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 # calling us. Error if it seems they did not. if not self.main_window_has_control(): - raise RuntimeError( - 'main_window_back() should only be called' - ' if main_window_has_control() returns True' - ' (it currently is False).' - ) + return # Get the 'back' window coming in. babase.app.ui_v1.do_main_window_back(self) self.main_window_close() - def main_window_replace( - self, new_window: MainWindow, group_id: str | None = None - ) -> None: + def main_window_replace(self, new_window: MainWindow) -> None: """Replace ourself with a new MainWindow.""" - # Users should always check main_window_has_control() before - # creating new MainWindows and passing them in here. Error if it - # seems they did not. + # Users should always check main_window_has_control() *before* + # creating new MainWindows and passing them in here. Kill the + # passed window and Error if it seems they did not. if not self.main_window_has_control(): + new_window.get_root_widget().delete() raise RuntimeError( - 'main_window_replace() should only be called' - ' if main_window_has_control() returns True' - ' (it currently is False).' + f'main_window_replace() called on a not-in-control window' + f' ({self}); always check main_window_has_control() before' + f' calling main_window_replace().' ) - # If we're navigating within a group, we want it to look like we're - # backing out of the old one and going into the new one. - if ( - 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' + # 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. try: @@ -166,9 +161,7 @@ class MainWindow(Window): logging.exception('Error in on_main_window_close() for %s.', self) _bauiv1.containerwidget(edit=self._root_widget, transition=transition) - babase.app.ui_v1.set_main_window( - new_window, from_window=self, group_id=group_id - ) + babase.app.ui_v1.set_main_window(new_window, from_window=self) def on_main_window_close(self) -> None: """Called before transitioning out a main window. diff --git a/src/assets/ba_data/python/bauiv1lib/gather/__init__.py b/src/assets/ba_data/python/bauiv1lib/gather/__init__.py index e71d2cc3..8d3c18f6 100644 --- a/src/assets/ba_data/python/bauiv1lib/gather/__init__.py +++ b/src/assets/ba_data/python/bauiv1lib/gather/__init__.py @@ -7,11 +7,14 @@ from __future__ import annotations import weakref import logging from enum import Enum -from typing import override +from typing import override, TYPE_CHECKING from bauiv1lib.tabs import TabRow import bauiv1 as bui +if TYPE_CHECKING: + from bauiv1lib.play import PlaylistSelectContext + class GatherTab: """Defines a tab for use in the gather UI.""" @@ -264,23 +267,27 @@ class GatherWindow(bui.MainWindow): def on_main_window_close(self) -> None: 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.""" from bauiv1lib.play import PlayWindow - classic = bui.app.classic - assert classic is not None - - # no-op if our underlying widget is dead or on its way out. - if not self._root_widget or self._root_widget.transitioning_out: + # Avoid redundant window spawns. + if not self.main_window_has_control(): return - self._save_state() - bui.containerwidget(edit=self._root_widget, transition='out_left') - classic.selecting_private_party_playlist = True - bui.app.ui_v1.set_main_window( - PlayWindow(origin_widget=origin_widget), from_window=self + playwindow = PlayWindow( + origin_widget=origin_widget, playlist_select_context=context ) + self.main_window_replace(playwindow) + + # Grab the newly-set main-window's back-state; that will lead us + # back here once we're done going down our main-window + # rabbit-hole for playlist selection. + context.back_state = playwindow.main_window_back_state def _set_tab(self, tab_id: TabID) -> None: if self._current_tab is tab_id: diff --git a/src/assets/ba_data/python/bauiv1lib/gather/privatetab.py b/src/assets/ba_data/python/bauiv1lib/gather/privatetab.py index 6bdf4c6b..8681fa12 100644 --- a/src/assets/ba_data/python/bauiv1lib/gather/privatetab.py +++ b/src/assets/ba_data/python/bauiv1lib/gather/privatetab.py @@ -22,6 +22,7 @@ from bacommon.net import ( PrivatePartyConnectResult, ) from bauiv1lib.gather import GatherTab +from bauiv1lib.play import PlaylistSelectContext from bauiv1lib.gettokens import GetTokensWindow, show_get_tokens_prompt import bascenev1 as bs @@ -49,6 +50,7 @@ class State: """Our core state that persists while the app is running.""" sub_tab: SubTabType = SubTabType.JOIN + playlist_select_context: PlaylistSelectContext | None = None class PrivateGatherTab(GatherTab): @@ -685,12 +687,13 @@ class PrivateGatherTab(GatherTab): # If it appears we're coming back from playlist selection, # 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( edit=self._container, selected_child=self._host_playlist_button, ) - classic.selecting_private_party_playlist = False + else: # We've got a current party; show its info. bui.textwidget( @@ -918,11 +921,17 @@ class PrivateGatherTab(GatherTab): ) def _playlist_press(self) -> None: - if bool(True): - bui.screenmessage('UNDER CONSTRUCTION') - return + # if bool(True): + # bui.screenmessage('UNDER CONSTRUCTION') + # return 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: assert self._hostingstate.party_code is not None diff --git a/src/assets/ba_data/python/bauiv1lib/mainmenu.py b/src/assets/ba_data/python/bauiv1lib/mainmenu.py index f4904fbd..555ff8d0 100644 --- a/src/assets/ba_data/python/bauiv1lib/mainmenu.py +++ b/src/assets/ba_data/python/bauiv1lib/mainmenu.py @@ -505,7 +505,6 @@ class MainMenuWindow(bui.MainWindow): self.main_window_replace( CreditsWindow(origin_widget=self._credits_button), - # group_id='mainmenutop', ) def _howtoplay(self) -> None: @@ -607,8 +606,4 @@ class MainMenuWindow(bui.MainWindow): if not self.main_window_has_control(): 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)) diff --git a/src/assets/ba_data/python/bauiv1lib/play.py b/src/assets/ba_data/python/bauiv1lib/play.py index 4bce5070..5548c67c 100644 --- a/src/assets/ba_data/python/bauiv1lib/play.py +++ b/src/assets/ba_data/python/bauiv1lib/play.py @@ -5,11 +5,20 @@ from __future__ import annotations import logging -from typing import override +from typing import override, TYPE_CHECKING import bascenev1 as bs 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): """Window for selecting overall play type.""" @@ -18,6 +27,7 @@ class PlayWindow(bui.MainWindow): self, transition: str | None = 'in_right', origin_widget: bui.Widget | None = None, + playlist_select_context: PlaylistSelectContext | None = None, ): # pylint: disable=too-many-statements # pylint: disable=too-many-locals @@ -29,11 +39,7 @@ class PlayWindow(bui.MainWindow): classic = bui.app.classic assert classic is not None - # We can currently be used either for main window duty or - # 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 + self._playlist_select_context = playlist_select_context uiscale = bui.app.ui_v1.uiscale width = 1100 if uiscale is bui.UIScale.SMALL else 800 @@ -54,7 +60,11 @@ class PlayWindow(bui.MainWindow): super().__init__( root_widget=bui.containerwidget( size=(width, height), - toolbar_visibility='menu_full', + toolbar_visibility=( + 'menu_full' + if playlist_select_context is None + else 'menu_minimal' + ), scale=( 1.35 if uiscale is bui.UIScale.SMALL @@ -88,7 +98,7 @@ class PlayWindow(bui.MainWindow): text=bui.Lstr( resource=( (f'{self._r}.titleText') - if self._is_main_menu + if self._playlist_select_context is None else 'playlistsText' ) ), @@ -109,13 +119,17 @@ class PlayWindow(bui.MainWindow): if uiscale is bui.UIScale.SMALL: 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 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 - hoffs = x_offs + 80 if self._is_main_menu else x_offs - 100 - scl = 1.13 if self._is_main_menu else 0.68 + hoffs = ( + 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') angry_computer_transparent_mesh = bui.getmesh( @@ -137,8 +151,8 @@ class PlayWindow(bui.MainWindow): self._coop_button: bui.Widget | None = None - # Only show coop button in main-menu variant. - if self._is_main_menu: + # Only show coop button in regular variant. + if self._playlist_select_context is None: self._coop_button = btn = bui.buttonwidget( parent=self._root_widget, position=(hoffs, v + (scl * 15)), @@ -238,16 +252,19 @@ class PlayWindow(bui.MainWindow): color=clr, ) - scl = 0.5 if self._is_main_menu else 0.68 - hoffs += 440 if self._is_main_menu else 216 - v += 180 if self._is_main_menu else -68 + scl = 0.5 if self._playlist_select_context is None else 0.68 + hoffs += 440 if self._playlist_select_context is None else 216 + v += 180 if self._playlist_select_context is None else -68 self._teams_button = btn = bui.buttonwidget( 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=( 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, autoselect=True, @@ -369,14 +386,17 @@ class PlayWindow(bui.MainWindow): color=clr, ) - hoffs += 0 if self._is_main_menu else 300 - v -= 155 if self._is_main_menu else 0 + hoffs += 0 if self._playlist_select_context is None else 300 + v -= 155 if self._playlist_select_context is None else 0 self._free_for_all_button = btn = bui.buttonwidget( 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=( 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, autoselect=True, @@ -493,22 +513,24 @@ class PlayWindow(bui.MainWindow): back_button.delete() bui.containerwidget( edit=self._root_widget, - on_cancel_call=self._back, + on_cancel_call=self.main_window_back, # cancel_button=bui.get_special_widget('back_button'), selected_child=( self._coop_button - if self._is_main_menu + if self._playlist_select_context is None else self._teams_button ), ) 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( edit=self._root_widget, cancel_button=back_button, selected_child=( self._coop_button - if self._is_main_menu + if self._playlist_select_context is None else self._teams_button ), ) @@ -519,9 +541,15 @@ class PlayWindow(bui.MainWindow): def get_main_window_state(self) -> bui.MainWindowState: # Support recreating our window for back/refresh purposes. 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( 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.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: # pylint: disable=cyclic-import from bauiv1lib.account import show_sign_in_prompt @@ -595,7 +599,9 @@ class PlayWindow(bui.MainWindow): self.main_window_replace( 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( origin_widget=self._free_for_all_button, sessiontype=bs.FreeForAllSession, + playlist_select_context=self._playlist_select_context, ), from_window=self, ) diff --git a/src/assets/ba_data/python/bauiv1lib/playlist/browser.py b/src/assets/ba_data/python/bauiv1lib/playlist/browser.py index ef0fcf57..6913d3a9 100644 --- a/src/assets/ba_data/python/bauiv1lib/playlist/browser.py +++ b/src/assets/ba_data/python/bauiv1lib/playlist/browser.py @@ -7,11 +7,14 @@ from __future__ import annotations import copy import math import logging -from typing import override +from typing import override, TYPE_CHECKING import bascenev1 as bs import bauiv1 as bui +if TYPE_CHECKING: + from bauiv1lib.play import PlaylistSelectContext + class PlaylistBrowserWindow(bui.MainWindow): """Window for starting teams games.""" @@ -21,6 +24,7 @@ class PlaylistBrowserWindow(bui.MainWindow): sessiontype: type[bs.Session], transition: str | None = 'in_right', origin_widget: bui.Widget | None = None, + playlist_select_context: PlaylistSelectContext | None = None, ): # pylint: disable=cyclic-import from bauiv1lib.playlist import PlaylistTypeVars @@ -39,6 +43,7 @@ class PlaylistBrowserWindow(bui.MainWindow): self._customize_button: bui.Widget | None = None self._sub_width: float | None = None self._sub_height: float | None = None + self._playlist_select_context = playlist_select_context self._ensure_standard_playlists_exist() @@ -63,7 +68,10 @@ class PlaylistBrowserWindow(bui.MainWindow): size=(self._width, self._height + top_extra), toolbar_visibility=( 'menu_minimal' - if uiscale is bui.UIScale.SMALL + if ( + uiscale is bui.UIScale.SMALL + or playlist_select_context is not None + ) else 'menu_full' ), scale=( @@ -160,11 +168,16 @@ class PlaylistBrowserWindow(bui.MainWindow): # then we keep self alive. 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( create_call=lambda transition, origin_widget: cls( transition=transition, origin_widget=origin_widget, sessiontype=sessiontype, + playlist_select_context=playlist_select_context, ) ) @@ -639,9 +652,30 @@ class PlaylistBrowserWindow(bui.MainWindow): def on_play_options_window_run_game(self) -> None: """(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 - 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') + # bui.containerwidget(edit=self._root_widget, transition='out_left') def _on_playlist_select(self, playlist_name: str) -> None: self._selected_playlist = playlist_name @@ -676,6 +710,7 @@ class PlaylistBrowserWindow(bui.MainWindow): scale_origin=button.get_screen_space_center(), playlist=playlist_name, delegate=self, + playlist_select_context=self._playlist_select_context, ) def _on_customize_press(self) -> None: @@ -690,7 +725,7 @@ class PlaylistBrowserWindow(bui.MainWindow): self._save_state() bui.containerwidget(edit=self._root_widget, transition='out_left') - assert bui.app.classic is not None + # assert bui.app.classic is not None bui.app.ui_v1.set_main_window( PlaylistCustomizeBrowserWindow( origin_widget=self._customize_button, diff --git a/src/assets/ba_data/python/bauiv1lib/playoptions.py b/src/assets/ba_data/python/bauiv1lib/playoptions.py index 2b81de6a..0428db3a 100644 --- a/src/assets/ba_data/python/bauiv1lib/playoptions.py +++ b/src/assets/ba_data/python/bauiv1lib/playoptions.py @@ -15,6 +15,8 @@ from bauiv1lib.popup import PopupWindow if TYPE_CHECKING: from typing import Any + from bauiv1lib.play import PlaylistSelectContext + class PlayOptionsWindow(PopupWindow): """A popup window for configuring play options.""" @@ -25,6 +27,7 @@ class PlayOptionsWindow(PopupWindow): playlist: str, scale_origin: tuple[float, float], delegate: Any = None, + playlist_select_context: PlaylistSelectContext | None = None, ): # FIXME: Tidy this up. # pylint: disable=too-many-branches @@ -39,11 +42,7 @@ class PlayOptionsWindow(PopupWindow): self._pvars = PlaylistTypeVars(sessiontype) self._transitioning_out = False - # We behave differently if we're being used for playlist selection - # 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._playlist_select_context = playlist_select_context self._do_randomize_val = bui.app.config.get( self._pvars.config_name + ' Playlist Randomize', 0 @@ -66,7 +65,8 @@ class PlayOptionsWindow(PopupWindow): mesh_transparent = bui.getmesh('level_select_button_transparent') 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_texture_entries = [] rows = 0 @@ -405,7 +405,11 @@ class PlayOptionsWindow(PopupWindow): on_activate_call=self._on_ok_press, autoselect=True, 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 # or start the game in regular mode. - if self._selecting_mode: - from bauiv1lib.gather import GatherWindow + if self._playlist_select_context is not None: + # from bauiv1lib.gather import GatherWindow if self._sessiontype is bs.FreeForAllSession: typename = 'ffa' @@ -509,14 +513,15 @@ class PlayOptionsWindow(PopupWindow): raise RuntimeError('Only teams and ffa currently supported') cfg['Private Party Host Session Type'] = typename bui.getsound('gunCocking').play() - assert bui.app.classic is not None - # Note: this is a wonky situation where we aren't actually - # the main window but we set it on behalf of the main window - # that popped us up. - bui.app.ui_v1.set_main_window( - GatherWindow(transition='in_right'), - from_window=False, # Disable this test. - ) + + # assert bui.app.classic is not None + # # Note: this is a wonky situation where we aren't actually + # # the main window but we set it on behalf of the main window + # # that popped us up. + # bui.app.ui_v1.set_main_window( + # GatherWindow(transition='in_right'), + # from_window=False, # Disable this test. + # ) self._transition_out(transition='out_left') if self._delegate is not None: self._delegate.on_play_options_window_run_game() diff --git a/src/assets/ba_data/python/bauiv1lib/soundtrack/edit.py b/src/assets/ba_data/python/bauiv1lib/soundtrack/edit.py index 77bcae57..5d7132ad 100644 --- a/src/assets/ba_data/python/bauiv1lib/soundtrack/edit.py +++ b/src/assets/ba_data/python/bauiv1lib/soundtrack/edit.py @@ -393,8 +393,7 @@ class SoundtrackEditWindow(bui.MainWindow): bui.Call(self._restore_editor, state, song_type), entry, selection_target_name, - ), - group_id='soundtrackentryselect', + ) ) # bui.containerwidget(edit=self._root_widget, transition='out_left') # bui.app.ui_v1.set_main_window( diff --git a/src/assets/ba_data/python/bauiv1lib/soundtrack/entrytypeselect.py b/src/assets/ba_data/python/bauiv1lib/soundtrack/entrytypeselect.py index 4556e00f..31879986 100644 --- a/src/assets/ba_data/python/bauiv1lib/soundtrack/entrytypeselect.py +++ b/src/assets/ba_data/python/bauiv1lib/soundtrack/entrytypeselect.py @@ -209,8 +209,7 @@ class SoundtrackEntryTypeSelectWindow(bui.MainWindow): self.main_window_replace( MacMusicAppPlaylistSelectWindow( self._callback, current_playlist_entry, self._current_entry - ), - group_id='soundtrackentryselect', + ) ) # MacMusicAppPlaylistSelectWindow( # self._callback, current_playlist_entry, self._current_entry @@ -241,7 +240,6 @@ class SoundtrackEntryTypeSelectWindow(bui.MainWindow): ), allow_folders=False, ), - group_id='soundtrackentryselect', ) # bui.app.ui_v1.set_main_window( # FileSelectorWindow( @@ -276,7 +274,6 @@ class SoundtrackEntryTypeSelectWindow(bui.MainWindow): valid_file_extensions=[], allow_folders=True, ), - group_id='soundtrackentryselect', ) # bui.app.ui_v1.set_main_window( # FileSelectorWindow( diff --git a/src/ballistica/shared/ballistica.cc b/src/ballistica/shared/ballistica.cc index 9fb9e776..45b5bcd9 100644 --- a/src/ballistica/shared/ballistica.cc +++ b/src/ballistica/shared/ballistica.cc @@ -39,7 +39,7 @@ auto main(int argc, char** argv) -> int { namespace ballistica { // These are set automatically via script; don't modify them here. -const int kEngineBuildNumber = 21977; +const int kEngineBuildNumber = 21978; const char* kEngineVersion = "1.7.37"; const int kEngineApiVersion = 9;