hopefully finally fixed multi-ui bug for real this time

This commit is contained in:
Eric 2023-11-30 14:14:45 -08:00
parent d5339e3193
commit 0e15df6f59
No known key found for this signature in database
GPG Key ID: 89C93F0F8D6D5A98
67 changed files with 914 additions and 308 deletions

58
.efrocachemap generated
View File

@ -4060,26 +4060,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": "9fe23e06319e4e256b9fa88814a14afa",
"build/prefab/full/linux_arm64_gui/release/ballisticakit": "4306acae21ce88235f9d1589086866e7",
"build/prefab/full/linux_arm64_server/debug/dist/ballisticakit_headless": "75e4f7d3a3df67dedd079ec3f4441094",
"build/prefab/full/linux_arm64_server/release/dist/ballisticakit_headless": "bd5eda13f239b81886ac80596d6ade73",
"build/prefab/full/linux_x86_64_gui/debug/ballisticakit": "0805235a92dd91f96d43ea54575eecac",
"build/prefab/full/linux_x86_64_gui/release/ballisticakit": "07589a61b11cbc5fca0bbc8b7fc1c955",
"build/prefab/full/linux_x86_64_server/debug/dist/ballisticakit_headless": "f28629761060c8152168b6792b71adae",
"build/prefab/full/linux_x86_64_server/release/dist/ballisticakit_headless": "1cfd1a33474cdb31834994f626385ed0",
"build/prefab/full/mac_arm64_gui/debug/ballisticakit": "d50879a92d9d344c376f6f196d78d1be",
"build/prefab/full/mac_arm64_gui/release/ballisticakit": "78cd0edf2698f197f2acd80ca364fae7",
"build/prefab/full/mac_arm64_server/debug/dist/ballisticakit_headless": "d656f47118ebc3af57c40423cb258bc8",
"build/prefab/full/mac_arm64_server/release/dist/ballisticakit_headless": "75df540b27779342a7c696e1bdbe593f",
"build/prefab/full/mac_x86_64_gui/debug/ballisticakit": "76f0dfacaa9ea67e45e8ccf3bb3bc1c6",
"build/prefab/full/mac_x86_64_gui/release/ballisticakit": "2acc754bed825a9265e0621dc09899e0",
"build/prefab/full/mac_x86_64_server/debug/dist/ballisticakit_headless": "62c2b6190de8784ea8750ea50e6a2304",
"build/prefab/full/mac_x86_64_server/release/dist/ballisticakit_headless": "e57358fd9a948a8ce82a54cdd5c766fc",
"build/prefab/full/windows_x86_gui/debug/BallisticaKit.exe": "d36e3303e13049eae5e7ec19861d300e",
"build/prefab/full/windows_x86_gui/release/BallisticaKit.exe": "46971a2ca1e3021e52ea5d0f4938d2ff",
"build/prefab/full/windows_x86_server/debug/dist/BallisticaKitHeadless.exe": "19ebd36613cf62c4bd50e70b93371368",
"build/prefab/full/windows_x86_server/release/dist/BallisticaKitHeadless.exe": "49ef5905b6e9e1a9caaed3d1c1da4ea5",
"build/prefab/full/linux_arm64_gui/debug/ballisticakit": "c2b80379179c8731be37581e52259377",
"build/prefab/full/linux_arm64_gui/release/ballisticakit": "0a2257e46a20ae6453d888515a00f1a8",
"build/prefab/full/linux_arm64_server/debug/dist/ballisticakit_headless": "a8bf3602e161931f96c41989d9d4e630",
"build/prefab/full/linux_arm64_server/release/dist/ballisticakit_headless": "15e5d036605366840182cf3f86d247ae",
"build/prefab/full/linux_x86_64_gui/debug/ballisticakit": "4212f5014bcf30f5ceb6e9ed00c1b443",
"build/prefab/full/linux_x86_64_gui/release/ballisticakit": "a9ee1b8f07dc7466b4daf90a34991f3b",
"build/prefab/full/linux_x86_64_server/debug/dist/ballisticakit_headless": "cbc9bf3f8ddce331912b6dbd8f1c6415",
"build/prefab/full/linux_x86_64_server/release/dist/ballisticakit_headless": "6b4458aa454391fa0070099ef4cac711",
"build/prefab/full/mac_arm64_gui/debug/ballisticakit": "fae27ee9108877bd75aaa222f02f239d",
"build/prefab/full/mac_arm64_gui/release/ballisticakit": "37f2b4219ffe85aa1d28ab7df7fd4c44",
"build/prefab/full/mac_arm64_server/debug/dist/ballisticakit_headless": "3f8de90abf2069a0881f8d91f1ec78b2",
"build/prefab/full/mac_arm64_server/release/dist/ballisticakit_headless": "62a6ee7583ef9d83f9bb1601fce6ddaa",
"build/prefab/full/mac_x86_64_gui/debug/ballisticakit": "f495a8547ca8b824f80006603537d1cf",
"build/prefab/full/mac_x86_64_gui/release/ballisticakit": "672c351fca7843d85a2be7aba13faf1f",
"build/prefab/full/mac_x86_64_server/debug/dist/ballisticakit_headless": "50f6d105ad4c2bb2df4f3d335b6d2cfa",
"build/prefab/full/mac_x86_64_server/release/dist/ballisticakit_headless": "44e1d633accc2410ca6d825e4c464f45",
"build/prefab/full/windows_x86_gui/debug/BallisticaKit.exe": "5cce0ce595304313ac28c02e235f19d7",
"build/prefab/full/windows_x86_gui/release/BallisticaKit.exe": "9433fc9da2f4b7255f2c7fa95868604a",
"build/prefab/full/windows_x86_server/debug/dist/BallisticaKitHeadless.exe": "18eb1871a6db9bbe17f5e9678d3d492a",
"build/prefab/full/windows_x86_server/release/dist/BallisticaKitHeadless.exe": "cf51dafe0553d06a44349d0911c21d71",
"build/prefab/lib/linux_arm64_gui/debug/libballisticaplus.a": "c22901e06e88a55cce0b4e08bbf41a4c",
"build/prefab/lib/linux_arm64_gui/release/libballisticaplus.a": "a27963487e346338e4c216bd4fbb9e2a",
"build/prefab/lib/linux_arm64_server/debug/libballisticaplus.a": "c22901e06e88a55cce0b4e08bbf41a4c",
@ -4096,14 +4096,14 @@
"build/prefab/lib/mac_x86_64_gui/release/libballisticaplus.a": "852fe46c736082611a831a618923c241",
"build/prefab/lib/mac_x86_64_server/debug/libballisticaplus.a": "36fbda7829ed5c2862c34feb09b03402",
"build/prefab/lib/mac_x86_64_server/release/libballisticaplus.a": "852fe46c736082611a831a618923c241",
"build/prefab/lib/windows/Debug_Win32/BallisticaKitGenericPlus.lib": "38b4b5b85a9bafdb76222d0f0c962b06",
"build/prefab/lib/windows/Debug_Win32/BallisticaKitGenericPlus.pdb": "9a8af3d217bcb0bacfaed4c30dd5f42e",
"build/prefab/lib/windows/Debug_Win32/BallisticaKitHeadlessPlus.lib": "6d10ca306f60d66efb4942636e4955d6",
"build/prefab/lib/windows/Debug_Win32/BallisticaKitHeadlessPlus.pdb": "1c65d36e4420ed79380dc8c041c94a8b",
"build/prefab/lib/windows/Release_Win32/BallisticaKitGenericPlus.lib": "9b1b72f3d41c89a6b06288be63e8f40a",
"build/prefab/lib/windows/Release_Win32/BallisticaKitGenericPlus.pdb": "e0f2eb8ea024bc88e999b9dc16317fd4",
"build/prefab/lib/windows/Release_Win32/BallisticaKitHeadlessPlus.lib": "42be1225757328f432d91de950444ba0",
"build/prefab/lib/windows/Release_Win32/BallisticaKitHeadlessPlus.pdb": "05bc2832cc0c9fba308668fc1a6d3b0f",
"build/prefab/lib/windows/Debug_Win32/BallisticaKitGenericPlus.lib": "8ec03141da397548f8a06259222c14e3",
"build/prefab/lib/windows/Debug_Win32/BallisticaKitGenericPlus.pdb": "9f20a365cc0ec1831e0e301a34198b0e",
"build/prefab/lib/windows/Debug_Win32/BallisticaKitHeadlessPlus.lib": "37980dc34b9ad281cdab738f7053af26",
"build/prefab/lib/windows/Debug_Win32/BallisticaKitHeadlessPlus.pdb": "148e0f3a1ee77607d0b93a636b253295",
"build/prefab/lib/windows/Release_Win32/BallisticaKitGenericPlus.lib": "e20a2aa49741757075634f422dc5ac7a",
"build/prefab/lib/windows/Release_Win32/BallisticaKitGenericPlus.pdb": "fa7018f7c2ee8c952bff7879b92709cb",
"build/prefab/lib/windows/Release_Win32/BallisticaKitHeadlessPlus.lib": "cd38a41872e74b43de005375330e3cd0",
"build/prefab/lib/windows/Release_Win32/BallisticaKitHeadlessPlus.pdb": "5bc4faf70f09f56e2ced27cafe2ad3e6",
"src/assets/ba_data/python/babase/_mgen/__init__.py": "f885fed7f2ed98ff2ba271f9dbe3391c",
"src/assets/ba_data/python/babase/_mgen/enums.py": "28323912b56ec07701eda3d41a6a4101",
"src/ballistica/base/mgen/pyembed/binding_base.inc": "72bfed2cce8ff19741989dec28302f3f",
@ -4114,5 +4114,5 @@
"src/ballistica/core/mgen/python_modules_monolithic.h": "fb967ed1c7db0c77d8deb4f00a7103c5",
"src/ballistica/scene_v1/mgen/pyembed/binding_scene_v1.inc": "d80f970053099b3044204bfe29ddefce",
"src/ballistica/template_fs/mgen/pyembed/binding_template_fs.inc": "44a45492db057bf7f7158c3b0fa11f0f",
"src/ballistica/ui_v1/mgen/pyembed/binding_ui_v1.inc": "8f4c2070174bdc2fbf735180394d7b3a"
"src/ballistica/ui_v1/mgen/pyembed/binding_ui_v1.inc": "f5f054050d2b2fcd3763a4833fb32269"
}

View File

@ -1,4 +1,4 @@
### 1.7.30 (build 21636, api 8, 2023-11-30)
### 1.7.30 (build 21639, api 8, 2023-11-30)
- Continued work on the big 1.7.28 update.
- Got the Android version back up and running. There's been lots of cleanup and
simplification on the Android layer, cleaning out years of cruft. This should
@ -21,9 +21,16 @@
builds when tv-border was on (Thanks for the heads-up Loup(Dliwk's fan)!).
- (build 21631) Fixes an issue where '^^^^^^^^^^^^^' lines in stack traces could
get chopped into tiny bits each on their own line in the dev console.
- Fixed a longstanding issue where multiple key presses simultaneously could
cause multiple windows to pop up where only one is expected. Please holler if
you still see this problem happening anywhere.
- Hopefully finally fixed a longstanding issue where obscure cases such as
multiple key presses simultaneously could cause multiple main menu windows to
pop up. Please holler if you still see this problem happening anywhere. Also
added a few related safety checks and warnings to help ensure UI code is free
from such problems going forward. To make sure your custom UIs are behaving
well in this system, do the following two things: 1) any time you call
`set_main_menu_window()`, pass your existing main menu window root widget as
`from_window`. 2) In any call that can lead to you switching the main menu
window, check if your root widget is dead or transitioning out first and abort
if it is. See any window in `ui_v1_lib` for examples.
### 1.7.29 (build 21619, api 8, 2023-11-21)

View File

@ -41,5 +41,6 @@ class AppDelegate:
sessiontype,
settings,
completion_call=completion_call,
).get_root_widget()
).get_root_widget(),
from_window=False, # Disable check since we don't know.
)

View File

@ -800,5 +800,6 @@ class ClassicSubsystem(babase.AppSubsystem):
bauiv1.getsound('swish').play()
babase.app.ui_v1.set_main_menu_window(
MainMenuWindow().get_root_widget()
MainMenuWindow().get_root_widget(),
from_window=False, # Disable check here.
)

View File

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

View File

