From cc1ec205c075e30ae4eedc5b470cd4783d51865f Mon Sep 17 00:00:00 2001 From: Eric Froemling Date: Tue, 20 Oct 2020 14:45:08 -0700 Subject: [PATCH] Updated store browser to use new type safe TabRow --- .../python/bastd/ui/characterpicker.py | 13 +- .../ba_data/python/bastd/ui/coop/browser.py | 8 +- .../src/ba_data/python/bastd/ui/iconpicker.py | 12 +- .../python/bastd/ui/playlist/addgame.py | 14 +- .../python/bastd/ui/playlist/mapselect.py | 10 +- .../ba_data/python/bastd/ui/store/browser.py | 145 ++++++++++-------- assets/src/ba_data/python/bastd/ui/watch.py | 40 ++--- 7 files changed, 131 insertions(+), 111 deletions(-) diff --git a/assets/src/ba_data/python/bastd/ui/characterpicker.py b/assets/src/ba_data/python/bastd/ui/characterpicker.py index 8b0dc502..6f57a20b 100644 --- a/assets/src/ba_data/python/bastd/ui/characterpicker.py +++ b/assets/src/ba_data/python/bastd/ui/characterpicker.py @@ -154,16 +154,15 @@ class CharacterPicker(popup.PopupWindow): ba.widget(edit=btn, show_buffer_top=30, show_buffer_bottom=30) def _on_store_press(self) -> None: - from bastd.ui import account - from bastd.ui.store import browser + from bastd.ui.account import show_sign_in_prompt + from bastd.ui.store.browser import StoreBrowserWindow if _ba.get_account_state() != 'signed_in': - account.show_sign_in_prompt() + show_sign_in_prompt() return self._transition_out() - browser.StoreBrowserWindow( - modal=True, - show_tab='characters', - origin_widget=self._get_more_characters_button) + StoreBrowserWindow(modal=True, + show_tab=StoreBrowserWindow.TabID.CHARACTERS, + origin_widget=self._get_more_characters_button) def _select_character(self, character: str) -> None: if self._delegate is not None: diff --git a/assets/src/ba_data/python/bastd/ui/coop/browser.py b/assets/src/ba_data/python/bastd/ui/coop/browser.py index 20b369cb..a09f4f54 100644 --- a/assets/src/ba_data/python/bastd/ui/coop/browser.py +++ b/assets/src/ba_data/python/bastd/ui/coop/browser.py @@ -13,6 +13,7 @@ import _ba import ba from bastd.ui.store.button import StoreButton from bastd.ui.league.rankbutton import LeagueRankButton +from bastd.ui.store.browser import StoreBrowserWindow if TYPE_CHECKING: from typing import Any, Optional, Tuple, Dict, List, Union @@ -1352,10 +1353,13 @@ class CoopBrowserWindow(ba.Window): LeagueRankWindow(origin_widget=self._league_rank_button.get_button( )).get_root_widget()) - def _switch_to_score(self, show_tab: Optional[str] = 'extras') -> None: + def _switch_to_score( + self, + show_tab: Optional[ + StoreBrowserWindow.TabID] = StoreBrowserWindow.TabID.EXTRAS + ) -> None: # pylint: disable=cyclic-import from bastd.ui.account import show_sign_in_prompt - from bastd.ui.store.browser import StoreBrowserWindow if _ba.get_account_state() != 'signed_in': show_sign_in_prompt() return diff --git a/assets/src/ba_data/python/bastd/ui/iconpicker.py b/assets/src/ba_data/python/bastd/ui/iconpicker.py index 1cab6e56..1d1820f4 100644 --- a/assets/src/ba_data/python/bastd/ui/iconpicker.py +++ b/assets/src/ba_data/python/bastd/ui/iconpicker.py @@ -135,15 +135,15 @@ class IconPicker(popup.PopupWindow): ba.widget(edit=btn, show_buffer_top=30, show_buffer_bottom=30) def _on_store_press(self) -> None: - from bastd.ui import account - from bastd.ui.store import browser + from bastd.ui.account import show_sign_in_prompt + from bastd.ui.store.browser import StoreBrowserWindow if _ba.get_account_state() != 'signed_in': - account.show_sign_in_prompt() + show_sign_in_prompt() return self._transition_out() - browser.StoreBrowserWindow(modal=True, - show_tab='icons', - origin_widget=self._get_more_icons_button) + StoreBrowserWindow(modal=True, + show_tab=StoreBrowserWindow.TabID.ICONS, + origin_widget=self._get_more_icons_button) def _select_icon(self, icon: str) -> None: if self._delegate is not None: diff --git a/assets/src/ba_data/python/bastd/ui/playlist/addgame.py b/assets/src/ba_data/python/bastd/ui/playlist/addgame.py index bcc183d7..af51c57e 100644 --- a/assets/src/ba_data/python/bastd/ui/playlist/addgame.py +++ b/assets/src/ba_data/python/bastd/ui/playlist/addgame.py @@ -174,15 +174,15 @@ class PlaylistAddGameWindow(ba.Window): visible_child=self._get_more_games_button) def _on_get_more_games_press(self) -> None: - from bastd.ui import account - from bastd.ui.store import browser + from bastd.ui.account import show_sign_in_prompt + from bastd.ui.store.browser import StoreBrowserWindow if _ba.get_account_state() != 'signed_in': - account.show_sign_in_prompt() + show_sign_in_prompt() return - browser.StoreBrowserWindow(modal=True, - show_tab='minigames', - on_close_call=self._on_store_close, - origin_widget=self._get_more_games_button) + StoreBrowserWindow(modal=True, + show_tab=StoreBrowserWindow.TabID.MINIGAMES, + on_close_call=self._on_store_close, + origin_widget=self._get_more_games_button) def _on_store_close(self) -> None: self._refresh(select_get_more_games_button=True) diff --git a/assets/src/ba_data/python/bastd/ui/playlist/mapselect.py b/assets/src/ba_data/python/bastd/ui/playlist/mapselect.py index d7b5201c..5c27c718 100644 --- a/assets/src/ba_data/python/bastd/ui/playlist/mapselect.py +++ b/assets/src/ba_data/python/bastd/ui/playlist/mapselect.py @@ -209,14 +209,14 @@ class PlaylistMapSelectWindow(ba.Window): def _on_store_press(self) -> None: from bastd.ui import account - from bastd.ui.store import browser + from bastd.ui.store.browser import StoreBrowserWindow if _ba.get_account_state() != 'signed_in': account.show_sign_in_prompt() return - browser.StoreBrowserWindow(modal=True, - show_tab='maps', - on_close_call=self._on_store_close, - origin_widget=self._get_more_maps_button) + StoreBrowserWindow(modal=True, + show_tab=StoreBrowserWindow.TabID.MAPS, + on_close_call=self._on_store_close, + origin_widget=self._get_more_maps_button) def _on_store_close(self) -> None: self._refresh(select_get_more_maps_button=True) diff --git a/assets/src/ba_data/python/bastd/ui/store/browser.py b/assets/src/ba_data/python/bastd/ui/store/browser.py index 5ea8ad30..85a697fa 100644 --- a/assets/src/ba_data/python/bastd/ui/store/browser.py +++ b/assets/src/ba_data/python/bastd/ui/store/browser.py @@ -7,6 +7,7 @@ from __future__ import annotations import copy import math import weakref +from enum import Enum from typing import TYPE_CHECKING import _ba @@ -20,25 +21,24 @@ if TYPE_CHECKING: class StoreBrowserWindow(ba.Window): """Window for browsing the store.""" - def _update_get_tickets_button_pos(self) -> None: - uiscale = ba.app.ui.uiscale - if self._get_tickets_button: - pos = (self._width - 252 - - (self._x_inset + (47 if uiscale is ba.UIScale.SMALL - and _ba.is_party_icon_visible() else 0)), - self._height - 70) - ba.buttonwidget(edit=self._get_tickets_button, position=pos) + class TabID(Enum): + """Our available tab types.""" + EXTRAS = 'extras' + MAPS = 'maps' + MINIGAMES = 'minigames' + CHARACTERS = 'characters' + ICONS = 'icons' def __init__(self, transition: str = 'in_right', modal: bool = False, - show_tab: str = None, + show_tab: StoreBrowserWindow.TabID = None, on_close_call: Callable[[], Any] = None, back_location: str = None, origin_widget: ba.Widget = None): # pylint: disable=too-many-statements # pylint: disable=too-many-locals - from bastd.ui import tabs + from bastd.ui.tabs import TabRow from ba import SpecialChar app = ba.app @@ -69,7 +69,7 @@ class StoreBrowserWindow(ba.Window): self._x_inset = x_inset = 100 if uiscale is ba.UIScale.SMALL else 0 self._height = (578 if uiscale is ba.UIScale.SMALL else 645 if uiscale is ba.UIScale.MEDIUM else 800) - self._current_tab: Optional[str] = None + self._current_tab: Optional[StoreBrowserWindow.TabID] = None extra_top = 30 if uiscale is ba.UIScale.SMALL else 0 self._request: Any = None @@ -159,29 +159,29 @@ class StoreBrowserWindow(ba.Window): tab_buffer_h = 250 + 2 * x_inset tabs_def = [ - ('extras', ba.Lstr(resource=self._r + '.extrasText')), - ('maps', ba.Lstr(resource=self._r + '.mapsText')), - ('minigames', ba.Lstr(resource=self._r + '.miniGamesText')), - ('characters', ba.Lstr(resource=self._r + '.charactersText')), - ('icons', ba.Lstr(resource=self._r + '.iconsText')), + (self.TabID.EXTRAS, ba.Lstr(resource=self._r + '.extrasText')), + (self.TabID.MAPS, ba.Lstr(resource=self._r + '.mapsText')), + (self.TabID.MINIGAMES, + ba.Lstr(resource=self._r + '.miniGamesText')), + (self.TabID.CHARACTERS, + ba.Lstr(resource=self._r + '.charactersText')), + (self.TabID.ICONS, ba.Lstr(resource=self._r + '.iconsText')), ] - tab_results = tabs.create_tab_buttons(self._root_widget, - tabs_def, - pos=(tab_buffer_h * 0.5, - self._height - 130), - size=(self._width - tab_buffer_h, - 50), - on_select_call=self._set_tab, - return_extra_info=True) + self._tab_row = TabRow(self._root_widget, + tabs_def, + pos=(tab_buffer_h * 0.5, self._height - 130), + size=(self._width - tab_buffer_h, 50), + on_select_call=self._set_tab) - self._purchasable_count_widgets: Dict[str, Dict[str, Any]] = {} + self._purchasable_count_widgets: Dict[StoreBrowserWindow.TabID, + Dict[str, Any]] = {} # Create our purchasable-items tags and have them update over time. - for i, tab in enumerate(tabs_def): - pos = tab_results['positions'][i] - size = tab_results['sizes'][i] - button = tab_results['buttons_indexed'][i] + for tab_id, tab in self._tab_row.tabs.items(): + pos = tab.position + size = tab.size + button = tab.button rad = 10 center = (pos[0] + 0.1 * size[0], pos[1] + 0.9 * size[1]) img = ba.imagewidget(parent=self._root_widget, @@ -231,7 +231,7 @@ class StoreBrowserWindow(ba.Window): shadow=0.0, flatness=1.0, color=(0, 1, 0)) - self._purchasable_count_widgets[tab[0]] = { + self._purchasable_count_widgets[tab_id] = { 'img': img, 'text': txt, 'sale_img': sale_img, @@ -244,10 +244,8 @@ class StoreBrowserWindow(ba.Window): repeat=True) self._update_tabs() - self._tab_buttons = tab_results['buttons'] - if self._get_tickets_button is not None: - last_tab_button = self._tab_buttons[tabs_def[-1][0]] + last_tab_button = self._tab_row.tabs[tabs_def[-1][0]].button ba.widget(edit=self._get_tickets_button, down_widget=last_tab_button) ba.widget(edit=last_tab_button, @@ -261,6 +259,15 @@ class StoreBrowserWindow(ba.Window): self._status_textwidget: Optional[ba.Widget] = None self._restore_state() + def _update_get_tickets_button_pos(self) -> None: + uiscale = ba.app.ui.uiscale + if self._get_tickets_button: + pos = (self._width - 252 - + (self._x_inset + (47 if uiscale is ba.UIScale.SMALL + and _ba.is_party_icon_visible() else 0)), + self._height - 70) + ba.buttonwidget(edit=self._get_tickets_button, position=pos) + def _restore_purchases(self) -> None: from bastd.ui import account if _ba.get_account_state() != 'signed_in': @@ -273,9 +280,8 @@ class StoreBrowserWindow(ba.Window): get_available_purchase_count) if not self._root_widget: return - for tab_name, tab_data in list( - self._purchasable_count_widgets.items()): - sale_time = get_available_sale_time(tab_name) + for tab_id, tab_data in list(self._purchasable_count_widgets.items()): + sale_time = get_available_sale_time(tab_id.value) if sale_time is not None: ba.textwidget(edit=tab_data['sale_title_text'], @@ -291,7 +297,7 @@ class StoreBrowserWindow(ba.Window): ba.textwidget(edit=tab_data['sale_title_text'], text='') ba.textwidget(edit=tab_data['sale_time_text'], text='') ba.imagewidget(edit=tab_data['sale_img'], opacity=0.0) - count = get_available_purchase_count(tab_name) + count = get_available_purchase_count(tab_id.value) if count > 0: ba.textwidget(edit=tab_data['text'], text=str(count)) @@ -312,19 +318,18 @@ class StoreBrowserWindow(ba.Window): sval = ba.Lstr(resource='getTicketsWindow.titleText') ba.buttonwidget(edit=self._get_tickets_button, label=sval) - def _set_tab(self, tab: str) -> None: - from bastd.ui import tabs - if self._current_tab == tab: + def _set_tab(self, tab_id: TabID) -> None: + if self._current_tab is tab_id: return - self._current_tab = tab + self._current_tab = tab_id # We wanna preserve our current tab between runs. cfg = ba.app.config - cfg['Store Tab'] = tab + cfg['Store Tab'] = tab_id.value cfg.commit() # Update tab colors based on which is selected. - tabs.update_tab_button_colors(self._tab_buttons, tab) + self._tab_row.update_appearance(tab_id) # (Re)create scroll widget. if self._scrollwidget: @@ -362,7 +367,7 @@ class StoreBrowserWindow(ba.Window): def __init__(self, window: StoreBrowserWindow): self._window = weakref.ref(window) - data = {'tab': tab} + data = {'tab': tab_id.value} ba.timer(0.1, ba.WeakCall(self._on_response, data), timetype=ba.TimeType.REAL) @@ -942,13 +947,14 @@ class StoreBrowserWindow(ba.Window): # Also update them immediately. self._store_window.update_buttons() - if self._current_tab in ('extras', 'minigames', 'characters', - 'maps', 'icons'): + if self._current_tab in (self.TabID.EXTRAS, self.TabID.MINIGAMES, + self.TabID.CHARACTERS, self.TabID.MAPS, + self.TabID.ICONS): store = _Store(self, data, self._scroll_width) assert self._scrollwidget is not None store.instantiate( scrollwidget=self._scrollwidget, - tab_button=self._tab_buttons[self._current_tab]) + tab_button=self._tab_row.tabs[self._current_tab].button) else: cnt = ba.containerwidget(parent=self._scrollwidget, scale=1.0, @@ -974,20 +980,23 @@ class StoreBrowserWindow(ba.Window): def _save_state(self) -> None: try: sel = self._root_widget.get_selected_child() + selected_tab_ids = [ + tab_id for tab_id, tab in self._tab_row.tabs.items() + if sel == tab.button + ] if sel == self._get_tickets_button: sel_name = 'GetTickets' elif sel == self._scrollwidget: sel_name = 'Scroll' elif sel == self._back_button: sel_name = 'Back' - elif sel in list(self._tab_buttons.values()): - sel_name = 'Tab:' + list(self._tab_buttons.keys())[list( - self._tab_buttons.values()).index(sel)] + elif selected_tab_ids: + assert len(selected_tab_ids) == 1 + sel_name = f'Tab:{selected_tab_ids[0].value}' else: raise ValueError(f'unrecognized selection \'{sel}\'') ba.app.ui.window_states[self.__class__.__name__] = { 'sel_name': sel_name, - 'tab': self._current_tab } except Exception: ba.print_exception(f'Error saving state for {self}.') @@ -997,11 +1006,14 @@ class StoreBrowserWindow(ba.Window): sel: Optional[ba.Widget] sel_name = ba.app.ui.window_states.get(self.__class__.__name__, {}).get('sel_name') - current_tab = ba.app.config.get('Store Tab') + assert isinstance(sel_name, (str, type(None))) + + try: + current_tab = self.TabID(ba.app.config.get('Store Tab')) + except ValueError: + current_tab = self.TabID.CHARACTERS if self._show_tab is not None: current_tab = self._show_tab - if current_tab is None or current_tab not in self._tab_buttons: - current_tab = 'characters' if sel_name == 'GetTickets': sel = self._get_tickets_button elif sel_name == 'Back': @@ -1009,13 +1021,18 @@ class StoreBrowserWindow(ba.Window): elif sel_name == 'Scroll': sel = self._scrollwidget elif isinstance(sel_name, str) and sel_name.startswith('Tab:'): - sel = self._tab_buttons[sel_name.split(':')[-1]] + try: + sel_tab_id = self.TabID(sel_name.split(':')[-1]) + except ValueError: + sel_tab_id = self.TabID.CHARACTERS + sel = self._tab_row.tabs[sel_tab_id].button else: - sel = self._tab_buttons[current_tab] - # if we were requested to show a tab, select it too.. + sel = self._tab_row.tabs[current_tab].button + + # If we were requested to show a tab, select it too.. if (self._show_tab is not None - and self._show_tab in self._tab_buttons): - sel = self._tab_buttons[self._show_tab] + and self._show_tab in self._tab_row.tabs): + sel = self._tab_row.tabs[self._show_tab].button self._set_tab(current_tab) if sel is not None: ba.containerwidget(edit=self._root_widget, selected_child=sel) @@ -1039,19 +1056,17 @@ class StoreBrowserWindow(ba.Window): def _back(self) -> None: # pylint: disable=cyclic-import - from bastd.ui.coop import browser - from bastd.ui import mainmenu + from bastd.ui.coop.browser import CoopBrowserWindow + from bastd.ui.mainmenu import MainMenuWindow self._save_state() ba.containerwidget(edit=self._root_widget, transition=self._transition_out) if not self._modal: if self._back_location == 'CoopBrowserWindow': ba.app.ui.set_main_menu_window( - browser.CoopBrowserWindow( - transition='in_left').get_root_widget()) + CoopBrowserWindow(transition='in_left').get_root_widget()) else: ba.app.ui.set_main_menu_window( - mainmenu.MainMenuWindow( - transition='in_left').get_root_widget()) + MainMenuWindow(transition='in_left').get_root_widget()) if self._on_close_call is not None: self._on_close_call() diff --git a/assets/src/ba_data/python/bastd/ui/watch.py b/assets/src/ba_data/python/bastd/ui/watch.py index c286a9a9..e5740ff3 100644 --- a/assets/src/ba_data/python/bastd/ui/watch.py +++ b/assets/src/ba_data/python/bastd/ui/watch.py @@ -15,15 +15,14 @@ if TYPE_CHECKING: from typing import Any, Optional, Tuple, Dict -class TabID(Enum): - """Our available tab types.""" - MY_REPLAYS = 'my_replays' - TEST_TAB = 'test_tab' - - class WatchWindow(ba.Window): """Window for watching replays.""" + class TabID(Enum): + """Our available tab types.""" + MY_REPLAYS = 'my_replays' + TEST_TAB = 'test_tab' + def __init__(self, transition: Optional[str] = 'in_right', origin_widget: ba.Widget = None): @@ -54,7 +53,7 @@ class WatchWindow(ba.Window): x_inset = 100 if uiscale is ba.UIScale.SMALL else 0 self._height = (578 if uiscale is ba.UIScale.SMALL else 670 if uiscale is ba.UIScale.MEDIUM else 800) - self._current_tab: Optional[TabID] = None + self._current_tab: Optional[WatchWindow.TabID] = None extra_top = 20 if uiscale is ba.UIScale.SMALL else 0 super().__init__(root_widget=ba.containerwidget( @@ -98,8 +97,9 @@ class WatchWindow(ba.Window): maxwidth=400) tabdefs = [ - (TabID.MY_REPLAYS, ba.Lstr(resource=self._r + '.myReplaysText')), - # (TabID.TEST_TAB, ba.Lstr(value='Testing')), + (self.TabID.MY_REPLAYS, + ba.Lstr(resource=self._r + '.myReplaysText')), + # (self.TabID.TEST_TAB, ba.Lstr(value='Testing')), ] scroll_buffer_h = 130 + 2 * x_inset @@ -167,7 +167,7 @@ class WatchWindow(ba.Window): self._tab_data = {} uiscale = ba.app.ui.uiscale - if tab_id is TabID.MY_REPLAYS: + if tab_id is self.TabID.MY_REPLAYS: c_width = self._scroll_width c_height = self._scroll_height - 20 sub_scroll_height = c_height - 63 @@ -366,15 +366,16 @@ class WatchWindow(ba.Window): new_name_raw = cast( str, ba.textwidget(query=self._my_replay_rename_text)) new_name = new_name_raw + '.brp' - # ignore attempts to change it to what it already is - # (or what it looks like to the user) + + # Ignore attempts to change it to what it already is + # (or what it looks like to the user). if (replay != new_name and self._get_replay_display_name(replay) != new_name_raw): old_name_full = (_ba.get_replays_dir() + '/' + replay).encode('utf-8') new_name_full = (_ba.get_replays_dir() + '/' + new_name).encode('utf-8') - # false alarm; ba.textwidget can return non-None val + # False alarm; ba.textwidget can return non-None val. # pylint: disable=unsupported-membership-test if os.path.exists(new_name_full): ba.playsound(ba.getsound('error')) @@ -449,7 +450,8 @@ class WatchWindow(ba.Window): t_scale = 1.6 try: names = os.listdir(_ba.get_replays_dir()) - # ignore random other files in there.. + + # Ignore random other files in there. names = [n for n in names if n.endswith('.brp')] names.sort(key=lambda x: x.lower()) except Exception: @@ -476,7 +478,7 @@ class WatchWindow(ba.Window): if i == 0: ba.widget( edit=txt, - up_widget=self._tab_row.tabs[TabID.MY_REPLAYS].button) + up_widget=self._tab_row.tabs[self.TabID.MY_REPLAYS].button) def _save_state(self) -> None: try: @@ -506,9 +508,9 @@ class WatchWindow(ba.Window): {}).get('sel_name') assert isinstance(sel_name, (str, type(None))) try: - current_tab = TabID(ba.app.config.get('Watch Tab')) + current_tab = self.TabID(ba.app.config.get('Watch Tab')) except ValueError: - current_tab = TabID.MY_REPLAYS + current_tab = self.TabID.MY_REPLAYS self._set_tab(current_tab) if sel_name == 'Back': @@ -517,9 +519,9 @@ class WatchWindow(ba.Window): sel = self._tab_container elif isinstance(sel_name, str) and sel_name.startswith('Tab:'): try: - sel_tab_id = TabID(sel_name.split(':')[-1]) + sel_tab_id = self.TabID(sel_name.split(':')[-1]) except ValueError: - sel_tab_id = TabID.MY_REPLAYS + sel_tab_id = self.TabID.MY_REPLAYS sel = self._tab_row.tabs[sel_tab_id].button else: if self._tab_container is not None: