# Released under the MIT License. See LICENSE for details.
#
"""Provides the top level play window."""
from __future__ import annotations
import logging
from typing import override, TYPE_CHECKING
import bascenev1 as bs
import bauiv1 as bui
if TYPE_CHECKING:
from bauiv1 import MainWindowState
[docs]
class PlaylistSelectContext:
"""For using PlayWindow to select a playlist instead of running game."""
back_state: MainWindowState | None = None
[docs]
class PlayWindow(bui.MainWindow):
"""Window for selecting overall play type."""
def __init__(
self,
transition: str | None = 'in_right',
origin_widget: bui.Widget | None = None,
playlist_select_context: PlaylistSelectContext | None = None,
):
# pylint: disable=too-many-statements
# pylint: disable=too-many-locals
# Preload some modules we use in a background thread so we won't
# have a visual hitch when the user taps them.
bui.app.threadpool.submit_no_wait(self._preload_modules)
classic = bui.app.classic
assert classic is not None
self._playlist_select_context = playlist_select_context
uiscale = bui.app.ui_v1.uiscale
width = 1100 if uiscale is bui.UIScale.SMALL else 800
x_offs = 150 if uiscale is bui.UIScale.SMALL else 0
y_offs = -60 if uiscale is bui.UIScale.SMALL else 0
height = 650 if uiscale is bui.UIScale.SMALL else 550
button_width = 400
if origin_widget is not None:
# Need to store this ourself since we can function as a
# non-main window.
self._transition_out = 'out_scale'
else:
self._transition_out = 'out_right'
self._r = 'playWindow'
super().__init__(
root_widget=bui.containerwidget(
size=(width, height),
toolbar_visibility=(
'menu_full'
if playlist_select_context is None
else 'menu_minimal'
),
scale=(
1.35
if uiscale is bui.UIScale.SMALL
else 0.9 if uiscale is bui.UIScale.MEDIUM else 0.8
),
stack_offset=(
(0, 20) if uiscale is bui.UIScale.SMALL else (0, 0)
),
),
transition=transition,
origin_widget=origin_widget,
)
self._back_button: bui.Widget | None
if uiscale is bui.UIScale.SMALL:
self._back_button = None
bui.containerwidget(
edit=self._root_widget,
on_cancel_call=self.main_window_back,
)
else:
self._back_button = bui.buttonwidget(
parent=self._root_widget,
position=(55 + x_offs, height - 132 + y_offs),
size=(60, 60),
scale=1.1,
text_res_scale=1.5,
text_scale=1.2,
autoselect=True,
label=bui.charstr(bui.SpecialChar.BACK),
button_type='backSmall',
on_activate_call=self.main_window_back,
)
bui.containerwidget(
edit=self._root_widget, cancel_button=self._back_button
)
txt = bui.textwidget(
parent=self._root_widget,
position=(width * 0.5, height - 101 + y_offs),
# position=(width * 0.5, height -
# (101 if main_menu else 61)),
size=(0, 0),
text=bui.Lstr(
resource=(
(f'{self._r}.titleText')
if self._playlist_select_context is None
else 'playlistsText'
)
),
scale=1.7,
res_scale=2.0,
maxwidth=400,
color=bui.app.ui_v1.heading_color,
h_align='center',
v_align='center',
)
if uiscale is bui.UIScale.SMALL:
bui.textwidget(edit=txt, text='')
v = (
height
- (110 if self._playlist_select_context is None else 90)
+ y_offs
)
v -= 100
clr = (0.6, 0.7, 0.6, 1.0)
v -= 280 if self._playlist_select_context is None else 180
v += 30 if uiscale is bui.UIScale.SMALL else 0
hoffs = (
x_offs + 80
if self._playlist_select_context is None
else x_offs - 100
)
scl = 1.13 if self._playlist_select_context is None else 0.68
self._lineup_tex = bui.gettexture('playerLineup')
angry_computer_transparent_mesh = bui.getmesh(
'angryComputerTransparent'
)
self._lineup_1_transparent_mesh = bui.getmesh(
'playerLineup1Transparent'
)
self._lineup_2_transparent_mesh = bui.getmesh(
'playerLineup2Transparent'
)
self._lineup_3_transparent_mesh = bui.getmesh(
'playerLineup3Transparent'
)
self._lineup_4_transparent_mesh = bui.getmesh(
'playerLineup4Transparent'
)
self._eyes_mesh = bui.getmesh('plasticEyesTransparent')
self._coop_button: bui.Widget | None = None
# Only show coop button in regular variant.
if self._playlist_select_context is None:
self._coop_button = btn = bui.buttonwidget(
parent=self._root_widget,
position=(hoffs, v + (scl * 15)),
size=(
scl * button_width,
scl * 300,
),
extra_touch_border_scale=0.1,
autoselect=True,
label='',
button_type='square',
text_scale=1.13,
on_activate_call=self._coop,
)
if uiscale is bui.UIScale.SMALL:
bui.widget(
edit=btn,
left_widget=bui.get_special_widget('back_button'),
)
bui.widget(
edit=btn,
up_widget=bui.get_special_widget('account_button'),
)
bui.widget(
edit=btn,
down_widget=bui.get_special_widget('settings_button'),
)
self._draw_dude(
0,
btn,
hoffs,
v,
scl,
position=(140, 30),
color=(0.72, 0.4, 1.0),
)
self._draw_dude(
1,
btn,
hoffs,
v,
scl,
position=(185, 53),
color=(0.71, 0.5, 1.0),
)
self._draw_dude(
2,
btn,
hoffs,
v,
scl,
position=(220, 27),
color=(0.67, 0.44, 1.0),
)
self._draw_dude(
3, btn, hoffs, v, scl, position=(255, 57), color=(0.7, 0.3, 1.0)
)
bui.imagewidget(
parent=self._root_widget,
draw_controller=btn,
position=(hoffs + scl * 230, v + scl * 153),
size=(scl * 115, scl * 115),
texture=self._lineup_tex,
mesh_transparent=angry_computer_transparent_mesh,
)
bui.textwidget(
parent=self._root_widget,
draw_controller=btn,
position=(hoffs + scl * (-10), v + scl * 95),
size=(scl * button_width, scl * 50),
text=bui.Lstr(
resource='playModes.singlePlayerCoopText',
fallback_resource='playModes.coopText',
),
maxwidth=scl * button_width * 0.7,
res_scale=1.5,
h_align='center',
v_align='center',
color=(0.7, 0.9, 0.7, 1.0),
scale=scl * 2.3,
)
bui.textwidget(
parent=self._root_widget,
draw_controller=btn,
position=(hoffs + scl * (-10), v + (scl * 54)),
size=(scl * button_width, scl * 30),
text=bui.Lstr(resource=f'{self._r}.oneToFourPlayersText'),
h_align='center',
v_align='center',
scale=0.83 * scl,
flatness=1.0,
maxwidth=scl * button_width * 0.7,
color=clr,
)
scl = 0.5 if self._playlist_select_context is None else 0.68
hoffs += 440 if self._playlist_select_context is None else 216
v += 180 if self._playlist_select_context is None else -68
self._teams_button = btn = bui.buttonwidget(
parent=self._root_widget,
position=(
hoffs,
v + (scl * 15 if self._playlist_select_context is None else 0),
),
size=(
scl * button_width,
scl * (300 if self._playlist_select_context is None else 360),
),
extra_touch_border_scale=0.1,
autoselect=True,
label='',
button_type='square',
text_scale=1.13,
on_activate_call=self._team_tourney,
)
bui.widget(
edit=btn,
up_widget=bui.get_special_widget('get_tokens_button'),
right_widget=bui.get_special_widget('squad_button'),
)
xxx = -14
self._draw_dude(
2,
btn,
hoffs,
v,
scl,
position=(xxx + 148, 30),
color=(0.2, 0.4, 1.0),
)
self._draw_dude(
3,
btn,
hoffs,
v,
scl,
position=(xxx + 181, 53),
color=(0.3, 0.4, 1.0),
)
self._draw_dude(
1,
btn,
hoffs,
v,
scl,
position=(xxx + 216, 33),
color=(0.3, 0.5, 1.0),
)
self._draw_dude(
0,
btn,
hoffs,
v,
scl,
position=(xxx + 245, 57),
color=(0.3, 0.5, 1.0),
)
xxx = 155
self._draw_dude(
0,
btn,
hoffs,
v,
scl,
position=(xxx + 151, 30),
color=(1.0, 0.5, 0.4),
)
self._draw_dude(
1,
btn,
hoffs,
v,
scl,
position=(xxx + 189, 53),
color=(1.0, 0.58, 0.58),
)
self._draw_dude(
3,
btn,
hoffs,
v,
scl,
position=(xxx + 223, 27),
color=(1.0, 0.5, 0.5),
)
self._draw_dude(
2,
btn,
hoffs,
v,
scl,
position=(xxx + 257, 57),
color=(1.0, 0.5, 0.5),
)
bui.textwidget(
parent=self._root_widget,
draw_controller=btn,
position=(hoffs + scl * (-10), v + scl * 95),
size=(scl * button_width, scl * 50),
text=bui.Lstr(
resource='playModes.teamsText', fallback_resource='teamsText'
),
res_scale=1.5,
maxwidth=scl * button_width * 0.7,
h_align='center',
v_align='center',
color=(0.7, 0.9, 0.7, 1.0),
scale=scl * 2.3,
)
bui.textwidget(
parent=self._root_widget,
draw_controller=btn,
position=(hoffs + scl * (-10), v + (scl * 54)),
size=(scl * button_width, scl * 30),
text=bui.Lstr(resource=f'{self._r}.twoToEightPlayersText'),
h_align='center',
v_align='center',
res_scale=1.5,
scale=0.9 * scl,
flatness=1.0,
maxwidth=scl * button_width * 0.7,
color=clr,
)
hoffs += 0 if self._playlist_select_context is None else 300
v -= 155 if self._playlist_select_context is None else 0
self._free_for_all_button = btn = bui.buttonwidget(
parent=self._root_widget,
position=(
hoffs,
v + (scl * 15 if self._playlist_select_context is None else 0),
),
size=(
scl * button_width,
scl * (300 if self._playlist_select_context is None else 360),
),
extra_touch_border_scale=0.1,
autoselect=True,
label='',
button_type='square',
text_scale=1.13,
on_activate_call=self._free_for_all,
)
xxx = -5
self._draw_dude(
0,
btn,
hoffs,
v,
scl,
position=(xxx + 140, 30),
color=(0.4, 1.0, 0.4),
)
self._draw_dude(
3,
btn,
hoffs,
v,
scl,
position=(xxx + 185, 53),
color=(1.0, 0.4, 0.5),
)
self._draw_dude(
1,
btn,
hoffs,
v,
scl,
position=(xxx + 220, 27),
color=(0.4, 0.5, 1.0),
)
self._draw_dude(
2,
btn,
hoffs,
v,
scl,
position=(xxx + 255, 57),
color=(0.5, 1.0, 0.4),
)
xxx = 140
self._draw_dude(
2,
btn,
hoffs,
v,
scl,
position=(xxx + 148, 30),
color=(1.0, 0.9, 0.4),
)
self._draw_dude(
0,
btn,
hoffs,
v,
scl,
position=(xxx + 182, 53),
color=(0.7, 1.0, 0.5),
)
self._draw_dude(
3,
btn,
hoffs,
v,
scl,
position=(xxx + 233, 27),
color=(0.7, 0.5, 0.9),
)
self._draw_dude(
1,
btn,
hoffs,
v,
scl,
position=(xxx + 266, 53),
color=(0.4, 0.5, 0.8),
)
bui.textwidget(
parent=self._root_widget,
draw_controller=btn,
position=(hoffs + scl * (-10), v + scl * 95),
size=(scl * button_width, scl * 50),
text=bui.Lstr(
resource='playModes.freeForAllText',
fallback_resource='freeForAllText',
),
maxwidth=scl * button_width * 0.7,
h_align='center',
v_align='center',
color=(0.7, 0.9, 0.7, 1.0),
scale=scl * 1.9,
)
bui.textwidget(
parent=self._root_widget,
draw_controller=btn,
position=(hoffs + scl * (-10), v + (scl * 54)),
size=(scl * button_width, scl * 30),
text=bui.Lstr(resource=f'{self._r}.twoToEightPlayersText'),
h_align='center',
v_align='center',
scale=0.9 * scl,
flatness=1.0,
maxwidth=scl * button_width * 0.7,
color=clr,
)
if uiscale is bui.UIScale.SMALL:
bui.containerwidget(
edit=self._root_widget,
selected_child=(
self._coop_button
if self._playlist_select_context is None
else self._teams_button
),
)
else:
bui.containerwidget(
edit=self._root_widget,
selected_child=(
self._coop_button
if self._playlist_select_context is None
else self._teams_button
),
)
self._restore_state()
[docs]
@override
def get_main_window_state(self) -> bui.MainWindowState:
# Support recreating our window for back/refresh purposes.
cls = type(self)
# Pull any values out of self here; if we do it in the lambda
# we'll keep our window alive inadvertantly.
playlist_select_context = self._playlist_select_context
return bui.BasicMainWindowState(
create_call=lambda transition, origin_widget: cls(
transition=transition,
origin_widget=origin_widget,
playlist_select_context=playlist_select_context,
)
)
[docs]
@override
def on_main_window_close(self) -> None:
self._save_state()
@staticmethod
def _preload_modules() -> None:
"""Preload modules we use; avoids hitches (called in bg thread)."""
import bauiv1lib.mainmenu as _unused1
import bauiv1lib.account as _unused2
import bauiv1lib.coop.browser as _unused3
import bauiv1lib.playlist.browser as _unused4
def _coop(self) -> None:
# pylint: disable=cyclic-import
from bauiv1lib.account import show_sign_in_prompt
from bauiv1lib.coop.browser import CoopBrowserWindow
# no-op if we're not currently in control.
if not self.main_window_has_control():
return
plus = bui.app.plus
assert plus is not None
if plus.get_v1_account_state() != 'signed_in':
show_sign_in_prompt()
return
self.main_window_replace(
CoopBrowserWindow(origin_widget=self._coop_button)
)
def _team_tourney(self) -> None:
# pylint: disable=cyclic-import
from bauiv1lib.playlist.browser import PlaylistBrowserWindow
# no-op if we're not currently in control.
if not self.main_window_has_control():
return
self.main_window_replace(
PlaylistBrowserWindow(
origin_widget=self._teams_button,
sessiontype=bs.DualTeamSession,
playlist_select_context=self._playlist_select_context,
)
)
def _free_for_all(self) -> None:
# pylint: disable=cyclic-import
from bauiv1lib.playlist.browser import PlaylistBrowserWindow
# no-op if we're not currently in control.
if not self.main_window_has_control():
return
self.main_window_replace(
PlaylistBrowserWindow(
origin_widget=self._free_for_all_button,
sessiontype=bs.FreeForAllSession,
playlist_select_context=self._playlist_select_context,
)
)
def _draw_dude(
self,
i: int,
btn: bui.Widget,
hoffs: float,
v: float,
scl: float,
position: tuple[float, float],
color: tuple[float, float, float],
) -> None:
# pylint: disable=too-many-positional-arguments
h_extra = -100
v_extra = 130
eye_color = (
0.7 * 1.0 + 0.3 * color[0],
0.7 * 1.0 + 0.3 * color[1],
0.7 * 1.0 + 0.3 * color[2],
)
if i == 0:
bui.imagewidget(
parent=self._root_widget,
draw_controller=btn,
position=(
hoffs + scl * (h_extra + position[0]),
v + scl * (v_extra + position[1]),
),
size=(scl * 60, scl * 80),
color=color,
texture=self._lineup_tex,
mesh_transparent=self._lineup_1_transparent_mesh,
)
bui.imagewidget(
parent=self._root_widget,
draw_controller=btn,
position=(
hoffs + scl * (h_extra + position[0] + 12),
v + scl * (v_extra + position[1] + 53),
),
size=(scl * 36, scl * 18),
texture=self._lineup_tex,
color=eye_color,
mesh_transparent=self._eyes_mesh,
)
elif i == 1:
bui.imagewidget(
parent=self._root_widget,
draw_controller=btn,
position=(
hoffs + scl * (h_extra + position[0]),
v + scl * (v_extra + position[1]),
),
size=(scl * 45, scl * 90),
color=color,
texture=self._lineup_tex,
mesh_transparent=self._lineup_2_transparent_mesh,
)
bui.imagewidget(
parent=self._root_widget,
draw_controller=btn,
position=(
hoffs + scl * (h_extra + position[0] + 5),
v + scl * (v_extra + position[1] + 67),
),
size=(scl * 32, scl * 16),
texture=self._lineup_tex,
color=eye_color,
mesh_transparent=self._eyes_mesh,
)
elif i == 2:
bui.imagewidget(
parent=self._root_widget,
draw_controller=btn,
position=(
hoffs + scl * (h_extra + position[0]),
v + scl * (v_extra + position[1]),
),
size=(scl * 45, scl * 90),
color=color,
texture=self._lineup_tex,
mesh_transparent=self._lineup_3_transparent_mesh,
)
bui.imagewidget(
parent=self._root_widget,
draw_controller=btn,
position=(
hoffs + scl * (h_extra + position[0] + 5),
v + scl * (v_extra + position[1] + 59),
),
size=(scl * 34, scl * 17),
texture=self._lineup_tex,
color=eye_color,
mesh_transparent=self._eyes_mesh,
)
elif i == 3:
bui.imagewidget(
parent=self._root_widget,
draw_controller=btn,
position=(
hoffs + scl * (h_extra + position[0]),
v + scl * (v_extra + position[1]),
),
size=(scl * 48, scl * 96),
color=color,
texture=self._lineup_tex,
mesh_transparent=self._lineup_4_transparent_mesh,
)
bui.imagewidget(
parent=self._root_widget,
draw_controller=btn,
position=(
hoffs + scl * (h_extra + position[0] + 2),
v + scl * (v_extra + position[1] + 62),
),
size=(scl * 38, scl * 19),
texture=self._lineup_tex,
color=eye_color,
mesh_transparent=self._eyes_mesh,
)
def _save_state(self) -> None:
try:
sel = self._root_widget.get_selected_child()
if sel == self._teams_button:
sel_name = 'Team Games'
elif self._coop_button is not None and sel == self._coop_button:
sel_name = 'Co-op Games'
elif sel == self._free_for_all_button:
sel_name = 'Free-for-All Games'
elif sel == self._back_button:
sel_name = 'Back'
else:
raise ValueError(f'unrecognized selection {sel}')
assert bui.app.classic is not None
bui.app.ui_v1.window_states[type(self)] = sel_name
except Exception:
logging.exception('Error saving state for %s.', self)
def _restore_state(self) -> None:
try:
assert bui.app.classic is not None
sel_name = bui.app.ui_v1.window_states.get(type(self))
if sel_name == 'Team Games':
sel = self._teams_button
elif sel_name == 'Co-op Games' and self._coop_button is not None:
sel = self._coop_button
elif sel_name == 'Free-for-All Games':
sel = self._free_for_all_button
elif sel_name == 'Back' and self._back_button is not None:
sel = self._back_button
else:
sel = (
self._coop_button
if self._coop_button is not None
else self._teams_button
)
bui.containerwidget(edit=self._root_widget, selected_child=sel)
except Exception:
logging.exception('Error restoring state for %s.', self)