@ -317,7 +317,8 @@ class MainMenuActivity(bs.Activity[bs.Player, bs.Team]):
from bauiv1lib.kiosk import KioskWindow
bs.app.ui_v1.set_main_menu_window(
KioskWindow().get_root_widget()
KioskWindow().get_root_widget(),
from_window=False, # Disable check here.
)
# ..or in normal cases go back to the main menu
else:
@ -326,14 +327,16 @@ class MainMenuActivity(bs.Activity[bs.Player, bs.Team]):
from bauiv1lib.gather import GatherWindow
bs.app.ui_v1.set_main_menu_window(
GatherWindow(transition=None).get_root_widget()
GatherWindow(transition=None).get_root_widget(),
from_window=False, # Disable check here.
)
elif main_menu_location == 'Watch':
# pylint: disable=cyclic-import
from bauiv1lib.watch import WatchWindow
bs.app.ui_v1.set_main_menu_window(
WatchWindow(transition=None).get_root_widget()
WatchWindow(transition=None).get_root_widget(),
from_window=False, # Disable check here.
)
elif main_menu_location == 'Team Game Select':
# pylint: disable=cyclic-import
@ -344,7 +347,8 @@ class MainMenuActivity(bs.Activity[bs.Player, bs.Team]):
bs.app.ui_v1.set_main_menu_window(
PlaylistBrowserWindow(
sessiontype=bs.DualTeamSession, transition=None
).get_root_widget()
).get_root_widget(),
from_window=False, # Disable check here.
)
elif main_menu_location == 'Free-for-All Game Select':
# pylint: disable=cyclic-import
@ -356,28 +360,34 @@ class MainMenuActivity(bs.Activity[bs.Player, bs.Team]):
PlaylistBrowserWindow(
sessiontype=bs.FreeForAllSession,
transition=None,
).get_root_widget()
).get_root_widget(),
from_window=False, # Disable check here.
)
elif main_menu_location == 'Coop Select':
# pylint: disable=cyclic-import
from bauiv1lib.coop.browser import CoopBrowserWindow
bs.app.ui_v1.set_main_menu_window(
CoopBrowserWindow(transition=None).get_root_widget()
CoopBrowserWindow(
transition=None
).get_root_widget(),
from_window=False, # Disable check here.
)
elif main_menu_location == 'Benchmarks & Stress Tests':
# pylint: disable=cyclic-import
from bauiv1lib.debug import DebugWindow
bs.app.ui_v1.set_main_menu_window(
DebugWindow(transition=None).get_root_widget()
DebugWindow(transition=None).get_root_widget(),
from_window=False, # Disable check here.
)
else:
# pylint: disable=cyclic-import
from bauiv1lib.mainmenu import MainMenuWindow
bs.app.ui_v1.set_main_menu_window(
MainMenuWindow(transition=None).get_root_widget()
MainMenuWindow(transition=None).get_root_widget(),
from_window=None,
)
# attempt to show any pending offers immediately.

View File

@ -6,6 +6,7 @@
from __future__ import annotations
import logging
import inspect
from typing import TYPE_CHECKING
import _bauiv1
@ -87,3 +88,19 @@ def show_url_window(address: str) -> None:
return
app.classic.show_url_window(address)
def double_transition_out_warning() -> None:
"""Called if a widget is set to transition out twice."""
caller_frame = inspect.stack()[1]
caller_filename = caller_frame.filename
caller_line_number = caller_frame.lineno
logging.warning(
'ContainerWidget was set to transition out twice;'
' this often implies buggy code (%s line %s).\n'
' Generally you should check the value of'
' _root_widget.transitioning_out and only kick off transitions'
' when that is False.',
caller_filename,
caller_line_number,
)

View File

@ -5,6 +5,7 @@
from __future__ import annotations
import logging
import inspect
from typing import TYPE_CHECKING
import babase
@ -116,21 +117,69 @@ class UIV1Subsystem(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 set_main_menu_window(self, window: bauiv1.Widget) -> None:
"""Set the current 'main' window, replacing any existing."""
def set_main_menu_window(
self,
window: bauiv1.Widget,
from_window: bauiv1.Widget | None | bool = True,
) -> None:
"""Set the current 'main' window, replacing any existing.
If 'from_window' is passed as a bauiv1.Widget or None, a warning
will be issued if it that value does not match the current main
window. This can help clean up flawed code that can lead to bad
UI states. A value of False will disable the check.
"""
existing = self._main_menu_window
from inspect import currentframe, getframeinfo
try:
if isinstance(from_window, bool):
# For default val True we warn that the arg wasn't
# passed. False can be explicitly passed to disable this
# check.
if from_window is True:
caller_frame = inspect.stack()[1]
caller_filename = caller_frame.filename
caller_line_number = caller_frame.lineno
logging.warning(
'set_main_menu_window() should be passed a'
" 'from_window' value to help ensure proper UI behavior"
' (%s line %i).',
caller_filename,
caller_line_number,
)
else:
# For everything else, warn if what they passed wasn't
# the previous main menu widget.
if from_window is not existing:
caller_frame = inspect.stack()[1]
caller_filename = caller_frame.filename
caller_line_number = caller_frame.lineno
logging.warning(
"set_main_menu_window() was passed 'from_window' %s"
' but existing main-menu-window is %s. (%s line %i).',
from_window,
existing,
caller_filename,
caller_line_number,
)
except Exception:
# Prevent any bugs in these checks from causing problems.
logging.exception('Error checking from_window')
# Once the above code leads to us fixing all leftover window bugs
# at the source, we can kill the code below.
# Let's grab the location where we were called from to report
# if we have to force-kill the existing window (which normally
# should not happen).
frameline = None
try:
frame = currentframe()
frame = inspect.currentframe()
if frame is not None:
frame = frame.f_back
if frame is not None:
frameinfo = getframeinfo(frame)
frameinfo = inspect.getframeinfo(frame)
frameline = f'{frameinfo.filename} {frameinfo.lineno}'
except Exception:
logging.exception('Error calcing line for set_main_menu_window')
@ -167,6 +216,7 @@ class UIV1Subsystem(babase.AppSubsystem):
)
else:
self._main_menu_window.delete()
self._main_menu_window = None
def add_main_menu_close_callback(self, call: Callable[[], Any]) -> None:
"""(internal)"""

View File

@ -1507,9 +1507,18 @@ class AccountSettingsWindow(bui.Window):
# pylint: disable=cyclic-import
from bauiv1lib.profile.browser import ProfileBrowserWindow
# no-op if our underlying widget is dead or on its way out.
if not self._root_widget or self._root_widget.transitioning_out:
return
self._save_state()
bui.containerwidget(edit=self._root_widget, transition='out_left')
ProfileBrowserWindow(origin_widget=self._player_profiles_button)
bui.app.ui_v1.set_main_menu_window(
ProfileBrowserWindow(
origin_widget=self._player_profiles_button
).get_root_widget(),
from_window=self._root_widget,
)
def _cancel_sign_in_press(self) -> None:
# If we're waiting on an adapter to give us credentials, abort.
@ -1670,6 +1679,10 @@ class AccountSettingsWindow(bui.Window):
# pylint: disable=cyclic-import
from bauiv1lib.mainmenu import MainMenuWindow
# 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._save_state()
bui.containerwidget(
edit=self._root_widget, transition=self._transition_out
@ -1678,7 +1691,8 @@ class AccountSettingsWindow(bui.Window):
if not self._modal:
assert bui.app.classic is not None
bui.app.ui_v1.set_main_menu_window(
MainMenuWindow(transition='in_left').get_root_widget()
MainMenuWindow(transition='in_left').get_root_widget(),
from_window=self._root_widget,
)
def _save_state(self) -> None:

View File

@ -1019,6 +1019,10 @@ class CoopBrowserWindow(bui.Window):
from bauiv1lib.account import show_sign_in_prompt
from bauiv1lib.league.rankwindow import LeagueRankWindow
# 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
plus = bui.app.plus
assert plus is not None
@ -1032,7 +1036,8 @@ class CoopBrowserWindow(bui.Window):
bui.app.ui_v1.set_main_menu_window(
LeagueRankWindow(
origin_widget=self._league_rank_button.get_button()
).get_root_widget()
).get_root_widget(),
from_window=self._root_widget,
)
def _switch_to_score(
@ -1043,6 +1048,10 @@ class CoopBrowserWindow(bui.Window):
# pylint: disable=cyclic-import
from bauiv1lib.account import show_sign_in_prompt
# 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
plus = bui.app.plus
assert plus is not None
@ -1058,7 +1067,8 @@ class CoopBrowserWindow(bui.Window):
origin_widget=self._store_button.get_button(),
show_tab=show_tab,
back_location='CoopBrowserWindow',
).get_root_widget()
).get_root_widget(),
from_window=self._root_widget,
)
def is_tourney_data_up_to_date(self) -> bool:
@ -1218,6 +1228,10 @@ class CoopBrowserWindow(bui.Window):
# pylint: disable=cyclic-import
from bauiv1lib.play import PlayWindow
# 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 something is selected, store it.
self._save_state()
bui.containerwidget(
@ -1225,7 +1239,8 @@ class CoopBrowserWindow(bui.Window):
)
assert bui.app.classic is not None
bui.app.ui_v1.set_main_menu_window(
PlayWindow(transition='in_left').get_root_widget()
PlayWindow(transition='in_left').get_root_widget(),
from_window=self._root_widget,
)
def _save_state(self) -> None:

View File

@ -359,10 +359,15 @@ class CreditsListWindow(bui.Window):
def _back(self) -> None:
from bauiv1lib.mainmenu import MainMenuWindow
# no-op if our underlying widget is dead or on its way out.
if not self._root_widget or self._root_widget.transitioning_out:
return
bui.containerwidget(
edit=self._root_widget, transition=self._transition_out
)
assert bui.app.classic is not None
bui.app.ui_v1.set_main_menu_window(
MainMenuWindow(transition='in_left').get_root_widget()
MainMenuWindow(transition='in_left').get_root_widget(),
from_window=self._root_widget,
)

View File

@ -379,8 +379,13 @@ class DebugWindow(bui.Window):
# pylint: disable=cyclic-import
from bauiv1lib.settings.advanced import AdvancedSettingsWindow
# no-op if our underlying widget is dead or on its way out.
if not self._root_widget or self._root_widget.transitioning_out:
return
bui.containerwidget(edit=self._root_widget, transition='out_right')
assert bui.app.classic is not None
bui.app.ui_v1.set_main_menu_window(
AdvancedSettingsWindow(transition='in_left').get_root_widget()
AdvancedSettingsWindow(transition='in_left').get_root_widget(),
from_window=self._root_widget,
)

View File

@ -270,12 +270,17 @@ class GatherWindow(bui.Window):
"""Called by the private-hosting tab to select a playlist."""
from bauiv1lib.play import PlayWindow
# 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._save_state()
bui.containerwidget(edit=self._root_widget, transition='out_left')
assert bui.app.classic is not None
bui.app.ui_v1.selecting_private_party_playlist = True
bui.app.ui_v1.set_main_menu_window(
PlayWindow(origin_widget=origin_widget).get_root_widget()
PlayWindow(origin_widget=origin_widget).get_root_widget(),
from_window=self._root_widget,
)
def _set_tab(self, tab_id: TabID) -> None:
@ -383,11 +388,16 @@ class GatherWindow(bui.Window):
def _back(self) -> None:
from bauiv1lib.mainmenu import MainMenuWindow
# 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._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_menu_window(
MainMenuWindow(transition='in_left').get_root_widget()
MainMenuWindow(transition='in_left').get_root_widget(),
from_window=self._root_widget,
)

View File

@ -732,8 +732,13 @@ class GetCurrencyWindow(bui.Window):
def _back(self) -> None:
from bauiv1lib.store 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._transitioning_out:
return
bui.containerwidget(
edit=self._root_widget, transition=self._transition_out
)
@ -745,7 +750,9 @@ class GetCurrencyWindow(bui.Window):
).get_root_widget()
if not self._from_modal_store:
assert bui.app.classic is not None
bui.app.ui_v1.set_main_menu_window(window)
bui.app.ui_v1.set_main_menu_window(
window, from_window=self._root_widget
)
self._transitioning_out = True

View File

@ -645,11 +645,16 @@ class HelpWindow(bui.Window):
# pylint: disable=cyclic-import
from bauiv1lib.mainmenu import MainMenuWindow
# no-op if our underlying widget is dead or on its way out.
if not self._root_widget or self._root_widget.transitioning_out:
return
bui.containerwidget(
edit=self._root_widget, transition=self._transition_out
)
if self._main_menu:
assert bui.app.classic is not None
bui.app.ui_v1.set_main_menu_window(
MainMenuWindow(transition='in_left').get_root_widget()
MainMenuWindow(transition='in_left').get_root_widget(),
from_window=self._root_widget,
)

View File

@ -501,9 +501,15 @@ class KioskWindow(bui.Window):
def _do_full_menu(self) -> None:
from bauiv1lib.mainmenu import MainMenuWindow
# 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
assert bui.app.classic is not None
self._save_state()
bui.containerwidget(edit=self._root_widget, transition='out_left')
bui.app.classic.did_menu_intro = True # prevent delayed transition-in
bui.app.ui_v1.set_main_menu_window(MainMenuWindow().get_root_widget())
bui.app.ui_v1.set_main_menu_window(
MainMenuWindow().get_root_widget(), from_window=self._root_widget
)

View File

