Source code for bauiv1lib.playlist.mapselect

# Released under the MIT License. See LICENSE for details.
#
"""Provides UI for selecting maps in playlists."""

from __future__ import annotations

import math
from typing import TYPE_CHECKING, override

import bauiv1 as bui

if TYPE_CHECKING:
    from typing import Any, Callable

    import bascenev1 as bs


[docs] class PlaylistMapSelectWindow(bui.MainWindow): """Window to select a map.""" def __init__( self, gametype: type[bs.GameActivity], sessiontype: type[bs.Session], config: dict[str, Any], edit_info: dict[str, Any], completion_call: Callable[[dict[str, Any] | None, bui.MainWindow], Any], transition: str | None = 'in_right', origin_widget: bui.Widget | None = None, select_get_more_maps_button: bool = False, ): # pylint: disable=too-many-locals # pylint: disable=too-many-positional-arguments from bascenev1 import get_filtered_map_name self._gametype = gametype self._sessiontype = sessiontype self._config = config self._completion_call = completion_call self._edit_info = edit_info self._maps: list[tuple[str, bui.Texture]] = [] self._selected_get_more_maps = False try: self._previous_map = get_filtered_map_name( config['settings']['map'] ) except Exception: self._previous_map = '' assert bui.app.classic is not None uiscale = bui.app.ui_v1.uiscale width = 815 if uiscale is bui.UIScale.SMALL else 615 x_inset = 100 if uiscale is bui.UIScale.SMALL else 0 height = ( 420 if uiscale is bui.UIScale.SMALL else 480 if uiscale is bui.UIScale.MEDIUM else 600 ) yoffs = -37 if uiscale is bui.UIScale.SMALL else 0 super().__init__( root_widget=bui.containerwidget( size=(width, height), scale=( 1.95 if uiscale is bui.UIScale.SMALL else 1.3 if uiscale is bui.UIScale.MEDIUM else 1.0 ), stack_offset=( (0, 0) if uiscale is bui.UIScale.SMALL else (0, 0) ), ), transition=transition, origin_widget=origin_widget, ) self._cancel_button = btn = bui.buttonwidget( parent=self._root_widget, position=(38 + x_inset, height - 67 + yoffs), size=(140, 50), scale=0.9, text_scale=1.0, autoselect=True, label=bui.Lstr(resource='cancelText'), on_activate_call=self.main_window_back, ) bui.containerwidget(edit=self._root_widget, cancel_button=btn) bui.textwidget( parent=self._root_widget, position=(width * 0.5, height - 46 + yoffs), size=(0, 0), maxwidth=260, scale=1.1, text=bui.Lstr( resource='mapSelectTitleText', subs=[('${GAME}', self._gametype.get_display_string())], ), color=bui.app.ui_v1.title_color, h_align='center', v_align='center', ) v = height - 70 + yoffs self._scroll_width = width - (80 + 2 * x_inset) self._scroll_height = height - ( 170 if uiscale is bui.UIScale.SMALL else 140 ) self._scrollwidget = bui.scrollwidget( parent=self._root_widget, position=(40 + x_inset, v - self._scroll_height), size=(self._scroll_width, self._scroll_height), ) bui.containerwidget( edit=self._root_widget, selected_child=self._scrollwidget ) bui.containerwidget(edit=self._scrollwidget, claims_left_right=True) self._subcontainer: bui.Widget | None = None self._refresh(select_get_more_maps_button=select_get_more_maps_button)
[docs] @override def get_main_window_state(self) -> bui.MainWindowState: # Support recreating our window for back/refresh purposes. cls = type(self) # Pull things out of self here; if we do it in the lambda we'll # keep ourself alive. gametype = self._gametype sessiontype = self._sessiontype config = self._config edit_info = self._edit_info completion_call = self._completion_call select_get_more_maps = self._selected_get_more_maps return bui.BasicMainWindowState( create_call=lambda transition, origin_widget: cls( transition=transition, origin_widget=origin_widget, gametype=gametype, sessiontype=sessiontype, config=config, edit_info=edit_info, completion_call=completion_call, select_get_more_maps_button=select_get_more_maps, ) )
def _refresh(self, select_get_more_maps_button: bool = False) -> None: # pylint: disable=too-many-statements # pylint: disable=too-many-branches # pylint: disable=too-many-locals from bascenev1 import ( get_map_class, get_map_display_string, ) assert bui.app.classic is not None store = bui.app.classic.store # Kill old. if self._subcontainer is not None: self._subcontainer.delete() mesh_opaque = bui.getmesh('level_select_button_opaque') mesh_transparent = bui.getmesh('level_select_button_transparent') self._maps = [] map_list = self._gametype.get_supported_maps(self._sessiontype) map_list_sorted = list(map_list) map_list_sorted.sort() unowned_maps = store.get_unowned_maps() for mapname in map_list_sorted: # Disallow ones we don't own. if mapname in unowned_maps: continue map_tex_name = get_map_class(mapname).get_preview_texture_name() if map_tex_name is not None: try: map_tex = bui.gettexture(map_tex_name) self._maps.append((mapname, map_tex)) except Exception: print(f'Invalid map preview texture: "{map_tex_name}".') else: print('Error: no map preview texture for map:', mapname) count = len(self._maps) columns = 2 rows = int(math.ceil(float(count) / columns)) button_width = 220 button_height = button_width * 0.5 button_buffer_h = 16 button_buffer_v = 19 self._sub_width = self._scroll_width * 0.95 self._sub_height = ( 5 + rows * (button_height + 2 * button_buffer_v) + 100 ) self._subcontainer = bui.containerwidget( parent=self._scrollwidget, size=(self._sub_width, self._sub_height), background=False, ) index = 0 mask_texture = bui.gettexture('mapPreviewMask') h_offs = 130 if len(self._maps) == 1 else 0 for y in range(rows): for x in range(columns): pos = ( x * (button_width + 2 * button_buffer_h) + button_buffer_h + h_offs, self._sub_height - (y + 1) * (button_height + 2 * button_buffer_v) + 12, ) btn = bui.buttonwidget( parent=self._subcontainer, button_type='square', size=(button_width, button_height), autoselect=True, texture=self._maps[index][1], mask_texture=mask_texture, mesh_opaque=mesh_opaque, mesh_transparent=mesh_transparent, label='', color=(1, 1, 1), on_activate_call=bui.Call( self._select_with_delay, self._maps[index][0] ), position=pos, ) if x == 0: bui.widget(edit=btn, left_widget=self._cancel_button) if y == 0: bui.widget(edit=btn, up_widget=self._cancel_button) if x == columns - 1: bui.widget( edit=btn, right_widget=bui.get_special_widget('squad_button'), ) bui.widget(edit=btn, show_buffer_top=60, show_buffer_bottom=60) if self._maps[index][0] == self._previous_map: bui.containerwidget( edit=self._subcontainer, selected_child=btn, visible_child=btn, ) name = get_map_display_string(self._maps[index][0]) bui.textwidget( parent=self._subcontainer, text=name, position=(pos[0] + button_width * 0.5, pos[1] - 12), size=(0, 0), scale=0.5, maxwidth=button_width, draw_controller=btn, h_align='center', v_align='center', color=(0.8, 0.8, 0.8, 0.8), ) index += 1 if index >= count: break if index >= count: break self._get_more_maps_button = btn = bui.buttonwidget( parent=self._subcontainer, size=(self._sub_width * 0.8, 60), position=(self._sub_width * 0.1, 30), label=bui.Lstr(resource='mapSelectGetMoreMapsText'), on_activate_call=self._on_store_press, color=(0.6, 0.53, 0.63), textcolor=(0.75, 0.7, 0.8), autoselect=True, ) bui.widget(edit=btn, show_buffer_top=30, show_buffer_bottom=30) if select_get_more_maps_button: bui.containerwidget( edit=self._subcontainer, selected_child=btn, visible_child=btn ) def _on_store_press(self) -> None: from bauiv1lib import account from bauiv1lib.store.browser import StoreBrowserWindow # No-op if we're not in control. if not self.main_window_has_control(): return plus = bui.app.plus assert plus is not None if plus.get_v1_account_state() != 'signed_in': account.show_sign_in_prompt() return self._selected_get_more_maps = True self.main_window_replace( StoreBrowserWindow( show_tab=StoreBrowserWindow.TabID.MAPS, origin_widget=self._get_more_maps_button, minimal_toolbars=True, ) ) def _select(self, map_name: str) -> None: # no-op if our underlying widget is dead or on its way out. if not self.main_window_has_control(): return self._config['settings']['map'] = map_name self.main_window_back() def _select_with_delay(self, map_name: str) -> None: bui.lock_all_input() bui.apptimer(0.1, bui.unlock_all_input) bui.apptimer(0.1, bui.WeakCall(self._select, map_name))