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