@ -1142,6 +1142,10 @@ class LeagueRankWindow(bui.Window):
def _back(self) -> None:
from bauiv1lib.coop.browser import CoopBrowserWindow
# 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._save_state()
bui.containerwidget(
edit=self._root_widget, transition=self._transition_out
@ -1149,5 +1153,6 @@ class LeagueRankWindow(bui.Window):
if not self._modal:
assert bui.app.classic is not None
bui.app.ui_v1.set_main_menu_window(
CoopBrowserWindow(transition='in_left').get_root_widget()
CoopBrowserWindow(transition='in_left').get_root_widget(),
from_window=self._root_widget,
)

View File

@ -1038,6 +1038,10 @@ class MainMenuWindow(bui.Window):
# pylint: disable=cyclic-import
from bauiv1lib.confirm import QuitWindow
# 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
# Note: Normally we should go through bui.quit(confirm=True) but
# invoking the window directly lets us scale it up from the
# button.
@ -1047,24 +1051,34 @@ class MainMenuWindow(bui.Window):
# pylint: disable=cyclic-import
from bauiv1lib.kiosk import KioskWindow
# 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._save_state()
bui.containerwidget(edit=self._root_widget, transition='out_right')
assert bui.app.classic is not None
bui.app.ui_v1.set_main_menu_window(
KioskWindow(transition='in_left').get_root_widget()
KioskWindow(transition='in_left').get_root_widget(),
from_window=self._root_widget,
)
def _show_account_window(self) -> None:
# pylint: disable=cyclic-import
from bauiv1lib.account.settings import AccountSettingsWindow
# 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._save_state()
bui.containerwidget(edit=self._root_widget, transition='out_left')
assert bui.app.classic is not None
bui.app.ui_v1.set_main_menu_window(
AccountSettingsWindow(
origin_widget=self._account_button
).get_root_widget()
).get_root_widget(),
from_window=self._root_widget,
)
def _on_store_pressed(self) -> None:
@ -1072,6 +1086,10 @@ class MainMenuWindow(bui.Window):
from bauiv1lib.store.browser import StoreBrowserWindow
from bauiv1lib.account import show_sign_in_prompt
# 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
plus = bui.app.plus
assert plus is not None
@ -1084,7 +1102,8 @@ class MainMenuWindow(bui.Window):
bui.app.ui_v1.set_main_menu_window(
StoreBrowserWindow(
origin_widget=self._store_button
).get_root_widget()
).get_root_widget(),
from_window=self._root_widget,
)
def _is_benchmark(self) -> bool:
@ -1149,8 +1168,11 @@ class MainMenuWindow(bui.Window):
def _end_game(self) -> None:
assert bui.app.classic is not None
if not self._root_widget:
# no-op if our underlying widget is dead or on its way out.
if not self._root_widget or self._root_widget.transitioning_out:
return
bui.containerwidget(edit=self._root_widget, transition='out_left')
bui.app.classic.return_to_main_menu_session_gracefully(reset_ui=False)
@ -1166,39 +1188,54 @@ class MainMenuWindow(bui.Window):
# pylint: disable=cyclic-import
from bauiv1lib.creditslist import CreditsListWindow
# 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._save_state()
bui.containerwidget(edit=self._root_widget, transition='out_left')
assert bui.app.classic is not None
bui.app.ui_v1.set_main_menu_window(
CreditsListWindow(
origin_widget=self._credits_button
).get_root_widget()
).get_root_widget(),
from_window=self._root_widget,
)
def _howtoplay(self) -> None:
# pylint: disable=cyclic-import
from bauiv1lib.helpui import HelpWindow
# 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._save_state()
bui.containerwidget(edit=self._root_widget, transition='out_left')
assert bui.app.classic is not None
bui.app.ui_v1.set_main_menu_window(
HelpWindow(
main_menu=True, origin_widget=self._how_to_play_button
).get_root_widget()
).get_root_widget(),
from_window=self._root_widget,
)
def _settings(self) -> None:
# pylint: disable=cyclic-import
from bauiv1lib.settings.allsettings import AllSettingsWindow
# 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._save_state()
bui.containerwidget(edit=self._root_widget, transition='out_left')
assert bui.app.classic is not None
bui.app.ui_v1.set_main_menu_window(
AllSettingsWindow(
origin_widget=self._settings_button
).get_root_widget()
).get_root_widget(),
from_window=self._root_widget,
)
def _resume_and_call(self, call: Callable[[], Any]) -> None:
@ -1281,35 +1318,50 @@ class MainMenuWindow(bui.Window):
# pylint: disable=cyclic-import
from bauiv1lib.gather import GatherWindow
# 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._save_state()
bui.containerwidget(edit=self._root_widget, transition='out_left')
assert bui.app.classic is not None
bui.app.ui_v1.set_main_menu_window(
GatherWindow(origin_widget=self._gather_button).get_root_widget()
GatherWindow(origin_widget=self._gather_button).get_root_widget(),
from_window=self._root_widget,
)
def _watch_press(self) -> None:
# pylint: disable=cyclic-import
from bauiv1lib.watch import WatchWindow
# 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._save_state()
bui.containerwidget(edit=self._root_widget, transition='out_left')
assert bui.app.classic is not None
bui.app.ui_v1.set_main_menu_window(
WatchWindow(origin_widget=self._watch_button).get_root_widget()
WatchWindow(origin_widget=self._watch_button).get_root_widget(),
from_window=self._root_widget,
)
def _play_press(self) -> None:
# pylint: disable=cyclic-import
from bauiv1lib.play import PlayWindow
# 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._save_state()
bui.containerwidget(edit=self._root_widget, transition='out_left')
assert bui.app.classic is not None
bui.app.ui_v1.selecting_private_party_playlist = False
bui.app.ui_v1.set_main_menu_window(
PlayWindow(origin_widget=self._start_button).get_root_widget()
PlayWindow(origin_widget=self._start_button).get_root_widget(),
from_window=self._root_widget,
)
def _resume(self) -> None:

View File

@ -521,13 +521,19 @@ class PlayWindow(bui.Window):
def _back(self) -> None:
# pylint: disable=cyclic-import
# 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._is_main_menu:
from bauiv1lib.mainmenu import MainMenuWindow
self._save_state()
assert bui.app.classic is not None
bui.app.ui_v1.set_main_menu_window(
MainMenuWindow(transition='in_left').get_root_widget()
MainMenuWindow(transition='in_left').get_root_widget(),
from_window=self._root_widget,
)
bui.containerwidget(
edit=self._root_widget, transition=self._transition_out
@ -538,7 +544,8 @@ class PlayWindow(bui.Window):
self._save_state()
assert bui.app.classic is not None
bui.app.ui_v1.set_main_menu_window(
GatherWindow(transition='in_left').get_root_widget()
GatherWindow(transition='in_left').get_root_widget(),
from_window=self._root_widget,
)
bui.containerwidget(
edit=self._root_widget, transition=self._transition_out
@ -549,6 +556,10 @@ class PlayWindow(bui.Window):
from bauiv1lib.account import show_sign_in_prompt
from bauiv1lib.coop.browser import CoopBrowserWindow
# 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
plus = bui.app.plus
assert plus is not None
@ -559,26 +570,38 @@ class PlayWindow(bui.Window):
bui.containerwidget(edit=self._root_widget, transition='out_left')
assert bui.app.classic is not None
bui.app.ui_v1.set_main_menu_window(
CoopBrowserWindow(origin_widget=self._coop_button).get_root_widget()
CoopBrowserWindow(
origin_widget=self._coop_button
).get_root_widget(),
from_window=self._root_widget,
)
def _team_tourney(self) -> None:
# pylint: disable=cyclic-import
from bauiv1lib.playlist.browser import PlaylistBrowserWindow
# 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._save_state()
bui.containerwidget(edit=self._root_widget, transition='out_left')
assert bui.app.classic is not None
bui.app.ui_v1.set_main_menu_window(
PlaylistBrowserWindow(
origin_widget=self._teams_button, sessiontype=bs.DualTeamSession
).get_root_widget()
).get_root_widget(),
from_window=self._root_widget,
)
def _free_for_all(self) -> None:
# pylint: disable=cyclic-import
from bauiv1lib.playlist.browser import PlaylistBrowserWindow
# 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._save_state()
bui.containerwidget(edit=self._root_widget, transition='out_left')
assert bui.app.classic is not None
@ -586,7 +609,8 @@ class PlayWindow(bui.Window):
PlaylistBrowserWindow(
origin_widget=self._free_for_all_button,
sessiontype=bs.FreeForAllSession,
).get_root_widget()
).get_root_widget(),
from_window=self._root_widget,
)
def _draw_dude(

View File

@ -684,6 +684,10 @@ class PlaylistBrowserWindow(bui.Window):
PlaylistCustomizeBrowserWindow,
)
# 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._save_state()
bui.containerwidget(edit=self._root_widget, transition='out_left')
assert bui.app.classic is not None
@ -691,13 +695,18 @@ class PlaylistBrowserWindow(bui.Window):
PlaylistCustomizeBrowserWindow(
origin_widget=self._customize_button,
sessiontype=self._sessiontype,
).get_root_widget()
).get_root_widget(),
from_window=self._root_widget,
)
def _on_back_press(self) -> None:
# pylint: disable=cyclic-import
from bauiv1lib.play import PlayWindow
# 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
# Store our selected playlist if that's changed.
if self._selected_playlist is not None:
prev_sel = bui.app.config.get(
@ -716,7 +725,8 @@ class PlaylistBrowserWindow(bui.Window):
)
assert bui.app.classic is not None
bui.app.ui_v1.set_main_menu_window(
PlayWindow(transition='in_left').get_root_widget()
PlayWindow(transition='in_left').get_root_widget(),
from_window=self._root_widget,
)
def _save_state(self) -> None:

View File

@ -323,6 +323,10 @@ class PlaylistCustomizeBrowserWindow(bui.Window):
# 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[
@ -337,7 +341,8 @@ class PlaylistCustomizeBrowserWindow(bui.Window):
bui.app.ui_v1.set_main_menu_window(
browser.PlaylistBrowserWindow(
transition='in_left', sessiontype=self._sessiontype
).get_root_widget()
).get_root_widget(),
from_window=self._root_widget,
)
def _select(self, name: str, index: int) -> None:

View File

@ -283,6 +283,10 @@ class PlaylistEditWindow(bui.Window):
PlaylistCustomizeBrowserWindow,
)
# no-op if our underlying widget is dead or on its way out.
if not self._root_widget or self._root_widget.transitioning_out:
return
bui.getsound('powerdown01').play()
bui.containerwidget(edit=self._root_widget, transition='out_right')
assert bui.app.classic is not None
@ -293,7 +297,8 @@ class PlaylistEditWindow(bui.Window):
select_playlist=(
self._editcontroller.get_existing_playlist_name()
),
).get_root_widget()
).get_root_widget(),
from_window=self._root_widget,
)
def _add(self) -> None:
@ -315,6 +320,10 @@ class PlaylistEditWindow(bui.Window):
PlaylistCustomizeBrowserWindow,
)
# 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
plus = bui.app.plus
assert plus is not None
@ -380,7 +389,8 @@ class PlaylistEditWindow(bui.Window):
transition='in_left',
sessiontype=self._editcontroller.get_session_type(),
select_playlist=new_name,
).get_root_widget()
).get_root_widget(),
from_window=self._root_widget,
)
def _save_press_with_sound(self) -> None:

View File

@ -92,7 +92,8 @@ class PlaylistEditController:
bui.app.ui_v1.set_main_menu_window(
PlaylistEditWindow(
editcontroller=self, transition=transition
).get_root_widget()
).get_root_widget(),
from_window=False, # Disable this check.
)
def get_config_name(self) -> str:
@ -150,7 +151,8 @@ class PlaylistEditController:
assert bui.app.classic is not None
bui.app.ui_v1.clear_main_menu_window(transition='out_left')
bui.app.ui_v1.set_main_menu_window(
PlaylistAddGameWindow(editcontroller=self).get_root_widget()
PlaylistAddGameWindow(editcontroller=self).get_root_widget(),
from_window=None,
)
def edit_game_pressed(self) -> None:
@ -175,7 +177,8 @@ class PlaylistEditController:
bui.app.ui_v1.set_main_menu_window(
PlaylistEditWindow(
editcontroller=self, transition='in_left'
).get_root_widget()
).get_root_widget(),
from_window=None,
)
def _show_edit_ui(
@ -205,7 +208,8 @@ class PlaylistEditController:
bui.app.ui_v1.set_main_menu_window(
PlaylistEditWindow(
editcontroller=self, transition='in_left'
).get_root_widget()
).get_root_widget(),
from_window=None,
)
# Otherwise we were adding; go back to the add type choice list.
@ -214,7 +218,8 @@ class PlaylistEditController:
bui.app.ui_v1.set_main_menu_window(
PlaylistAddGameWindow(
editcontroller=self, transition='in_left'
).get_root_widget()
).get_root_widget(),
from_window=None,
)
else:
# Make sure type is in there.
@ -236,5 +241,6 @@ class PlaylistEditController:
bui.app.ui_v1.set_main_menu_window(
PlaylistEditWindow(
editcontroller=self, transition='in_left'
).get_root_widget()
).get_root_widget(),
from_window=None,
)

View File

@ -514,6 +514,10 @@ class PlaylistEditGameWindow(bui.Window):
# pylint: disable=cyclic-import
from bauiv1lib.playlist.mapselect import PlaylistMapSelectWindow
# 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
# Replace ourself with the map-select UI.
bui.containerwidget(edit=self._root_widget, transition='out_left')
assert bui.app.classic is not None
@ -524,7 +528,8 @@ class PlaylistEditGameWindow(bui.Window):
copy.deepcopy(self._getconfig()),
self._edit_info,
self._completion_call,
).get_root_widget()
).get_root_widget(),
from_window=self._root_widget,
)
def _choice_inc(

View File

@ -273,6 +273,10 @@ class PlaylistMapSelectWindow(bui.Window):
def _select(self, map_name: str) -> None:
from bauiv1lib.playlist.editgame import PlaylistEditGameWindow
# 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['settings']['map'] = map_name
bui.containerwidget(edit=self._root_widget, transition='out_right')
assert bui.app.classic is not None
@ -285,7 +289,8 @@ class PlaylistMapSelectWindow(bui.Window):
default_selection='map',
transition='in_left',
edit_info=self._edit_info,
).get_root_widget()
).get_root_widget(),
from_window=self._root_widget,
)
def _select_with_delay(self, map_name: str) -> None:
@ -296,6 +301,10 @@ class PlaylistMapSelectWindow(bui.Window):
def _cancel(self) -> None:
from bauiv1lib.playlist.editgame import PlaylistEditGameWindow
# no-op if our underlying widget is dead or on its way out.
if not self._root_widget or self._root_widget.transitioning_out:
return
bui.containerwidget(edit=self._root_widget, transition='out_right')
assert bui.app.classic is not None
bui.app.ui_v1.set_main_menu_window(
@ -307,5 +316,6 @@ class PlaylistMapSelectWindow(bui.Window):
default_selection='map',
transition='in_left',
edit_info=self._edit_info,
).get_root_widget()
).get_root_widget(),
from_window=self._root_widget,
)

