bauiv1lib.soundtrack.browser
Provides UI for browsing soundtracks.
1# Released under the MIT License. See LICENSE for details. 2# 3"""Provides UI for browsing soundtracks.""" 4 5from __future__ import annotations 6 7import copy 8import logging 9from typing import TYPE_CHECKING, override 10 11import bauiv1 as bui 12 13if TYPE_CHECKING: 14 from typing import Any 15 16REQUIRE_PRO = False 17 18# Temp. 19UNDER_CONSTRUCTION = True 20 21 22class SoundtrackBrowserWindow(bui.MainWindow): 23 """Window for browsing soundtracks.""" 24 25 def __init__( 26 self, 27 transition: str | None = 'in_right', 28 origin_widget: bui.Widget | None = None, 29 ): 30 # pylint: disable=too-many-statements 31 32 self._r = 'editSoundtrackWindow' 33 assert bui.app.classic is not None 34 uiscale = bui.app.ui_v1.uiscale 35 self._width = 800 if uiscale is bui.UIScale.SMALL else 600 36 x_inset = 100 if uiscale is bui.UIScale.SMALL else 0 37 self._height = ( 38 340 39 if uiscale is bui.UIScale.SMALL 40 else 370 if uiscale is bui.UIScale.MEDIUM else 440 41 ) 42 spacing = 40.0 43 v = self._height - 40.0 44 v -= spacing * 1.0 45 46 super().__init__( 47 root_widget=bui.containerwidget( 48 size=(self._width, self._height), 49 toolbar_visibility=( 50 'menu_minimal' 51 if uiscale is bui.UIScale.SMALL 52 else 'menu_full' 53 ), 54 scale=( 55 2.1 56 if uiscale is bui.UIScale.SMALL 57 else 1.6 if uiscale is bui.UIScale.MEDIUM else 1.0 58 ), 59 stack_offset=( 60 (0, -18) if uiscale is bui.UIScale.SMALL else (0, 0) 61 ), 62 ), 63 transition=transition, 64 origin_widget=origin_widget, 65 ) 66 67 assert bui.app.classic is not None 68 if uiscale is bui.UIScale.SMALL: 69 self._back_button = None 70 else: 71 self._back_button = bui.buttonwidget( 72 parent=self._root_widget, 73 position=(45 + x_inset, self._height - 60), 74 size=(120, 60), 75 scale=0.8, 76 label=bui.Lstr(resource='backText'), 77 button_type='back', 78 autoselect=True, 79 ) 80 bui.buttonwidget( 81 edit=self._back_button, 82 button_type='backSmall', 83 size=(60, 60), 84 label=bui.charstr(bui.SpecialChar.BACK), 85 ) 86 bui.textwidget( 87 parent=self._root_widget, 88 position=(self._width * 0.5, self._height - 35), 89 size=(0, 0), 90 maxwidth=300, 91 text=bui.Lstr(resource=f'{self._r}.titleText'), 92 color=bui.app.ui_v1.title_color, 93 h_align='center', 94 v_align='center', 95 ) 96 97 h = 43 + x_inset 98 v = self._height - 60 99 b_color = (0.6, 0.53, 0.63) 100 b_textcolor = (0.75, 0.7, 0.8) 101 lock_tex = bui.gettexture('lock') 102 self._lock_images: list[bui.Widget] = [] 103 104 scl = ( 105 1.0 106 if uiscale is bui.UIScale.SMALL 107 else 1.13 if uiscale is bui.UIScale.MEDIUM else 1.4 108 ) 109 v -= 60.0 * scl 110 self._new_button = btn = bui.buttonwidget( 111 parent=self._root_widget, 112 position=(h, v), 113 size=(100, 55.0 * scl), 114 on_activate_call=self._new_soundtrack, 115 color=b_color, 116 button_type='square', 117 autoselect=True, 118 textcolor=b_textcolor, 119 text_scale=0.7, 120 label=bui.Lstr(resource=f'{self._r}.newText'), 121 ) 122 self._lock_images.append( 123 bui.imagewidget( 124 parent=self._root_widget, 125 size=(30, 30), 126 draw_controller=btn, 127 position=(h - 10, v + 55.0 * scl - 28), 128 texture=lock_tex, 129 ) 130 ) 131 132 if self._back_button is None: 133 bui.widget( 134 edit=btn, 135 left_widget=bui.get_special_widget('back_button'), 136 ) 137 v -= 60.0 * scl 138 139 self._edit_button = btn = bui.buttonwidget( 140 parent=self._root_widget, 141 position=(h, v), 142 size=(100, 55.0 * scl), 143 on_activate_call=self._edit_soundtrack, 144 color=b_color, 145 button_type='square', 146 autoselect=True, 147 textcolor=b_textcolor, 148 text_scale=0.7, 149 label=bui.Lstr(resource=f'{self._r}.editText'), 150 ) 151 self._lock_images.append( 152 bui.imagewidget( 153 parent=self._root_widget, 154 size=(30, 30), 155 draw_controller=btn, 156 position=(h - 10, v + 55.0 * scl - 28), 157 texture=lock_tex, 158 ) 159 ) 160 if self._back_button is None: 161 bui.widget( 162 edit=btn, 163 left_widget=bui.get_special_widget('back_button'), 164 ) 165 v -= 60.0 * scl 166 167 self._duplicate_button = btn = bui.buttonwidget( 168 parent=self._root_widget, 169 position=(h, v), 170 size=(100, 55.0 * scl), 171 on_activate_call=self._duplicate_soundtrack, 172 button_type='square', 173 autoselect=True, 174 color=b_color, 175 textcolor=b_textcolor, 176 text_scale=0.7, 177 label=bui.Lstr(resource=f'{self._r}.duplicateText'), 178 ) 179 self._lock_images.append( 180 bui.imagewidget( 181 parent=self._root_widget, 182 size=(30, 30), 183 draw_controller=btn, 184 position=(h - 10, v + 55.0 * scl - 28), 185 texture=lock_tex, 186 ) 187 ) 188 if self._back_button is None: 189 bui.widget( 190 edit=btn, 191 left_widget=bui.get_special_widget('back_button'), 192 ) 193 v -= 60.0 * scl 194 195 self._delete_button = btn = bui.buttonwidget( 196 parent=self._root_widget, 197 position=(h, v), 198 size=(100, 55.0 * scl), 199 on_activate_call=self._delete_soundtrack, 200 color=b_color, 201 button_type='square', 202 autoselect=True, 203 textcolor=b_textcolor, 204 text_scale=0.7, 205 label=bui.Lstr(resource=f'{self._r}.deleteText'), 206 ) 207 self._lock_images.append( 208 bui.imagewidget( 209 parent=self._root_widget, 210 size=(30, 30), 211 draw_controller=btn, 212 position=(h - 10, v + 55.0 * scl - 28), 213 texture=lock_tex, 214 ) 215 ) 216 if self._back_button is None: 217 bui.widget( 218 edit=btn, 219 left_widget=bui.get_special_widget('back_button'), 220 ) 221 222 # Keep our lock images up to date/etc. 223 self._update_timer = bui.AppTimer( 224 1.0, bui.WeakCall(self._update), repeat=True 225 ) 226 self._update() 227 228 v = self._height - 65 229 scroll_height = self._height - 105 230 v -= scroll_height 231 self._scrollwidget = scrollwidget = bui.scrollwidget( 232 parent=self._root_widget, 233 position=(152 + x_inset, v), 234 highlight=False, 235 size=(self._width - (205 + 2 * x_inset), scroll_height), 236 ) 237 bui.widget( 238 edit=self._scrollwidget, 239 left_widget=self._new_button, 240 right_widget=bui.get_special_widget('squad_button'), 241 ) 242 self._col = bui.columnwidget(parent=scrollwidget, border=2, margin=0) 243 244 self._soundtracks: dict[str, Any] | None = None 245 self._selected_soundtrack: str | None = None 246 self._selected_soundtrack_index: int | None = None 247 self._soundtrack_widgets: list[bui.Widget] = [] 248 self._allow_changing_soundtracks = False 249 self._refresh() 250 if self._back_button is not None: 251 bui.buttonwidget( 252 edit=self._back_button, on_activate_call=self.main_window_back 253 ) 254 bui.containerwidget( 255 edit=self._root_widget, cancel_button=self._back_button 256 ) 257 else: 258 bui.containerwidget( 259 edit=self._root_widget, on_cancel_call=self.main_window_back 260 ) 261 262 @override 263 def get_main_window_state(self) -> bui.MainWindowState: 264 # Support recreating our window for back/refresh purposes. 265 cls = type(self) 266 return bui.BasicMainWindowState( 267 create_call=lambda transition, origin_widget: cls( 268 transition=transition, origin_widget=origin_widget 269 ) 270 ) 271 272 @override 273 def on_main_window_close(self) -> None: 274 self._save_state() 275 276 def _update(self) -> None: 277 have_pro = ( 278 bui.app.classic is None 279 or bui.app.classic.accounts.have_pro_options() 280 ) 281 for lock in self._lock_images: 282 bui.imagewidget( 283 edit=lock, opacity=0.0 if (have_pro or not REQUIRE_PRO) else 1.0 284 ) 285 286 def _do_delete_soundtrack(self) -> None: 287 cfg = bui.app.config 288 soundtracks = cfg.setdefault('Soundtracks', {}) 289 if self._selected_soundtrack in soundtracks: 290 del soundtracks[self._selected_soundtrack] 291 cfg.commit() 292 bui.getsound('shieldDown').play() 293 assert self._selected_soundtrack_index is not None 294 assert self._soundtracks is not None 295 self._selected_soundtrack_index = min( 296 self._selected_soundtrack_index, len(self._soundtracks) 297 ) 298 self._refresh() 299 300 def _delete_soundtrack(self) -> None: 301 # pylint: disable=cyclic-import 302 from bauiv1lib.purchase import PurchaseWindow 303 from bauiv1lib.confirm import ConfirmWindow 304 305 if REQUIRE_PRO and ( 306 bui.app.classic is not None 307 and not bui.app.classic.accounts.have_pro_options() 308 ): 309 PurchaseWindow(items=['pro']) 310 return 311 if self._selected_soundtrack is None: 312 return 313 if self._selected_soundtrack == '__default__': 314 bui.getsound('error').play() 315 bui.screenmessage( 316 bui.Lstr(resource=f'{self._r}.cantDeleteDefaultText'), 317 color=(1, 0, 0), 318 ) 319 else: 320 ConfirmWindow( 321 bui.Lstr( 322 resource=f'{self._r}.deleteConfirmText', 323 subs=[('${NAME}', self._selected_soundtrack)], 324 ), 325 self._do_delete_soundtrack, 326 450, 327 150, 328 ) 329 330 def _duplicate_soundtrack(self) -> None: 331 # pylint: disable=cyclic-import 332 from bauiv1lib.purchase import PurchaseWindow 333 334 if REQUIRE_PRO and ( 335 bui.app.classic is not None 336 and not bui.app.classic.accounts.have_pro_options() 337 ): 338 PurchaseWindow(items=['pro']) 339 return 340 cfg = bui.app.config 341 cfg.setdefault('Soundtracks', {}) 342 343 if self._selected_soundtrack is None: 344 return 345 sdtk: dict[str, Any] 346 if self._selected_soundtrack == '__default__': 347 sdtk = {} 348 else: 349 sdtk = cfg['Soundtracks'][self._selected_soundtrack] 350 351 # Find a valid dup name that doesn't exist. 352 test_index = 1 353 copy_text = bui.Lstr(resource='copyOfText').evaluate() 354 # Get just 'Copy' or whatnot. 355 copy_word = copy_text.replace('${NAME}', '').strip() 356 base_name = self._get_soundtrack_display_name( 357 self._selected_soundtrack 358 ).evaluate() 359 assert isinstance(base_name, str) 360 361 # If it looks like a copy, strip digits and spaces off the end. 362 if copy_word in base_name: 363 while base_name[-1].isdigit() or base_name[-1] == ' ': 364 base_name = base_name[:-1] 365 while True: 366 if copy_word in base_name: 367 test_name = base_name 368 else: 369 test_name = copy_text.replace('${NAME}', base_name) 370 if test_index > 1: 371 test_name += ' ' + str(test_index) 372 if test_name not in cfg['Soundtracks']: 373 break 374 test_index += 1 375 376 cfg['Soundtracks'][test_name] = copy.deepcopy(sdtk) 377 cfg.commit() 378 self._refresh(select_soundtrack=test_name) 379 380 def _select(self, name: str, index: int) -> None: 381 assert bui.app.classic is not None 382 music = bui.app.classic.music 383 self._selected_soundtrack_index = index 384 self._selected_soundtrack = name 385 cfg = bui.app.config 386 current_soundtrack = cfg.setdefault('Soundtrack', '__default__') 387 388 # If it varies from current, commit and play. 389 if current_soundtrack != name and self._allow_changing_soundtracks: 390 bui.getsound('gunCocking').play() 391 cfg['Soundtrack'] = self._selected_soundtrack 392 cfg.commit() 393 394 # Just play whats already playing.. this'll grab it from the 395 # new soundtrack. 396 music.do_play_music( 397 music.music_types[bui.app.classic.MusicPlayMode.REGULAR] 398 ) 399 400 # def _back(self) -> None: 401 # # pylint: disable=cyclic-import 402 # from bauiv1lib.settings.audio import AudioSettingsWindow 403 404 # # no-op if our underlying widget is dead or on its way out. 405 # if not self._root_widget or self._root_widget.transitioning_out: 406 # return 407 408 # self._save_state() 409 # bui.containerwidget( 410 # edit=self._root_widget, transition=self._transition_out 411 # ) 412 # assert bui.app.classic is not None 413 # bui.app.ui_v1.set_main_window( 414 # AudioSettingsWindow(transition='in_left'), 415 # from_window=self, 416 # is_back=True, 417 # ) 418 419 def _edit_soundtrack_with_sound(self) -> None: 420 # pylint: disable=cyclic-import 421 from bauiv1lib.purchase import PurchaseWindow 422 423 if REQUIRE_PRO and ( 424 bui.app.classic is not None 425 and not bui.app.classic.accounts.have_pro_options() 426 ): 427 PurchaseWindow(items=['pro']) 428 return 429 bui.getsound('swish').play() 430 self._edit_soundtrack() 431 432 def _edit_soundtrack(self) -> None: 433 # pylint: disable=cyclic-import 434 from bauiv1lib.purchase import PurchaseWindow 435 from bauiv1lib.soundtrack.edit import SoundtrackEditWindow 436 437 if UNDER_CONSTRUCTION: 438 bui.screenmessage('UNDER CONSTRUCTION') 439 return 440 441 # no-op if our underlying widget is dead or on its way out. 442 if not self._root_widget or self._root_widget.transitioning_out: 443 return 444 445 if REQUIRE_PRO and ( 446 bui.app.classic is not None 447 and not bui.app.classic.accounts.have_pro_options() 448 ): 449 PurchaseWindow(items=['pro']) 450 return 451 if self._selected_soundtrack is None: 452 return 453 if self._selected_soundtrack == '__default__': 454 bui.getsound('error').play() 455 bui.screenmessage( 456 bui.Lstr(resource=f'{self._r}.cantEditDefaultText'), 457 color=(1, 0, 0), 458 ) 459 return 460 461 self._save_state() 462 bui.containerwidget(edit=self._root_widget, transition='out_left') 463 assert bui.app.classic is not None 464 bui.app.ui_v1.set_main_window( 465 SoundtrackEditWindow(existing_soundtrack=self._selected_soundtrack), 466 from_window=self, 467 ) 468 469 def _get_soundtrack_display_name(self, soundtrack: str) -> bui.Lstr: 470 if soundtrack == '__default__': 471 return bui.Lstr(resource=f'{self._r}.defaultSoundtrackNameText') 472 return bui.Lstr(value=soundtrack) 473 474 def _refresh(self, select_soundtrack: str | None = None) -> None: 475 from efro.util import asserttype 476 477 self._allow_changing_soundtracks = False 478 old_selection = self._selected_soundtrack 479 480 # If there was no prev selection, look in prefs. 481 if old_selection is None: 482 old_selection = bui.app.config.get('Soundtrack') 483 old_selection_index = self._selected_soundtrack_index 484 485 # Delete old. 486 while self._soundtrack_widgets: 487 self._soundtrack_widgets.pop().delete() 488 489 self._soundtracks = bui.app.config.get('Soundtracks', {}) 490 assert self._soundtracks is not None 491 items = list(self._soundtracks.items()) 492 items.sort(key=lambda x: asserttype(x[0], str).lower()) 493 items = [('__default__', None)] + items # default is always first 494 index = 0 495 for pname, _pval in items: 496 assert pname is not None 497 txtw = bui.textwidget( 498 parent=self._col, 499 size=(self._width - 40, 24), 500 text=self._get_soundtrack_display_name(pname), 501 h_align='left', 502 v_align='center', 503 maxwidth=self._width - 110, 504 always_highlight=True, 505 on_select_call=bui.WeakCall(self._select, pname, index), 506 on_activate_call=self._edit_soundtrack_with_sound, 507 selectable=True, 508 ) 509 if index == 0: 510 bui.widget(edit=txtw, up_widget=self._back_button) 511 self._soundtrack_widgets.append(txtw) 512 513 # Select this one if the user requested it 514 if select_soundtrack is not None: 515 if pname == select_soundtrack: 516 bui.columnwidget( 517 edit=self._col, selected_child=txtw, visible_child=txtw 518 ) 519 else: 520 # Select this one if it was previously selected. 521 # Go by index if there's one. 522 if old_selection_index is not None: 523 if index == old_selection_index: 524 bui.columnwidget( 525 edit=self._col, 526 selected_child=txtw, 527 visible_child=txtw, 528 ) 529 else: # Otherwise look by name. 530 if pname == old_selection: 531 bui.columnwidget( 532 edit=self._col, 533 selected_child=txtw, 534 visible_child=txtw, 535 ) 536 index += 1 537 538 # Explicitly run select callback on current one and re-enable 539 # callbacks. 540 541 # Eww need to run this in a timer so it happens after our select 542 # callbacks. With a small-enough time sometimes it happens before 543 # anyway. Ew. need a way to just schedule a callable i guess. 544 bui.apptimer(0.1, bui.WeakCall(self._set_allow_changing)) 545 546 def _set_allow_changing(self) -> None: 547 self._allow_changing_soundtracks = True 548 assert self._selected_soundtrack is not None 549 assert self._selected_soundtrack_index is not None 550 self._select(self._selected_soundtrack, self._selected_soundtrack_index) 551 552 def _new_soundtrack(self) -> None: 553 # pylint: disable=cyclic-import 554 from bauiv1lib.purchase import PurchaseWindow 555 from bauiv1lib.soundtrack.edit import SoundtrackEditWindow 556 557 if UNDER_CONSTRUCTION: 558 bui.screenmessage('UNDER CONSTRUCTION') 559 return 560 561 if REQUIRE_PRO and ( 562 bui.app.classic is not None 563 and not bui.app.classic.accounts.have_pro_options() 564 ): 565 PurchaseWindow(items=['pro']) 566 return 567 self._save_state() 568 bui.containerwidget(edit=self._root_widget, transition='out_left') 569 bui.app.ui_v1.set_main_window( 570 SoundtrackEditWindow(existing_soundtrack=None), from_window=self 571 ) 572 573 def _create_done(self, new_soundtrack: str) -> None: 574 if new_soundtrack is not None: 575 bui.getsound('gunCocking').play() 576 self._refresh(select_soundtrack=new_soundtrack) 577 578 def _save_state(self) -> None: 579 try: 580 sel = self._root_widget.get_selected_child() 581 if sel == self._scrollwidget: 582 sel_name = 'Scroll' 583 elif sel == self._new_button: 584 sel_name = 'New' 585 elif sel == self._edit_button: 586 sel_name = 'Edit' 587 elif sel == self._duplicate_button: 588 sel_name = 'Duplicate' 589 elif sel == self._delete_button: 590 sel_name = 'Delete' 591 elif sel == self._back_button: 592 sel_name = 'Back' 593 else: 594 raise ValueError(f'unrecognized selection \'{sel}\'') 595 assert bui.app.classic is not None 596 bui.app.ui_v1.window_states[type(self)] = sel_name 597 except Exception: 598 logging.exception('Error saving state for %s.', self) 599 600 def _restore_state(self) -> None: 601 try: 602 assert bui.app.classic is not None 603 sel_name = bui.app.ui_v1.window_states.get(type(self)) 604 if sel_name == 'Scroll': 605 sel = self._scrollwidget 606 elif sel_name == 'New': 607 sel = self._new_button 608 elif sel_name == 'Edit': 609 sel = self._edit_button 610 elif sel_name == 'Duplicate': 611 sel = self._duplicate_button 612 elif sel_name == 'Delete': 613 sel = self._delete_button 614 else: 615 sel = self._scrollwidget 616 bui.containerwidget(edit=self._root_widget, selected_child=sel) 617 except Exception: 618 logging.exception('Error restoring state for %s.', self)
REQUIRE_PRO =
False
UNDER_CONSTRUCTION =
True
class
SoundtrackBrowserWindow(bauiv1._uitypes.MainWindow):
23class SoundtrackBrowserWindow(bui.MainWindow): 24 """Window for browsing soundtracks.""" 25 26 def __init__( 27 self, 28 transition: str | None = 'in_right', 29 origin_widget: bui.Widget | None = None, 30 ): 31 # pylint: disable=too-many-statements 32 33 self._r = 'editSoundtrackWindow' 34 assert bui.app.classic is not None 35 uiscale = bui.app.ui_v1.uiscale 36 self._width = 800 if uiscale is bui.UIScale.SMALL else 600 37 x_inset = 100 if uiscale is bui.UIScale.SMALL else 0 38 self._height = ( 39 340 40 if uiscale is bui.UIScale.SMALL 41 else 370 if uiscale is bui.UIScale.MEDIUM else 440 42 ) 43 spacing = 40.0 44 v = self._height - 40.0 45 v -= spacing * 1.0 46 47 super().__init__( 48 root_widget=bui.containerwidget( 49 size=(self._width, self._height), 50 toolbar_visibility=( 51 'menu_minimal' 52 if uiscale is bui.UIScale.SMALL 53 else 'menu_full' 54 ), 55 scale=( 56 2.1 57 if uiscale is bui.UIScale.SMALL 58 else 1.6 if uiscale is bui.UIScale.MEDIUM else 1.0 59 ), 60 stack_offset=( 61 (0, -18) if uiscale is bui.UIScale.SMALL else (0, 0) 62 ), 63 ), 64 transition=transition, 65 origin_widget=origin_widget, 66 ) 67 68 assert bui.app.classic is not None 69 if uiscale is bui.UIScale.SMALL: 70 self._back_button = None 71 else: 72 self._back_button = bui.buttonwidget( 73 parent=self._root_widget, 74 position=(45 + x_inset, self._height - 60), 75 size=(120, 60), 76 scale=0.8, 77 label=bui.Lstr(resource='backText'), 78 button_type='back', 79 autoselect=True, 80 ) 81 bui.buttonwidget( 82 edit=self._back_button, 83 button_type='backSmall', 84 size=(60, 60), 85 label=bui.charstr(bui.SpecialChar.BACK), 86 ) 87 bui.textwidget( 88 parent=self._root_widget, 89 position=(self._width * 0.5, self._height - 35), 90 size=(0, 0), 91 maxwidth=300, 92 text=bui.Lstr(resource=f'{self._r}.titleText'), 93 color=bui.app.ui_v1.title_color, 94 h_align='center', 95 v_align='center', 96 ) 97 98 h = 43 + x_inset 99 v = self._height - 60 100 b_color = (0.6, 0.53, 0.63) 101 b_textcolor = (0.75, 0.7, 0.8) 102 lock_tex = bui.gettexture('lock') 103 self._lock_images: list[bui.Widget] = [] 104 105 scl = ( 106 1.0 107 if uiscale is bui.UIScale.SMALL 108 else 1.13 if uiscale is bui.UIScale.MEDIUM else 1.4 109 ) 110 v -= 60.0 * scl 111 self._new_button = btn = bui.buttonwidget( 112 parent=self._root_widget, 113 position=(h, v), 114 size=(100, 55.0 * scl), 115 on_activate_call=self._new_soundtrack, 116 color=b_color, 117 button_type='square', 118 autoselect=True, 119 textcolor=b_textcolor, 120 text_scale=0.7, 121 label=bui.Lstr(resource=f'{self._r}.newText'), 122 ) 123 self._lock_images.append( 124 bui.imagewidget( 125 parent=self._root_widget, 126 size=(30, 30), 127 draw_controller=btn, 128 position=(h - 10, v + 55.0 * scl - 28), 129 texture=lock_tex, 130 ) 131 ) 132 133 if self._back_button is None: 134 bui.widget( 135 edit=btn, 136 left_widget=bui.get_special_widget('back_button'), 137 ) 138 v -= 60.0 * scl 139 140 self._edit_button = btn = bui.buttonwidget( 141 parent=self._root_widget, 142 position=(h, v), 143 size=(100, 55.0 * scl), 144 on_activate_call=self._edit_soundtrack, 145 color=b_color, 146 button_type='square', 147 autoselect=True, 148 textcolor=b_textcolor, 149 text_scale=0.7, 150 label=bui.Lstr(resource=f'{self._r}.editText'), 151 ) 152 self._lock_images.append( 153 bui.imagewidget( 154 parent=self._root_widget, 155 size=(30, 30), 156 draw_controller=btn, 157 position=(h - 10, v + 55.0 * scl - 28), 158 texture=lock_tex, 159 ) 160 ) 161 if self._back_button is None: 162 bui.widget( 163 edit=btn, 164 left_widget=bui.get_special_widget('back_button'), 165 ) 166 v -= 60.0 * scl 167 168 self._duplicate_button = btn = bui.buttonwidget( 169 parent=self._root_widget, 170 position=(h, v), 171 size=(100, 55.0 * scl), 172 on_activate_call=self._duplicate_soundtrack, 173 button_type='square', 174 autoselect=True, 175 color=b_color, 176 textcolor=b_textcolor, 177 text_scale=0.7, 178 label=bui.Lstr(resource=f'{self._r}.duplicateText'), 179 ) 180 self._lock_images.append( 181 bui.imagewidget( 182 parent=self._root_widget, 183 size=(30, 30), 184 draw_controller=btn, 185 position=(h - 10, v + 55.0 * scl - 28), 186 texture=lock_tex, 187 ) 188 ) 189 if self._back_button is None: 190 bui.widget( 191 edit=btn, 192 left_widget=bui.get_special_widget('back_button'), 193 ) 194 v -= 60.0 * scl 195 196 self._delete_button = btn = bui.buttonwidget( 197 parent=self._root_widget, 198 position=(h, v), 199 size=(100, 55.0 * scl), 200 on_activate_call=self._delete_soundtrack, 201 color=b_color, 202 button_type='square', 203 autoselect=True, 204 textcolor=b_textcolor, 205 text_scale=0.7, 206 label=bui.Lstr(resource=f'{self._r}.deleteText'), 207 ) 208 self._lock_images.append( 209 bui.imagewidget( 210 parent=self._root_widget, 211 size=(30, 30), 212 draw_controller=btn, 213 position=(h - 10, v + 55.0 * scl - 28), 214 texture=lock_tex, 215 ) 216 ) 217 if self._back_button is None: 218 bui.widget( 219 edit=btn, 220 left_widget=bui.get_special_widget('back_button'), 221 ) 222 223 # Keep our lock images up to date/etc. 224 self._update_timer = bui.AppTimer( 225 1.0, bui.WeakCall(self._update), repeat=True 226 ) 227 self._update() 228 229 v = self._height - 65 230 scroll_height = self._height - 105 231 v -= scroll_height 232 self._scrollwidget = scrollwidget = bui.scrollwidget( 233 parent=self._root_widget, 234 position=(152 + x_inset, v), 235 highlight=False, 236 size=(self._width - (205 + 2 * x_inset), scroll_height), 237 ) 238 bui.widget( 239 edit=self._scrollwidget, 240 left_widget=self._new_button, 241 right_widget=bui.get_special_widget('squad_button'), 242 ) 243 self._col = bui.columnwidget(parent=scrollwidget, border=2, margin=0) 244 245 self._soundtracks: dict[str, Any] | None = None 246 self._selected_soundtrack: str | None = None 247 self._selected_soundtrack_index: int | None = None 248 self._soundtrack_widgets: list[bui.Widget] = [] 249 self._allow_changing_soundtracks = False 250 self._refresh() 251 if self._back_button is not None: 252 bui.buttonwidget( 253 edit=self._back_button, on_activate_call=self.main_window_back 254 ) 255 bui.containerwidget( 256 edit=self._root_widget, cancel_button=self._back_button 257 ) 258 else: 259 bui.containerwidget( 260 edit=self._root_widget, on_cancel_call=self.main_window_back 261 ) 262 263 @override 264 def get_main_window_state(self) -> bui.MainWindowState: 265 # Support recreating our window for back/refresh purposes. 266 cls = type(self) 267 return bui.BasicMainWindowState( 268 create_call=lambda transition, origin_widget: cls( 269 transition=transition, origin_widget=origin_widget 270 ) 271 ) 272 273 @override 274 def on_main_window_close(self) -> None: 275 self._save_state() 276 277 def _update(self) -> None: 278 have_pro = ( 279 bui.app.classic is None 280 or bui.app.classic.accounts.have_pro_options() 281 ) 282 for lock in self._lock_images: 283 bui.imagewidget( 284 edit=lock, opacity=0.0 if (have_pro or not REQUIRE_PRO) else 1.0 285 ) 286 287 def _do_delete_soundtrack(self) -> None: 288 cfg = bui.app.config 289 soundtracks = cfg.setdefault('Soundtracks', {}) 290 if self._selected_soundtrack in soundtracks: 291 del soundtracks[self._selected_soundtrack] 292 cfg.commit() 293 bui.getsound('shieldDown').play() 294 assert self._selected_soundtrack_index is not None 295 assert self._soundtracks is not None 296 self._selected_soundtrack_index = min( 297 self._selected_soundtrack_index, len(self._soundtracks) 298 ) 299 self._refresh() 300 301 def _delete_soundtrack(self) -> None: 302 # pylint: disable=cyclic-import 303 from bauiv1lib.purchase import PurchaseWindow 304 from bauiv1lib.confirm import ConfirmWindow 305 306 if REQUIRE_PRO and ( 307 bui.app.classic is not None 308 and not bui.app.classic.accounts.have_pro_options() 309 ): 310 PurchaseWindow(items=['pro']) 311 return 312 if self._selected_soundtrack is None: 313 return 314 if self._selected_soundtrack == '__default__': 315 bui.getsound('error').play() 316 bui.screenmessage( 317 bui.Lstr(resource=f'{self._r}.cantDeleteDefaultText'), 318 color=(1, 0, 0), 319 ) 320 else: 321 ConfirmWindow( 322 bui.Lstr( 323 resource=f'{self._r}.deleteConfirmText', 324 subs=[('${NAME}', self._selected_soundtrack)], 325 ), 326 self._do_delete_soundtrack, 327 450, 328 150, 329 ) 330 331 def _duplicate_soundtrack(self) -> None: 332 # pylint: disable=cyclic-import 333 from bauiv1lib.purchase import PurchaseWindow 334 335 if REQUIRE_PRO and ( 336 bui.app.classic is not None 337 and not bui.app.classic.accounts.have_pro_options() 338 ): 339 PurchaseWindow(items=['pro']) 340 return 341 cfg = bui.app.config 342 cfg.setdefault('Soundtracks', {}) 343 344 if self._selected_soundtrack is None: 345 return 346 sdtk: dict[str, Any] 347 if self._selected_soundtrack == '__default__': 348 sdtk = {} 349 else: 350 sdtk = cfg['Soundtracks'][self._selected_soundtrack] 351 352 # Find a valid dup name that doesn't exist. 353 test_index = 1 354 copy_text = bui.Lstr(resource='copyOfText').evaluate() 355 # Get just 'Copy' or whatnot. 356 copy_word = copy_text.replace('${NAME}', '').strip() 357 base_name = self._get_soundtrack_display_name( 358 self._selected_soundtrack 359 ).evaluate() 360 assert isinstance(base_name, str) 361 362 # If it looks like a copy, strip digits and spaces off the end. 363 if copy_word in base_name: 364 while base_name[-1].isdigit() or base_name[-1] == ' ': 365 base_name = base_name[:-1] 366 while True: 367 if copy_word in base_name: 368 test_name = base_name 369 else: 370 test_name = copy_text.replace('${NAME}', base_name) 371 if test_index > 1: 372 test_name += ' ' + str(test_index) 373 if test_name not in cfg['Soundtracks']: 374 break 375 test_index += 1 376 377 cfg['Soundtracks'][test_name] = copy.deepcopy(sdtk) 378 cfg.commit() 379 self._refresh(select_soundtrack=test_name) 380 381 def _select(self, name: str, index: int) -> None: 382 assert bui.app.classic is not None 383 music = bui.app.classic.music 384 self._selected_soundtrack_index = index 385 self._selected_soundtrack = name 386 cfg = bui.app.config 387 current_soundtrack = cfg.setdefault('Soundtrack', '__default__') 388 389 # If it varies from current, commit and play. 390 if current_soundtrack != name and self._allow_changing_soundtracks: 391 bui.getsound('gunCocking').play() 392 cfg['Soundtrack'] = self._selected_soundtrack 393 cfg.commit() 394 395 # Just play whats already playing.. this'll grab it from the 396 # new soundtrack. 397 music.do_play_music( 398 music.music_types[bui.app.classic.MusicPlayMode.REGULAR] 399 ) 400 401 # def _back(self) -> None: 402 # # pylint: disable=cyclic-import 403 # from bauiv1lib.settings.audio import AudioSettingsWindow 404 405 # # no-op if our underlying widget is dead or on its way out. 406 # if not self._root_widget or self._root_widget.transitioning_out: 407 # return 408 409 # self._save_state() 410 # bui.containerwidget( 411 # edit=self._root_widget, transition=self._transition_out 412 # ) 413 # assert bui.app.classic is not None 414 # bui.app.ui_v1.set_main_window( 415 # AudioSettingsWindow(transition='in_left'), 416 # from_window=self, 417 # is_back=True, 418 # ) 419 420 def _edit_soundtrack_with_sound(self) -> None: 421 # pylint: disable=cyclic-import 422 from bauiv1lib.purchase import PurchaseWindow 423 424 if REQUIRE_PRO and ( 425 bui.app.classic is not None 426 and not bui.app.classic.accounts.have_pro_options() 427 ): 428 PurchaseWindow(items=['pro']) 429 return 430 bui.getsound('swish').play() 431 self._edit_soundtrack() 432 433 def _edit_soundtrack(self) -> None: 434 # pylint: disable=cyclic-import 435 from bauiv1lib.purchase import PurchaseWindow 436 from bauiv1lib.soundtrack.edit import SoundtrackEditWindow 437 438 if UNDER_CONSTRUCTION: 439 bui.screenmessage('UNDER CONSTRUCTION') 440 return 441 442 # no-op if our underlying widget is dead or on its way out. 443 if not self._root_widget or self._root_widget.transitioning_out: 444 return 445 446 if REQUIRE_PRO and ( 447 bui.app.classic is not None 448 and not bui.app.classic.accounts.have_pro_options() 449 ): 450 PurchaseWindow(items=['pro']) 451 return 452 if self._selected_soundtrack is None: 453 return 454 if self._selected_soundtrack == '__default__': 455 bui.getsound('error').play() 456 bui.screenmessage( 457 bui.Lstr(resource=f'{self._r}.cantEditDefaultText'), 458 color=(1, 0, 0), 459 ) 460 return 461 462 self._save_state() 463 bui.containerwidget(edit=self._root_widget, transition='out_left') 464 assert bui.app.classic is not None 465 bui.app.ui_v1.set_main_window( 466 SoundtrackEditWindow(existing_soundtrack=self._selected_soundtrack), 467 from_window=self, 468 ) 469 470 def _get_soundtrack_display_name(self, soundtrack: str) -> bui.Lstr: 471 if soundtrack == '__default__': 472 return bui.Lstr(resource=f'{self._r}.defaultSoundtrackNameText') 473 return bui.Lstr(value=soundtrack) 474 475 def _refresh(self, select_soundtrack: str | None = None) -> None: 476 from efro.util import asserttype 477 478 self._allow_changing_soundtracks = False 479 old_selection = self._selected_soundtrack 480 481 # If there was no prev selection, look in prefs. 482 if old_selection is None: 483 old_selection = bui.app.config.get('Soundtrack') 484 old_selection_index = self._selected_soundtrack_index 485 486 # Delete old. 487 while self._soundtrack_widgets: 488 self._soundtrack_widgets.pop().delete() 489 490 self._soundtracks = bui.app.config.get('Soundtracks', {}) 491 assert self._soundtracks is not None 492 items = list(self._soundtracks.items()) 493 items.sort(key=lambda x: asserttype(x[0], str).lower()) 494 items = [('__default__', None)] + items # default is always first 495 index = 0 496 for pname, _pval in items: 497 assert pname is not None 498 txtw = bui.textwidget( 499 parent=self._col, 500 size=(self._width - 40, 24), 501 text=self._get_soundtrack_display_name(pname), 502 h_align='left', 503 v_align='center', 504 maxwidth=self._width - 110, 505 always_highlight=True, 506 on_select_call=bui.WeakCall(self._select, pname, index), 507 on_activate_call=self._edit_soundtrack_with_sound, 508 selectable=True, 509 ) 510 if index == 0: 511 bui.widget(edit=txtw, up_widget=self._back_button) 512 self._soundtrack_widgets.append(txtw) 513 514 # Select this one if the user requested it 515 if select_soundtrack is not None: 516 if pname == select_soundtrack: 517 bui.columnwidget( 518 edit=self._col, selected_child=txtw, visible_child=txtw 519 ) 520 else: 521 # Select this one if it was previously selected. 522 # Go by index if there's one. 523 if old_selection_index is not None: 524 if index == old_selection_index: 525 bui.columnwidget( 526 edit=self._col, 527 selected_child=txtw, 528 visible_child=txtw, 529 ) 530 else: # Otherwise look by name. 531 if pname == old_selection: 532 bui.columnwidget( 533 edit=self._col, 534 selected_child=txtw, 535 visible_child=txtw, 536 ) 537 index += 1 538 539 # Explicitly run select callback on current one and re-enable 540 # callbacks. 541 542 # Eww need to run this in a timer so it happens after our select 543 # callbacks. With a small-enough time sometimes it happens before 544 # anyway. Ew. need a way to just schedule a callable i guess. 545 bui.apptimer(0.1, bui.WeakCall(self._set_allow_changing)) 546 547 def _set_allow_changing(self) -> None: 548 self._allow_changing_soundtracks = True 549 assert self._selected_soundtrack is not None 550 assert self._selected_soundtrack_index is not None 551 self._select(self._selected_soundtrack, self._selected_soundtrack_index) 552 553 def _new_soundtrack(self) -> None: 554 # pylint: disable=cyclic-import 555 from bauiv1lib.purchase import PurchaseWindow 556 from bauiv1lib.soundtrack.edit import SoundtrackEditWindow 557 558 if UNDER_CONSTRUCTION: 559 bui.screenmessage('UNDER CONSTRUCTION') 560 return 561 562 if REQUIRE_PRO and ( 563 bui.app.classic is not None 564 and not bui.app.classic.accounts.have_pro_options() 565 ): 566 PurchaseWindow(items=['pro']) 567 return 568 self._save_state() 569 bui.containerwidget(edit=self._root_widget, transition='out_left') 570 bui.app.ui_v1.set_main_window( 571 SoundtrackEditWindow(existing_soundtrack=None), from_window=self 572 ) 573 574 def _create_done(self, new_soundtrack: str) -> None: 575 if new_soundtrack is not None: 576 bui.getsound('gunCocking').play() 577 self._refresh(select_soundtrack=new_soundtrack) 578 579 def _save_state(self) -> None: 580 try: 581 sel = self._root_widget.get_selected_child() 582 if sel == self._scrollwidget: 583 sel_name = 'Scroll' 584 elif sel == self._new_button: 585 sel_name = 'New' 586 elif sel == self._edit_button: 587 sel_name = 'Edit' 588 elif sel == self._duplicate_button: 589 sel_name = 'Duplicate' 590 elif sel == self._delete_button: 591 sel_name = 'Delete' 592 elif sel == self._back_button: 593 sel_name = 'Back' 594 else: 595 raise ValueError(f'unrecognized selection \'{sel}\'') 596 assert bui.app.classic is not None 597 bui.app.ui_v1.window_states[type(self)] = sel_name 598 except Exception: 599 logging.exception('Error saving state for %s.', self) 600 601 def _restore_state(self) -> None: 602 try: 603 assert bui.app.classic is not None 604 sel_name = bui.app.ui_v1.window_states.get(type(self)) 605 if sel_name == 'Scroll': 606 sel = self._scrollwidget 607 elif sel_name == 'New': 608 sel = self._new_button 609 elif sel_name == 'Edit': 610 sel = self._edit_button 611 elif sel_name == 'Duplicate': 612 sel = self._duplicate_button 613 elif sel_name == 'Delete': 614 sel = self._delete_button 615 else: 616 sel = self._scrollwidget 617 bui.containerwidget(edit=self._root_widget, selected_child=sel) 618 except Exception: 619 logging.exception('Error restoring state for %s.', self)
Window for browsing soundtracks.
SoundtrackBrowserWindow( transition: str | None = 'in_right', origin_widget: _bauiv1.Widget | None = None)
26 def __init__( 27 self, 28 transition: str | None = 'in_right', 29 origin_widget: bui.Widget | None = None, 30 ): 31 # pylint: disable=too-many-statements 32 33 self._r = 'editSoundtrackWindow' 34 assert bui.app.classic is not None 35 uiscale = bui.app.ui_v1.uiscale 36 self._width = 800 if uiscale is bui.UIScale.SMALL else 600 37 x_inset = 100 if uiscale is bui.UIScale.SMALL else 0 38 self._height = ( 39 340 40 if uiscale is bui.UIScale.SMALL 41 else 370 if uiscale is bui.UIScale.MEDIUM else 440 42 ) 43 spacing = 40.0 44 v = self._height - 40.0 45 v -= spacing * 1.0 46 47 super().__init__( 48 root_widget=bui.containerwidget( 49 size=(self._width, self._height), 50 toolbar_visibility=( 51 'menu_minimal' 52 if uiscale is bui.UIScale.SMALL 53 else 'menu_full' 54 ), 55 scale=( 56 2.1 57 if uiscale is bui.UIScale.SMALL 58 else 1.6 if uiscale is bui.UIScale.MEDIUM else 1.0 59 ), 60 stack_offset=( 61 (0, -18) if uiscale is bui.UIScale.SMALL else (0, 0) 62 ), 63 ), 64 transition=transition, 65 origin_widget=origin_widget, 66 ) 67 68 assert bui.app.classic is not None 69 if uiscale is bui.UIScale.SMALL: 70 self._back_button = None 71 else: 72 self._back_button = bui.buttonwidget( 73 parent=self._root_widget, 74 position=(45 + x_inset, self._height - 60), 75 size=(120, 60), 76 scale=0.8, 77 label=bui.Lstr(resource='backText'), 78 button_type='back', 79 autoselect=True, 80 ) 81 bui.buttonwidget( 82 edit=self._back_button, 83 button_type='backSmall', 84 size=(60, 60), 85 label=bui.charstr(bui.SpecialChar.BACK), 86 ) 87 bui.textwidget( 88 parent=self._root_widget, 89 position=(self._width * 0.5, self._height - 35), 90 size=(0, 0), 91 maxwidth=300, 92 text=bui.Lstr(resource=f'{self._r}.titleText'), 93 color=bui.app.ui_v1.title_color, 94 h_align='center', 95 v_align='center', 96 ) 97 98 h = 43 + x_inset 99 v = self._height - 60 100 b_color = (0.6, 0.53, 0.63) 101 b_textcolor = (0.75, 0.7, 0.8) 102 lock_tex = bui.gettexture('lock') 103 self._lock_images: list[bui.Widget] = [] 104 105 scl = ( 106 1.0 107 if uiscale is bui.UIScale.SMALL 108 else 1.13 if uiscale is bui.UIScale.MEDIUM else 1.4 109 ) 110 v -= 60.0 * scl 111 self._new_button = btn = bui.buttonwidget( 112 parent=self._root_widget, 113 position=(h, v), 114 size=(100, 55.0 * scl), 115 on_activate_call=self._new_soundtrack, 116 color=b_color, 117 button_type='square', 118 autoselect=True, 119 textcolor=b_textcolor, 120 text_scale=0.7, 121 label=bui.Lstr(resource=f'{self._r}.newText'), 122 ) 123 self._lock_images.append( 124 bui.imagewidget( 125 parent=self._root_widget, 126 size=(30, 30), 127 draw_controller=btn, 128 position=(h - 10, v + 55.0 * scl - 28), 129 texture=lock_tex, 130 ) 131 ) 132 133 if self._back_button is None: 134 bui.widget( 135 edit=btn, 136 left_widget=bui.get_special_widget('back_button'), 137 ) 138 v -= 60.0 * scl 139 140 self._edit_button = btn = bui.buttonwidget( 141 parent=self._root_widget, 142 position=(h, v), 143 size=(100, 55.0 * scl), 144 on_activate_call=self._edit_soundtrack, 145 color=b_color, 146 button_type='square', 147 autoselect=True, 148 textcolor=b_textcolor, 149 text_scale=0.7, 150 label=bui.Lstr(resource=f'{self._r}.editText'), 151 ) 152 self._lock_images.append( 153 bui.imagewidget( 154 parent=self._root_widget, 155 size=(30, 30), 156 draw_controller=btn, 157 position=(h - 10, v + 55.0 * scl - 28), 158 texture=lock_tex, 159 ) 160 ) 161 if self._back_button is None: 162 bui.widget( 163 edit=btn, 164 left_widget=bui.get_special_widget('back_button'), 165 ) 166 v -= 60.0 * scl 167 168 self._duplicate_button = btn = bui.buttonwidget( 169 parent=self._root_widget, 170 position=(h, v), 171 size=(100, 55.0 * scl), 172 on_activate_call=self._duplicate_soundtrack, 173 button_type='square', 174 autoselect=True, 175 color=b_color, 176 textcolor=b_textcolor, 177 text_scale=0.7, 178 label=bui.Lstr(resource=f'{self._r}.duplicateText'), 179 ) 180 self._lock_images.append( 181 bui.imagewidget( 182 parent=self._root_widget, 183 size=(30, 30), 184 draw_controller=btn, 185 position=(h - 10, v + 55.0 * scl - 28), 186 texture=lock_tex, 187 ) 188 ) 189 if self._back_button is None: 190 bui.widget( 191 edit=btn, 192 left_widget=bui.get_special_widget('back_button'), 193 ) 194 v -= 60.0 * scl 195 196 self._delete_button = btn = bui.buttonwidget( 197 parent=self._root_widget, 198 position=(h, v), 199 size=(100, 55.0 * scl), 200 on_activate_call=self._delete_soundtrack, 201 color=b_color, 202 button_type='square', 203 autoselect=True, 204 textcolor=b_textcolor, 205 text_scale=0.7, 206 label=bui.Lstr(resource=f'{self._r}.deleteText'), 207 ) 208 self._lock_images.append( 209 bui.imagewidget( 210 parent=self._root_widget, 211 size=(30, 30), 212 draw_controller=btn, 213 position=(h - 10, v + 55.0 * scl - 28), 214 texture=lock_tex, 215 ) 216 ) 217 if self._back_button is None: 218 bui.widget( 219 edit=btn, 220 left_widget=bui.get_special_widget('back_button'), 221 ) 222 223 # Keep our lock images up to date/etc. 224 self._update_timer = bui.AppTimer( 225 1.0, bui.WeakCall(self._update), repeat=True 226 ) 227 self._update() 228 229 v = self._height - 65 230 scroll_height = self._height - 105 231 v -= scroll_height 232 self._scrollwidget = scrollwidget = bui.scrollwidget( 233 parent=self._root_widget, 234 position=(152 + x_inset, v), 235 highlight=False, 236 size=(self._width - (205 + 2 * x_inset), scroll_height), 237 ) 238 bui.widget( 239 edit=self._scrollwidget, 240 left_widget=self._new_button, 241 right_widget=bui.get_special_widget('squad_button'), 242 ) 243 self._col = bui.columnwidget(parent=scrollwidget, border=2, margin=0) 244 245 self._soundtracks: dict[str, Any] | None = None 246 self._selected_soundtrack: str | None = None 247 self._selected_soundtrack_index: int | None = None 248 self._soundtrack_widgets: list[bui.Widget] = [] 249 self._allow_changing_soundtracks = False 250 self._refresh() 251 if self._back_button is not None: 252 bui.buttonwidget( 253 edit=self._back_button, on_activate_call=self.main_window_back 254 ) 255 bui.containerwidget( 256 edit=self._root_widget, cancel_button=self._back_button 257 ) 258 else: 259 bui.containerwidget( 260 edit=self._root_widget, on_cancel_call=self.main_window_back 261 )
Create a MainWindow given a root widget and transition info.
Automatically handles in and out transitions on the provided widget, so there is no need to set transitions when creating it.
263 @override 264 def get_main_window_state(self) -> bui.MainWindowState: 265 # Support recreating our window for back/refresh purposes. 266 cls = type(self) 267 return bui.BasicMainWindowState( 268 create_call=lambda transition, origin_widget: cls( 269 transition=transition, origin_widget=origin_widget 270 ) 271 )
Return a WindowState to recreate this window, if supported.
@override
def
on_main_window_close(self) -> None:
Called before transitioning out a main window.
A good opportunity to save window state/etc.
Inherited Members
- bauiv1._uitypes.MainWindow
- main_window_back_state
- main_window_close
- can_change_main_window
- main_window_back
- main_window_replace
- bauiv1._uitypes.Window
- get_root_widget