Source code for bauiv1lib.achievements

# Released under the MIT License. See LICENSE for details.
#
"""Provides a popup window to view achievements."""

from __future__ import annotations  # Docs-generation hack.

from typing import override

from bauiv1lib.utils import (
    get_screen_margins,
    scroll_fade_bottom,
    scroll_fade_top,
)
import bauiv1 as bui
from bauiv1 import _commonassets, _classicassets


[docs] class AchievementsWindow(bui.MainWindow): """Popup window to view achievements.""" def __init__( self, transition: str | None = 'in_right', origin_widget: bui.Widget | None = None, auxiliary_style: bool = True, ): # pylint: disable=too-many-locals # pylint: disable=cyclic-import from baclassic import ( CHEST_APPEARANCE_DISPLAY_INFOS, CHEST_APPEARANCE_DISPLAY_INFO_DEFAULT, ) assert bui.app.classic is not None uiscale = bui.app.ui_v1.uiscale self._uiopenstate = bui.UIOpenState('classicachievements') self._width = 800 if uiscale is bui.UIScale.SMALL else 550 self._height = ( 450 if uiscale is bui.UIScale.SMALL else 370 if uiscale is bui.UIScale.MEDIUM else 450 ) # 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 = ( 2.4 if uiscale is bui.UIScale.SMALL else 1.5 if uiscale is bui.UIScale.MEDIUM else 1.2 ) # Calc screen size in our local container space and clamp to a # bit smaller than our container size. target_width = min(self._width - 60, screensize[0] / scale) target_height = min(self._height - 70, screensize[1] / scale) # In small ui we extend our scrollable area out into the screen # margins (space between the virtual bounds and the actual # screen edges) while keeping content laid out within the # virtual bounds. margin_left, margin_right, margin_bottom, margin_top = ( get_screen_margins(scale) if uiscale is bui.UIScale.SMALL else (0.0, 0.0, 0.0, 0.0) ) # 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 scroll_width = target_width scroll_height = target_height - 25 scroll_bottom = yoffs - 54 - scroll_height # Go with full-screen scrollable area in small ui. if uiscale is bui.UIScale.SMALL: scroll_height += 30 scroll_bottom -= 3 # Extend the scroll area out into the screen margins (content # stays put; see below). scroll_x = self._width * 0.5 - scroll_width * 0.5 - margin_left scroll_width, scroll_height, scroll_bottom = ( scroll_width + margin_left + margin_right, scroll_height + margin_bottom + margin_top, scroll_bottom - margin_bottom, ) super().__init__( root_widget=bui.containerwidget( size=(self._width, self._height), toolbar_visibility='menu_full', toolbar_cancel_button_style=( 'close' if auxiliary_style else 'back' ), scale=scale, ), 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: bui.containerwidget( edit=self._root_widget, on_cancel_call=self.main_window_back ) self._back_button = None else: self._back_button = bui.buttonwidget( parent=self._root_widget, id=f'{self.main_window_id_prefix}|back', autoselect=True, position=(50, yoffs - 48), size=(60, 60), scale=0.6, label=bui.charstr( bui.SpecialChar.CLOSE if auxiliary_style else bui.SpecialChar.BACK ), 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 ) achievements = bui.app.classic.ach.achievements num_complete = len([a for a in achievements if a.complete]) self._scrollwidget = bui.scrollwidget( parent=self._root_widget, id=f'{self.main_window_id_prefix}|scroll', size=(scroll_width, scroll_height), position=(scroll_x, scroll_bottom), capture_arrows=True, simple_culling_v=10, border_opacity=0.4, ) bui.containerwidget( edit=self._root_widget, selected_child=self._scrollwidget ) bui.widget(edit=self._scrollwidget, autoselect=True) if uiscale is bui.UIScale.SMALL: bui.widget( edit=self._scrollwidget, left_widget=bui.get_special_widget('back_button'), ) # With full-screen scrolling, fade content as it approaches # toolbars. if uiscale is bui.UIScale.SMALL: # Note that we intentionally use the original # un-margin-extended scroll geometry here; the fades were # placed to coincide with toolbar elements, which don't # move when we extend out into screen margins. scroll_fade_top( self._root_widget, scroll_x + margin_left, scroll_bottom + margin_bottom, scroll_width - margin_left - margin_right, scroll_height - margin_bottom - margin_top, ) scroll_fade_bottom( self._root_widget, scroll_x + margin_left, scroll_bottom + margin_bottom, scroll_width - margin_left - margin_right, scroll_height - margin_bottom - margin_top, ) # In small UI mode when the screen is narrow enough we need to # go with a smaller title to avoid it overlapping with toolbar # bits. if uiscale is bui.UIScale.SMALL and target_width < 630: self._title_text = bui.textwidget( parent=self._root_widget, position=(self._width * 0.5, yoffs - 42), size=(0, 0), h_align='center', v_align='center', scale=0.6, text=_commonassets.strings.compose.spaced_pair( first=_commonassets.strings.compose.heading_suffix( main=_classicassets.strings.ui.achievements ), second=f'{num_complete}/{len(achievements)}', ), maxwidth=86, color=bui.app.ui_v1.title_color, ) else: self._title_text = bui.textwidget( parent=self._root_widget, position=( self._width * 0.5, yoffs - (42 if uiscale is bui.UIScale.SMALL else 30), ), size=(0, 0), h_align='center', v_align='center', scale=0.6, text=_classicassets.strings.account.achievement_progress( complete=str(num_complete), total=str(len(achievements)), ), maxwidth=180, color=bui.app.ui_v1.title_color, ) bui.containerwidget( edit=self._root_widget, cancel_button=self._back_button ) incr = 36 sub_width = scroll_width - 25 sub_height = 85 + len(achievements) * incr + margin_bottom + margin_top # For fullscreen scrollable, account for toolbar. if uiscale is bui.UIScale.SMALL: sub_height += 30 # Our scroll area is widened out into the screen margins, but # content should stay laid out within the virtual bounds; do # our layout math against this un-widened width offset by the # left margin. content_width = sub_width - margin_left - margin_right self._subcontainer = bui.containerwidget( parent=self._scrollwidget, id=f'{self.main_window_id_prefix}|subcontainer', size=(sub_width, sub_height), background=False, ) basey = sub_height - margin_top # For fullscreen scrollable, account for toolbar. if uiscale is bui.UIScale.SMALL: basey -= 30 total_pts = 0 for i, ach in enumerate(achievements): complete = ach.complete bui.textwidget( parent=self._subcontainer, position=( margin_left + content_width * 0.08 - 5, basey - 20 - incr * i, ), maxwidth=20, scale=0.5, color=(0.6, 0.6, 0.7) if complete else (0.6, 0.6, 0.7, 0.2), flatness=1.0, shadow=0.0, text=str(i + 1), size=(0, 0), h_align='right', v_align='center', ) bui.imagewidget( parent=self._subcontainer, position=( ( margin_left + content_width * 0.10 + 1, basey - 20 - incr * i - 9, ) if complete else ( margin_left + content_width * 0.10 - 4, basey - 20 - incr * i - 14, ) ), size=(18, 18) if complete else (27, 27), opacity=1.0 if complete else 0.3, color=ach.get_icon_color(complete)[:3], texture=ach.get_icon_ui_texture(complete), ) if complete: bui.imagewidget( parent=self._subcontainer, position=( margin_left + content_width * 0.10 - 4, basey - 25 - incr * i - 9, ), size=(28, 28), color=(2, 1.4, 0), texture=_classicassets.textures.achievement_outline.get(), ) bui.textwidget( parent=self._subcontainer, position=( margin_left + content_width * 0.19, basey - 19 - incr * i + 3, ), maxwidth=content_width * 0.62, scale=0.6, flatness=1.0, shadow=0.0, color=(1, 1, 1) if complete else (1, 1, 1, 0.2), text=ach.display_name_langstr, size=(0, 0), h_align='left', v_align='center', ) bui.textwidget( parent=self._subcontainer, position=( margin_left + content_width * 0.19, basey - 19 - incr * i - 10, ), maxwidth=content_width * 0.62, scale=0.4, flatness=1.0, shadow=0.0, color=(0.83, 0.8, 0.85) if complete else (0.8, 0.8, 0.8, 0.2), text=( ach.description_full_complete_langstr if complete else ach.description_full_langstr ), size=(0, 0), h_align='left', v_align='center', ) chest_type = ach.get_award_chest_type() chestdisplayinfo = CHEST_APPEARANCE_DISPLAY_INFOS.get( chest_type, CHEST_APPEARANCE_DISPLAY_INFO_DEFAULT ) chestsize = 24.0 bui.imagewidget( parent=self._subcontainer, opacity=0.0 if complete else 1.0, position=( margin_left + content_width * 0.92 - 40.0 - chestsize * 0.5, basey - 20 - incr * i - chestsize * 0.5, ), size=(chestsize, chestsize), color=chestdisplayinfo.color, texture=chestdisplayinfo.texclosed.ui().get(), tint_texture=chestdisplayinfo.texclosedtint.ui().get(), tint_color=chestdisplayinfo.tint, tint2_color=chestdisplayinfo.tint2, ) pts = ach.power_ranking_value bui.textwidget( parent=self._subcontainer, position=( margin_left + content_width * 0.92, basey - 20 - incr * i, ), maxwidth=content_width * 0.15, color=(0.7, 0.8, 1.0) if complete else (0.9, 0.9, 1.0, 0.3), flatness=1.0, shadow=0.0, scale=0.6, text=_classicassets.strings.coop.power_ranking_points( number=str(pts) ), size=(0, 0), h_align='center', v_align='center', ) if complete: total_pts += pts bui.textwidget( parent=self._subcontainer, position=( margin_left + content_width * 1.0, basey - 20 - incr * len(achievements), ), maxwidth=content_width * 0.5, scale=0.7, color=(0.7, 0.8, 1.0), flatness=1.0, shadow=0.0, text=_commonassets.strings.compose.spaced_pair( first=_commonassets.strings.values.total, second=( _classicassets.strings.league ).power_ranking_points_equals(number=str(total_pts)), ), size=(0, 0), h_align='right', v_align='center', )
[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
# 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) import asyncio from concurrent.futures import Future from pathlib import Path from enum import Enum