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