View File

@ -140,7 +140,6 @@ class PlayOptionsWindow(PopupWindow):
if show_shuffle_check_box:
self._height += 40
# Creates our _root_widget.
uiscale = bui.app.ui_v1.uiscale
scale = (
1.69
@ -149,6 +148,7 @@ class PlayOptionsWindow(PopupWindow):
if uiscale is bui.UIScale.MEDIUM
else 0.85
)
# Creates our _root_widget.
super().__init__(
position=scale_origin, size=(self._width, self._height), scale=scale
)
@ -448,6 +448,10 @@ class PlayOptionsWindow(PopupWindow):
self._transition_out()
def _on_ok_press(self) -> None:
# no-op if our underlying widget is dead or on its way out.
if not self.root_widget or self.root_widget.transitioning_out:
return
# Disallow if our playlist has disappeared.
if not self._does_target_playlist_exist():
return
@ -479,7 +483,8 @@ class PlayOptionsWindow(PopupWindow):
bui.getsound('gunCocking').play()
assert bui.app.classic is not None
bui.app.ui_v1.set_main_menu_window(
GatherWindow(transition='in_right').get_root_widget()
GatherWindow(transition='in_right').get_root_widget(),
from_window=self.root_widget,
)
self._transition_out(transition='out_left')
if self._delegate is not None:

View File

@ -212,6 +212,10 @@ class ProfileBrowserWindow(bui.Window):
from bauiv1lib.profile.edit import EditProfileWindow
from bauiv1lib.purchase import PurchaseWindow
# 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
plus = bui.app.plus
assert plus is not None
@ -252,7 +256,8 @@ class ProfileBrowserWindow(bui.Window):
bui.app.ui_v1.set_main_menu_window(
EditProfileWindow(
existing_profile=None, in_main_menu=self._in_main_menu
).get_root_widget()
).get_root_widget(),
from_window=self._root_widget,
)
def _delete_profile(self) -> None:
@ -301,6 +306,10 @@ class ProfileBrowserWindow(bui.Window):
# pylint: disable=cyclic-import
from bauiv1lib.profile.edit import EditProfileWindow
# 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_profile is None:
bui.getsound('error').play()
bui.screenmessage(
@ -313,7 +322,8 @@ class ProfileBrowserWindow(bui.Window):
bui.app.ui_v1.set_main_menu_window(
EditProfileWindow(
self._selected_profile, in_main_menu=self._in_main_menu
).get_root_widget()
).get_root_widget(),
from_window=self._root_widget,
)
def _select(self, name: str, index: int) -> None:
@ -324,6 +334,10 @@ class ProfileBrowserWindow(bui.Window):
# pylint: disable=cyclic-import
from bauiv1lib.account.settings import AccountSettingsWindow
# 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
assert bui.app.classic is not None
self._save_state()
@ -333,7 +347,8 @@ class ProfileBrowserWindow(bui.Window):
if self._in_main_menu:
assert bui.app.classic is not None
bui.app.ui_v1.set_main_menu_window(
AccountSettingsWindow(transition='in_left').get_root_widget()
AccountSettingsWindow(transition='in_left').get_root_widget(),
from_window=self._root_widget,
)
# If we're being called up standalone, handle pause/resume ourself.

View File

@ -18,12 +18,18 @@ class EditProfileWindow(bui.Window):
# FIXME: WILL NEED TO CHANGE THIS FOR UILOCATION.
def reload_window(self) -> None:
"""Transitions out and recreates ourself."""
# no-op if our underlying widget is dead or on its way out.
if not self._root_widget or self._root_widget.transitioning_out:
return
bui.containerwidget(edit=self._root_widget, transition='out_left')
assert bui.app.classic is not None
bui.app.ui_v1.set_main_menu_window(
EditProfileWindow(
self.getname(), self._in_main_menu
).get_root_widget()
).get_root_widget(),
from_window=self._root_widget,
)
def __init__(
@ -653,6 +659,10 @@ class EditProfileWindow(bui.Window):
def _cancel(self) -> None:
from bauiv1lib.profile.browser import ProfileBrowserWindow
# no-op if our underlying widget is dead or on its way out.
if not self._root_widget or self._root_widget.transitioning_out:
return
bui.containerwidget(edit=self._root_widget, transition='out_right')
assert bui.app.classic is not None
bui.app.ui_v1.set_main_menu_window(
@ -660,7 +670,8 @@ class EditProfileWindow(bui.Window):
'in_left',
selected_profile=self._existing_profile,
in_main_menu=self._in_main_menu,
).get_root_widget()
).get_root_widget(),
from_window=self._root_widget,
)
def _set_color(self, color: tuple[float, float, float]) -> None:
@ -759,6 +770,10 @@ class EditProfileWindow(bui.Window):
"""Save has been selected."""
from bauiv1lib.profile.browser import ProfileBrowserWindow
# no-op if our underlying widget is dead or on its way out.
if not self._root_widget or self._root_widget.transitioning_out:
return False
plus = bui.app.plus
assert plus is not None
@ -808,6 +823,7 @@ class EditProfileWindow(bui.Window):
'in_left',
selected_profile=new_name,
in_main_menu=self._in_main_menu,
).get_root_widget()
).get_root_widget(),
from_window=self._root_widget,
)
return True

View File

@ -142,13 +142,18 @@ class PromoCodeWindow(bui.Window):
# pylint: disable=cyclic-import
from bauiv1lib.settings.advanced import AdvancedSettingsWindow
# no-op if our underlying widget is dead or on its way out.
if not self._root_widget or self._root_widget.transitioning_out:
return
bui.containerwidget(
edit=self._root_widget, transition=self._transition_out
)
if not self._modal:
assert bui.app.classic is not None
bui.app.ui_v1.set_main_menu_window(
AdvancedSettingsWindow(transition='in_left').get_root_widget()
AdvancedSettingsWindow(transition='in_left').get_root_widget(),
from_window=self._root_widget,
)
def _activate_enter_button(self) -> None:
@ -158,6 +163,10 @@ class PromoCodeWindow(bui.Window):
# pylint: disable=cyclic-import
from bauiv1lib.settings.advanced import AdvancedSettingsWindow
# 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
plus = bui.app.plus
assert plus is not None
@ -167,7 +176,8 @@ class PromoCodeWindow(bui.Window):
if not self._modal:
assert bui.app.classic is not None
bui.app.ui_v1.set_main_menu_window(
AdvancedSettingsWindow(transition='in_left').get_root_widget()
AdvancedSettingsWindow(transition='in_left').get_root_widget(),
from_window=self._root_widget,
)
plus.add_v1_account_transaction(
{

View File

@ -682,11 +682,16 @@ class AdvancedSettingsWindow(bui.Window):
def _on_vr_test_press(self) -> None:
from bauiv1lib.settings.vrtesting import VRTestingWindow
# 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._save_state()
bui.containerwidget(edit=self._root_widget, transition='out_left')
assert bui.app.classic is not None
bui.app.ui_v1.set_main_menu_window(
VRTestingWindow(transition='in_right').get_root_widget()
VRTestingWindow(transition='in_right').get_root_widget(),
from_window=self._root_widget,
)
def _on_net_test_press(self) -> None:
@ -694,6 +699,10 @@ class AdvancedSettingsWindow(bui.Window):
assert plus is not None
from bauiv1lib.settings.nettesting import NetTestingWindow
# 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
# Net-testing requires a signed in v1 account.
if plus.get_v1_account_state() != 'signed_in':
bui.screenmessage(
@ -706,7 +715,8 @@ class AdvancedSettingsWindow(bui.Window):
bui.containerwidget(edit=self._root_widget, transition='out_left')
assert bui.app.classic is not None
bui.app.ui_v1.set_main_menu_window(
NetTestingWindow(transition='in_right').get_root_widget()
NetTestingWindow(transition='in_right').get_root_widget(),
from_window=self._root_widget,
)
def _on_friend_promo_code_press(self) -> None:
@ -724,17 +734,26 @@ class AdvancedSettingsWindow(bui.Window):
def _on_plugins_button_press(self) -> None:
from bauiv1lib.settings.plugins import PluginWindow
# 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._save_state()
bui.containerwidget(edit=self._root_widget, transition='out_left')
assert bui.app.classic is not None
bui.app.ui_v1.set_main_menu_window(
PluginWindow(origin_widget=self._plugins_button).get_root_widget()
PluginWindow(origin_widget=self._plugins_button).get_root_widget(),
from_window=self._root_widget,
)
def _on_promo_code_press(self) -> None:
from bauiv1lib.promocode import PromoCodeWindow
from bauiv1lib.account import show_sign_in_prompt
# 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
plus = bui.app.plus
assert plus is not None
@ -742,23 +761,30 @@ class AdvancedSettingsWindow(bui.Window):
if plus.get_v1_account_state() != 'signed_in':
show_sign_in_prompt()
return
self._save_state()
bui.containerwidget(edit=self._root_widget, transition='out_left')
assert bui.app.classic is not None
bui.app.ui_v1.set_main_menu_window(
PromoCodeWindow(
origin_widget=self._promo_code_button
).get_root_widget()
).get_root_widget(),
from_window=self._root_widget,
)
def _on_benchmark_press(self) -> None:
from bauiv1lib.debug import DebugWindow
# 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._save_state()
bui.containerwidget(edit=self._root_widget, transition='out_left')
assert bui.app.classic is not None
bui.app.ui_v1.set_main_menu_window(
DebugWindow(transition='in_right').get_root_widget()
DebugWindow(transition='in_right').get_root_widget(),
from_window=self._root_widget,
)
def _save_state(self) -> None:
@ -908,11 +934,16 @@ class AdvancedSettingsWindow(bui.Window):
def _do_back(self) -> None:
from bauiv1lib.settings.allsettings import AllSettingsWindow
# 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._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_menu_window(
AllSettingsWindow(transition='in_left').get_root_widget()
AllSettingsWindow(transition='in_left').get_root_widget(),
from_window=self._root_widget,
)

View File

@ -235,65 +235,90 @@ class AllSettingsWindow(bui.Window):
# pylint: disable=cyclic-import
from bauiv1lib.mainmenu import MainMenuWindow
# 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._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_menu_window(
MainMenuWindow(transition='in_left').get_root_widget()
MainMenuWindow(transition='in_left').get_root_widget(),
from_window=self._root_widget,
)
def _do_controllers(self) -> None:
# pylint: disable=cyclic-import
from bauiv1lib.settings.controls import ControlsSettingsWindow
# 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._save_state()
bui.containerwidget(edit=self._root_widget, transition='out_left')
assert bui.app.classic is not None
bui.app.ui_v1.set_main_menu_window(
ControlsSettingsWindow(
origin_widget=self._controllers_button
).get_root_widget()
).get_root_widget(),
from_window=self._root_widget,
)
def _do_graphics(self) -> None:
# pylint: disable=cyclic-import
from bauiv1lib.settings.graphics import GraphicsSettingsWindow
# 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._save_state()
bui.containerwidget(edit=self._root_widget, transition='out_left')
assert bui.app.classic is not None
bui.app.ui_v1.set_main_menu_window(
GraphicsSettingsWindow(
origin_widget=self._graphics_button
).get_root_widget()
).get_root_widget(),
from_window=self._root_widget,
)
def _do_audio(self) -> None:
# pylint: disable=cyclic-import
from bauiv1lib.settings.audio import AudioSettingsWindow
# 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._save_state()
bui.containerwidget(edit=self._root_widget, transition='out_left')
assert bui.app.classic is not None
bui.app.ui_v1.set_main_menu_window(
AudioSettingsWindow(
origin_widget=self._audio_button
).get_root_widget()
).get_root_widget(),
from_window=self._root_widget,
)
def _do_advanced(self) -> None:
# pylint: disable=cyclic-import
from bauiv1lib.settings.advanced import AdvancedSettingsWindow
# 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._save_state()
bui.containerwidget(edit=self._root_widget, transition='out_left')
assert bui.app.classic is not None
bui.app.ui_v1.set_main_menu_window(
AdvancedSettingsWindow(
origin_widget=self._advanced_button
).get_root_widget()
).get_root_widget(),
from_window=self._root_widget,
)
def _save_state(self) -> None:

View File

@ -237,6 +237,10 @@ class AudioSettingsWindow(bui.Window):
# pylint: disable=cyclic-import
from bauiv1lib.soundtrack import browser as stb
# 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
# We require disk access for soundtracks;
# if we don't have it, request it.
if not bui.have_permission(bui.Permission.STORAGE):
@ -256,13 +260,18 @@ class AudioSettingsWindow(bui.Window):
bui.app.ui_v1.set_main_menu_window(
stb.SoundtrackBrowserWindow(
origin_widget=self._soundtrack_button
).get_root_widget()
).get_root_widget(),
from_window=self._root_widget,
)
def _back(self) -> None:
# pylint: disable=cyclic-import
from bauiv1lib.settings import allsettings
# 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._save_state()
bui.containerwidget(
edit=self._root_widget, transition=self._transition_out
@ -271,7 +280,8 @@ class AudioSettingsWindow(bui.Window):
bui.app.ui_v1.set_main_menu_window(
allsettings.AllSettingsWindow(
transition='in_left'
).get_root_widget()
).get_root_widget(),
from_window=self._root_widget,
)
def _save_state(self) -> None:

View File

