bastd.ui.playlist.customizebrowser
Provides UI for viewing/creating/editing playlists.
1# Released under the MIT License. See LICENSE for details. 2# 3"""Provides UI for viewing/creating/editing playlists.""" 4 5from __future__ import annotations 6 7import copy 8import time 9from typing import TYPE_CHECKING 10 11import _ba 12import ba 13 14if TYPE_CHECKING: 15 from typing import Any 16 17 18class PlaylistCustomizeBrowserWindow(ba.Window): 19 """Window for viewing a playlist.""" 20 21 def __init__(self, 22 sessiontype: type[ba.Session], 23 transition: str = 'in_right', 24 select_playlist: str | None = None, 25 origin_widget: ba.Widget | None = None): 26 # Yes this needs tidying. 27 # pylint: disable=too-many-locals 28 # pylint: disable=too-many-statements 29 # pylint: disable=cyclic-import 30 from bastd.ui import playlist 31 scale_origin: tuple[float, float] | None 32 if origin_widget is not None: 33 self._transition_out = 'out_scale' 34 scale_origin = origin_widget.get_screen_space_center() 35 transition = 'in_scale' 36 else: 37 self._transition_out = 'out_right' 38 scale_origin = None 39 40 self._sessiontype = sessiontype 41 self._pvars = playlist.PlaylistTypeVars(sessiontype) 42 self._max_playlists = 30 43 self._r = 'gameListWindow' 44 uiscale = ba.app.ui.uiscale 45 self._width = 750.0 if uiscale is ba.UIScale.SMALL else 650.0 46 x_inset = 50.0 if uiscale is ba.UIScale.SMALL else 0.0 47 self._height = (380.0 if uiscale is ba.UIScale.SMALL else 48 420.0 if uiscale is ba.UIScale.MEDIUM else 500.0) 49 top_extra = 20.0 if uiscale is ba.UIScale.SMALL else 0.0 50 51 super().__init__(root_widget=ba.containerwidget( 52 size=(self._width, self._height + top_extra), 53 transition=transition, 54 scale_origin_stack_offset=scale_origin, 55 scale=(2.05 if uiscale is ba.UIScale.SMALL else 56 1.5 if uiscale is ba.UIScale.MEDIUM else 1.0), 57 stack_offset=(0, -10) if uiscale is ba.UIScale.SMALL else (0, 0))) 58 59 self._back_button = back_button = btn = ba.buttonwidget( 60 parent=self._root_widget, 61 position=(43 + x_inset, self._height - 60), 62 size=(160, 68), 63 scale=0.77, 64 autoselect=True, 65 text_scale=1.3, 66 label=ba.Lstr(resource='backText'), 67 button_type='back') 68 69 ba.textwidget(parent=self._root_widget, 70 position=(0, self._height - 47), 71 size=(self._width, 25), 72 text=ba.Lstr(resource=self._r + '.titleText', 73 subs=[('${TYPE}', 74 self._pvars.window_title_name)]), 75 color=ba.app.ui.heading_color, 76 maxwidth=290, 77 h_align='center', 78 v_align='center') 79 80 ba.buttonwidget(edit=btn, 81 button_type='backSmall', 82 size=(60, 60), 83 label=ba.charstr(ba.SpecialChar.BACK)) 84 85 v = self._height - 59.0 86 h = 41 + x_inset 87 b_color = (0.6, 0.53, 0.63) 88 b_textcolor = (0.75, 0.7, 0.8) 89 self._lock_images: list[ba.Widget] = [] 90 lock_tex = ba.gettexture('lock') 91 92 scl = (1.1 if uiscale is ba.UIScale.SMALL else 93 1.27 if uiscale is ba.UIScale.MEDIUM else 1.57) 94 scl *= 0.63 95 v -= 65.0 * scl 96 new_button = btn = ba.buttonwidget( 97 parent=self._root_widget, 98 position=(h, v), 99 size=(90, 58.0 * scl), 100 on_activate_call=self._new_playlist, 101 color=b_color, 102 autoselect=True, 103 button_type='square', 104 textcolor=b_textcolor, 105 text_scale=0.7, 106 label=ba.Lstr(resource='newText', 107 fallback_resource=self._r + '.newText')) 108 self._lock_images.append( 109 ba.imagewidget(parent=self._root_widget, 110 size=(30, 30), 111 draw_controller=btn, 112 position=(h - 10, v + 58.0 * scl - 28), 113 texture=lock_tex)) 114 115 v -= 65.0 * scl 116 self._edit_button = edit_button = btn = ba.buttonwidget( 117 parent=self._root_widget, 118 position=(h, v), 119 size=(90, 58.0 * scl), 120 on_activate_call=self._edit_playlist, 121 color=b_color, 122 autoselect=True, 123 textcolor=b_textcolor, 124 button_type='square', 125 text_scale=0.7, 126 label=ba.Lstr(resource='editText', 127 fallback_resource=self._r + '.editText')) 128 self._lock_images.append( 129 ba.imagewidget(parent=self._root_widget, 130 size=(30, 30), 131 draw_controller=btn, 132 position=(h - 10, v + 58.0 * scl - 28), 133 texture=lock_tex)) 134 135 v -= 65.0 * scl 136 duplicate_button = btn = ba.buttonwidget( 137 parent=self._root_widget, 138 position=(h, v), 139 size=(90, 58.0 * scl), 140 on_activate_call=self._duplicate_playlist, 141 color=b_color, 142 autoselect=True, 143 textcolor=b_textcolor, 144 button_type='square', 145 text_scale=0.7, 146 label=ba.Lstr(resource='duplicateText', 147 fallback_resource=self._r + '.duplicateText')) 148 self._lock_images.append( 149 ba.imagewidget(parent=self._root_widget, 150 size=(30, 30), 151 draw_controller=btn, 152 position=(h - 10, v + 58.0 * scl - 28), 153 texture=lock_tex)) 154 155 v -= 65.0 * scl 156 delete_button = btn = ba.buttonwidget( 157 parent=self._root_widget, 158 position=(h, v), 159 size=(90, 58.0 * scl), 160 on_activate_call=self._delete_playlist, 161 color=b_color, 162 autoselect=True, 163 textcolor=b_textcolor, 164 button_type='square', 165 text_scale=0.7, 166 label=ba.Lstr(resource='deleteText', 167 fallback_resource=self._r + '.deleteText')) 168 self._lock_images.append( 169 ba.imagewidget(parent=self._root_widget, 170 size=(30, 30), 171 draw_controller=btn, 172 position=(h - 10, v + 58.0 * scl - 28), 173 texture=lock_tex)) 174 v -= 65.0 * scl 175 self._import_button = ba.buttonwidget( 176 parent=self._root_widget, 177 position=(h, v), 178 size=(90, 58.0 * scl), 179 on_activate_call=self._import_playlist, 180 color=b_color, 181 autoselect=True, 182 textcolor=b_textcolor, 183 button_type='square', 184 text_scale=0.7, 185 label=ba.Lstr(resource='importText')) 186 v -= 65.0 * scl 187 btn = ba.buttonwidget(parent=self._root_widget, 188 position=(h, v), 189 size=(90, 58.0 * scl), 190 on_activate_call=self._share_playlist, 191 color=b_color, 192 autoselect=True, 193 textcolor=b_textcolor, 194 button_type='square', 195 text_scale=0.7, 196 label=ba.Lstr(resource='shareText')) 197 self._lock_images.append( 198 ba.imagewidget(parent=self._root_widget, 199 size=(30, 30), 200 draw_controller=btn, 201 position=(h - 10, v + 58.0 * scl - 28), 202 texture=lock_tex)) 203 204 v = self._height - 75 205 self._scroll_height = self._height - 119 206 scrollwidget = ba.scrollwidget(parent=self._root_widget, 207 position=(140 + x_inset, 208 v - self._scroll_height), 209 size=(self._width - (180 + 2 * x_inset), 210 self._scroll_height + 10), 211 highlight=False) 212 ba.widget(edit=back_button, right_widget=scrollwidget) 213 self._columnwidget = ba.columnwidget(parent=scrollwidget, 214 border=2, 215 margin=0) 216 217 h = 145 218 219 self._do_randomize_val = ba.app.config.get( 220 self._pvars.config_name + ' Playlist Randomize', 0) 221 222 h += 210 223 224 for btn in [new_button, delete_button, edit_button, duplicate_button]: 225 ba.widget(edit=btn, right_widget=scrollwidget) 226 ba.widget(edit=scrollwidget, 227 left_widget=new_button, 228 right_widget=_ba.get_special_widget('party_button') 229 if ba.app.ui.use_toolbars else None) 230 231 # make sure config exists 232 self._config_name_full = self._pvars.config_name + ' Playlists' 233 234 if self._config_name_full not in ba.app.config: 235 ba.app.config[self._config_name_full] = {} 236 237 self._selected_playlist_name: str | None = None 238 self._selected_playlist_index: int | None = None 239 self._playlist_widgets: list[ba.Widget] = [] 240 241 self._refresh(select_playlist=select_playlist) 242 243 ba.buttonwidget(edit=back_button, on_activate_call=self._back) 244 ba.containerwidget(edit=self._root_widget, cancel_button=back_button) 245 246 ba.containerwidget(edit=self._root_widget, selected_child=scrollwidget) 247 248 # Keep our lock images up to date/etc. 249 self._update_timer = ba.Timer(1.0, 250 ba.WeakCall(self._update), 251 timetype=ba.TimeType.REAL, 252 repeat=True) 253 self._update() 254 255 def _update(self) -> None: 256 have = ba.app.accounts_v1.have_pro_options() 257 for lock in self._lock_images: 258 ba.imagewidget(edit=lock, opacity=0.0 if have else 1.0) 259 260 def _back(self) -> None: 261 # pylint: disable=cyclic-import 262 from bastd.ui.playlist import browser 263 if self._selected_playlist_name is not None: 264 cfg = ba.app.config 265 cfg[self._pvars.config_name + 266 ' Playlist Selection'] = self._selected_playlist_name 267 cfg.commit() 268 269 ba.containerwidget(edit=self._root_widget, 270 transition=self._transition_out) 271 ba.app.ui.set_main_menu_window( 272 browser.PlaylistBrowserWindow( 273 transition='in_left', 274 sessiontype=self._sessiontype).get_root_widget()) 275 276 def _select(self, name: str, index: int) -> None: 277 self._selected_playlist_name = name 278 self._selected_playlist_index = index 279 280 def _run_selected_playlist(self) -> None: 281 # pylint: disable=cyclic-import 282 _ba.unlock_all_input() 283 try: 284 _ba.new_host_session(self._sessiontype) 285 except Exception: 286 from bastd import mainmenu 287 ba.print_exception(f'Error running session {self._sessiontype}.') 288 289 # Drop back into a main menu session. 290 _ba.new_host_session(mainmenu.MainMenuSession) 291 292 def _choose_playlist(self) -> None: 293 if self._selected_playlist_name is None: 294 return 295 self._save_playlist_selection() 296 ba.containerwidget(edit=self._root_widget, transition='out_left') 297 _ba.fade_screen(False, endcall=self._run_selected_playlist) 298 _ba.lock_all_input() 299 300 def _refresh(self, select_playlist: str | None = None) -> None: 301 from efro.util import asserttype 302 old_selection = self._selected_playlist_name 303 304 # If there was no prev selection, look in prefs. 305 if old_selection is None: 306 old_selection = ba.app.config.get(self._pvars.config_name + 307 ' Playlist Selection') 308 309 old_selection_index = self._selected_playlist_index 310 311 # Delete old. 312 while self._playlist_widgets: 313 self._playlist_widgets.pop().delete() 314 315 items = list(ba.app.config[self._config_name_full].items()) 316 317 # Make sure everything is unicode now. 318 items = [(i[0].decode(), i[1]) if not isinstance(i[0], str) else i 319 for i in items] 320 321 items.sort(key=lambda x: asserttype(x[0], str).lower()) 322 323 items = [['__default__', None]] + items # Default is always first. 324 index = 0 325 for pname, _ in items: 326 assert pname is not None 327 txtw = ba.textwidget( 328 parent=self._columnwidget, 329 size=(self._width - 40, 30), 330 maxwidth=self._width - 110, 331 text=self._get_playlist_display_name(pname), 332 h_align='left', 333 v_align='center', 334 color=(0.6, 0.6, 0.7, 1.0) if pname == '__default__' else 335 (0.85, 0.85, 0.85, 1), 336 always_highlight=True, 337 on_select_call=ba.Call(self._select, pname, index), 338 on_activate_call=ba.Call(self._edit_button.activate), 339 selectable=True) 340 ba.widget(edit=txtw, show_buffer_top=50, show_buffer_bottom=50) 341 342 # Hitting up from top widget should jump to 'back' 343 if index == 0: 344 ba.widget(edit=txtw, up_widget=self._back_button) 345 346 self._playlist_widgets.append(txtw) 347 348 # Select this one if the user requested it. 349 if select_playlist is not None: 350 if pname == select_playlist: 351 ba.columnwidget(edit=self._columnwidget, 352 selected_child=txtw, 353 visible_child=txtw) 354 else: 355 # Select this one if it was previously selected. 356 # Go by index if there's one. 357 if old_selection_index is not None: 358 if index == old_selection_index: 359 ba.columnwidget(edit=self._columnwidget, 360 selected_child=txtw, 361 visible_child=txtw) 362 else: # Otherwise look by name. 363 if pname == old_selection: 364 ba.columnwidget(edit=self._columnwidget, 365 selected_child=txtw, 366 visible_child=txtw) 367 368 index += 1 369 370 def _save_playlist_selection(self) -> None: 371 # Store the selected playlist in prefs. 372 # This serves dual purposes of letting us re-select it next time 373 # if we want and also lets us pass it to the game (since we reset 374 # the whole python environment that's not actually easy). 375 cfg = ba.app.config 376 cfg[self._pvars.config_name + 377 ' Playlist Selection'] = self._selected_playlist_name 378 cfg[self._pvars.config_name + 379 ' Playlist Randomize'] = self._do_randomize_val 380 cfg.commit() 381 382 def _new_playlist(self) -> None: 383 # pylint: disable=cyclic-import 384 from bastd.ui.playlist.editcontroller import PlaylistEditController 385 from bastd.ui.purchase import PurchaseWindow 386 if not ba.app.accounts_v1.have_pro_options(): 387 PurchaseWindow(items=['pro']) 388 return 389 390 # Clamp at our max playlist number. 391 if len(ba.app.config[self._config_name_full]) > self._max_playlists: 392 ba.screenmessage( 393 ba.Lstr(translate=('serverResponses', 394 'Max number of playlists reached.')), 395 color=(1, 0, 0)) 396 ba.playsound(ba.getsound('error')) 397 return 398 399 # In case they cancel so we can return to this state. 400 self._save_playlist_selection() 401 402 # Kick off the edit UI. 403 PlaylistEditController(sessiontype=self._sessiontype) 404 ba.containerwidget(edit=self._root_widget, transition='out_left') 405 406 def _edit_playlist(self) -> None: 407 # pylint: disable=cyclic-import 408 from bastd.ui.playlist.editcontroller import PlaylistEditController 409 from bastd.ui.purchase import PurchaseWindow 410 if not ba.app.accounts_v1.have_pro_options(): 411 PurchaseWindow(items=['pro']) 412 return 413 if self._selected_playlist_name is None: 414 return 415 if self._selected_playlist_name == '__default__': 416 ba.playsound(ba.getsound('error')) 417 ba.screenmessage(ba.Lstr(resource=self._r + 418 '.cantEditDefaultText')) 419 return 420 self._save_playlist_selection() 421 PlaylistEditController( 422 existing_playlist_name=self._selected_playlist_name, 423 sessiontype=self._sessiontype) 424 ba.containerwidget(edit=self._root_widget, transition='out_left') 425 426 def _do_delete_playlist(self) -> None: 427 _ba.add_transaction({ 428 'type': 'REMOVE_PLAYLIST', 429 'playlistType': self._pvars.config_name, 430 'playlistName': self._selected_playlist_name 431 }) 432 _ba.run_transactions() 433 ba.playsound(ba.getsound('shieldDown')) 434 435 # (we don't use len()-1 here because the default list adds one) 436 assert self._selected_playlist_index is not None 437 if self._selected_playlist_index > len( 438 ba.app.config[self._pvars.config_name + ' Playlists']): 439 self._selected_playlist_index = len( 440 ba.app.config[self._pvars.config_name + ' Playlists']) 441 self._refresh() 442 443 def _import_playlist(self) -> None: 444 # pylint: disable=cyclic-import 445 from bastd.ui.playlist import share 446 447 # Gotta be signed in for this to work. 448 if _ba.get_v1_account_state() != 'signed_in': 449 ba.screenmessage(ba.Lstr(resource='notSignedInErrorText'), 450 color=(1, 0, 0)) 451 ba.playsound(ba.getsound('error')) 452 return 453 454 share.SharePlaylistImportWindow(origin_widget=self._import_button, 455 on_success_callback=ba.WeakCall( 456 self._on_playlist_import_success)) 457 458 def _on_playlist_import_success(self) -> None: 459 self._refresh() 460 461 def _on_share_playlist_response(self, name: str, response: Any) -> None: 462 # pylint: disable=cyclic-import 463 from bastd.ui.playlist import share 464 if response is None: 465 ba.screenmessage( 466 ba.Lstr(resource='internal.unavailableNoConnectionText'), 467 color=(1, 0, 0)) 468 ba.playsound(ba.getsound('error')) 469 return 470 share.SharePlaylistResultsWindow(name, response) 471 472 def _share_playlist(self) -> None: 473 # pylint: disable=cyclic-import 474 from bastd.ui.purchase import PurchaseWindow 475 if not ba.app.accounts_v1.have_pro_options(): 476 PurchaseWindow(items=['pro']) 477 return 478 479 # Gotta be signed in for this to work. 480 if _ba.get_v1_account_state() != 'signed_in': 481 ba.screenmessage(ba.Lstr(resource='notSignedInErrorText'), 482 color=(1, 0, 0)) 483 ba.playsound(ba.getsound('error')) 484 return 485 if self._selected_playlist_name == '__default__': 486 ba.playsound(ba.getsound('error')) 487 ba.screenmessage(ba.Lstr(resource=self._r + 488 '.cantShareDefaultText'), 489 color=(1, 0, 0)) 490 return 491 492 if self._selected_playlist_name is None: 493 return 494 495 _ba.add_transaction( 496 { 497 'type': 'SHARE_PLAYLIST', 498 'expire_time': time.time() + 5, 499 'playlistType': self._pvars.config_name, 500 'playlistName': self._selected_playlist_name 501 }, 502 callback=ba.WeakCall(self._on_share_playlist_response, 503 self._selected_playlist_name)) 504 _ba.run_transactions() 505 ba.screenmessage(ba.Lstr(resource='sharingText')) 506 507 def _delete_playlist(self) -> None: 508 # pylint: disable=cyclic-import 509 from bastd.ui.purchase import PurchaseWindow 510 from bastd.ui.confirm import ConfirmWindow 511 if not ba.app.accounts_v1.have_pro_options(): 512 PurchaseWindow(items=['pro']) 513 return 514 515 if self._selected_playlist_name is None: 516 return 517 if self._selected_playlist_name == '__default__': 518 ba.playsound(ba.getsound('error')) 519 ba.screenmessage( 520 ba.Lstr(resource=self._r + '.cantDeleteDefaultText')) 521 else: 522 ConfirmWindow( 523 ba.Lstr(resource=self._r + '.deleteConfirmText', 524 subs=[('${LIST}', self._selected_playlist_name)]), 525 self._do_delete_playlist, 450, 150) 526 527 def _get_playlist_display_name(self, playlist: str) -> ba.Lstr: 528 if playlist == '__default__': 529 return self._pvars.default_list_name 530 return playlist if isinstance(playlist, ba.Lstr) else ba.Lstr( 531 value=playlist) 532 533 def _duplicate_playlist(self) -> None: 534 # pylint: disable=too-many-branches 535 # pylint: disable=cyclic-import 536 from bastd.ui.purchase import PurchaseWindow 537 if not ba.app.accounts_v1.have_pro_options(): 538 PurchaseWindow(items=['pro']) 539 return 540 if self._selected_playlist_name is None: 541 return 542 plst: list[dict[str, Any]] | None 543 if self._selected_playlist_name == '__default__': 544 plst = self._pvars.get_default_list_call() 545 else: 546 plst = ba.app.config[self._config_name_full].get( 547 self._selected_playlist_name) 548 if plst is None: 549 ba.playsound(ba.getsound('error')) 550 return 551 552 # clamp at our max playlist number 553 if len(ba.app.config[self._config_name_full]) > self._max_playlists: 554 ba.screenmessage( 555 ba.Lstr(translate=('serverResponses', 556 'Max number of playlists reached.')), 557 color=(1, 0, 0)) 558 ba.playsound(ba.getsound('error')) 559 return 560 561 copy_text = ba.Lstr(resource='copyOfText').evaluate() 562 # get just 'Copy' or whatnot 563 copy_word = copy_text.replace('${NAME}', '').strip() 564 # find a valid dup name that doesn't exist 565 566 test_index = 1 567 base_name = self._get_playlist_display_name( 568 self._selected_playlist_name).evaluate() 569 570 # If it looks like a copy, strip digits and spaces off the end. 571 if copy_word in base_name: 572 while base_name[-1].isdigit() or base_name[-1] == ' ': 573 base_name = base_name[:-1] 574 while True: 575 if copy_word in base_name: 576 test_name = base_name 577 else: 578 test_name = copy_text.replace('${NAME}', base_name) 579 if test_index > 1: 580 test_name += ' ' + str(test_index) 581 if test_name not in ba.app.config[self._config_name_full]: 582 break 583 test_index += 1 584 585 _ba.add_transaction({ 586 'type': 'ADD_PLAYLIST', 587 'playlistType': self._pvars.config_name, 588 'playlistName': test_name, 589 'playlist': copy.deepcopy(plst) 590 }) 591 _ba.run_transactions() 592 593 ba.playsound(ba.getsound('gunCocking')) 594 self._refresh(select_playlist=test_name)
class
PlaylistCustomizeBrowserWindow(ba.ui.Window):
19class PlaylistCustomizeBrowserWindow(ba.Window): 20 """Window for viewing a playlist.""" 21 22 def __init__(self, 23 sessiontype: type[ba.Session], 24 transition: str = 'in_right', 25 select_playlist: str | None = None, 26 origin_widget: ba.Widget | None = None): 27 # Yes this needs tidying. 28 # pylint: disable=too-many-locals 29 # pylint: disable=too-many-statements 30 # pylint: disable=cyclic-import 31 from bastd.ui import playlist 32 scale_origin: tuple[float, float] | None 33 if origin_widget is not None: 34 self._transition_out = 'out_scale' 35 scale_origin = origin_widget.get_screen_space_center() 36 transition = 'in_scale' 37 else: 38 self._transition_out = 'out_right' 39 scale_origin = None 40 41 self._sessiontype = sessiontype 42 self._pvars = playlist.PlaylistTypeVars(sessiontype) 43 self._max_playlists = 30 44 self._r = 'gameListWindow' 45 uiscale = ba.app.ui.uiscale 46 self._width = 750.0 if uiscale is ba.UIScale.SMALL else 650.0 47 x_inset = 50.0 if uiscale is ba.UIScale.SMALL else 0.0 48 self._height = (380.0 if uiscale is ba.UIScale.SMALL else 49 420.0 if uiscale is ba.UIScale.MEDIUM else 500.0) 50 top_extra = 20.0 if uiscale is ba.UIScale.SMALL else 0.0 51 52 super().__init__(root_widget=ba.containerwidget( 53 size=(self._width, self._height + top_extra), 54 transition=transition, 55 scale_origin_stack_offset=scale_origin, 56 scale=(2.05 if uiscale is ba.UIScale.SMALL else 57 1.5 if uiscale is ba.UIScale.MEDIUM else 1.0), 58 stack_offset=(0, -10) if uiscale is ba.UIScale.SMALL else (0, 0))) 59 60 self._back_button = back_button = btn = ba.buttonwidget( 61 parent=self._root_widget, 62 position=(43 + x_inset, self._height - 60), 63 size=(160, 68), 64 scale=0.77, 65 autoselect=True, 66 text_scale=1.3, 67 label=ba.Lstr(resource='backText'), 68 button_type='back') 69 70 ba.textwidget(parent=self._root_widget, 71 position=(0, self._height - 47), 72 size=(self._width, 25), 73 text=ba.Lstr(resource=self._r + '.titleText', 74 subs=[('${TYPE}', 75 self._pvars.window_title_name)]), 76 color=ba.app.ui.heading_color, 77 maxwidth=290, 78 h_align='center', 79 v_align='center') 80 81 ba.buttonwidget(edit=btn, 82 button_type='backSmall', 83 size=(60, 60), 84 label=ba.charstr(ba.SpecialChar.BACK)) 85 86 v = self._height - 59.0 87 h = 41 + x_inset 88 b_color = (0.6, 0.53, 0.63) 89 b_textcolor = (0.75, 0.7, 0.8) 90 self._lock_images: list[ba.Widget] = [] 91 lock_tex = ba.gettexture('lock') 92 93 scl = (1.1 if uiscale is ba.UIScale.SMALL else 94 1.27 if uiscale is ba.UIScale.MEDIUM else 1.57) 95 scl *= 0.63 96 v -= 65.0 * scl 97 new_button = btn = ba.buttonwidget( 98 parent=self._root_widget, 99 position=(h, v), 100 size=(90, 58.0 * scl), 101 on_activate_call=self._new_playlist, 102 color=b_color, 103 autoselect=True, 104 button_type='square', 105 textcolor=b_textcolor, 106 text_scale=0.7, 107 label=ba.Lstr(resource='newText', 108 fallback_resource=self._r + '.newText')) 109 self._lock_images.append( 110 ba.imagewidget(parent=self._root_widget, 111 size=(30, 30), 112 draw_controller=btn, 113 position=(h - 10, v + 58.0 * scl - 28), 114 texture=lock_tex)) 115 116 v -= 65.0 * scl 117 self._edit_button = edit_button = btn = ba.buttonwidget( 118 parent=self._root_widget, 119 position=(h, v), 120 size=(90, 58.0 * scl), 121 on_activate_call=self._edit_playlist, 122 color=b_color, 123 autoselect=True, 124 textcolor=b_textcolor, 125 button_type='square', 126 text_scale=0.7, 127 label=ba.Lstr(resource='editText', 128 fallback_resource=self._r + '.editText')) 129 self._lock_images.append( 130 ba.imagewidget(parent=self._root_widget, 131 size=(30, 30), 132 draw_controller=btn, 133 position=(h - 10, v + 58.0 * scl - 28), 134 texture=lock_tex)) 135 136 v -= 65.0 * scl 137 duplicate_button = btn = ba.buttonwidget( 138 parent=self._root_widget, 139 position=(h, v), 140 size=(90, 58.0 * scl), 141 on_activate_call=self._duplicate_playlist, 142 color=b_color, 143 autoselect=True, 144 textcolor=b_textcolor, 145 button_type='square', 146 text_scale=0.7, 147 label=ba.Lstr(resource='duplicateText', 148 fallback_resource=self._r + '.duplicateText')) 149 self._lock_images.append( 150 ba.imagewidget(parent=self._root_widget, 151 size=(30, 30), 152 draw_controller=btn, 153 position=(h - 10, v + 58.0 * scl - 28), 154 texture=lock_tex)) 155 156 v -= 65.0 * scl 157 delete_button = btn = ba.buttonwidget( 158 parent=self._root_widget, 159 position=(h, v), 160 size=(90, 58.0 * scl), 161 on_activate_call=self._delete_playlist, 162 color=b_color, 163 autoselect=True, 164 textcolor=b_textcolor, 165 button_type='square', 166 text_scale=0.7, 167 label=ba.Lstr(resource='deleteText', 168 fallback_resource=self._r + '.deleteText')) 169 self._lock_images.append( 170 ba.imagewidget(parent=self._root_widget, 171 size=(30, 30), 172 draw_controller=btn, 173 position=(h - 10, v + 58.0 * scl - 28), 174 texture=lock_tex)) 175 v -= 65.0 * scl 176 self._import_button = ba.buttonwidget( 177 parent=self._root_widget, 178 position=(h, v), 179 size=(90, 58.0 * scl), 180 on_activate_call=self._import_playlist, 181 color=b_color, 182 autoselect=True, 183 textcolor=b_textcolor, 184 button_type='square', 185 text_scale=0.7, 186 label=ba.Lstr(resource='importText')) 187 v -= 65.0 * scl 188 btn = ba.buttonwidget(parent=self._root_widget, 189 position=(h, v), 190 size=(90, 58.0 * scl), 191 on_activate_call=self._share_playlist, 192 color=b_color, 193 autoselect=True, 194 textcolor=b_textcolor, 195 button_type='square', 196 text_scale=0.7, 197 label=ba.Lstr(resource='shareText')) 198 self._lock_images.append( 199 ba.imagewidget(parent=self._root_widget, 200 size=(30, 30), 201 draw_controller=btn, 202 position=(h - 10, v + 58.0 * scl - 28), 203 texture=lock_tex)) 204 205 v = self._height - 75 206 self._scroll_height = self._height - 119 207 scrollwidget = ba.scrollwidget(parent=self._root_widget, 208 position=(140 + x_inset, 209 v - self._scroll_height), 210 size=(self._width - (180 + 2 * x_inset), 211 self._scroll_height + 10), 212 highlight=False) 213 ba.widget(edit=back_button, right_widget=scrollwidget) 214 self._columnwidget = ba.columnwidget(parent=scrollwidget, 215 border=2, 216 margin=0) 217 218 h = 145 219 220 self._do_randomize_val = ba.app.config.get( 221 self._pvars.config_name + ' Playlist Randomize', 0) 222 223 h += 210 224 225 for btn in [new_button, delete_button, edit_button, duplicate_button]: 226 ba.widget(edit=btn, right_widget=scrollwidget) 227 ba.widget(edit=scrollwidget, 228 left_widget=new_button, 229 right_widget=_ba.get_special_widget('party_button') 230 if ba.app.ui.use_toolbars else None) 231 232 # make sure config exists 233 self._config_name_full = self._pvars.config_name + ' Playlists' 234 235 if self._config_name_full not in ba.app.config: 236 ba.app.config[self._config_name_full] = {} 237 238 self._selected_playlist_name: str | None = None 239 self._selected_playlist_index: int | None = None 240 self._playlist_widgets: list[ba.Widget] = [] 241 242 self._refresh(select_playlist=select_playlist) 243 244 ba.buttonwidget(edit=back_button, on_activate_call=self._back) 245 ba.containerwidget(edit=self._root_widget, cancel_button=back_button) 246 247 ba.containerwidget(edit=self._root_widget, selected_child=scrollwidget) 248 249 # Keep our lock images up to date/etc. 250 self._update_timer = ba.Timer(1.0, 251 ba.WeakCall(self._update), 252 timetype=ba.TimeType.REAL, 253 repeat=True) 254 self._update() 255 256 def _update(self) -> None: 257 have = ba.app.accounts_v1.have_pro_options() 258 for lock in self._lock_images: 259 ba.imagewidget(edit=lock, opacity=0.0 if have else 1.0) 260 261 def _back(self) -> None: 262 # pylint: disable=cyclic-import 263 from bastd.ui.playlist import browser 264 if self._selected_playlist_name is not None: 265 cfg = ba.app.config 266 cfg[self._pvars.config_name + 267 ' Playlist Selection'] = self._selected_playlist_name 268 cfg.commit() 269 270 ba.containerwidget(edit=self._root_widget, 271 transition=self._transition_out) 272 ba.app.ui.set_main_menu_window( 273 browser.PlaylistBrowserWindow( 274 transition='in_left', 275 sessiontype=self._sessiontype).get_root_widget()) 276 277 def _select(self, name: str, index: int) -> None: 278 self._selected_playlist_name = name 279 self._selected_playlist_index = index 280 281 def _run_selected_playlist(self) -> None: 282 # pylint: disable=cyclic-import 283 _ba.unlock_all_input() 284 try: 285 _ba.new_host_session(self._sessiontype) 286 except Exception: 287 from bastd import mainmenu 288 ba.print_exception(f'Error running session {self._sessiontype}.') 289 290 # Drop back into a main menu session. 291 _ba.new_host_session(mainmenu.MainMenuSession) 292 293 def _choose_playlist(self) -> None: 294 if self._selected_playlist_name is None: 295 return 296 self._save_playlist_selection() 297 ba.containerwidget(edit=self._root_widget, transition='out_left') 298 _ba.fade_screen(False, endcall=self._run_selected_playlist) 299 _ba.lock_all_input() 300 301 def _refresh(self, select_playlist: str | None = None) -> None: 302 from efro.util import asserttype 303 old_selection = self._selected_playlist_name 304 305 # If there was no prev selection, look in prefs. 306 if old_selection is None: 307 old_selection = ba.app.config.get(self._pvars.config_name + 308 ' Playlist Selection') 309 310 old_selection_index = self._selected_playlist_index 311 312 # Delete old. 313 while self._playlist_widgets: 314 self._playlist_widgets.pop().delete() 315 316 items = list(ba.app.config[self._config_name_full].items()) 317 318 # Make sure everything is unicode now. 319 items = [(i[0].decode(), i[1]) if not isinstance(i[0], str) else i 320 for i in items] 321 322 items.sort(key=lambda x: asserttype(x[0], str).lower()) 323 324 items = [['__default__', None]] + items # Default is always first. 325 index = 0 326 for pname, _ in items: 327 assert pname is not None 328 txtw = ba.textwidget( 329 parent=self._columnwidget, 330 size=(self._width - 40, 30), 331 maxwidth=self._width - 110, 332 text=self._get_playlist_display_name(pname), 333 h_align='left', 334 v_align='center', 335 color=(0.6, 0.6, 0.7, 1.0) if pname == '__default__' else 336 (0.85, 0.85, 0.85, 1), 337 always_highlight=True, 338 on_select_call=ba.Call(self._select, pname, index), 339 on_activate_call=ba.Call(self._edit_button.activate), 340 selectable=True) 341 ba.widget(edit=txtw, show_buffer_top=50, show_buffer_bottom=50) 342 343 # Hitting up from top widget should jump to 'back' 344 if index == 0: 345 ba.widget(edit=txtw, up_widget=self._back_button) 346 347 self._playlist_widgets.append(txtw) 348 349 # Select this one if the user requested it. 350 if select_playlist is not None: 351 if pname == select_playlist: 352 ba.columnwidget(edit=self._columnwidget, 353 selected_child=txtw, 354 visible_child=txtw) 355 else: 356 # Select this one if it was previously selected. 357 # Go by index if there's one. 358 if old_selection_index is not None: 359 if index == old_selection_index: 360 ba.columnwidget(edit=self._columnwidget, 361 selected_child=txtw, 362 visible_child=txtw) 363 else: # Otherwise look by name. 364 if pname == old_selection: 365 ba.columnwidget(edit=self._columnwidget, 366 selected_child=txtw, 367 visible_child=txtw) 368 369 index += 1 370 371 def _save_playlist_selection(self) -> None: 372 # Store the selected playlist in prefs. 373 # This serves dual purposes of letting us re-select it next time 374 # if we want and also lets us pass it to the game (since we reset 375 # the whole python environment that's not actually easy). 376 cfg = ba.app.config 377 cfg[self._pvars.config_name + 378 ' Playlist Selection'] = self._selected_playlist_name 379 cfg[self._pvars.config_name + 380 ' Playlist Randomize'] = self._do_randomize_val 381 cfg.commit() 382 383 def _new_playlist(self) -> None: 384 # pylint: disable=cyclic-import 385 from bastd.ui.playlist.editcontroller import PlaylistEditController 386 from bastd.ui.purchase import PurchaseWindow 387 if not ba.app.accounts_v1.have_pro_options(): 388 PurchaseWindow(items=['pro']) 389 return 390 391 # Clamp at our max playlist number. 392 if len(ba.app.config[self._config_name_full]) > self._max_playlists: 393 ba.screenmessage( 394 ba.Lstr(translate=('serverResponses', 395 'Max number of playlists reached.')), 396 color=(1, 0, 0)) 397 ba.playsound(ba.getsound('error')) 398 return 399 400 # In case they cancel so we can return to this state. 401 self._save_playlist_selection() 402 403 # Kick off the edit UI. 404 PlaylistEditController(sessiontype=self._sessiontype) 405 ba.containerwidget(edit=self._root_widget, transition='out_left') 406 407 def _edit_playlist(self) -> None: 408 # pylint: disable=cyclic-import 409 from bastd.ui.playlist.editcontroller import PlaylistEditController 410 from bastd.ui.purchase import PurchaseWindow 411 if not ba.app.accounts_v1.have_pro_options(): 412 PurchaseWindow(items=['pro']) 413 return 414 if self._selected_playlist_name is None: 415 return 416 if self._selected_playlist_name == '__default__': 417 ba.playsound(ba.getsound('error')) 418 ba.screenmessage(ba.Lstr(resource=self._r + 419 '.cantEditDefaultText')) 420 return 421 self._save_playlist_selection() 422 PlaylistEditController( 423 existing_playlist_name=self._selected_playlist_name, 424 sessiontype=self._sessiontype) 425 ba.containerwidget(edit=self._root_widget, transition='out_left') 426 427 def _do_delete_playlist(self) -> None: 428 _ba.add_transaction({ 429 'type': 'REMOVE_PLAYLIST', 430 'playlistType': self._pvars.config_name, 431 'playlistName': self._selected_playlist_name 432 }) 433 _ba.run_transactions() 434 ba.playsound(ba.getsound('shieldDown')) 435 436 # (we don't use len()-1 here because the default list adds one) 437 assert self._selected_playlist_index is not None 438 if self._selected_playlist_index > len( 439 ba.app.config[self._pvars.config_name + ' Playlists']): 440 self._selected_playlist_index = len( 441 ba.app.config[self._pvars.config_name + ' Playlists']) 442 self._refresh() 443 444 def _import_playlist(self) -> None: 445 # pylint: disable=cyclic-import 446 from bastd.ui.playlist import share 447 448 # Gotta be signed in for this to work. 449 if _ba.get_v1_account_state() != 'signed_in': 450 ba.screenmessage(ba.Lstr(resource='notSignedInErrorText'), 451 color=(1, 0, 0)) 452 ba.playsound(ba.getsound('error')) 453 return 454 455 share.SharePlaylistImportWindow(origin_widget=self._import_button, 456 on_success_callback=ba.WeakCall( 457 self._on_playlist_import_success)) 458 459 def _on_playlist_import_success(self) -> None: 460 self._refresh() 461 462 def _on_share_playlist_response(self, name: str, response: Any) -> None: 463 # pylint: disable=cyclic-import 464 from bastd.ui.playlist import share 465 if response is None: 466 ba.screenmessage( 467 ba.Lstr(resource='internal.unavailableNoConnectionText'), 468 color=(1, 0, 0)) 469 ba.playsound(ba.getsound('error')) 470 return 471 share.SharePlaylistResultsWindow(name, response) 472 473 def _share_playlist(self) -> None: 474 # pylint: disable=cyclic-import 475 from bastd.ui.purchase import PurchaseWindow 476 if not ba.app.accounts_v1.have_pro_options(): 477 PurchaseWindow(items=['pro']) 478 return 479 480 # Gotta be signed in for this to work. 481 if _ba.get_v1_account_state() != 'signed_in': 482 ba.screenmessage(ba.Lstr(resource='notSignedInErrorText'), 483 color=(1, 0, 0)) 484 ba.playsound(ba.getsound('error')) 485 return 486 if self._selected_playlist_name == '__default__': 487 ba.playsound(ba.getsound('error')) 488 ba.screenmessage(ba.Lstr(resource=self._r + 489 '.cantShareDefaultText'), 490 color=(1, 0, 0)) 491 return 492 493 if self._selected_playlist_name is None: 494 return 495 496 _ba.add_transaction( 497 { 498 'type': 'SHARE_PLAYLIST', 499 'expire_time': time.time() + 5, 500 'playlistType': self._pvars.config_name, 501 'playlistName': self._selected_playlist_name 502 }, 503 callback=ba.WeakCall(self._on_share_playlist_response, 504 self._selected_playlist_name)) 505 _ba.run_transactions() 506 ba.screenmessage(ba.Lstr(resource='sharingText')) 507 508 def _delete_playlist(self) -> None: 509 # pylint: disable=cyclic-import 510 from bastd.ui.purchase import PurchaseWindow 511 from bastd.ui.confirm import ConfirmWindow 512 if not ba.app.accounts_v1.have_pro_options(): 513 PurchaseWindow(items=['pro']) 514 return 515 516 if self._selected_playlist_name is None: 517 return 518 if self._selected_playlist_name == '__default__': 519 ba.playsound(ba.getsound('error')) 520 ba.screenmessage( 521 ba.Lstr(resource=self._r + '.cantDeleteDefaultText')) 522 else: 523 ConfirmWindow( 524 ba.Lstr(resource=self._r + '.deleteConfirmText', 525 subs=[('${LIST}', self._selected_playlist_name)]), 526 self._do_delete_playlist, 450, 150) 527 528 def _get_playlist_display_name(self, playlist: str) -> ba.Lstr: 529 if playlist == '__default__': 530 return self._pvars.default_list_name 531 return playlist if isinstance(playlist, ba.Lstr) else ba.Lstr( 532 value=playlist) 533 534 def _duplicate_playlist(self) -> None: 535 # pylint: disable=too-many-branches 536 # pylint: disable=cyclic-import 537 from bastd.ui.purchase import PurchaseWindow 538 if not ba.app.accounts_v1.have_pro_options(): 539 PurchaseWindow(items=['pro']) 540 return 541 if self._selected_playlist_name is None: 542 return 543 plst: list[dict[str, Any]] | None 544 if self._selected_playlist_name == '__default__': 545 plst = self._pvars.get_default_list_call() 546 else: 547 plst = ba.app.config[self._config_name_full].get( 548 self._selected_playlist_name) 549 if plst is None: 550 ba.playsound(ba.getsound('error')) 551 return 552 553 # clamp at our max playlist number 554 if len(ba.app.config[self._config_name_full]) > self._max_playlists: 555 ba.screenmessage( 556 ba.Lstr(translate=('serverResponses', 557 'Max number of playlists reached.')), 558 color=(1, 0, 0)) 559 ba.playsound(ba.getsound('error')) 560 return 561 562 copy_text = ba.Lstr(resource='copyOfText').evaluate() 563 # get just 'Copy' or whatnot 564 copy_word = copy_text.replace('${NAME}', '').strip() 565 # find a valid dup name that doesn't exist 566 567 test_index = 1 568 base_name = self._get_playlist_display_name( 569 self._selected_playlist_name).evaluate() 570 571 # If it looks like a copy, strip digits and spaces off the end. 572 if copy_word in base_name: 573 while base_name[-1].isdigit() or base_name[-1] == ' ': 574 base_name = base_name[:-1] 575 while True: 576 if copy_word in base_name: 577 test_name = base_name 578 else: 579 test_name = copy_text.replace('${NAME}', base_name) 580 if test_index > 1: 581 test_name += ' ' + str(test_index) 582 if test_name not in ba.app.config[self._config_name_full]: 583 break 584 test_index += 1 585 586 _ba.add_transaction({ 587 'type': 'ADD_PLAYLIST', 588 'playlistType': self._pvars.config_name, 589 'playlistName': test_name, 590 'playlist': copy.deepcopy(plst) 591 }) 592 _ba.run_transactions() 593 594 ba.playsound(ba.getsound('gunCocking')) 595 self._refresh(select_playlist=test_name)
Window for viewing a playlist.
PlaylistCustomizeBrowserWindow( sessiontype: type[ba._session.Session], transition: str = 'in_right', select_playlist: str | None = None, origin_widget: _ba.Widget | None = None)
22 def __init__(self, 23 sessiontype: type[ba.Session], 24 transition: str = 'in_right', 25 select_playlist: str | None = None, 26 origin_widget: ba.Widget | None = None): 27 # Yes this needs tidying. 28 # pylint: disable=too-many-locals 29 # pylint: disable=too-many-statements 30 # pylint: disable=cyclic-import 31 from bastd.ui import playlist 32 scale_origin: tuple[float, float] | None 33 if origin_widget is not None: 34 self._transition_out = 'out_scale' 35 scale_origin = origin_widget.get_screen_space_center() 36 transition = 'in_scale' 37 else: 38 self._transition_out = 'out_right' 39 scale_origin = None 40 41 self._sessiontype = sessiontype 42 self._pvars = playlist.PlaylistTypeVars(sessiontype) 43 self._max_playlists = 30 44 self._r = 'gameListWindow' 45 uiscale = ba.app.ui.uiscale 46 self._width = 750.0 if uiscale is ba.UIScale.SMALL else 650.0 47 x_inset = 50.0 if uiscale is ba.UIScale.SMALL else 0.0 48 self._height = (380.0 if uiscale is ba.UIScale.SMALL else 49 420.0 if uiscale is ba.UIScale.MEDIUM else 500.0) 50 top_extra = 20.0 if uiscale is ba.UIScale.SMALL else 0.0 51 52 super().__init__(root_widget=ba.containerwidget( 53 size=(self._width, self._height + top_extra), 54 transition=transition, 55 scale_origin_stack_offset=scale_origin, 56 scale=(2.05 if uiscale is ba.UIScale.SMALL else 57 1.5 if uiscale is ba.UIScale.MEDIUM else 1.0), 58 stack_offset=(0, -10) if uiscale is ba.UIScale.SMALL else (0, 0))) 59 60 self._back_button = back_button = btn = ba.buttonwidget( 61 parent=self._root_widget, 62 position=(43 + x_inset, self._height - 60), 63 size=(160, 68), 64 scale=0.77, 65 autoselect=True, 66 text_scale=1.3, 67 label=ba.Lstr(resource='backText'), 68 button_type='back') 69 70 ba.textwidget(parent=self._root_widget, 71 position=(0, self._height - 47), 72 size=(self._width, 25), 73 text=ba.Lstr(resource=self._r + '.titleText', 74 subs=[('${TYPE}', 75 self._pvars.window_title_name)]), 76 color=ba.app.ui.heading_color, 77 maxwidth=290, 78 h_align='center', 79 v_align='center') 80 81 ba.buttonwidget(edit=btn, 82 button_type='backSmall', 83 size=(60, 60), 84 label=ba.charstr(ba.SpecialChar.BACK)) 85 86 v = self._height - 59.0 87 h = 41 + x_inset 88 b_color = (0.6, 0.53, 0.63) 89 b_textcolor = (0.75, 0.7, 0.8) 90 self._lock_images: list[ba.Widget] = [] 91 lock_tex = ba.gettexture('lock') 92 93 scl = (1.1 if uiscale is ba.UIScale.SMALL else 94 1.27 if uiscale is ba.UIScale.MEDIUM else 1.57) 95 scl *= 0.63 96 v -= 65.0 * scl 97 new_button = btn = ba.buttonwidget( 98 parent=self._root_widget, 99 position=(h, v), 100 size=(90, 58.0 * scl), 101 on_activate_call=self._new_playlist, 102 color=b_color, 103 autoselect=True, 104 button_type='square', 105 textcolor=b_textcolor, 106 text_scale=0.7, 107 label=ba.Lstr(resource='newText', 108 fallback_resource=self._r + '.newText')) 109 self._lock_images.append( 110 ba.imagewidget(parent=self._root_widget, 111 size=(30, 30), 112 draw_controller=btn, 113 position=(h - 10, v + 58.0 * scl - 28), 114 texture=lock_tex)) 115 116 v -= 65.0 * scl 117 self._edit_button = edit_button = btn = ba.buttonwidget( 118 parent=self._root_widget, 119 position=(h, v), 120 size=(90, 58.0 * scl), 121 on_activate_call=self._edit_playlist, 122 color=b_color, 123 autoselect=True, 124 textcolor=b_textcolor, 125 button_type='square', 126 text_scale=0.7, 127 label=ba.Lstr(resource='editText', 128 fallback_resource=self._r + '.editText')) 129 self._lock_images.append( 130 ba.imagewidget(parent=self._root_widget, 131 size=(30, 30), 132 draw_controller=btn, 133 position=(h - 10, v + 58.0 * scl - 28), 134 texture=lock_tex)) 135 136 v -= 65.0 * scl 137 duplicate_button = btn = ba.buttonwidget( 138 parent=self._root_widget, 139 position=(h, v), 140 size=(90, 58.0 * scl), 141 on_activate_call=self._duplicate_playlist, 142 color=b_color, 143 autoselect=True, 144 textcolor=b_textcolor, 145 button_type='square', 146 text_scale=0.7, 147 label=ba.Lstr(resource='duplicateText', 148 fallback_resource=self._r + '.duplicateText')) 149 self._lock_images.append( 150 ba.imagewidget(parent=self._root_widget, 151 size=(30, 30), 152 draw_controller=btn, 153 position=(h - 10, v + 58.0 * scl - 28), 154 texture=lock_tex)) 155 156 v -= 65.0 * scl 157 delete_button = btn = ba.buttonwidget( 158 parent=self._root_widget, 159 position=(h, v), 160 size=(90, 58.0 * scl), 161 on_activate_call=self._delete_playlist, 162 color=b_color, 163 autoselect=True, 164 textcolor=b_textcolor, 165 button_type='square', 166 text_scale=0.7, 167 label=ba.Lstr(resource='deleteText', 168 fallback_resource=self._r + '.deleteText')) 169 self._lock_images.append( 170 ba.imagewidget(parent=self._root_widget, 171 size=(30, 30), 172 draw_controller=btn, 173 position=(h - 10, v + 58.0 * scl - 28), 174 texture=lock_tex)) 175 v -= 65.0 * scl 176 self._import_button = ba.buttonwidget( 177 parent=self._root_widget, 178 position=(h, v), 179 size=(90, 58.0 * scl), 180 on_activate_call=self._import_playlist, 181 color=b_color, 182 autoselect=True, 183 textcolor=b_textcolor, 184 button_type='square', 185 text_scale=0.7, 186 label=ba.Lstr(resource='importText')) 187 v -= 65.0 * scl 188 btn = ba.buttonwidget(parent=self._root_widget, 189 position=(h, v), 190 size=(90, 58.0 * scl), 191 on_activate_call=self._share_playlist, 192 color=b_color, 193 autoselect=True, 194 textcolor=b_textcolor, 195 button_type='square', 196 text_scale=0.7, 197 label=ba.Lstr(resource='shareText')) 198 self._lock_images.append( 199 ba.imagewidget(parent=self._root_widget, 200 size=(30, 30), 201 draw_controller=btn, 202 position=(h - 10, v + 58.0 * scl - 28), 203 texture=lock_tex)) 204 205 v = self._height - 75 206 self._scroll_height = self._height - 119 207 scrollwidget = ba.scrollwidget(parent=self._root_widget, 208 position=(140 + x_inset, 209 v - self._scroll_height), 210 size=(self._width - (180 + 2 * x_inset), 211 self._scroll_height + 10), 212 highlight=False) 213 ba.widget(edit=back_button, right_widget=scrollwidget) 214 self._columnwidget = ba.columnwidget(parent=scrollwidget, 215 border=2, 216 margin=0) 217 218 h = 145 219 220 self._do_randomize_val = ba.app.config.get( 221 self._pvars.config_name + ' Playlist Randomize', 0) 222 223 h += 210 224 225 for btn in [new_button, delete_button, edit_button, duplicate_button]: 226 ba.widget(edit=btn, right_widget=scrollwidget) 227 ba.widget(edit=scrollwidget, 228 left_widget=new_button, 229 right_widget=_ba.get_special_widget('party_button') 230 if ba.app.ui.use_toolbars else None) 231 232 # make sure config exists 233 self._config_name_full = self._pvars.config_name + ' Playlists' 234 235 if self._config_name_full not in ba.app.config: 236 ba.app.config[self._config_name_full] = {} 237 238 self._selected_playlist_name: str | None = None 239 self._selected_playlist_index: int | None = None 240 self._playlist_widgets: list[ba.Widget] = [] 241 242 self._refresh(select_playlist=select_playlist) 243 244 ba.buttonwidget(edit=back_button, on_activate_call=self._back) 245 ba.containerwidget(edit=self._root_widget, cancel_button=back_button) 246 247 ba.containerwidget(edit=self._root_widget, selected_child=scrollwidget) 248 249 # Keep our lock images up to date/etc. 250 self._update_timer = ba.Timer(1.0, 251 ba.WeakCall(self._update), 252 timetype=ba.TimeType.REAL, 253 repeat=True) 254 self._update()
Inherited Members
- ba.ui.Window
- get_root_widget