Source code for bauiv1lib.league.rankwindow

# Released under the MIT License. See LICENSE for details.
#
"""UI related to league rank."""

from __future__ import annotations  # Docs-generation hack.

# pylint: disable=too-many-lines

import copy
import logging
from typing import TYPE_CHECKING, override

import bacommon.classic
import bauiv1 as bui
from bauiv1 import _commonassets, _classicassets
from bauiv1 import _builtinassets
from bauiv1 import _uiv1assets
from bauiv1lib.utils import (
    TOOLBAR_SCROLL_FADE,
    apply_toolbar_scroll_fades,
    get_screen_margins,
)
from bauiv1lib.league import league_display_name
from bauiv1lib.popup import PopupMenu

if TYPE_CHECKING:
    from typing import Any


[docs] class LeagueRankWindow(bui.MainWindow): """Window for showing league rank.""" def __init__( self, transition: str | None = 'in_right', origin_widget: bui.Widget | None = None, auxiliary_style: bool = True, ): # pylint: disable=too-many-statements plus = bui.app.plus assert plus is not None self._uiopenstate = bui.UIOpenState('classicleaguerank') bui.set_analytics_screen('League Rank Window') self._league_rank_data: dict[str, Any] | None = None self._power_ranking_achievements_button: bui.Widget | None = None self._up_to_date_bonus_button: bui.Widget | None = None self._power_ranking_trophies_button: bui.Widget | None = None self._league_title_text: bui.Widget | None = None self._league_text: bui.Widget | None = None self._league_number_text: bui.Widget | None = None self._your_power_ranking_text: bui.Widget | None = None self._loading_spinner: bui.Widget | None = None self._season_ends_text: bui.Widget | None = None self._power_ranking_rank_text: bui.Widget | None = None self._to_ranked_text: bui.Widget | None = None self._trophy_counts_reset_text: bui.Widget | None = None assert bui.app.classic is not None uiscale = bui.app.ui_v1.uiscale # Note: small-ui width is sized so our backing covers the # widest-case screen (21:9 aspect plus max screen margins) and # our scroll clamp doesn't stop short of the screen edges. self._width = 1630 if uiscale is bui.UIScale.SMALL else 1120 x_inset = 100 if uiscale is bui.UIScale.SMALL else 0 self._height = 1000 if uiscale is bui.UIScale.SMALL else 710 # Large ui-scale is medium's layout drawn smaller, by the same # ratio doc-ui windows use between the two (root scale 0.9 vs # 0.65); nothing here benefits from a bigger window. Popups we # open shrink by it too, keeping their size relative to us. self._large_shrink = 0.65 / 0.9 if uiscale is bui.UIScale.LARGE else 1.0 self._r = 'coopSelectWindow' self._xoffs = 40 self._league_url_arg = '' self._is_current_season = False self._can_do_more_button = True # Do some fancy math to fill all available screen area up to the # size of our backing container. This lets us fit to the exact # screen shape at small ui scale. screensize = bui.get_virtual_screen_size() scale = ( 1.13 if uiscale is bui.UIScale.SMALL else 0.93 * self._large_shrink ) # Calc screen size in our local container space and clamp to a # bit smaller than our container size. target_width = min(self._width - 130, screensize[0] / scale) target_height = min(self._height - 130, screensize[1] / scale) # To get top/left coords, go to the center of our window and # offset by half the width/height of our target area. yoffs = 0.5 * self._height + 0.5 * target_height + 30.0 self._scroll_width = target_width self._scroll_height = target_height - 50 scroll_bottom = yoffs - 80 - self._scroll_height # Go with full-screen scrollable area in small ui. if uiscale is bui.UIScale.SMALL: self._scroll_height += 53 scroll_bottom -= 1 # In small ui we extend our scrollable area out into the side # screen margins (space between the virtual bounds and the # actual screen edges) while keeping content laid out within the # virtual bounds. Our top and bottom stay at the virtual # bounds', where our toolbar fades are calibrated. self._margin_left, self._margin_right, _, _ = ( get_screen_margins(scale) if uiscale is bui.UIScale.SMALL else (0.0, 0.0, 0.0, 0.0) ) super().__init__( root_widget=bui.containerwidget( size=(self._width, self._height), scale=scale, toolbar_visibility='menu_full', toolbar_cancel_button_style=( 'close' if auxiliary_style else 'back' ), ), transition=transition, origin_widget=origin_widget, # We're affected by screen size only at small ui-scale. refresh_on_screen_size_changes=uiscale is bui.UIScale.SMALL, ) if uiscale is bui.UIScale.SMALL: self._back_button = bui.get_special_widget('back_button') bui.containerwidget( edit=self._root_widget, on_cancel_call=self.main_window_back ) else: # Sized to match doc-ui windows' back/close button on screen # (scale 0.88 under their 0.9 medium root scale; ours is # 0.93), keeping the center it has always had. back_size = (50.0, 50.0) if auxiliary_style else (60.0, 55.0) back_scale = 0.88 * 0.9 / 0.93 back_center = (75.0 + x_inset + 36.0, yoffs - 27.0) self._back_button = bui.buttonwidget( parent=self._root_widget, id=f'{self.main_window_id_prefix}|back', position=( back_center[0] - 0.5 * back_size[0] * back_scale, back_center[1] - 0.5 * back_size[1] * back_scale, ), size=back_size, scale=back_scale, autoselect=True, label=( bui.SpecialChar.CLOSE if auxiliary_style else bui.SpecialChar.BACK ).value, button_type=None if auxiliary_style else 'backSmall', on_activate_call=self.main_window_back, ) bui.containerwidget( edit=self._root_widget, cancel_button=self._back_button, selected_child=self._back_button, ) self._scrollwidget = bui.scrollwidget( parent=self._root_widget, highlight=False, size=( self._scroll_width + self._margin_left + self._margin_right, self._scroll_height, ), position=( self._width * 0.5 - self._scroll_width * 0.5 - self._margin_left, scroll_bottom, ), center_small_content=True, center_small_content_horizontally=True, # Outside small ui our content always fits; no outline. border_opacity=0.4 if uiscale is bui.UIScale.SMALL else 0.0, fading_scrollbar=True, ) bui.widget(edit=self._scrollwidget, autoselect=True) bui.containerwidget(edit=self._scrollwidget, claims_left_right=True) # With full-screen scrolling, fade content as it approaches # toolbars (only half as far along the top, as our old blotches # did there). if uiscale is bui.UIScale.SMALL: apply_toolbar_scroll_fades( self._scrollwidget, top_fade=0.5 * TOOLBAR_SCROLL_FADE ) self._title_text = bui.textwidget( parent=self._root_widget, # (Outside small ui, centered on the back button and sized # like doc-ui windows' titles on screen: 1.0 under their 0.9 # medium root scale; ours is 0.93.) position=( self._width * 0.5, yoffs - (55 if uiscale is bui.UIScale.SMALL else 27), ), size=(0, 0), text=_classicassets.strings.league.league_rank, h_align='center', color=bui.app.ui_v1.title_color, scale=1.2 if uiscale is bui.UIScale.SMALL else 0.9 / 0.93, maxwidth=600, v_align='center', ) self._last_power_ranking_query_time: float | None = None self._doing_power_ranking_query = False self._subcontainer: bui.Widget | None = None self._subcontainerwidth = 1024.0 self._subcontainerheight = 573.0 # For fullscreen scrollable, account for toolbar. Elsewhere, drop # the empty space below our lowest content (the 'More...' # button), which only small ui's full-screen layout wants; this # way everything fits our scroll area without scrolling. if uiscale is bui.UIScale.SMALL: self._subcontainerheight += 53 else: self._subcontainerheight -= 80 self._power_ranking_score_widgets: list[bui.Widget] = [] self._season_popup_menu: PopupMenu | None = None self._requested_season: str | None = None self._season: str | None = None # Take note of our account state; we'll refresh later if this # changes. (v1 sign-in state on purpose, here and below: league # rank data comes from the v1 master server.) self._account_state = plus.get_v1_account_state() self._refresh() # If we've got cached power-ranking data already, display it. assert bui.app.classic is not None info = bui.app.classic.accounts.get_cached_league_rank_data() if info is not None: self._update_for_league_rank_data(info) self._update_timer = bui.AppTimer( 1.0, bui.WeakCallStrict(self._update), repeat=True ) self._update(show=info is None) def _on_p_button_info_response( self, response: ( bacommon.classic.GetClassicLeaguePresidentButtonInfoResponse | Exception ), ) -> None: # If our window has died, no-op. if not self._root_widget: return if isinstance(response, Exception) or response.name is None: bui.textwidget( edit=self._president_name, color=(0.6, 0.6, 1, 0.2), text='-' ) return bui.textwidget( edit=self._president_name, color=(0.6, 0.6, 1, 0.9), text=response.name, literal=True, )
[docs] @override def get_main_window_state(self) -> bui.MainWindowState: # Support recreating our window for back/refresh purposes. cls = type(self) return bui.BasicMainWindowState( create_call=lambda transition, origin_widget: cls( transition=transition, origin_widget=origin_widget ) )
[docs] @override def main_window_should_preserve_selection(self) -> bool: return True
def _on_achievements_press(self) -> None: from bauiv1lib.achievements import AchievementsWindow # Only allow this for all-time or the current season (we # currently don't keep specific achievement data for old # seasons). if self._season == 'a' or self._is_current_season: prab = self._power_ranking_achievements_button assert prab is not None self.main_window_replace( lambda: AchievementsWindow(origin_widget=prab) ) else: bui.screenmessage( ( _classicassets.strings.league ).achievements_unavailable_old_seasons, color=(1, 0, 0), ) _builtinassets.audio.error.get().play() def _on_activity_mult_press(self) -> None: from bauiv1lib import confirm plus = bui.app.plus assert plus is not None # (The legacy form passed a ${MAX} sub that neither string # actually contained; dropped with the port.) lstrs = _classicassets.strings.league txt = ( lstrs.activeness_all_time_info if self._season == 'a' else lstrs.activeness_info ) confirm.ConfirmWindow( txt, cancel_button=False, width=460, height=150, origin_widget=self._activity_mult_button, scale=self._popup_scale(1.5), ) def _on_up_to_date_bonus_press(self) -> None: from bauiv1lib import confirm plus = bui.app.plus assert plus is not None txt = _classicassets.strings.league.up_to_date_bonus_description( percent=str( plus.get_v1_account_misc_read_val('proPowerRankingBoost', 10) ) ) confirm.ConfirmWindow( txt, cancel_button=False, width=460, height=130, origin_widget=self._up_to_date_bonus_button, scale=self._popup_scale(1.5), ) def _on_trophies_press(self) -> None: from bauiv1lib.trophies import TrophiesWindow info = self._league_rank_data if info is not None: prtb = self._power_ranking_trophies_button assert prtb is not None TrophiesWindow( position=prtb.get_screen_space_center(), data=info, scale=self._popup_scale(1.65), ) else: _builtinassets.audio.error.get().play() def _popup_scale(self, medium_scale: float) -> float | None: """Scale for a popup we open, given its medium ui-scale scale. None (the popup's own default) at small ui-scale. At large it is the medium scale shrunk as we are, so popups keep the same size relative to us at medium and large. """ if bui.app.ui_v1.uiscale is bui.UIScale.SMALL: return None return medium_scale * self._large_shrink def _on_power_ranking_query_response( self, data: dict[str, Any] | None ) -> None: self._doing_power_ranking_query = False # Important: *only* cache this if we requested the current # season. if data is not None and data.get('s', None) is None: assert bui.app.classic is not None bui.app.classic.accounts.cache_league_rank_data(data) # Always store a copy locally though (even for other seasons). self._league_rank_data = copy.deepcopy(data) self._update_for_league_rank_data(data) def _update(self, show: bool = False) -> None: plus = bui.app.plus assert plus is not None cur_time = bui.apptime() # If our account state has changed, refresh our UI. (v1 sign-in # state on purpose; see __init__.) account_state = plus.get_v1_account_state() if account_state != self._account_state: self._account_state = account_state self._refresh() # And power ranking too. if not self._doing_power_ranking_query: self._last_power_ranking_query_time = None # Send off a new power-ranking query if its been long enough or # our requested season has changed or whatnot. if not self._doing_power_ranking_query and ( self._last_power_ranking_query_time is None or cur_time - self._last_power_ranking_query_time > 30.0 ): try: if show: bui.textwidget(edit=self._league_title_text, text='') bui.textwidget(edit=self._league_text, text='') bui.textwidget(edit=self._league_number_text, text='') bui.textwidget(edit=self._your_power_ranking_text, text='') bui.spinnerwidget(edit=self._loading_spinner, visible=True) bui.textwidget(edit=self._to_ranked_text, text='') bui.textwidget(edit=self._power_ranking_rank_text, text='') bui.textwidget(edit=self._season_ends_text, text='') bui.textwidget(edit=self._trophy_counts_reset_text, text='') except Exception: logging.exception('Error showing updated rank info.') self._last_power_ranking_query_time = cur_time self._doing_power_ranking_query = True plus.power_ranking_query( season=self._requested_season, callback=bui.WeakCallPartial( self._on_power_ranking_query_response ), ) # Also kick off a query to v2 for latest league-president # info. if plus.accounts.primary is not None: with plus.accounts.primary: plus.cloud.send_message_cb( ( bacommon.classic ).GetClassicLeaguePresidentButtonInfoMessage( season=self._requested_season ), on_response=bui.WeakCallPartial( self._on_p_button_info_response ), ) def _refresh(self) -> None: plus = bui.app.plus assert plus is not None # (Re)create the sub-container if need be. if self._subcontainer is not None: self._subcontainer.delete() self._subcontainer = bui.containerwidget( parent=self._scrollwidget, size=(self._subcontainerwidth, self._subcontainerheight), background=False, ) w_parent = self._subcontainer v = self._subcontainerheight - 20 v -= 0 # For fullscreen scrollable, account for toolbar. uiscale = bui.app.ui_v1.uiscale if uiscale is bui.UIScale.SMALL: v -= 52 h2 = 80 v2 = v - 60 worth_color = (0.6, 0.6, 0.65) tally_color = (0.5, 0.6, 0.8) spc = 23 h_offs_tally = 150 tally_maxwidth = 120 v2 -= 70 bui.textwidget( parent=w_parent, position=(self._xoffs + h2 - 60, v2 + 106), size=(0, 0), flatness=1.0, shadow=0.0, text=_classicassets.strings.ui.points, h_align='left', v_align='center', scale=0.8, color=(1, 1, 1, 0.3), maxwidth=200, ) self._power_ranking_achievements_button = bui.buttonwidget( parent=w_parent, id=f'{self.main_window_id_prefix}|ach', position=(self._xoffs + h2 - 60, v2 + 10), size=(200, 80), icon=_classicassets.textures.achievements_icon.get(), autoselect=True, on_activate_call=bui.WeakCallStrict(self._on_achievements_press), up_widget=self._back_button, left_widget=self._back_button, color=(0.5, 0.5, 0.6), textcolor=(0.7, 0.7, 0.8), label='', ) self._power_ranking_achievement_total_text = bui.textwidget( parent=w_parent, position=(self._xoffs + h2 + h_offs_tally, v2 + 45), size=(0, 0), flatness=1.0, shadow=0.0, text='-', h_align='left', v_align='center', scale=0.8, color=tally_color, maxwidth=tally_maxwidth, ) v2 -= 80 self._power_ranking_trophies_button = bui.buttonwidget( parent=w_parent, id=f'{self.main_window_id_prefix}|trophies', position=(self._xoffs + h2 - 60, v2 + 10), size=(200, 80), icon=_classicassets.textures.medal_silver.get(), autoselect=True, on_activate_call=bui.WeakCallStrict(self._on_trophies_press), left_widget=self._back_button, color=(0.5, 0.5, 0.6), textcolor=(0.7, 0.7, 0.8), label='', ) self._power_ranking_trophies_total_text = bui.textwidget( parent=w_parent, position=(self._xoffs + h2 + h_offs_tally, v2 + 45), size=(0, 0), flatness=1.0, shadow=0.0, text='-', h_align='left', v_align='center', scale=0.8, color=tally_color, maxwidth=tally_maxwidth, ) v2 -= 100 bui.textwidget( parent=w_parent, position=(self._xoffs + h2 - 60, v2 + 86), size=(0, 0), flatness=1.0, shadow=0.0, text=_classicassets.strings.league.multipliers, h_align='left', v_align='center', scale=0.8, color=(1, 1, 1, 0.3), maxwidth=200, ) self._activity_mult_button: bui.Widget | None if plus.get_v1_account_misc_read_val('act', False): self._activity_mult_button = bui.buttonwidget( parent=w_parent, id=f'{self.main_window_id_prefix}|amult', position=(self._xoffs + h2 - 60, v2 + 10), size=(200, 60), icon=_classicassets.textures.heart.get(), icon_color=(0.5, 0, 0.5), label=_classicassets.strings.ui.activity, autoselect=True, on_activate_call=bui.WeakCallStrict( self._on_activity_mult_press ), left_widget=self._back_button, color=(0.5, 0.5, 0.6), textcolor=(0.7, 0.7, 0.8), ) self._activity_mult_text = bui.textwidget( parent=w_parent, position=(self._xoffs + h2 + h_offs_tally, v2 + 40), size=(0, 0), flatness=1.0, shadow=0.0, text='-', h_align='left', v_align='center', scale=0.8, color=tally_color, maxwidth=tally_maxwidth, ) v2 -= 65 else: self._activity_mult_button = None self._up_to_date_bonus_button = bui.buttonwidget( parent=w_parent, id=f'{self.main_window_id_prefix}|uptodatebonus', position=(self._xoffs + h2 - 60, v2 + 10), size=(200, 60), icon=_classicassets.textures.logo.get(), icon_color=(0.3, 0, 0.3), label=_classicassets.strings.league.up_to_date_bonus, autoselect=True, on_activate_call=bui.WeakCallStrict( self._on_up_to_date_bonus_press ), left_widget=self._back_button, color=(0.5, 0.5, 0.6), textcolor=(0.7, 0.7, 0.8), ) self._up_to_date_bonus_text = bui.textwidget( parent=w_parent, position=(self._xoffs + h2 + h_offs_tally, v2 + 40), size=(0, 0), flatness=1.0, shadow=0.0, text='-', h_align='left', v_align='center', scale=0.8, color=tally_color, maxwidth=tally_maxwidth, ) v2 -= 30 v2 -= spc bui.textwidget( parent=w_parent, position=(self._xoffs + h2 + h_offs_tally - 10 - 40, v2 + 35), size=(0, 0), flatness=1.0, shadow=0.0, text=_classicassets.strings.ui.final_score, h_align='right', v_align='center', scale=0.9, color=worth_color, maxwidth=150, ) self._power_ranking_total_text = bui.textwidget( parent=w_parent, position=(self._xoffs + h2 + h_offs_tally - 40, v2 + 35), size=(0, 0), flatness=1.0, shadow=0.0, text='-', h_align='left', v_align='center', scale=0.9, color=tally_color, maxwidth=tally_maxwidth, ) self._president_button = bui.buttonwidget( parent=w_parent, id=f'{self.main_window_id_prefix}|president', label='', position=(self._xoffs + h2 - 60, v2 - 100), color=(0.7, 0.55, 0.9), texture=_uiv1assets.textures.button_square_wide.get(), opacity=0.3, size=(200, 80), autoselect=True, on_activate_call=bui.WeakCallStrict(self._on_president_press), ) self._president_label = bui.textwidget( parent=w_parent, text=_classicassets.strings.league.league_president, flatness=1.0, shadow=0.0, color=(0.6, 0.6, 1, 0.7), draw_controller=self._president_button, scale=0.5, h_align='center', v_align='center', maxwidth=140, position=(self._xoffs + h2 - 60 + 100, v2 - 100 + 59), size=(0, 0), ) self._president_name = bui.textwidget( parent=w_parent, text='-', draw_controller=self._president_button, color=(0.6, 0.6, 1, 0.2), flatness=1.0, shadow=0.0, h_align='center', v_align='center', maxwidth=120, position=(self._xoffs + h2 - 60 + 100, v2 - 100 + 34), size=(0, 0), ) self._president_star1 = bui.imagewidget( parent=w_parent, draw_controller=self._president_button, texture=_classicassets.textures.star.get(), color=(0.7, 0.55, 0.9), opacity=0.2, position=(self._xoffs + h2 - 60 + 5, v2 - 100 + 17), size=(32, 32), ) self._president_star1 = bui.imagewidget( parent=w_parent, draw_controller=self._president_button, texture=_classicassets.textures.star.get(), color=(0.7, 0.55, 0.9), opacity=0.2, position=(self._xoffs + h2 - 60 + 200 - 5 - 32, v2 - 100 + 17), size=(32, 32), ) self._season_show_text = bui.textwidget( parent=w_parent, position=(self._xoffs + 390 - 15, v - 20), size=(0, 0), color=(0.6, 0.6, 0.7), maxwidth=200, text=_commonassets.strings.actions.show, h_align='right', v_align='center', scale=0.8, shadow=0, flatness=1.0, ) self._league_title_text = bui.textwidget( parent=w_parent, position=(self._xoffs + 470, v - 97), size=(0, 0), color=(0.6, 0.6, 0.7), maxwidth=230, text='', h_align='center', v_align='center', scale=0.9, shadow=0, flatness=1.0, ) self._league_text_scale = 1.8 self._league_text_maxwidth = 210 self._league_text = bui.textwidget( parent=w_parent, position=(self._xoffs + 470, v - 140), size=(0, 0), color=(1, 1, 1), maxwidth=self._league_text_maxwidth, text='-', h_align='center', v_align='center', scale=self._league_text_scale, shadow=1.0, flatness=1.0, ) self._league_number_base_pos = (470, v - 140) self._league_number_text = bui.textwidget( parent=w_parent, position=(self._xoffs + 470, v - 140), size=(0, 0), color=(1, 1, 1), maxwidth=100, text='', h_align='left', v_align='center', scale=0.8, shadow=1.0, flatness=1.0, ) self._loading_spinner = bui.spinnerwidget( parent=w_parent, position=( self._subcontainerwidth * 0.5, self._subcontainerheight * 0.5, ), style='bomb', size=64, ) self._your_power_ranking_text = bui.textwidget( parent=w_parent, position=(self._xoffs + 470, v - 142 - 70), size=(0, 0), color=(0.6, 0.6, 0.7), maxwidth=230, text='', h_align='center', v_align='center', scale=0.9, shadow=0, flatness=1.0, ) self._to_ranked_text = bui.textwidget( parent=w_parent, position=(self._xoffs + 470, v - 250 - 70), size=(0, 0), color=(0.6, 0.6, 0.7), maxwidth=230, text='', h_align='center', v_align='center', scale=0.8, shadow=0, flatness=1.0, ) self._power_ranking_rank_text = bui.textwidget( parent=w_parent, position=(self._xoffs + 473, v - 210 - 70), size=(0, 0), big=False, text='-', h_align='center', v_align='center', scale=1.0, ) self._season_ends_text = bui.textwidget( parent=w_parent, position=(self._xoffs + 470, v - 380), size=(0, 0), color=(0.6, 0.6, 0.6), maxwidth=230, text='', h_align='center', v_align='center', scale=0.9, shadow=0, flatness=1.0, ) self._trophy_counts_reset_text = bui.textwidget( parent=w_parent, position=(self._xoffs + 470, v - 410), size=(0, 0), color=(0.5, 0.5, 0.5), maxwidth=230, text='Trophy counts will reset next season.', h_align='center', v_align='center', scale=0.8, shadow=0, flatness=1.0, ) self._power_ranking_score_widgets = [] self._power_ranking_score_v = v - 56 h = 707 v -= 451 self._see_more_button = bui.buttonwidget( parent=w_parent, id=f'{self.main_window_id_prefix}|seemore', label=bui.app.lang.get_resource(f'{self._r}.seeMoreText'), position=(self._xoffs + h, v), color=(0.5, 0.5, 0.6), textcolor=(0.7, 0.7, 0.8), size=(230, 60), autoselect=True, on_activate_call=bui.WeakCallStrict(self._on_more_press), ) def _on_president_press(self) -> None: import bacommon.docui.routes.classicleaguepresidency as lroutes from bauiv1lib.league.presidency import LeaguePresidencyUIController from bauiv1lib.connectivity import wait_for_connectivity # 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 # We should be signed in at this point, but let's be sure. if plus.accounts.primary is None: bui.screenmessage( _classicassets.strings.account.not_signed_in, color=(1, 0, 0) ) _builtinassets.audio.error.get().play() return # Wait for connectivity if need be, then bring up a cloud based # doc-ui window for showing/futzing-with league president stuff. wait_for_connectivity( on_connected=lambda: self.main_window_replace( bui.CallStrict( LeaguePresidencyUIController().create_window, lroutes.Root(season=self._season), origin_widget=self._president_button, auxiliary_style=False, ), extra_type_id=( LeaguePresidencyUIController ).get_window_extra_type_id(), ) ) def _on_more_press(self) -> None: plus = bui.app.plus assert plus is not None our_login_id = plus.get_v1_account_public_login_id() if not self._can_do_more_button or our_login_id is None: _builtinassets.audio.error.get().play() bui.screenmessage( _commonassets.strings.status.unavailable_status, color=(1, 0, 0) ) return if self._season is None: season_str = '' else: season_str = '&season=' + ( 'all_time' if self._season == 'a' else self._season ) if self._league_url_arg != '': league_str = '&league=' + self._league_url_arg else: league_str = '' bui.open_url( plus.get_legacy_master_server_address() + '/highscores?list=powerRankings&v=2' + league_str + season_str + '&player=' + our_login_id ) def _update_for_league_rank_data(self, data: dict[str, Any] | None) -> None: # pylint: disable=too-many-statements # pylint: disable=too-many-branches # pylint: disable=too-many-locals if not self._root_widget: return plus = bui.app.plus uiscale = bui.app.ui_v1.uiscale assert plus is not None assert bui.app.classic is not None accounts = bui.app.classic.accounts in_top = data is not None and data['rank'] is not None eq_text = bui.app.lang.get_resource( f'{self._r}.powerRankingPointsEqualsText' ) pts_txt = bui.app.lang.get_resource(f'{self._r}.powerRankingPointsText') def num_str(number: str) -> str: # Layout code (flat widths/placement); evaluate locally. return ( _classicassets.strings.league.number_badge(number=number) ).evaluate() do_percent = False finished_season_unranked = False self._can_do_more_button = True extra_text = '' # (v1 sign-in state on purpose; see __init__.) if plus.get_v1_account_state() != 'signed_in': status_text = ( '(' + _classicassets.strings.ui.not_signed_in_status.evaluate() + ')' ) elif in_top: assert data is not None status_text = num_str(str(data['rank'])) elif data is not None: try: # Handle old seasons where we didn't wind up ranked at # the end. if not data['scores']: status_text = bui.app.lang.get_resource( f'{self._r}.powerRankingFinishedSeasonUnrankedText' ) extra_text = '' finished_season_unranked = True self._can_do_more_button = False else: our_points = accounts.get_league_rank_points(data) progress = float(our_points) / max(1, data['scores'][-1][1]) status_text = str(int(progress * 100.0)) + '%' extra_text = '\n' + bui.app.lang.get_resource( f'{self._r}.powerRankingPointsToRankedText' ).replace('${CURRENT}', str(our_points)).replace( '${REMAINING}', str(data['scores'][-1][1]) ) do_percent = True except Exception: logging.exception('Error updating power ranking.') status_text = bui.app.lang.get_resource( f'{self._r}.powerRankingNotInTopText' ).replace('${NUMBER}', str(data['listSize'])) extra_text = '' else: status_text = '-' self._season = data['s'] if data is not None else None v = self._subcontainerheight - 20 # For fullscreen scrollable, account for toolbar. uiscale = bui.app.ui_v1.uiscale if uiscale is bui.UIScale.SMALL: v -= 52 popup_was_selected = False if self._season_popup_menu is not None: btn = self._season_popup_menu.get_button() assert self._subcontainer if self._subcontainer.get_selected_child() == btn: popup_was_selected = True btn.delete() season_choices = [] season_choices_display = [] did_first = False self._is_current_season = False if data is not None: # Build our list of seasons we have available. for ssn in data['sl']: season_choices.append(ssn) if ssn != 'a' and not did_first: season_choices_display.append( _classicassets.strings.league.current_season(number=ssn) ) did_first = True # If we either did not specify a season or specified # the first, we're looking at the current. if self._season in [ssn, None]: self._is_current_season = True elif ssn == 'a': season_choices_display.append( _classicassets.strings.league.all_time ) else: season_choices_display.append( _classicassets.strings.league.season(number=ssn) ) assert self._subcontainer self._season_popup_menu = PopupMenu( parent=self._subcontainer, button_id=f'{self.main_window_id_prefix}|season', position=(self._xoffs + 390, v - 45), button_size=(200, 50), choices=season_choices, on_value_change_call=bui.WeakCallPartial( self._on_season_change ), choices_display=season_choices_display, current_choice=self._season, # (Medium's default popup-menu scale.) scale=self._popup_scale(1.65), ) if popup_was_selected: bui.containerwidget( edit=self._subcontainer, selected_child=self._season_popup_menu.get_button(), ) bui.widget(edit=self._see_more_button, show_buffer_bottom=100) bui.widget( edit=self._season_popup_menu.get_button(), up_widget=self._back_button, ) if uiscale is not bui.UIScale.SMALL: bui.widget( edit=self._back_button, down_widget=self._power_ranking_achievements_button, right_widget=self._season_popup_menu.get_button(), ) bui.textwidget( edit=self._league_title_text, text=( '' if self._season == 'a' else _classicassets.strings.league.league ), ) if data is None: lname = '' lnum = '' lcolor = (1, 1, 1) self._league_url_arg = '' elif self._season == 'a': lname = _classicassets.strings.league.all_time.evaluate() lnum = '' lcolor = (1, 1, 1) self._league_url_arg = '' else: lnum = ('[' + str(data['l']['i']) + ']') if data['l']['i2'] else '' lnameval = league_display_name(data['l']['n']) lname = ( lnameval if isinstance(lnameval, str) else lnameval.evaluate() ) lcolor = data['l']['c'] self._league_url_arg = ( data['l']['n'] + '_' + str(data['l']['i']) ).lower() to_end_string: bui.Lstr | bui.LangStr | str if data is None or self._season == 'a' or data['se'] is None: to_end_string = '' show_season_end = False else: show_season_end = True days_to_end = data['se'][0] minutes_to_end = data['se'][1] if days_to_end > 0: to_end_string = _classicassets.strings.league.season_ends_days( days=days_to_end ) elif days_to_end == 0 and minutes_to_end >= 60: to_end_string = _classicassets.strings.league.season_ends_hours( hours=minutes_to_end // 60 ) elif days_to_end == 0 and minutes_to_end >= 0: to_end_string = ( _classicassets.strings.league.season_ends_minutes( minutes=minutes_to_end ) ) else: to_end_string = ( _classicassets.strings.league.season_ended_days_ago( days=-(days_to_end + 1) ) ) bui.textwidget(edit=self._season_ends_text, text=to_end_string) bui.textwidget( edit=self._trophy_counts_reset_text, text=( _classicassets.strings.league.trophy_counts_reset if self._is_current_season and show_season_end else '' ), ) bui.textwidget(edit=self._league_text, text=lname, color=lcolor) l_text_width = min( self._league_text_maxwidth, bui.get_string_width( lname, suppress_warning=True, suppress_logic_thread_warning=True ) * self._league_text_scale, ) bui.textwidget( edit=self._league_number_text, text=lnum, color=lcolor, position=( self._xoffs + self._league_number_base_pos[0] + l_text_width * 0.5 + 8, self._league_number_base_pos[1] + 10, ), ) bui.textwidget( edit=self._to_ranked_text, text=( _classicassets.strings.league.to_ranked.evaluate() + '' + extra_text if do_percent else '' ), ) bui.textwidget( edit=self._your_power_ranking_text, text=(_classicassets.strings.ui.rank if (not do_percent) else ''), ) bui.spinnerwidget(edit=self._loading_spinner, visible=False) bui.textwidget( edit=self._power_ranking_rank_text, position=(self._xoffs + 473, v - 70 - (170 if do_percent else 220)), text=status_text, big=(in_top or do_percent), scale=( 3.0 if (in_top or do_percent) else 0.7 if finished_season_unranked else 1.0 ), ) if self._activity_mult_button is not None: if data is None or data['act'] is None: bui.buttonwidget( edit=self._activity_mult_button, textcolor=(0.7, 0.7, 0.8, 0.5), icon_color=(0.5, 0, 0.5, 0.3), ) bui.textwidget(edit=self._activity_mult_text, text=' -') else: bui.buttonwidget( edit=self._activity_mult_button, textcolor=(0.7, 0.7, 0.8, 1.0), icon_color=(0.5, 0, 0.5, 1.0), ) bui.textwidget( edit=self._activity_mult_text, text=f'x {data['act']:.2f}', ) # This used to be a bonus for 'BombSquad Pro' holders, but since # we're transitioning away from that it is now a bonus for # everyone running a recent-ish version of the game. have_up_to_date_bonus = data is not None pro_mult = ( 1.0 + float( plus.get_v1_account_misc_read_val('proPowerRankingBoost', 0.0) ) * 0.01 ) bui.textwidget( edit=self._up_to_date_bonus_text, text=( ' -' if (data is None or not have_up_to_date_bonus) else f'x {pro_mult:.2f}' ), ) bui.buttonwidget( edit=self._up_to_date_bonus_button, textcolor=(0.7, 0.7, 0.8, (1.0 if have_up_to_date_bonus else 0.5)), icon_color=( (0.5, 0, 0.5) if have_up_to_date_bonus else (0.5, 0, 0.5, 0.2) ), ) bui.buttonwidget( edit=self._power_ranking_achievements_button, label=('' if data is None else str(data['a']) + ' ') + _classicassets.strings.ui.achievements.evaluate(), ) # For the achievement value, use the number they gave us for # non-current seasons; otherwise calc our own. total_ach_value = 0 for ach in bui.app.classic.ach.achievements: if ach.complete: total_ach_value += ach.power_ranking_value if self._season != 'a' and not self._is_current_season: if data is not None and 'at' in data: total_ach_value = data['at'] bui.textwidget( edit=self._power_ranking_achievement_total_text, text=( '-' if data is None else ('+ ' + pts_txt.replace('${NUMBER}', str(total_ach_value))) ), ) total_trophies_count = accounts.get_league_rank_points( data, 'trophyCount' ) total_trophies_value = accounts.get_league_rank_points(data, 'trophies') bui.buttonwidget( edit=self._power_ranking_trophies_button, label=('' if data is None else str(total_trophies_count) + ' ') + _classicassets.strings.ui.trophies.evaluate(), ) bui.textwidget( edit=self._power_ranking_trophies_total_text, text=( '-' if data is None else ( '+ ' + pts_txt.replace('${NUMBER}', str(total_trophies_value)) ) ), ) bui.textwidget( edit=self._power_ranking_total_text, text=( '-' if data is None else eq_text.replace( '${NUMBER}', str(accounts.get_league_rank_points(data)) ) ), ) for widget in self._power_ranking_score_widgets: widget.delete() self._power_ranking_score_widgets = [] scores: list = data['scores'] if data is not None else [] tally_color = (0.5, 0.6, 0.8) w_parent = self._subcontainer v2 = self._power_ranking_score_v for score in scores: h2 = 680 is_us = score[3] self._power_ranking_score_widgets.append( bui.textwidget( parent=w_parent, position=(self._xoffs + h2 - 20, v2), size=(0, 0), color=(1, 1, 1) if is_us else (0.6, 0.6, 0.7), maxwidth=40, flatness=1.0, shadow=0.0, text=num_str(str(score[0])), h_align='right', v_align='center', scale=0.5, ) ) self._power_ranking_score_widgets.append( bui.textwidget( parent=w_parent, position=(self._xoffs + h2 + 20, v2), size=(0, 0), color=(1, 1, 1) if is_us else tally_color, maxwidth=60, text=str(score[1]), flatness=1.0, shadow=0.0, h_align='center', v_align='center', scale=0.7, ) ) txt = bui.textwidget( parent=w_parent, position=(self._xoffs + h2 + 60, v2 - (28 * 0.5) / 0.9), size=(210 / 0.9, 28), color=(1, 1, 1) if is_us else (0.6, 0.6, 0.6), maxwidth=210, flatness=1.0, shadow=0.0, autoselect=True, selectable=True, click_activate=True, text=score[2], h_align='left', v_align='center', scale=0.9, ) # Giving these ids doesn't work cleanly with sel # save/restore due to refreshing, so let's just not for now. bui.widget(edit=txt, allow_preserve_selection=False) self._power_ranking_score_widgets.append(txt) bui.textwidget( edit=txt, on_activate_call=bui.CallStrict( self._show_account_info, score[4], txt ), ) assert self._season_popup_menu is not None bui.widget( edit=txt, left_widget=self._season_popup_menu.get_button() ) v2 -= 28 def _show_account_info( self, account_id: str, textwidget: bui.Widget ) -> None: from bauiv1lib.account.viewer import AccountViewerWindow bui.play_swish() AccountViewerWindow( account_id=account_id, position=textwidget.get_screen_space_center() ) def _on_season_change(self, value: str) -> None: self._requested_season = value self._last_power_ranking_query_time = None # Update asap. self._update(show=True)
# Docs-generation hack; import some stuff that we likely only forward-declared # in our actual source code so that docs tools can find it. from typing import (Coroutine, Any, Literal, Callable, Generator, Awaitable, Sequence, Self, TypeIs) import asyncio from concurrent.futures import Future from pathlib import Path from enum import Enum