@ -367,59 +367,84 @@ class ControlsSettingsWindow(bui.Window):
# pylint: disable=cyclic-import
from bauiv1lib.settings.keyboard import ConfigKeyboardWindow
# 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._save_state()
bui.containerwidget(edit=self._root_widget, transition='out_left')
assert bui.app.classic is not None
bui.app.ui_v1.set_main_menu_window(
ConfigKeyboardWindow(
bs.getinputdevice('Keyboard', '#1')
).get_root_widget()
).get_root_widget(),
from_window=self._root_widget,
)
def _config_keyboard2(self) -> None:
# pylint: disable=cyclic-import
from bauiv1lib.settings.keyboard import ConfigKeyboardWindow
# 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._save_state()
bui.containerwidget(edit=self._root_widget, transition='out_left')
assert bui.app.classic is not None
bui.app.ui_v1.set_main_menu_window(
ConfigKeyboardWindow(
bs.getinputdevice('Keyboard', '#2')
).get_root_widget()
).get_root_widget(),
from_window=self._root_widget,
)
def _do_mobile_devices(self) -> None:
# pylint: disable=cyclic-import
from bauiv1lib.settings.remoteapp import RemoteAppSettingsWindow
# 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._save_state()
bui.containerwidget(edit=self._root_widget, transition='out_left')
assert bui.app.classic is not None
bui.app.ui_v1.set_main_menu_window(
RemoteAppSettingsWindow().get_root_widget()
RemoteAppSettingsWindow().get_root_widget(),
from_window=self._root_widget,
)
def _do_gamepads(self) -> None:
# pylint: disable=cyclic-import
from bauiv1lib.settings.gamepadselect import GamepadSelectWindow
# 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._save_state()
bui.containerwidget(edit=self._root_widget, transition='out_left')
assert bui.app.classic is not None
bui.app.ui_v1.set_main_menu_window(
GamepadSelectWindow().get_root_widget()
GamepadSelectWindow().get_root_widget(),
from_window=self._root_widget,
)
def _do_touchscreen(self) -> None:
# pylint: disable=cyclic-import
from bauiv1lib.settings.touchscreen import TouchscreenSettingsWindow
# 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._save_state()
bui.containerwidget(edit=self._root_widget, transition='out_left')
assert bui.app.classic is not None
bui.app.ui_v1.set_main_menu_window(
TouchscreenSettingsWindow().get_root_widget()
TouchscreenSettingsWindow().get_root_widget(),
from_window=self._root_widget,
)
def _save_state(self) -> None:
@ -466,11 +491,16 @@ class ControlsSettingsWindow(bui.Window):
# pylint: disable=cyclic-import
from bauiv1lib.settings.allsettings import AllSettingsWindow
# 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._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_menu_window(
AllSettingsWindow(transition='in_left').get_root_widget()
AllSettingsWindow(transition='in_left').get_root_widget(),
from_window=self._root_widget,
)

View File

@ -795,19 +795,28 @@ class GamepadSettingsWindow(bui.Window):
def _cancel(self) -> None:
from bauiv1lib.settings.controls import ControlsSettingsWindow
# no-op if our underlying widget is dead or on its way out.
if not self._root_widget or self._root_widget.transitioning_out:
return
bui.containerwidget(
edit=self._root_widget, transition=self._transition_out
)
if self._is_main_menu:
assert bui.app.classic is not None
bui.app.ui_v1.set_main_menu_window(
ControlsSettingsWindow(transition='in_left').get_root_widget()
ControlsSettingsWindow(transition='in_left').get_root_widget(),
from_window=self._root_widget,
)
def _save(self) -> None:
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:
return
bui.containerwidget(
edit=self._root_widget, transition=self._transition_out
)
@ -852,7 +861,8 @@ class GamepadSettingsWindow(bui.Window):
assert bui.app.classic is not None
bui.app.ui_v1.set_main_menu_window(
ControlsSettingsWindow(transition='in_left').get_root_widget()
ControlsSettingsWindow(transition='in_left').get_root_widget(),
from_window=self._root_widget,
)

View File

@ -33,7 +33,8 @@ def gamepad_configure_callback(event: dict[str, Any]) -> None:
assert isinstance(device, bs.InputDevice)
if device.allows_configuring:
bui.app.ui_v1.set_main_menu_window(
gamepad.GamepadSettingsWindow(device).get_root_widget()
gamepad.GamepadSettingsWindow(device).get_root_widget(),
from_window=None,
)
else:
width = 700
@ -51,7 +52,7 @@ def gamepad_configure_callback(event: dict[str, Any]) -> None:
size=(width, height),
transition='in_right',
)
bui.app.ui_v1.set_main_menu_window(dlg)
bui.app.ui_v1.set_main_menu_window(dlg, from_window=None)
if device.allows_configuring_in_system_settings:
msg = bui.Lstr(
@ -81,12 +82,17 @@ def gamepad_configure_callback(event: dict[str, Any]) -> None:
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_menu_window(
controls.ControlsSettingsWindow(
transition='in_left'
).get_root_widget()
).get_root_widget(),
from_window=dlg,
)
bui.buttonwidget(
@ -191,11 +197,16 @@ class GamepadSelectWindow(bui.Window):
def _back(self) -> None:
from bauiv1lib.settings import controls
# 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
bs.release_gamepad_input()
bui.containerwidget(edit=self._root_widget, transition='out_right')
assert bui.app.classic is not None
bui.app.ui_v1.set_main_menu_window(
controls.ControlsSettingsWindow(
transition='in_left'
).get_root_widget()
).get_root_widget(),
from_window=self._root_widget,
)

View File

@ -436,6 +436,10 @@ class GraphicsSettingsWindow(bui.Window):
def _back(self) -> None:
from bauiv1lib.settings import allsettings
# 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
# Applying max-fps takes a few moments. Apply if it hasn't been
# yet.
self._apply_max_fps()
@ -447,7 +451,8 @@ class GraphicsSettingsWindow(bui.Window):
bui.app.ui_v1.set_main_menu_window(
allsettings.AllSettingsWindow(
transition='in_left'
).get_root_widget()
).get_root_widget(),
from_window=self._root_widget,
)
def _set_quality(self, quality: str) -> None:

View File

@ -271,15 +271,24 @@ class ConfigKeyboardWindow(bui.Window):
def _cancel(self) -> None:
from bauiv1lib.settings.controls import ControlsSettingsWindow
# no-op if our underlying widget is dead or on its way out.
if not self._root_widget or self._root_widget.transitioning_out:
return
bui.containerwidget(edit=self._root_widget, transition='out_right')
assert bui.app.classic is not None
bui.app.ui_v1.set_main_menu_window(
ControlsSettingsWindow(transition='in_left').get_root_widget()
ControlsSettingsWindow(transition='in_left').get_root_widget(),
from_window=self._root_widget,
)
def _save(self) -> None:
from bauiv1lib.settings.controls import ControlsSettingsWindow
# 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
assert bui.app.classic is not None
bui.containerwidget(edit=self._root_widget, transition='out_right')
bui.getsound('gunCocking').play()
@ -314,7 +323,8 @@ class ConfigKeyboardWindow(bui.Window):
)
bui.app.config.apply_and_commit()
bui.app.ui_v1.set_main_menu_window(
ControlsSettingsWindow(transition='in_left').get_root_widget()
ControlsSettingsWindow(transition='in_left').get_root_widget(),
from_window=self._root_widget,
)

View File

@ -135,8 +135,14 @@ class NetTestingWindow(bui.Window):
def _show_val_testing(self) -> None:
assert bui.app.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:
return
bui.app.ui_v1.set_main_menu_window(
NetValTestingWindow().get_root_widget()
NetValTestingWindow().get_root_widget(),
from_window=self._root_widget,
)
bui.containerwidget(edit=self._root_widget, transition='out_left')
@ -144,9 +150,14 @@ class NetTestingWindow(bui.Window):
# pylint: disable=cyclic-import
from bauiv1lib.settings.advanced import AdvancedSettingsWindow
# 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
assert bui.app.classic is not None
bui.app.ui_v1.set_main_menu_window(
AdvancedSettingsWindow(transition='in_left').get_root_widget()
AdvancedSettingsWindow(transition='in_left').get_root_widget(),
from_window=self._root_widget,
)
bui.containerwidget(edit=self._root_widget, transition='out_right')

View File

@ -212,11 +212,16 @@ class PluginWindow(bui.Window):
# pylint: disable=cyclic-import
from bauiv1lib.settings.pluginsettings import PluginSettingsWindow
# 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._save_state()
bui.containerwidget(edit=self._root_widget, transition='out_left')
assert bui.app.classic is not None
bui.app.ui_v1.set_main_menu_window(
PluginSettingsWindow(transition='in_right').get_root_widget()
PluginSettingsWindow(transition='in_right').get_root_widget(),
from_window=self._root_widget,
)
def _show_category_options(self) -> None:
@ -412,11 +417,16 @@ class PluginWindow(bui.Window):
# pylint: disable=cyclic-import
from bauiv1lib.settings.advanced import AdvancedSettingsWindow
# 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._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_menu_window(
AdvancedSettingsWindow(transition='in_left').get_root_widget()
AdvancedSettingsWindow(transition='in_left').get_root_widget(),
from_window=self._root_widget,
)

View File

@ -161,10 +161,15 @@ class PluginSettingsWindow(bui.Window):
# pylint: disable=cyclic-import
from bauiv1lib.settings.plugins import PluginWindow
# no-op if our underlying widget is dead or on its way out.
if not self._root_widget or self._root_widget.transitioning_out:
return
bui.containerwidget(
edit=self._root_widget, transition=self._transition_out
)
assert bui.app.classic is not None
bui.app.ui_v1.set_main_menu_window(
PluginWindow(transition='in_left').get_root_widget()
PluginWindow(transition='in_left').get_root_widget(),
from_window=self._root_widget,
)

View File

@ -138,10 +138,15 @@ class RemoteAppSettingsWindow(bui.Window):
def _back(self) -> None:
from bauiv1lib.settings import controls
# no-op if our underlying widget is dead or on its way out.
if not self._root_widget or self._root_widget.transitioning_out:
return
bui.containerwidget(edit=self._root_widget, transition='out_right')
assert bui.app.classic is not None
bui.app.ui_v1.set_main_menu_window(
controls.ControlsSettingsWindow(
transition='in_left'
).get_root_widget()
).get_root_widget(),
from_window=self._root_widget,
)

View File

@ -217,6 +217,10 @@ class TestingWindow(bui.Window):
# pylint: disable=cyclic-import
from bauiv1lib.settings.advanced import AdvancedSettingsWindow
# no-op if our underlying widget is dead or on its way out.
if not self._root_widget or self._root_widget.transitioning_out:
return
bui.containerwidget(edit=self._root_widget, transition='out_right')
backwin = (
self._back_call()
@ -224,4 +228,6 @@ class TestingWindow(bui.Window):
else AdvancedSettingsWindow(transition='in_left')
)
assert bui.app.classic is not None
bui.app.ui_v1.set_main_menu_window(backwin.get_root_widget())
bui.app.ui_v1.set_main_menu_window(
backwin.get_root_widget(), from_window=self._root_widget
)

View File

@ -276,11 +276,16 @@ class TouchscreenSettingsWindow(bui.Window):
def _back(self) -> None:
from bauiv1lib.settings import controls
# no-op if our underlying widget is dead or on its way out.
if not self._root_widget or self._root_widget.transitioning_out:
return
bui.containerwidget(edit=self._root_widget, transition='out_right')
assert bui.app.classic is not None
bui.app.ui_v1.set_main_menu_window(
controls.ControlsSettingsWindow(
transition='in_left'
).get_root_widget()
).get_root_widget(),
from_window=self._root_widget,
)
bs.set_touchscreen_editing(False)

View File

@ -394,13 +394,18 @@ class SoundtrackBrowserWindow(bui.Window):
# pylint: disable=cyclic-import
from bauiv1lib.settings import audio
# 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._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_menu_window(
audio.AudioSettingsWindow(transition='in_left').get_root_widget()
audio.AudioSettingsWindow(transition='in_left').get_root_widget(),
from_window=self._root_widget,
)
def _edit_soundtrack_with_sound(self) -> None:
@ -421,6 +426,10 @@ class SoundtrackBrowserWindow(bui.Window):
from bauiv1lib.purchase import PurchaseWindow
from bauiv1lib.soundtrack.edit import SoundtrackEditWindow
# 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 (
bui.app.classic is not None
and not bui.app.classic.accounts.have_pro_options()
@ -443,7 +452,8 @@ class SoundtrackBrowserWindow(bui.Window):
bui.app.ui_v1.set_main_menu_window(
SoundtrackEditWindow(
existing_soundtrack=self._selected_soundtrack
).get_root_widget()
).get_root_widget(),
from_window=self._root_widget,
)
def _get_soundtrack_display_name(self, soundtrack: str) -> bui.Lstr:

View File

