bastd.ui.profile.browser
UI functionality related to browsing player profiles.
1# Released under the MIT License. See LICENSE for details. 2# 3"""UI functionality related to browsing player profiles.""" 4 5from __future__ import annotations 6 7from typing import TYPE_CHECKING 8 9import _ba 10import ba 11 12if TYPE_CHECKING: 13 from typing import Any 14 15 16class ProfileBrowserWindow(ba.Window): 17 """Window for browsing player profiles.""" 18 19 def __init__(self, 20 transition: str = 'in_right', 21 in_main_menu: bool = True, 22 selected_profile: str | None = None, 23 origin_widget: ba.Widget | None = None): 24 # pylint: disable=too-many-statements 25 # pylint: disable=too-many-locals 26 self._in_main_menu = in_main_menu 27 if self._in_main_menu: 28 back_label = ba.Lstr(resource='backText') 29 else: 30 back_label = ba.Lstr(resource='doneText') 31 uiscale = ba.app.ui.uiscale 32 self._width = 700.0 if uiscale is ba.UIScale.SMALL else 600.0 33 x_inset = 50.0 if uiscale is ba.UIScale.SMALL else 0.0 34 self._height = (360.0 if uiscale is ba.UIScale.SMALL else 35 385.0 if uiscale is ba.UIScale.MEDIUM else 410.0) 36 37 # If we're being called up standalone, handle pause/resume ourself. 38 if not self._in_main_menu: 39 ba.app.pause() 40 41 # If they provided an origin-widget, scale up from that. 42 scale_origin: tuple[float, float] | None 43 if origin_widget is not None: 44 self._transition_out = 'out_scale' 45 scale_origin = origin_widget.get_screen_space_center() 46 transition = 'in_scale' 47 else: 48 self._transition_out = 'out_right' 49 scale_origin = None 50 51 self._r = 'playerProfilesWindow' 52 53 # Ensure we've got an account-profile in cases where we're signed in. 54 ba.app.accounts_v1.ensure_have_account_player_profile() 55 56 top_extra = 20 if uiscale is ba.UIScale.SMALL else 0 57 58 super().__init__(root_widget=ba.containerwidget( 59 size=(self._width, self._height + top_extra), 60 transition=transition, 61 scale_origin_stack_offset=scale_origin, 62 scale=(2.2 if uiscale is ba.UIScale.SMALL else 63 1.6 if uiscale is ba.UIScale.MEDIUM else 1.0), 64 stack_offset=(0, -14) if uiscale is ba.UIScale.SMALL else (0, 0))) 65 66 self._back_button = btn = ba.buttonwidget( 67 parent=self._root_widget, 68 position=(40 + x_inset, self._height - 59), 69 size=(120, 60), 70 scale=0.8, 71 label=back_label, 72 button_type='back' if self._in_main_menu else None, 73 autoselect=True, 74 on_activate_call=self._back) 75 ba.containerwidget(edit=self._root_widget, cancel_button=btn) 76 77 ba.textwidget(parent=self._root_widget, 78 position=(self._width * 0.5, self._height - 36), 79 size=(0, 0), 80 text=ba.Lstr(resource=self._r + '.titleText'), 81 maxwidth=300, 82 color=ba.app.ui.title_color, 83 scale=0.9, 84 h_align='center', 85 v_align='center') 86 87 if self._in_main_menu: 88 ba.buttonwidget(edit=btn, 89 button_type='backSmall', 90 size=(60, 60), 91 label=ba.charstr(ba.SpecialChar.BACK)) 92 93 scroll_height = self._height - 140.0 94 self._scroll_width = self._width - (188 + x_inset * 2) 95 v = self._height - 84.0 96 h = 50 + x_inset 97 b_color = (0.6, 0.53, 0.63) 98 99 scl = (1.055 if uiscale is ba.UIScale.SMALL else 100 1.18 if uiscale is ba.UIScale.MEDIUM else 1.3) 101 v -= 70.0 * scl 102 self._new_button = ba.buttonwidget(parent=self._root_widget, 103 position=(h, v), 104 size=(80, 66.0 * scl), 105 on_activate_call=self._new_profile, 106 color=b_color, 107 button_type='square', 108 autoselect=True, 109 textcolor=(0.75, 0.7, 0.8), 110 text_scale=0.7, 111 label=ba.Lstr(resource=self._r + 112 '.newButtonText')) 113 v -= 70.0 * scl 114 self._edit_button = ba.buttonwidget( 115 parent=self._root_widget, 116 position=(h, v), 117 size=(80, 66.0 * scl), 118 on_activate_call=self._edit_profile, 119 color=b_color, 120 button_type='square', 121 autoselect=True, 122 textcolor=(0.75, 0.7, 0.8), 123 text_scale=0.7, 124 label=ba.Lstr(resource=self._r + '.editButtonText')) 125 v -= 70.0 * scl 126 self._delete_button = ba.buttonwidget( 127 parent=self._root_widget, 128 position=(h, v), 129 size=(80, 66.0 * scl), 130 on_activate_call=self._delete_profile, 131 color=b_color, 132 button_type='square', 133 autoselect=True, 134 textcolor=(0.75, 0.7, 0.8), 135 text_scale=0.7, 136 label=ba.Lstr(resource=self._r + '.deleteButtonText')) 137 138 v = self._height - 87 139 140 ba.textwidget(parent=self._root_widget, 141 position=(self._width * 0.5, self._height - 71), 142 size=(0, 0), 143 text=ba.Lstr(resource=self._r + '.explanationText'), 144 color=ba.app.ui.infotextcolor, 145 maxwidth=self._width * 0.83, 146 scale=0.6, 147 h_align='center', 148 v_align='center') 149 150 self._scrollwidget = ba.scrollwidget(parent=self._root_widget, 151 highlight=False, 152 position=(140 + x_inset, 153 v - scroll_height), 154 size=(self._scroll_width, 155 scroll_height)) 156 ba.widget(edit=self._scrollwidget, 157 autoselect=True, 158 left_widget=self._new_button) 159 ba.containerwidget(edit=self._root_widget, 160 selected_child=self._scrollwidget) 161 self._columnwidget = ba.columnwidget(parent=self._scrollwidget, 162 border=2, 163 margin=0) 164 v -= 255 165 self._profiles: dict[str, dict[str, Any]] | None = None 166 self._selected_profile = selected_profile 167 self._profile_widgets: list[ba.Widget] = [] 168 self._refresh() 169 self._restore_state() 170 171 def _new_profile(self) -> None: 172 # pylint: disable=cyclic-import 173 from bastd.ui.profile.edit import EditProfileWindow 174 from bastd.ui.purchase import PurchaseWindow 175 176 # Limit to a handful profiles if they don't have pro-options. 177 max_non_pro_profiles = _ba.get_v1_account_misc_read_val('mnpp', 5) 178 assert self._profiles is not None 179 if (not ba.app.accounts_v1.have_pro_options() 180 and len(self._profiles) >= max_non_pro_profiles): 181 PurchaseWindow(items=['pro'], 182 header_text=ba.Lstr( 183 resource='unlockThisProfilesText', 184 subs=[('${NUM}', str(max_non_pro_profiles))])) 185 return 186 187 # Clamp at 100 profiles (otherwise the server will and that's less 188 # elegant looking). 189 if len(self._profiles) > 100: 190 ba.screenmessage( 191 ba.Lstr(translate=('serverResponses', 192 'Max number of profiles reached.')), 193 color=(1, 0, 0)) 194 ba.playsound(ba.getsound('error')) 195 return 196 197 self._save_state() 198 ba.containerwidget(edit=self._root_widget, transition='out_left') 199 ba.app.ui.set_main_menu_window( 200 EditProfileWindow( 201 existing_profile=None, 202 in_main_menu=self._in_main_menu).get_root_widget()) 203 204 def _delete_profile(self) -> None: 205 # pylint: disable=cyclic-import 206 from bastd.ui import confirm 207 if self._selected_profile is None: 208 ba.playsound(ba.getsound('error')) 209 ba.screenmessage(ba.Lstr(resource='nothingIsSelectedErrorText'), 210 color=(1, 0, 0)) 211 return 212 if self._selected_profile == '__account__': 213 ba.playsound(ba.getsound('error')) 214 ba.screenmessage(ba.Lstr(resource=self._r + 215 '.cantDeleteAccountProfileText'), 216 color=(1, 0, 0)) 217 return 218 confirm.ConfirmWindow( 219 ba.Lstr(resource=self._r + '.deleteConfirmText', 220 subs=[('${PROFILE}', self._selected_profile)]), 221 self._do_delete_profile, 350) 222 223 def _do_delete_profile(self) -> None: 224 _ba.add_transaction({ 225 'type': 'REMOVE_PLAYER_PROFILE', 226 'name': self._selected_profile 227 }) 228 _ba.run_transactions() 229 ba.playsound(ba.getsound('shieldDown')) 230 self._refresh() 231 232 # Select profile list. 233 ba.containerwidget(edit=self._root_widget, 234 selected_child=self._scrollwidget) 235 236 def _edit_profile(self) -> None: 237 # pylint: disable=cyclic-import 238 from bastd.ui.profile.edit import EditProfileWindow 239 if self._selected_profile is None: 240 ba.playsound(ba.getsound('error')) 241 ba.screenmessage(ba.Lstr(resource='nothingIsSelectedErrorText'), 242 color=(1, 0, 0)) 243 return 244 self._save_state() 245 ba.containerwidget(edit=self._root_widget, transition='out_left') 246 ba.app.ui.set_main_menu_window( 247 EditProfileWindow( 248 self._selected_profile, 249 in_main_menu=self._in_main_menu).get_root_widget()) 250 251 def _select(self, name: str, index: int) -> None: 252 del index # Unused. 253 self._selected_profile = name 254 255 def _back(self) -> None: 256 # pylint: disable=cyclic-import 257 from bastd.ui.account.settings import AccountSettingsWindow 258 self._save_state() 259 ba.containerwidget(edit=self._root_widget, 260 transition=self._transition_out) 261 if self._in_main_menu: 262 ba.app.ui.set_main_menu_window( 263 AccountSettingsWindow(transition='in_left').get_root_widget()) 264 265 # If we're being called up standalone, handle pause/resume ourself. 266 else: 267 ba.app.resume() 268 269 def _refresh(self) -> None: 270 # pylint: disable=too-many-locals 271 from efro.util import asserttype 272 from ba.internal import (PlayerProfilesChangedMessage, 273 get_player_profile_colors, 274 get_player_profile_icon) 275 old_selection = self._selected_profile 276 277 # Delete old. 278 while self._profile_widgets: 279 self._profile_widgets.pop().delete() 280 self._profiles = ba.app.config.get('Player Profiles', {}) 281 assert self._profiles is not None 282 items = list(self._profiles.items()) 283 items.sort(key=lambda x: asserttype(x[0], str).lower()) 284 index = 0 285 account_name: str | None 286 if _ba.get_v1_account_state() == 'signed_in': 287 account_name = _ba.get_v1_account_display_string() 288 else: 289 account_name = None 290 widget_to_select = None 291 for p_name, _ in items: 292 if p_name == '__account__' and account_name is None: 293 continue 294 color, _highlight = get_player_profile_colors(p_name) 295 scl = 1.1 296 tval = (account_name if p_name == '__account__' else 297 get_player_profile_icon(p_name) + p_name) 298 assert isinstance(tval, str) 299 txtw = ba.textwidget( 300 parent=self._columnwidget, 301 position=(0, 32), 302 size=((self._width - 40) / scl, 28), 303 text=ba.Lstr(value=tval), 304 h_align='left', 305 v_align='center', 306 on_select_call=ba.WeakCall(self._select, p_name, index), 307 maxwidth=self._scroll_width * 0.92, 308 corner_scale=scl, 309 color=ba.safecolor(color, 0.4), 310 always_highlight=True, 311 on_activate_call=ba.Call(self._edit_button.activate), 312 selectable=True) 313 if index == 0: 314 ba.widget(edit=txtw, up_widget=self._back_button) 315 ba.widget(edit=txtw, show_buffer_top=40, show_buffer_bottom=40) 316 self._profile_widgets.append(txtw) 317 318 # Select/show this one if it was previously selected 319 # (but defer till after this loop since our height is 320 # still changing). 321 if p_name == old_selection: 322 widget_to_select = txtw 323 324 index += 1 325 326 if widget_to_select is not None: 327 ba.columnwidget(edit=self._columnwidget, 328 selected_child=widget_to_select, 329 visible_child=widget_to_select) 330 331 # If there's a team-chooser in existence, tell it the profile-list 332 # has probably changed. 333 session = _ba.get_foreground_host_session() 334 if session is not None: 335 session.handlemessage(PlayerProfilesChangedMessage()) 336 337 def _save_state(self) -> None: 338 try: 339 sel = self._root_widget.get_selected_child() 340 if sel == self._new_button: 341 sel_name = 'New' 342 elif sel == self._edit_button: 343 sel_name = 'Edit' 344 elif sel == self._delete_button: 345 sel_name = 'Delete' 346 elif sel == self._scrollwidget: 347 sel_name = 'Scroll' 348 else: 349 sel_name = 'Back' 350 ba.app.ui.window_states[type(self)] = sel_name 351 except Exception: 352 ba.print_exception(f'Error saving state for {self}.') 353 354 def _restore_state(self) -> None: 355 try: 356 sel_name = ba.app.ui.window_states.get(type(self)) 357 if sel_name == 'Scroll': 358 sel = self._scrollwidget 359 elif sel_name == 'New': 360 sel = self._new_button 361 elif sel_name == 'Delete': 362 sel = self._delete_button 363 elif sel_name == 'Edit': 364 sel = self._edit_button 365 elif sel_name == 'Back': 366 sel = self._back_button 367 else: 368 # By default we select our scroll widget if we have profiles; 369 # otherwise our new widget. 370 if not self._profile_widgets: 371 sel = self._new_button 372 else: 373 sel = self._scrollwidget 374 ba.containerwidget(edit=self._root_widget, selected_child=sel) 375 except Exception: 376 ba.print_exception(f'Error restoring state for {self}.')
class
ProfileBrowserWindow(ba.ui.Window):
17class ProfileBrowserWindow(ba.Window): 18 """Window for browsing player profiles.""" 19 20 def __init__(self, 21 transition: str = 'in_right', 22 in_main_menu: bool = True, 23 selected_profile: str | None = None, 24 origin_widget: ba.Widget | None = None): 25 # pylint: disable=too-many-statements 26 # pylint: disable=too-many-locals 27 self._in_main_menu = in_main_menu 28 if self._in_main_menu: 29 back_label = ba.Lstr(resource='backText') 30 else: 31 back_label = ba.Lstr(resource='doneText') 32 uiscale = ba.app.ui.uiscale 33 self._width = 700.0 if uiscale is ba.UIScale.SMALL else 600.0 34 x_inset = 50.0 if uiscale is ba.UIScale.SMALL else 0.0 35 self._height = (360.0 if uiscale is ba.UIScale.SMALL else 36 385.0 if uiscale is ba.UIScale.MEDIUM else 410.0) 37 38 # If we're being called up standalone, handle pause/resume ourself. 39 if not self._in_main_menu: 40 ba.app.pause() 41 42 # If they provided an origin-widget, scale up from that. 43 scale_origin: tuple[float, float] | None 44 if origin_widget is not None: 45 self._transition_out = 'out_scale' 46 scale_origin = origin_widget.get_screen_space_center() 47 transition = 'in_scale' 48 else: 49 self._transition_out = 'out_right' 50 scale_origin = None 51 52 self._r = 'playerProfilesWindow' 53 54 # Ensure we've got an account-profile in cases where we're signed in. 55 ba.app.accounts_v1.ensure_have_account_player_profile() 56 57 top_extra = 20 if uiscale is ba.UIScale.SMALL else 0 58 59 super().__init__(root_widget=ba.containerwidget( 60 size=(self._width, self._height + top_extra), 61 transition=transition, 62 scale_origin_stack_offset=scale_origin, 63 scale=(2.2 if uiscale is ba.UIScale.SMALL else 64 1.6 if uiscale is ba.UIScale.MEDIUM else 1.0), 65 stack_offset=(0, -14) if uiscale is ba.UIScale.SMALL else (0, 0))) 66 67 self._back_button = btn = ba.buttonwidget( 68 parent=self._root_widget, 69 position=(40 + x_inset, self._height - 59), 70 size=(120, 60), 71 scale=0.8, 72 label=back_label, 73 button_type='back' if self._in_main_menu else None, 74 autoselect=True, 75 on_activate_call=self._back) 76 ba.containerwidget(edit=self._root_widget, cancel_button=btn) 77 78 ba.textwidget(parent=self._root_widget, 79 position=(self._width * 0.5, self._height - 36), 80 size=(0, 0), 81 text=ba.Lstr(resource=self._r + '.titleText'), 82 maxwidth=300, 83 color=ba.app.ui.title_color, 84 scale=0.9, 85 h_align='center', 86 v_align='center') 87 88 if self._in_main_menu: 89 ba.buttonwidget(edit=btn, 90 button_type='backSmall', 91 size=(60, 60), 92 label=ba.charstr(ba.SpecialChar.BACK)) 93 94 scroll_height = self._height - 140.0 95 self._scroll_width = self._width - (188 + x_inset * 2) 96 v = self._height - 84.0 97 h = 50 + x_inset 98 b_color = (0.6, 0.53, 0.63) 99 100 scl = (1.055 if uiscale is ba.UIScale.SMALL else 101 1.18 if uiscale is ba.UIScale.MEDIUM else 1.3) 102 v -= 70.0 * scl 103 self._new_button = ba.buttonwidget(parent=self._root_widget, 104 position=(h, v), 105 size=(80, 66.0 * scl), 106 on_activate_call=self._new_profile, 107 color=b_color, 108 button_type='square', 109 autoselect=True, 110 textcolor=(0.75, 0.7, 0.8), 111 text_scale=0.7, 112 label=ba.Lstr(resource=self._r + 113 '.newButtonText')) 114 v -= 70.0 * scl 115 self._edit_button = ba.buttonwidget( 116 parent=self._root_widget, 117 position=(h, v), 118 size=(80, 66.0 * scl), 119 on_activate_call=self._edit_profile, 120 color=b_color, 121 button_type='square', 122 autoselect=True, 123 textcolor=(0.75, 0.7, 0.8), 124 text_scale=0.7, 125 label=ba.Lstr(resource=self._r + '.editButtonText')) 126 v -= 70.0 * scl 127 self._delete_button = ba.buttonwidget( 128 parent=self._root_widget, 129 position=(h, v), 130 size=(80, 66.0 * scl), 131 on_activate_call=self._delete_profile, 132 color=b_color, 133 button_type='square', 134 autoselect=True, 135 textcolor=(0.75, 0.7, 0.8), 136 text_scale=0.7, 137 label=ba.Lstr(resource=self._r + '.deleteButtonText')) 138 139 v = self._height - 87 140 141 ba.textwidget(parent=self._root_widget, 142 position=(self._width * 0.5, self._height - 71), 143 size=(0, 0), 144 text=ba.Lstr(resource=self._r + '.explanationText'), 145 color=ba.app.ui.infotextcolor, 146 maxwidth=self._width * 0.83, 147 scale=0.6, 148 h_align='center', 149 v_align='center') 150 151 self._scrollwidget = ba.scrollwidget(parent=self._root_widget, 152 highlight=False, 153 position=(140 + x_inset, 154 v - scroll_height), 155 size=(self._scroll_width, 156 scroll_height)) 157 ba.widget(edit=self._scrollwidget, 158 autoselect=True, 159 left_widget=self._new_button) 160 ba.containerwidget(edit=self._root_widget, 161 selected_child=self._scrollwidget) 162 self._columnwidget = ba.columnwidget(parent=self._scrollwidget, 163 border=2, 164 margin=0) 165 v -= 255 166 self._profiles: dict[str, dict[str, Any]] | None = None 167 self._selected_profile = selected_profile 168 self._profile_widgets: list[ba.Widget] = [] 169 self._refresh() 170 self._restore_state() 171 172 def _new_profile(self) -> None: 173 # pylint: disable=cyclic-import 174 from bastd.ui.profile.edit import EditProfileWindow 175 from bastd.ui.purchase import PurchaseWindow 176 177 # Limit to a handful profiles if they don't have pro-options. 178 max_non_pro_profiles = _ba.get_v1_account_misc_read_val('mnpp', 5) 179 assert self._profiles is not None 180 if (not ba.app.accounts_v1.have_pro_options() 181 and len(self._profiles) >= max_non_pro_profiles): 182 PurchaseWindow(items=['pro'], 183 header_text=ba.Lstr( 184 resource='unlockThisProfilesText', 185 subs=[('${NUM}', str(max_non_pro_profiles))])) 186 return 187 188 # Clamp at 100 profiles (otherwise the server will and that's less 189 # elegant looking). 190 if len(self._profiles) > 100: 191 ba.screenmessage( 192 ba.Lstr(translate=('serverResponses', 193 'Max number of profiles reached.')), 194 color=(1, 0, 0)) 195 ba.playsound(ba.getsound('error')) 196 return 197 198 self._save_state() 199 ba.containerwidget(edit=self._root_widget, transition='out_left') 200 ba.app.ui.set_main_menu_window( 201 EditProfileWindow( 202 existing_profile=None, 203 in_main_menu=self._in_main_menu).get_root_widget()) 204 205 def _delete_profile(self) -> None: 206 # pylint: disable=cyclic-import 207 from bastd.ui import confirm 208 if self._selected_profile is None: 209 ba.playsound(ba.getsound('error')) 210 ba.screenmessage(ba.Lstr(resource='nothingIsSelectedErrorText'), 211 color=(1, 0, 0)) 212 return 213 if self._selected_profile == '__account__': 214 ba.playsound(ba.getsound('error')) 215 ba.screenmessage(ba.Lstr(resource=self._r + 216 '.cantDeleteAccountProfileText'), 217 color=(1, 0, 0)) 218 return 219 confirm.ConfirmWindow( 220 ba.Lstr(resource=self._r + '.deleteConfirmText', 221 subs=[('${PROFILE}', self._selected_profile)]), 222 self._do_delete_profile, 350) 223 224 def _do_delete_profile(self) -> None: 225 _ba.add_transaction({ 226 'type': 'REMOVE_PLAYER_PROFILE', 227 'name': self._selected_profile 228 }) 229 _ba.run_transactions() 230 ba.playsound(ba.getsound('shieldDown')) 231 self._refresh() 232 233 # Select profile list. 234 ba.containerwidget(edit=self._root_widget, 235 selected_child=self._scrollwidget) 236 237 def _edit_profile(self) -> None: 238 # pylint: disable=cyclic-import 239 from bastd.ui.profile.edit import EditProfileWindow 240 if self._selected_profile is None: 241 ba.playsound(ba.getsound('error')) 242 ba.screenmessage(ba.Lstr(resource='nothingIsSelectedErrorText'), 243 color=(1, 0, 0)) 244 return 245 self._save_state() 246 ba.containerwidget(edit=self._root_widget, transition='out_left') 247 ba.app.ui.set_main_menu_window( 248 EditProfileWindow( 249 self._selected_profile, 250 in_main_menu=self._in_main_menu).get_root_widget()) 251 252 def _select(self, name: str, index: int) -> None: 253 del index # Unused. 254 self._selected_profile = name 255 256 def _back(self) -> None: 257 # pylint: disable=cyclic-import 258 from bastd.ui.account.settings import AccountSettingsWindow 259 self._save_state() 260 ba.containerwidget(edit=self._root_widget, 261 transition=self._transition_out) 262 if self._in_main_menu: 263 ba.app.ui.set_main_menu_window( 264 AccountSettingsWindow(transition='in_left').get_root_widget()) 265 266 # If we're being called up standalone, handle pause/resume ourself. 267 else: 268 ba.app.resume() 269 270 def _refresh(self) -> None: 271 # pylint: disable=too-many-locals 272 from efro.util import asserttype 273 from ba.internal import (PlayerProfilesChangedMessage, 274 get_player_profile_colors, 275 get_player_profile_icon) 276 old_selection = self._selected_profile 277 278 # Delete old. 279 while self._profile_widgets: 280 self._profile_widgets.pop().delete() 281 self._profiles = ba.app.config.get('Player Profiles', {}) 282 assert self._profiles is not None 283 items = list(self._profiles.items()) 284 items.sort(key=lambda x: asserttype(x[0], str).lower()) 285 index = 0 286 account_name: str | None 287 if _ba.get_v1_account_state() == 'signed_in': 288 account_name = _ba.get_v1_account_display_string() 289 else: 290 account_name = None 291 widget_to_select = None 292 for p_name, _ in items: 293 if p_name == '__account__' and account_name is None: 294 continue 295 color, _highlight = get_player_profile_colors(p_name) 296 scl = 1.1 297 tval = (account_name if p_name == '__account__' else 298 get_player_profile_icon(p_name) + p_name) 299 assert isinstance(tval, str) 300 txtw = ba.textwidget( 301 parent=self._columnwidget, 302 position=(0, 32), 303 size=((self._width - 40) / scl, 28), 304 text=ba.Lstr(value=tval), 305 h_align='left', 306 v_align='center', 307 on_select_call=ba.WeakCall(self._select, p_name, index), 308 maxwidth=self._scroll_width * 0.92, 309 corner_scale=scl, 310 color=ba.safecolor(color, 0.4), 311 always_highlight=True, 312 on_activate_call=ba.Call(self._edit_button.activate), 313 selectable=True) 314 if index == 0: 315 ba.widget(edit=txtw, up_widget=self._back_button) 316 ba.widget(edit=txtw, show_buffer_top=40, show_buffer_bottom=40) 317 self._profile_widgets.append(txtw) 318 319 # Select/show this one if it was previously selected 320 # (but defer till after this loop since our height is 321 # still changing). 322 if p_name == old_selection: 323 widget_to_select = txtw 324 325 index += 1 326 327 if widget_to_select is not None: 328 ba.columnwidget(edit=self._columnwidget, 329 selected_child=widget_to_select, 330 visible_child=widget_to_select) 331 332 # If there's a team-chooser in existence, tell it the profile-list 333 # has probably changed. 334 session = _ba.get_foreground_host_session() 335 if session is not None: 336 session.handlemessage(PlayerProfilesChangedMessage()) 337 338 def _save_state(self) -> None: 339 try: 340 sel = self._root_widget.get_selected_child() 341 if sel == self._new_button: 342 sel_name = 'New' 343 elif sel == self._edit_button: 344 sel_name = 'Edit' 345 elif sel == self._delete_button: 346 sel_name = 'Delete' 347 elif sel == self._scrollwidget: 348 sel_name = 'Scroll' 349 else: 350 sel_name = 'Back' 351 ba.app.ui.window_states[type(self)] = sel_name 352 except Exception: 353 ba.print_exception(f'Error saving state for {self}.') 354 355 def _restore_state(self) -> None: 356 try: 357 sel_name = ba.app.ui.window_states.get(type(self)) 358 if sel_name == 'Scroll': 359 sel = self._scrollwidget 360 elif sel_name == 'New': 361 sel = self._new_button 362 elif sel_name == 'Delete': 363 sel = self._delete_button 364 elif sel_name == 'Edit': 365 sel = self._edit_button 366 elif sel_name == 'Back': 367 sel = self._back_button 368 else: 369 # By default we select our scroll widget if we have profiles; 370 # otherwise our new widget. 371 if not self._profile_widgets: 372 sel = self._new_button 373 else: 374 sel = self._scrollwidget 375 ba.containerwidget(edit=self._root_widget, selected_child=sel) 376 except Exception: 377 ba.print_exception(f'Error restoring state for {self}.')
Window for browsing player profiles.
ProfileBrowserWindow( transition: str = 'in_right', in_main_menu: bool = True, selected_profile: str | None = None, origin_widget: _ba.Widget | None = None)
20 def __init__(self, 21 transition: str = 'in_right', 22 in_main_menu: bool = True, 23 selected_profile: str | None = None, 24 origin_widget: ba.Widget | None = None): 25 # pylint: disable=too-many-statements 26 # pylint: disable=too-many-locals 27 self._in_main_menu = in_main_menu 28 if self._in_main_menu: 29 back_label = ba.Lstr(resource='backText') 30 else: 31 back_label = ba.Lstr(resource='doneText') 32 uiscale = ba.app.ui.uiscale 33 self._width = 700.0 if uiscale is ba.UIScale.SMALL else 600.0 34 x_inset = 50.0 if uiscale is ba.UIScale.SMALL else 0.0 35 self._height = (360.0 if uiscale is ba.UIScale.SMALL else 36 385.0 if uiscale is ba.UIScale.MEDIUM else 410.0) 37 38 # If we're being called up standalone, handle pause/resume ourself. 39 if not self._in_main_menu: 40 ba.app.pause() 41 42 # If they provided an origin-widget, scale up from that. 43 scale_origin: tuple[float, float] | None 44 if origin_widget is not None: 45 self._transition_out = 'out_scale' 46 scale_origin = origin_widget.get_screen_space_center() 47 transition = 'in_scale' 48 else: 49 self._transition_out = 'out_right' 50 scale_origin = None 51 52 self._r = 'playerProfilesWindow' 53 54 # Ensure we've got an account-profile in cases where we're signed in. 55 ba.app.accounts_v1.ensure_have_account_player_profile() 56 57 top_extra = 20 if uiscale is ba.UIScale.SMALL else 0 58 59 super().__init__(root_widget=ba.containerwidget( 60 size=(self._width, self._height + top_extra), 61 transition=transition, 62 scale_origin_stack_offset=scale_origin, 63 scale=(2.2 if uiscale is ba.UIScale.SMALL else 64 1.6 if uiscale is ba.UIScale.MEDIUM else 1.0), 65 stack_offset=(0, -14) if uiscale is ba.UIScale.SMALL else (0, 0))) 66 67 self._back_button = btn = ba.buttonwidget( 68 parent=self._root_widget, 69 position=(40 + x_inset, self._height - 59), 70 size=(120, 60), 71 scale=0.8, 72 label=back_label, 73 button_type='back' if self._in_main_menu else None, 74 autoselect=True, 75 on_activate_call=self._back) 76 ba.containerwidget(edit=self._root_widget, cancel_button=btn) 77 78 ba.textwidget(parent=self._root_widget, 79 position=(self._width * 0.5, self._height - 36), 80 size=(0, 0), 81 text=ba.Lstr(resource=self._r + '.titleText'), 82 maxwidth=300, 83 color=ba.app.ui.title_color, 84 scale=0.9, 85 h_align='center', 86 v_align='center') 87 88 if self._in_main_menu: 89 ba.buttonwidget(edit=btn, 90 button_type='backSmall', 91 size=(60, 60), 92 label=ba.charstr(ba.SpecialChar.BACK)) 93 94 scroll_height = self._height - 140.0 95 self._scroll_width = self._width - (188 + x_inset * 2) 96 v = self._height - 84.0 97 h = 50 + x_inset 98 b_color = (0.6, 0.53, 0.63) 99 100 scl = (1.055 if uiscale is ba.UIScale.SMALL else 101 1.18 if uiscale is ba.UIScale.MEDIUM else 1.3) 102 v -= 70.0 * scl 103 self._new_button = ba.buttonwidget(parent=self._root_widget, 104 position=(h, v), 105 size=(80, 66.0 * scl), 106 on_activate_call=self._new_profile, 107 color=b_color, 108 button_type='square', 109 autoselect=True, 110 textcolor=(0.75, 0.7, 0.8), 111 text_scale=0.7, 112 label=ba.Lstr(resource=self._r + 113 '.newButtonText')) 114 v -= 70.0 * scl 115 self._edit_button = ba.buttonwidget( 116 parent=self._root_widget, 117 position=(h, v), 118 size=(80, 66.0 * scl), 119 on_activate_call=self._edit_profile, 120 color=b_color, 121 button_type='square', 122 autoselect=True, 123 textcolor=(0.75, 0.7, 0.8), 124 text_scale=0.7, 125 label=ba.Lstr(resource=self._r + '.editButtonText')) 126 v -= 70.0 * scl 127 self._delete_button = ba.buttonwidget( 128 parent=self._root_widget, 129 position=(h, v), 130 size=(80, 66.0 * scl), 131 on_activate_call=self._delete_profile, 132 color=b_color, 133 button_type='square', 134 autoselect=True, 135 textcolor=(0.75, 0.7, 0.8), 136 text_scale=0.7, 137 label=ba.Lstr(resource=self._r + '.deleteButtonText')) 138 139 v = self._height - 87 140 141 ba.textwidget(parent=self._root_widget, 142 position=(self._width * 0.5, self._height - 71), 143 size=(0, 0), 144 text=ba.Lstr(resource=self._r + '.explanationText'), 145 color=ba.app.ui.infotextcolor, 146 maxwidth=self._width * 0.83, 147 scale=0.6, 148 h_align='center', 149 v_align='center') 150 151 self._scrollwidget = ba.scrollwidget(parent=self._root_widget, 152 highlight=False, 153 position=(140 + x_inset, 154 v - scroll_height), 155 size=(self._scroll_width, 156 scroll_height)) 157 ba.widget(edit=self._scrollwidget, 158 autoselect=True, 159 left_widget=self._new_button) 160 ba.containerwidget(edit=self._root_widget, 161 selected_child=self._scrollwidget) 162 self._columnwidget = ba.columnwidget(parent=self._scrollwidget, 163 border=2, 164 margin=0) 165 v -= 255 166 self._profiles: dict[str, dict[str, Any]] | None = None 167 self._selected_profile = selected_profile 168 self._profile_widgets: list[ba.Widget] = [] 169 self._refresh() 170 self._restore_state()
Inherited Members
- ba.ui.Window
- get_root_widget