@ -351,7 +351,8 @@ class SoundtrackEditWindow(bui.Window):
soundtrack[musictype] = entry
bui.app.ui_v1.set_main_menu_window(
cls(state, transition='in_left').get_root_widget()
cls(state, transition='in_left').get_root_widget(),
from_window=False, # Disable check here.
)
def _get_entry(
@ -359,6 +360,11 @@ class SoundtrackEditWindow(bui.Window):
) -> None:
assert bui.app.classic is not None
music = bui.app.classic.music
# 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 selection_target_name != '':
selection_target_name = "'" + selection_target_name + "'"
state = {
@ -375,7 +381,8 @@ class SoundtrackEditWindow(bui.Window):
entry,
selection_target_name,
)
.get_root_widget()
.get_root_widget(),
from_window=self._root_widget,
)
def _test(self, song_type: bs.MusicType) -> None:
@ -422,6 +429,10 @@ class SoundtrackEditWindow(bui.Window):
def _cancel(self) -> None:
from bauiv1lib.soundtrack import browser as stb
# 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
assert bui.app.classic is not None
music = bui.app.classic.music
@ -429,12 +440,17 @@ class SoundtrackEditWindow(bui.Window):
music.set_music_play_mode(bui.app.classic.MusicPlayMode.REGULAR)
bui.containerwidget(edit=self._root_widget, transition='out_right')
bui.app.ui_v1.set_main_menu_window(
stb.SoundtrackBrowserWindow(transition='in_left').get_root_widget()
stb.SoundtrackBrowserWindow(transition='in_left').get_root_widget(),
from_window=self._root_widget,
)
def _do_it(self) -> None:
from bauiv1lib.soundtrack import browser as stb
# 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
assert bui.app.classic is not None
music = bui.app.classic.music
cfg = bui.app.config
@ -483,7 +499,8 @@ class SoundtrackEditWindow(bui.Window):
)
bui.app.ui_v1.set_main_menu_window(
stb.SoundtrackBrowserWindow(transition='in_left').get_root_widget()
stb.SoundtrackBrowserWindow(transition='in_left').get_root_widget(),
from_window=self._root_widget,
)
def _do_it_with_sound(self) -> None:

View File

@ -166,6 +166,10 @@ class SoundtrackEntryTypeSelectWindow(bui.Window):
MacMusicAppPlaylistSelectWindow,
)
# no-op if our underlying widget is dead or on its way out.
if not self._root_widget or self._root_widget.transitioning_out:
return
bui.containerwidget(edit=self._root_widget, transition='out_left')
current_playlist_entry: str | None
@ -181,7 +185,8 @@ class SoundtrackEntryTypeSelectWindow(bui.Window):
bui.app.ui_v1.set_main_menu_window(
MacMusicAppPlaylistSelectWindow(
self._callback, current_playlist_entry, self._current_entry
).get_root_widget()
).get_root_widget(),
from_window=self._root_widget,
)
def _on_music_file_press(self) -> None:
@ -189,6 +194,10 @@ class SoundtrackEntryTypeSelectWindow(bui.Window):
from baclassic.osmusic import OSMusicPlayer
from bauiv1lib.fileselector import FileSelectorWindow
# no-op if our underlying widget is dead or on its way out.
if not self._root_widget or self._root_widget.transitioning_out:
return
bui.containerwidget(edit=self._root_widget, transition='out_left')
base_path = android_get_external_files_dir()
assert bui.app.classic is not None
@ -201,13 +210,18 @@ class SoundtrackEntryTypeSelectWindow(bui.Window):
OSMusicPlayer.get_valid_music_file_extensions()
),
allow_folders=False,
).get_root_widget()
).get_root_widget(),
from_window=self._root_widget,
)
def _on_music_folder_press(self) -> None:
from bauiv1lib.fileselector import FileSelectorWindow
from babase import android_get_external_files_dir
# no-op if our underlying widget is dead or on its way out.
if not self._root_widget or self._root_widget.transitioning_out:
return
bui.containerwidget(edit=self._root_widget, transition='out_left')
base_path = android_get_external_files_dir()
assert bui.app.classic is not None
@ -218,7 +232,8 @@ class SoundtrackEntryTypeSelectWindow(bui.Window):
show_base_path=False,
valid_file_extensions=[],
allow_folders=True,
).get_root_widget()
).get_root_widget(),
from_window=self._root_widget,
)
def _music_file_selector_cb(self, result: str | None) -> None:

View File

@ -1329,6 +1329,10 @@ class StoreBrowserWindow(bui.Window):
from bauiv1lib.account import show_sign_in_prompt
from bauiv1lib.getcurrency import GetCurrencyWindow
# 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
plus = bui.app.plus
assert plus is not None
@ -1343,13 +1347,19 @@ class StoreBrowserWindow(bui.Window):
).get_root_widget()
if not self._modal:
assert bui.app.classic is not None
bui.app.ui_v1.set_main_menu_window(window)
bui.app.ui_v1.set_main_menu_window(
window, from_window=self._root_widget
)
def _back(self) -> None:
# pylint: disable=cyclic-import
from bauiv1lib.coop.browser import CoopBrowserWindow
from bauiv1lib.mainmenu import MainMenuWindow
# 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._save_state()
bui.containerwidget(
edit=self._root_widget, transition=self._transition_out
@ -1358,11 +1368,13 @@ class StoreBrowserWindow(bui.Window):
assert bui.app.classic is not None
if self._back_location == 'CoopBrowserWindow':
bui.app.ui_v1.set_main_menu_window(
CoopBrowserWindow(transition='in_left').get_root_widget()
CoopBrowserWindow(transition='in_left').get_root_widget(),
from_window=self._root_widget,
)
else:
bui.app.ui_v1.set_main_menu_window(
MainMenuWindow(transition='in_left').get_root_widget()
MainMenuWindow(transition='in_left').get_root_widget(),
from_window=self._root_widget,
)
if self._on_close_call is not None:
self._on_close_call()

View File

@ -663,11 +663,16 @@ class WatchWindow(bui.Window):
def _back(self) -> None:
from bauiv1lib.mainmenu import MainMenuWindow
# 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._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_menu_window(
MainMenuWindow(transition='in_left').get_root_widget()
MainMenuWindow(transition='in_left').get_root_widget(),
from_window=self._root_widget,
)

View File

@ -615,7 +615,7 @@ void Graphics::FadeScreen(bool to, millisecs_t time, PyObject* endcall) {
Log(LogLevel::kWarning,
"2 fades overlapping; running first fade-end-call early.");
}
fade_end_call_->ScheduleOnce();
fade_end_call_->Schedule();
fade_end_call_.Clear();
}
set_fade_start_on_next_draw_ = true;
@ -1021,7 +1021,7 @@ void Graphics::DrawFades(FrameDef* frame_def) {
} else {
fade_ = 0;
if (!was_done && fade_end_call_.Exists()) {
fade_end_call_->ScheduleOnce();
fade_end_call_->Schedule();
fade_end_call_.Clear();
}
}

View File

@ -279,7 +279,7 @@ static auto PyPushCall(PyObject* self, PyObject* args, PyObject* keywds)
if (!g_base->InLogicThread()) {
throw Exception("You must use from_other_thread mode.");
}
Object::New<PythonContextCall>(call_obj)->ScheduleOnce();
Object::New<PythonContextCall>(call_obj)->Schedule();
}
Py_RETURN_NONE;
BA_PYTHON_CATCH;

View File

@ -125,83 +125,45 @@ void PythonContextCall::Schedule() {
Object::Ref<PythonContextCall> ref(this);
assert(base::g_base);
schedule_count_++;
base::g_base->logic->event_loop()->PushCall([ref] {
assert(ref.Exists());
ref->schedule_count_--;
assert(ref->schedule_count_ >= 0);
ref->Run();
});
}
void PythonContextCall::ScheduleOnce() {
if (schedule_count_ > 0) {
return;
}
Schedule();
}
void PythonContextCall::Schedule(const PythonRef& args) {
// Since we're mucking with Object::Refs, need to limit to logic thread.
BA_PRECONDITION(g_base->InLogicThread());
Object::Ref<PythonContextCall> ref(this);
assert(base::g_base);
schedule_count_++;
base::g_base->logic->event_loop()->PushCall([ref, args] {
assert(ref.Exists());
ref->schedule_count_--;
assert(ref->schedule_count_ >= 0);
ref->Run(args);
});
}
void PythonContextCall::ScheduleOnce(const PythonRef& args) {
if (schedule_count_ > 0) {
return;
}
Schedule(args);
}
void PythonContextCall::ScheduleWeak() {
// Since we're mucking with Object::WeakRefs, need to limit to logic thread.
BA_PRECONDITION(g_base->InLogicThread());
Object::WeakRef<PythonContextCall> ref(this);
assert(base::g_base);
schedule_count_++;
base::g_base->logic->event_loop()->PushCall([ref] {
if (auto* call = ref.Get()) {
call->schedule_count_--;
assert(call->schedule_count_ >= 0);
call->Run();
}
});
}
void PythonContextCall::ScheduleWeakOnce() {
if (schedule_count_ > 0) {
return;
}
ScheduleWeak();
}
void PythonContextCall::ScheduleWeak(const PythonRef& args) {
// Since we're mucking with Object::WeakRefs, need to limit to logic thread.
BA_PRECONDITION(g_base->InLogicThread());
Object::WeakRef<PythonContextCall> ref(this);
assert(base::g_base);
schedule_count_++;
base::g_base->logic->event_loop()->PushCall([ref, args] {
if (auto* call = ref.Get()) {
call->schedule_count_--;
assert(call->schedule_count_ >= 0);
call->Run(args);
}
});
}
void PythonContextCall::ScheduleWeakOnce(const PythonRef& args) {
if (schedule_count_ > 0) {
return;
}
ScheduleWeak(args);
}
} // namespace ballistica::base

View File

@ -42,56 +42,26 @@ class PythonContextCall : public Object {
/// context_ref-call is guaranteed to exist until run.
void Schedule();
/// Schedule only if this instance is not already scheduled. Generally a
/// good idea unless you know you need multiple runs scheduled. Avoids
/// problems such as UIs expecting to be activated only once getting
/// activated twice due to two simultenous key presses.
void ScheduleOnce();
/// Run in an upcoming cycle of the logic thread with provided args. Must
/// be called from the logic thread. This form creates a strong-reference
/// so the context_ref-call is guaranteed to exist until run.
void Schedule(const PythonRef& args);
/// Schedule only if this instance is not already scheduled. Generally a
/// good idea unless you know you need multiple runs scheduled. Avoids
/// problems such as UIs expecting to be activated only once getting
/// activated twice due to two simultenous key presses.
void ScheduleOnce(const PythonRef& args);
/// Run in an upcoming cycle of the logic thread. Must be called from the
/// logic thread. This form creates a weak-reference and is a no-op if the
/// context_ref-call is destroyed before its scheduled run.
void ScheduleWeak();
/// Schedule weakly only if this instance is not already scheduled.
/// Generally a good idea unless you know you need multiple runs
/// scheduled. Avoids problems such as UIs expecting to be activated only
/// once getting activated twice due to two simultenous key presses.
void ScheduleWeakOnce();
/// Run in an upcoming cycle of the logic thread with provided args. Must
/// be called from the logic thread. This form creates a weak-reference
/// and is a no-op if the context_ref-call is destroyed before its
/// scheduled run.
void ScheduleWeak(const PythonRef& args);
/// Schedule weakly only if this instance is not already scheduled.
/// Generally a good idea unless you know you need multiple runs
/// scheduled. Avoids problems such as UIs expecting to be activated only
/// once getting activated twice due to two simultenous key presses.
void ScheduleWeakOnce(const PythonRef& args);
auto IsScheduled() const {
assert(g_base->InLogicThread());
return schedule_count_ > 0;
}
private:
void GetTrace(); // we try to grab basic trace info
int line_{};
int schedule_count_{};
bool dead_{};
std::string file_loc_;
PythonRef object_;

View File

@ -208,7 +208,7 @@ auto PythonClassSessionPlayer::tp_getattro(PythonClassSessionPlayer* self,
PyObject* attr) -> PyObject* {
BA_PYTHON_TRY;
assert(g_base->InLogicThread());
BA_PRECONDITION(g_base->InLogicThread());
// Assuming this will always be a str?
assert(PyUnicode_Check(attr));
@ -327,6 +327,9 @@ auto PythonClassSessionPlayer::tp_setattro(PythonClassSessionPlayer* self,
PyObject* attr, PyObject* val)
-> int {
BA_PYTHON_TRY;
BA_PRECONDITION(g_base->InLogicThread());
// Assuming this will always be a str?
assert(PyUnicode_Check(attr));
const char* s = PyUnicode_AsUTF8(attr);

View File

@ -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 = 21636;
const int kEngineBuildNumber = 21639;
const char* kEngineVersion = "1.7.30";
const int kEngineApiVersion = 8;

View File

@ -17,6 +17,12 @@ auto PythonClassWidget::nb_bool(PythonClassWidget* self) -> int {
PyNumberMethods PythonClassWidget::as_number_;
// Attrs we expose through our custom getattr/setattr.
#define ATTR_TRANSITIONING_OUT "transitioning_out"
// The set we expose via dir().
static const char* extra_dir_attrs[] = {ATTR_TRANSITIONING_OUT, nullptr};
auto PythonClassWidget::type_name() -> const char* { return "Widget"; }
void PythonClassWidget::SetupType(PyTypeObject* cls) {
@ -24,6 +30,9 @@ void PythonClassWidget::SetupType(PyTypeObject* cls) {
// Fully qualified type path we will be exposed as:
cls->tp_name = "bauiv1.Widget";
cls->tp_basicsize = sizeof(PythonClassWidget);
// clang-format off
cls->tp_doc =
"Internal type for low level UI elements; buttons, windows, etc.\n"
"\n"
@ -31,11 +40,22 @@ void PythonClassWidget::SetupType(PyTypeObject* cls) {
"\n"
"This class represents a weak reference to a widget object\n"
"in the internal C++ layer. Currently, functions such as\n"
"babase.buttonwidget() must be used to instantiate or edit these.";
"babase.buttonwidget() must be used to instantiate or edit these.\n"
"Attributes:\n"
" " ATTR_TRANSITIONING_OUT " (bool):\n"
" Whether this widget is in the process of dying (read only).\n"
"\n"
" It can be useful to check this on a window's root widget to\n"
" prevent multiple window actions from firing simultaneously,\n"
" potentially leaving the UI in a broken state.\n";
// clang-format on
cls->tp_new = tp_new;
cls->tp_dealloc = (destructor)tp_dealloc;
cls->tp_repr = (reprfunc)tp_repr;
cls->tp_methods = tp_methods;
cls->tp_getattro = (getattrofunc)tp_getattro;
// we provide number methods only for bool functionality
memset(&as_number_, 0, sizeof(as_number_));
@ -44,7 +64,7 @@ void PythonClassWidget::SetupType(PyTypeObject* cls) {
}
auto PythonClassWidget::Create(Widget* widget) -> PyObject* {
// Make sure we only have one python ref per widget.
// Make sure we only have one Python ref per Widget.
if (widget) {
assert(!widget->has_py_ref());
}
@ -62,10 +82,56 @@ auto PythonClassWidget::Create(Widget* widget) -> PyObject* {
auto PythonClassWidget::GetWidget() const -> Widget* {
Widget* w = widget_->Get();
if (!w) throw Exception("Invalid widget");
if (!w) {
throw Exception("Invalid Widget", PyExcType::kReference);
}
return w;
}
auto PythonClassWidget::tp_getattro(PythonClassWidget* self, PyObject* attr)
-> PyObject* {
BA_PYTHON_TRY;
BA_PRECONDITION(g_base->InLogicThread());
// Assuming this will always be a str?
assert(PyUnicode_Check(attr));
const char* s = PyUnicode_AsUTF8(attr);
if (!strcmp(s, ATTR_TRANSITIONING_OUT)) {
Widget* w = self->widget_->Get();
if (!w) {
throw Exception("Invalid Widget", PyExcType::kReference);
}
if (w->IsTransitioningOut()) {
Py_RETURN_TRUE;
}
Py_RETURN_FALSE;
}
// Fall back to generic behavior.
PyObject* val;
val = PyObject_GenericGetAttr(reinterpret_cast<PyObject*>(self), attr);
return val;
BA_PYTHON_CATCH;
}
auto PythonClassWidget::tp_setattro(PythonClassWidget* self, PyObject* attr,
PyObject* val) -> int {
BA_PYTHON_TRY;
BA_PRECONDITION(g_base->InLogicThread());
// Assuming this will always be a str?
assert(PyUnicode_Check(attr));
const char* s = PyUnicode_AsUTF8(attr);
throw Exception("Attr '" + std::string(PyUnicode_AsUTF8(attr))
+ "' is not settable on SessionPlayer objects.",
PyExcType::kAttribute);
BA_PYTHON_INT_CATCH;
}
auto PythonClassWidget::tp_repr(PythonClassWidget* self) -> PyObject* {
BA_PYTHON_TRY;
Widget* w = self->widget_->Get();
@ -96,8 +162,8 @@ auto PythonClassWidget::tp_new(PyTypeObject* type, PyObject* args,
void PythonClassWidget::tp_dealloc(PythonClassWidget* self) {
BA_PYTHON_TRY;
// these have to be destructed in the logic thread - send them along to it if
// need be
// these have to be destructed in the logic thread - send them along to it
// if need be
if (!g_base->InLogicThread()) {
Object::WeakRef<Widget>* w = self->widget_;
g_base->logic->event_loop()->PushCall([w] { delete w; });

View File

@ -26,6 +26,9 @@ class PythonClassWidget : public PythonClass {
static auto tp_new(PyTypeObject* type, PyObject* args, PyObject* keywds)
-> PyObject*;
static void tp_dealloc(PythonClassWidget* self);
static auto tp_getattro(PythonClassWidget* self, PyObject* attr) -> PyObject*;
static auto tp_setattro(PythonClassWidget* self, PyObject* attr,
PyObject* val) -> int;
static auto Exists(PythonClassWidget* self) -> PyObject*;
static auto GetWidgetType(PythonClassWidget* self) -> PyObject*;
static auto Activate(PythonClassWidget* self) -> PyObject*;

View File

@ -1219,17 +1219,17 @@ static auto PyContainerWidget(PyObject* self, PyObject* args, PyObject* keywds)
if (transition_obj != Py_None) {
std::string t = Python::GetPyString(transition_obj);
if (t == "in_left")
widget->SetTransition(ContainerWidget::TRANSITION_IN_LEFT);
widget->SetTransition(ContainerWidget::TransitionType::kInLeft);
else if (t == "in_right")
widget->SetTransition(ContainerWidget::TRANSITION_IN_RIGHT);
widget->SetTransition(ContainerWidget::TransitionType::kInRight);
else if (t == "out_left")
widget->SetTransition(ContainerWidget::TRANSITION_OUT_LEFT);
widget->SetTransition(ContainerWidget::TransitionType::kOutLeft);
else if (t == "out_right")
widget->SetTransition(ContainerWidget::TRANSITION_OUT_RIGHT);
widget->SetTransition(ContainerWidget::TransitionType::kOutRight);
else if (t == "in_scale")
widget->SetTransition(ContainerWidget::TRANSITION_IN_SCALE);
widget->SetTransition(ContainerWidget::TransitionType::kInScale);
else if (t == "out_scale")
widget->SetTransition(ContainerWidget::TRANSITION_OUT_SCALE);
widget->SetTransition(ContainerWidget::TransitionType::kOutScale);
}
if (cancel_button_obj != Py_None) {

View File

@ -115,7 +115,7 @@ void UIV1Python::InvokeStringEditor(PyObject* string_edit_adapter_instance) {
PythonRef::kSteal);
Object::New<base::PythonContextCall>(
objs().Get(ObjID::kOnScreenKeyboardClass))
->ScheduleOnce(args);
->Schedule(args);
}
void UIV1Python::LaunchStringEditOld(TextWidget* w) {
@ -131,7 +131,7 @@ void UIV1Python::LaunchStringEditOld(TextWidget* w) {
PythonRef::kSteal);
Object::New<base::PythonContextCall>(
objs().Get(ObjID::kOnScreenKeyboardClass))
->ScheduleOnce(args);
->Schedule(args);
}
void UIV1Python::InvokeQuitWindow(QuitType quit_type) {

View File

@ -39,6 +39,7 @@ class UIV1Python {
kQuitWindowCall,
kDeviceMenuPressCall,
kShowURLWindowCall,
kDoubleTransitionOutWarningCall,
kTextWidgetStringEditAdapterClass,
kLast // Sentinel; must be at end.
};

View File

@ -562,7 +562,7 @@ void ButtonWidget::DoActivate(bool is_repeat) {
if (auto* call = on_activate_call_.Get()) {
// Call this in the next cycle (don't want to risk mucking with UI from
// within a UI loop.)
call->ScheduleWeakOnce();
call->ScheduleWeak();
return;
}
}

View File

@ -247,7 +247,7 @@ void CheckBoxWidget::Activate() {
// Call this in the next cycle (don't want to risk mucking with UI from
// within a UI loop)
call->ScheduleWeakOnce(args);
call->ScheduleWeak(args);
}
}
@ -271,12 +271,13 @@ auto CheckBoxWidget::HandleMessage(const base::WidgetMessage& m) -> bool {
float x = m.fval1;
float y = m.fval2;
bool claimed = (m.fval3 > 0.0f);
if (claimed)
if (claimed) {
mouse_over_ = false;
else
} else {
mouse_over_ =
((x >= (-left_overlap)) && (x < (width_ + right_overlap))
&& (y >= (-bottom_overlap)) && (y < (height_ + top_overlap)));
}
return mouse_over_;
}
case base::WidgetMessage::Type::kMouseDown: {

View File

@ -11,6 +11,7 @@
#include "ballistica/shared/generic/utils.h"
#include "ballistica/shared/math/random.h"
#include "ballistica/shared/python/python.h"
#include "ballistica/ui_v1/python/ui_v1_python.h"
#include "ballistica/ui_v1/widget/button_widget.h"
#include "ballistica/ui_v1/widget/root_widget.h"
#include "ballistica/ui_v1/widget/stack_widget.h"
@ -347,7 +348,7 @@ auto ContainerWidget::HandleMessage(const base::WidgetMessage& m) -> bool {
// Call this in the next cycle (don't wanna risk mucking with UI from
// within a UI loop).
call->ScheduleWeakOnce();
call->ScheduleWeak();
} else {
OnCancelCustom();
}
@ -631,7 +632,7 @@ auto ContainerWidget::HandleMessage(const base::WidgetMessage& m) -> bool {
if (!claimed && on_outside_click_call_.Exists()) {
// Call this in the next cycle (don't wanna risk mucking with UI from
// within a UI loop).
on_outside_click_call_->ScheduleWeakOnce();
on_outside_click_call_->ScheduleWeak();
}
// Always claim if they want.
@ -793,7 +794,7 @@ void ContainerWidget::Draw(base::RenderPass* pass, bool draw_transparent) {
bg_dirty_ = true;
if (!draw_transparent) {
if (transition_type_ == TRANSITION_IN_SCALE) {
if (transition_type_ == TransitionType::kInScale) {
if (net_time - dynamics_update_time_millisecs_ > 1000)
dynamics_update_time_millisecs_ = net_time - 1000;
while (net_time - dynamics_update_time_millisecs_ > 5) {
@ -808,7 +809,7 @@ void ContainerWidget::Draw(base::RenderPass* pass, bool draw_transparent) {
transitioning_ = false;
}
}
} else if (transition_type_ == TRANSITION_OUT_SCALE) {
} else if (transition_type_ == TransitionType::kOutScale) {
if (net_time - dynamics_update_time_millisecs_ > 1000)
dynamics_update_time_millisecs_ = net_time - 1000;
while (net_time - dynamics_update_time_millisecs_ > 5) {
@ -908,8 +909,8 @@ void ContainerWidget::Draw(base::RenderPass* pass, bool draw_transparent) {
// If we're scaling in or out, update our transition offset
// (so we can zoom from a point somewhere else on screen).
if (transition_type_ == TRANSITION_IN_SCALE
|| transition_type_ == TRANSITION_OUT_SCALE) {
if (transition_type_ == TransitionType::kInScale
|| transition_type_ == TransitionType::kOutScale) {
// Add a fudge factor since our scale point isn't exactly in our center.
// :-(
float xdiff = scale_origin_stack_offset_x_ - stack_offset_x()
@ -1071,7 +1072,7 @@ void ContainerWidget::Activate() {
if (auto* call = on_activate_call_.Get()) {
// Call this in the next cycle (don't wanna risk mucking with UI from within
// a UI loop).
call->ScheduleWeakOnce();
call->ScheduleWeak();
}
}
@ -1156,8 +1157,23 @@ void ContainerWidget::SetStartButton(ButtonWidget* button) {
button->set_icon_type(ButtonWidget::IconType::kStart);
}
static auto _IsTransitionOut(ContainerWidget::TransitionType type) {
switch (type) {
case ContainerWidget::TransitionType::kUnset:
case ContainerWidget::TransitionType::kInLeft:
case ContainerWidget::TransitionType::kInRight:
case ContainerWidget::TransitionType::kInScale:
return false;
case ContainerWidget::TransitionType::kOutLeft:
case ContainerWidget::TransitionType::kOutRight:
case ContainerWidget::TransitionType::kOutScale:
return true;
}
}
void ContainerWidget::SetTransition(TransitionType t) {
BA_DEBUG_UI_READ_LOCK;
assert(g_base->InLogicThread());
bg_dirty_ = glow_dirty_ = true;
ContainerWidget* parent = parent_widget();
@ -1167,17 +1183,26 @@ void ContainerWidget::SetTransition(TransitionType t) {
parent->CheckLayout();
auto display_time_millisecs =
static_cast<millisecs_t>(g_base->logic->display_time() * 1000.0);
// Warn if setting out-transition twice. This likely means a window is
// switching to another window twice which can leave the UI broken.
if (_IsTransitionOut(transition_type_) && _IsTransitionOut(t)) {
g_ui_v1->python->objs()
.Get(UIV1Python::ObjID::kDoubleTransitionOutWarningCall)
.Call();
}
transition_type_ = t;
// Scale transitions are simpler.
if (t == TRANSITION_IN_SCALE) {
if (t == TransitionType::kInScale) {
transition_start_time_ = display_time_millisecs;
dynamics_update_time_millisecs_ = display_time_millisecs;
transitioning_ = true;
transitioning_out_ = false;
transition_scale_ = 0.0f;
d_transition_scale_ = 0.0f;
} else if (t == TRANSITION_OUT_SCALE) {
} else if (t == TransitionType::kOutScale) {
transition_start_time_ = display_time_millisecs;
dynamics_update_time_millisecs_ = display_time_millisecs;
transitioning_ = true;
@ -1195,7 +1220,7 @@ void ContainerWidget::SetTransition(TransitionType t) {
// In case we're mid-transition, this avoids hitches.
float y_offs = 2.0f;
if (t == TRANSITION_IN_LEFT) {
if (t == TransitionType::kInLeft) {
transition_start_time_ = display_time_millisecs;
transition_start_offset_ = screen_min_x - width_ - 100;
transition_offset_x_smoothed_ = transition_start_offset_;
@ -1204,7 +1229,7 @@ void ContainerWidget::SetTransition(TransitionType t) {
transitioning_ = true;
dynamics_update_time_millisecs_ = display_time_millisecs;
transitioning_out_ = false;
} else if (t == TRANSITION_IN_RIGHT) {
} else if (t == TransitionType::kInRight) {
transition_start_time_ = display_time_millisecs;
transition_start_offset_ = screen_max_x + 100;
transition_offset_x_smoothed_ = transition_start_offset_;
@ -1213,7 +1238,7 @@ void ContainerWidget::SetTransition(TransitionType t) {
transitioning_ = true;
dynamics_update_time_millisecs_ = display_time_millisecs;
transitioning_out_ = false;
} else if (t == TRANSITION_OUT_LEFT) {
} else if (t == TransitionType::kOutLeft) {
transition_start_time_ = display_time_millisecs;
transition_start_offset_ = transition_offset_x_;
transition_target_offset_ = -2.0f * (screen_max_x - screen_min_x);
@ -1223,7 +1248,7 @@ void ContainerWidget::SetTransition(TransitionType t) {
dynamics_update_time_millisecs_ = display_time_millisecs;
transitioning_out_ = true;
ignore_input_ = true;
} else if (t == TRANSITION_OUT_RIGHT) {
} else if (t == TransitionType::kOutRight) {
transition_start_time_ = display_time_millisecs;
transition_start_offset_ = transition_offset_x_;
transition_target_offset_ = 2.0f * (screen_max_x - screen_min_x);
@ -1279,8 +1304,9 @@ void ContainerWidget::DeleteWidget(Widget* w) {
assert(found);
// Special case: if we're the overlay stack and we've deleted our last widget,
// try to reselect whatever was last selected before the overlay stack.
// Special case: if we're the overlay stack and we've deleted our last
// widget, try to reselect whatever was last selected before the overlay
// stack.
if (is_overlay_window_stack_) {
if (widgets_.empty()) {
// Eww this logic should be in some sort of controller.
@ -1298,8 +1324,9 @@ void ContainerWidget::DeleteWidget(Widget* w) {
if ((**i).IsSelectable()) {
// A change on the main or overlay window stack changes the global
// selection (unless its on the main window stack and there's already
// something on the overlay stack) in all other cases we just shift our
// direct selected child (which may not affect the global selection).
// something on the overlay stack) in all other cases we just shift
// our direct selected child (which may not affect the global
// selection).
if (is_window_stack_
&& (is_overlay_window_stack_
|| !g_ui_v1->root_widget()
@ -1322,8 +1349,8 @@ void ContainerWidget::DeleteWidget(Widget* w) {
}
auto ContainerWidget::GetTopmostToolbarInfluencingWidget() -> Widget* {
// Look for the first window that is accepting input (filters out windows that
// are transitioning out) and also set to affect the toolbar state.
// Look for the first window that is accepting input (filters out windows
// that are transitioning out) and also set to affect the toolbar state.
for (auto w = widgets_.rbegin(); w != widgets_.rend(); ++w) {
if ((**w).IsAcceptingInput()
&& (**w).toolbar_visibility() != ToolbarVisibility::kInherit) {
@ -1438,9 +1465,8 @@ void ContainerWidget::SetSelected(bool s, SelectionCause cause) {
}
}
} else {
// if we're being deselected and we have a selected child, tell them they're
// deselected
// if (selected_widget_) {
// if we're being deselected and we have a selected child, tell them
// they're deselected if (selected_widget_) {
// }
}
}
@ -1583,8 +1609,8 @@ void ContainerWidget::SelectDownWidget() {
selected_widget_->GetCenter(&our_x, &our_y);
w = GetClosestDownWidget(our_x, our_y, selected_widget_);
if (!w) {
// If we found no viable children and we're under the main window stack,
// see if we should pass focus to a toolbar widget.
// If we found no viable children and we're under the main window
// stack, see if we should pass focus to a toolbar widget.
if (IsInMainStack()) {
float x = our_x;
float y = our_y;
@ -1712,7 +1738,8 @@ void ContainerWidget::SelectLeftWidget() {
float our_x, our_y;
selected_widget_->GetCenter(&our_x, &our_y);
w = GetClosestLeftWidget(our_x, our_y, selected_widget_);
// When we find no viable targets for an autoselect widget we do nothing.
// When we find no viable targets for an autoselect widget we do
// nothing.
if (!w) {
return;
}
@ -1843,8 +1870,8 @@ void ContainerWidget::SelectNextWidget() {
return;
} else if (selected_widget_
== nullptr) { // NOLINT(bugprone-branch-clone)
// We've got no selection and we've scanned the whole list to no avail,
// fail.
// We've got no selection and we've scanned the whole list to no
// avail, fail.
PrintExitListInstructions(old_last_prev_next_time);
return;
} else if (selection_loops()) {
@ -1997,4 +2024,8 @@ void ContainerWidget::OnLanguageChange() {
}
}
auto ContainerWidget::IsTransitioningOut() const -> bool {
return transitioning_out_;
}
} // namespace ballistica::ui_v1

View File

@ -14,20 +14,21 @@ namespace ballistica::ui_v1 {
// Base class for widgets that contain other widgets.
class ContainerWidget : public Widget {
public:
explicit ContainerWidget(float width = 0, float height = 0);
explicit ContainerWidget(float width = 0.0f, float height = 0.0f);
~ContainerWidget() override;
void Draw(base::RenderPass* pass, bool transparent) override;
auto HandleMessage(const base::WidgetMessage& m) -> bool override;
enum TransitionType {
TRANSITION_OUT_LEFT,
TRANSITION_OUT_RIGHT,
TRANSITION_IN_LEFT,
TRANSITION_IN_RIGHT,
TRANSITION_IN_SCALE,
TRANSITION_OUT_SCALE
enum class TransitionType {
kUnset,
kOutLeft,
kOutRight,
kInLeft,
kInRight,
kInScale,
kOutScale
};
void SetTransition(TransitionType t);
@ -49,6 +50,7 @@ class ContainerWidget : public Widget {
width_ = w;
MarkForUpdate();
}
virtual void SetHeight(float h) {
bg_dirty_ = glow_dirty_ = true;
height_ = h;
@ -67,6 +69,7 @@ class ContainerWidget : public Widget {
CheckLayout();
return width_;
}
auto GetHeight() -> float override {
CheckLayout();
return height_;
@ -76,8 +79,8 @@ class ContainerWidget : public Widget {
auto HasKeySelectableChild() const -> bool;
void set_is_window_stack(bool a) { is_window_stack_ = a; }
auto is_window_stack() const -> bool { return is_window_stack_; }
void set_is_window_stack(bool a) { is_window_stack_ = a; }
auto GetChildCount() const -> int {
assert(g_base->InLogicThread());
@ -167,6 +170,8 @@ class ContainerWidget : public Widget {
// if the topmost one is transitioning out, etc.)
auto GetTopmostToolbarInfluencingWidget() -> Widget*;
auto IsTransitioningOut() const -> bool override;
protected:
virtual void OnCancelCustom() {}
void set_single_depth_root(bool s) { single_depth_root_ = s; }

View File

@ -592,7 +592,7 @@ void TextWidget::Activate() {
if (auto* call = on_activate_call_.Get()) {
// Call this in the next cycle (don't wanna risk mucking with UI from
// within a UI loop).
call->ScheduleWeakOnce();
call->ScheduleWeak();
}
// Bring up an editor if applicable.
@ -720,7 +720,7 @@ auto TextWidget::HandleMessage(const base::WidgetMessage& m) -> bool {
claimed = true;
// Call this in the next cycle (don't wanna risk mucking with UI
// from within a UI loop)
call->ScheduleWeakOnce();
call->ScheduleWeak();
}
}
break;

View File

@ -88,7 +88,7 @@ void Widget::SetSelected(bool s, SelectionCause cause) {
if (selected_ && on_select_call_.Exists()) {
// Call this in the next cycle (don't wanna risk mucking
// with UI from within a UI loop).
on_select_call_->ScheduleWeakOnce();
on_select_call_->ScheduleWeak();
}
}
@ -238,4 +238,6 @@ auto Widget::IsAcceptingInput() const -> bool { return true; }
void Widget::Activate() {}
auto Widget::IsTransitioningOut() const -> bool { return false; }
} // namespace ballistica::ui_v1

View File

@ -46,7 +46,8 @@ class Widget : public Object {
// Whether the widget (or its children) is selectable in any way.
virtual auto IsSelectable() -> bool;
// Whether the widget can be selected by default with direction/tab presses.
// Whether the widget can be selected by default with direction/tab
// presses.
virtual auto IsSelectableViaKeys() -> bool;
// Is the widget currently accepting input?
@ -83,8 +84,8 @@ class Widget : public Object {
// If this widget is in a container, return it.
auto parent_widget() const -> ContainerWidget* { return parent_widget_; }
// Return the container_widget containing this widget, or the owner-widget if
// there is no parent.
// Return the container_widget containing this widget, or the owner-widget
// if there is no parent.
auto GetOwnerWidget() const -> Widget*;
auto down_widget() const -> Widget* { return down_widget_.Get(); }
@ -116,25 +117,23 @@ class Widget : public Object {
// redirecting them to transient per-window stuff).
void set_neighbors_locked(bool locked) { neighbors_locked_ = locked; }
// Widgets normally draw with a local depth range of 0-1.
// It can be useful to limit drawing to a subsection of that region however
// (for manually resolving overlap issues with widgets at the same depth,
// etc).
// Widgets normally draw with a local depth range of 0-1. It can be useful
// to limit drawing to a subsection of that region however (for manually
// resolving overlap issues with widgets at the same depth, etc).
void SetDepthRange(float minDepth, float maxDepth);
auto depth_range_min() const -> float { return depth_range_min_; }
auto depth_range_max() const -> float { return depth_range_max_; }
// For use by ContainerWidgets.
// (we probably should just this functionality to all widgets)
// For use by ContainerWidgets (we probably should just add this
// functionality to all widgets).
void set_parent_widget(ContainerWidget* c) { parent_widget_ = c; }
auto IsInMainStack() const -> bool;
auto IsInOverlayStack() const -> bool;
// For use when embedding widgets inside others manually.
// This will allow proper selection states/etc to trickle down to the
// lowest-level child.
// For use when embedding widgets inside others manually. This will allow
// proper selection states/etc to trickle down to the lowest-level child.
void set_owner_widget(Widget* o) { owner_widget_ = o; }
virtual auto GetWidgetTypeName() -> std::string { return "widget"; }
virtual auto HasChildren() const -> bool { return false; }
@ -167,22 +166,25 @@ class Widget : public Object {
void ScreenPointToWidget(float* x, float* y) const;
void WidgetPointToScreen(float* x, float* y) const;
// Draw-control parents are used to give one widget some basic visual control
// over others, allowing them to inherit things like draw-brightness and tilt
// shift (for cases such as images drawn over buttons).
// Ideally we'd probably want to extend the parent mechanism for this, but
// this works for now.
// Draw-control parents are used to give one widget some basic visual
// control over others, allowing them to inherit things like
// draw-brightness and tilt shift (for cases such as images drawn over
// buttons). Ideally we'd probably want to extend the parent mechanism for
// this, but this works for now.
auto draw_control_parent() const -> Widget* {
return draw_control_parent_.Get();
}
void set_draw_control_parent(Widget* w) { draw_control_parent_ = w; }
// Can be used to ask link-parents how bright to draw.
// Note: make sure the value returned here does not get changed when draw()
// is run, since parts of draw-controlled children may query this before
// draw() and parts after. (and they need to line up visually)
// Can be used to ask link-parents how bright to draw. Note: make sure the
// value returned here does not get changed when draw() is run, since
// parts of draw-controlled children may query this before draw() and
// parts after. (and they need to line up visually)
virtual auto GetDrawBrightness(millisecs_t current_time) const -> float;
/// Is this widget in the process of transitioning out before dying?
virtual auto IsTransitioningOut() const -> bool;
// Extra buffer added around widgets when they are centered-on.
void set_show_buffer_top(float b) { show_buffer_top_ = b; }
void set_show_buffer_bottom(float b) { show_buffer_bottom_ = b; }
@ -206,8 +208,8 @@ class Widget : public Object {
virtual void OnLanguageChange() {}
// Primitive janktastic child culling for use by containers.
// (should really implement something more proper...)
// Primitive janktastic child culling for use by containers (should really
// implement something more proper).
auto simple_culling_v() const -> float { return simple_culling_v_; }
auto simple_culling_h() const -> float { return simple_culling_h_; }
auto simple_culling_bottom() const -> float { return simple_culling_bottom_; }
@ -224,14 +226,17 @@ class Widget : public Object {
private:
auto GetPyWidget(bool new_ref) -> PyObject*;
virtual void SetSelected(bool s, SelectionCause cause);
bool selected_{};
bool visible_in_container_{true};
bool neighbors_locked_{};
bool auto_select_{};
ToolbarVisibility toolbar_visibility_{ToolbarVisibility::kMenuMinimalNoBack};
float simple_culling_h_{-1.0f};
float simple_culling_v_{-1.0f};
float simple_culling_left_{};
float simple_culling_right_{};
float simple_culling_bottom_{};
float simple_culling_top_{};
ToolbarVisibility toolbar_visibility_{ToolbarVisibility::kMenuMinimalNoBack};
PyObject* py_ref_{};
float show_buffer_top_{20.0f};
float show_buffer_bottom_{20.0f};
float show_buffer_left_{20.0f};
@ -241,12 +246,9 @@ class Widget : public Object {
Object::WeakRef<Widget> up_widget_;
Object::WeakRef<Widget> left_widget_;
Object::WeakRef<Widget> right_widget_;
bool neighbors_locked_{};
bool auto_select_{};
ContainerWidget* parent_widget_{};
PyObject* py_ref_{};
Widget* owner_widget_{};
bool selected_{};
bool visible_in_container_{true};
float tx_{};
float ty_{};
float stack_offset_x_{};

View File

@ -22,5 +22,6 @@ values = [
_hooks.quit_window, # kQuitWindowCall
_hooks.device_menu_press, # kDeviceMenuPressCall
_hooks.show_url_window, # kShowURLWindowCall
_hooks.double_transition_out_warning, # kDoubleTransitionOutWarningCall
TextWidgetStringEditAdapter, # kTextWidgetStringEditAdapterClass
]