bauiv1lib.soundtrack.edit
Provides UI for editing a soundtrack.
1# Released under the MIT License. See LICENSE for details. 2# 3"""Provides UI for editing a soundtrack.""" 4 5from __future__ import annotations 6 7import copy 8import os 9from typing import TYPE_CHECKING, cast, override 10 11import bascenev1 as bs 12import bauiv1 as bui 13 14if TYPE_CHECKING: 15 from typing import Any 16 17 18class SoundtrackEditWindow(bui.MainWindow): 19 """Window for editing a soundtrack.""" 20 21 def __init__( 22 self, 23 existing_soundtrack: str | dict[str, Any] | None, 24 transition: str | None = 'in_right', 25 origin_widget: bui.Widget | None = None, 26 ): 27 # pylint: disable=too-many-statements 28 29 print('SPAWNING EDIT') 30 31 appconfig = bui.app.config 32 self._r = 'editSoundtrackWindow' 33 self._folder_tex = bui.gettexture('folder') 34 self._file_tex = bui.gettexture('file') 35 assert bui.app.classic is not None 36 uiscale = bui.app.ui_v1.uiscale 37 self._width = 900 if uiscale is bui.UIScale.SMALL else 648 38 x_inset = 100 if uiscale is bui.UIScale.SMALL else 0 39 self._height = ( 40 395 41 if uiscale is bui.UIScale.SMALL 42 else 450 if uiscale is bui.UIScale.MEDIUM else 560 43 ) 44 super().__init__( 45 root_widget=bui.containerwidget( 46 size=(self._width, self._height), 47 scale=( 48 1.8 49 if uiscale is bui.UIScale.SMALL 50 else 1.5 if uiscale is bui.UIScale.MEDIUM else 1.0 51 ), 52 stack_offset=( 53 (0, -37) 54 if uiscale is bui.UIScale.SMALL 55 else (0, 15) if uiscale is bui.UIScale.MEDIUM else (0, 0) 56 ), 57 ), 58 transition=transition, 59 origin_widget=origin_widget, 60 ) 61 cancel_button = bui.buttonwidget( 62 parent=self._root_widget, 63 position=(38 + x_inset, self._height - 60), 64 size=(160, 60), 65 autoselect=True, 66 label=bui.Lstr(resource='cancelText'), 67 scale=0.8, 68 ) 69 save_button = bui.buttonwidget( 70 parent=self._root_widget, 71 position=(self._width - (168 + x_inset), self._height - 60), 72 autoselect=True, 73 size=(160, 60), 74 label=bui.Lstr(resource='saveText'), 75 scale=0.8, 76 ) 77 bui.widget(edit=save_button, left_widget=cancel_button) 78 bui.widget(edit=cancel_button, right_widget=save_button) 79 bui.textwidget( 80 parent=self._root_widget, 81 position=(0, self._height - 50), 82 size=(self._width, 25), 83 text=bui.Lstr( 84 resource=self._r 85 + ( 86 '.editSoundtrackText' 87 if existing_soundtrack is not None 88 else '.newSoundtrackText' 89 ) 90 ), 91 color=bui.app.ui_v1.title_color, 92 h_align='center', 93 v_align='center', 94 maxwidth=280, 95 ) 96 v = self._height - 110 97 if 'Soundtracks' not in appconfig: 98 appconfig['Soundtracks'] = {} 99 100 self._soundtrack_name: str | None 101 self._existing_soundtrack = existing_soundtrack 102 self._existing_soundtrack_name: str | None 103 if existing_soundtrack is not None: 104 # if they passed just a name, pull info from that soundtrack 105 if isinstance(existing_soundtrack, str): 106 self._soundtrack = copy.deepcopy( 107 appconfig['Soundtracks'][existing_soundtrack] 108 ) 109 self._soundtrack_name = existing_soundtrack 110 self._existing_soundtrack_name = existing_soundtrack 111 self._last_edited_song_type = None 112 else: 113 # otherwise they can pass info on an in-progress edit 114 self._soundtrack = existing_soundtrack['soundtrack'] 115 self._soundtrack_name = existing_soundtrack['name'] 116 self._existing_soundtrack_name = existing_soundtrack[ 117 'existing_name' 118 ] 119 self._last_edited_song_type = existing_soundtrack[ 120 'last_edited_song_type' 121 ] 122 else: 123 self._soundtrack_name = None 124 self._existing_soundtrack_name = None 125 self._soundtrack = {} 126 self._last_edited_song_type = None 127 128 bui.textwidget( 129 parent=self._root_widget, 130 text=bui.Lstr(resource=f'{self._r}.nameText'), 131 maxwidth=80, 132 scale=0.8, 133 position=(105 + x_inset, v + 19), 134 color=(0.8, 0.8, 0.8, 0.5), 135 size=(0, 0), 136 h_align='right', 137 v_align='center', 138 ) 139 140 # if there's no initial value, find a good initial unused name 141 if existing_soundtrack is None: 142 i = 1 143 st_name_text = bui.Lstr( 144 resource=f'{self._r}.newSoundtrackNameText' 145 ).evaluate() 146 if '${COUNT}' not in st_name_text: 147 # make sure we insert number *somewhere* 148 st_name_text = st_name_text + ' ${COUNT}' 149 while True: 150 self._soundtrack_name = st_name_text.replace('${COUNT}', str(i)) 151 if self._soundtrack_name not in appconfig['Soundtracks']: 152 break 153 i += 1 154 155 self._text_field = bui.textwidget( 156 parent=self._root_widget, 157 position=(120 + x_inset, v - 5), 158 size=(self._width - (160 + 2 * x_inset), 43), 159 text=self._soundtrack_name, 160 h_align='left', 161 v_align='center', 162 max_chars=32, 163 autoselect=True, 164 description=bui.Lstr(resource=f'{self._r}.nameText'), 165 editable=True, 166 padding=4, 167 on_return_press_call=self._do_it_with_sound, 168 ) 169 170 scroll_height = self._height - 180 171 self._scrollwidget = scrollwidget = bui.scrollwidget( 172 parent=self._root_widget, 173 highlight=False, 174 position=(40 + x_inset, v - (scroll_height + 10)), 175 size=(self._width - (80 + 2 * x_inset), scroll_height), 176 simple_culling_v=10, 177 claims_left_right=True, 178 claims_tab=True, 179 selection_loops_to_parent=True, 180 ) 181 bui.widget(edit=self._text_field, down_widget=self._scrollwidget) 182 self._col = bui.columnwidget( 183 parent=scrollwidget, 184 claims_left_right=True, 185 claims_tab=True, 186 selection_loops_to_parent=True, 187 ) 188 189 self._song_type_buttons: dict[str, bui.Widget] = {} 190 self._refresh() 191 bui.buttonwidget(edit=cancel_button, on_activate_call=self._cancel) 192 bui.containerwidget(edit=self._root_widget, cancel_button=cancel_button) 193 bui.buttonwidget(edit=save_button, on_activate_call=self._do_it) 194 bui.containerwidget(edit=self._root_widget, start_button=save_button) 195 bui.widget(edit=self._text_field, up_widget=cancel_button) 196 bui.widget(edit=cancel_button, down_widget=self._text_field) 197 198 @override 199 def get_main_window_state(self) -> bui.MainWindowState: 200 # Support recreating our window for back/refresh purposes. 201 cls = type(self) 202 203 # Pull this out of self here; if we do it in the lambda we'll 204 # keep our window alive due to the 'self' reference. 205 existing_soundtrack = self._existing_soundtrack 206 return bui.BasicMainWindowState( 207 create_call=lambda transition, origin_widget: cls( 208 transition=transition, 209 origin_widget=origin_widget, 210 existing_soundtrack=existing_soundtrack, 211 ) 212 ) 213 214 def _refresh(self) -> None: 215 for widget in self._col.get_children(): 216 widget.delete() 217 218 types = [ 219 'Menu', 220 'CharSelect', 221 'ToTheDeath', 222 'Onslaught', 223 'Keep Away', 224 'Race', 225 'Epic Race', 226 'ForwardMarch', 227 'FlagCatcher', 228 'Survival', 229 'Epic', 230 'Hockey', 231 'Football', 232 'Flying', 233 'Scary', 234 'Marching', 235 'GrandRomp', 236 'Chosen One', 237 'Scores', 238 'Victory', 239 ] 240 241 # FIXME: We should probably convert this to use translations. 242 type_names_translated = bui.app.lang.get_resource('soundtrackTypeNames') 243 prev_type_button: bui.Widget | None = None 244 prev_test_button: bui.Widget | None = None 245 246 for index, song_type in enumerate(types): 247 row = bui.rowwidget( 248 parent=self._col, 249 size=(self._width - 40, 40), 250 claims_left_right=True, 251 claims_tab=True, 252 selection_loops_to_parent=True, 253 ) 254 type_name = type_names_translated.get(song_type, song_type) 255 bui.textwidget( 256 parent=row, 257 size=(230, 25), 258 always_highlight=True, 259 text=type_name, 260 scale=0.7, 261 h_align='left', 262 v_align='center', 263 maxwidth=190, 264 ) 265 266 if song_type in self._soundtrack: 267 entry = self._soundtrack[song_type] 268 else: 269 entry = None 270 271 if entry is not None: 272 # Make sure they don't muck with this after it gets to us. 273 entry = copy.deepcopy(entry) 274 275 icon_type = self._get_entry_button_display_icon_type(entry) 276 self._song_type_buttons[song_type] = btn = bui.buttonwidget( 277 parent=row, 278 size=(230, 32), 279 label=self._get_entry_button_display_name(entry), 280 text_scale=0.6, 281 on_activate_call=bui.Call( 282 self._get_entry, song_type, entry, type_name 283 ), 284 icon=( 285 self._file_tex 286 if icon_type == 'file' 287 else self._folder_tex if icon_type == 'folder' else None 288 ), 289 icon_color=( 290 (1.1, 0.8, 0.2) if icon_type == 'folder' else (1, 1, 1) 291 ), 292 left_widget=self._text_field, 293 iconscale=0.7, 294 autoselect=True, 295 up_widget=prev_type_button, 296 ) 297 if index == 0: 298 bui.widget(edit=btn, up_widget=self._text_field) 299 bui.widget(edit=btn, down_widget=btn) 300 301 if ( 302 self._last_edited_song_type is not None 303 and song_type == self._last_edited_song_type 304 ): 305 bui.containerwidget( 306 edit=row, selected_child=btn, visible_child=btn 307 ) 308 bui.containerwidget( 309 edit=self._col, selected_child=row, visible_child=row 310 ) 311 bui.containerwidget( 312 edit=self._scrollwidget, 313 selected_child=self._col, 314 visible_child=self._col, 315 ) 316 bui.containerwidget( 317 edit=self._root_widget, 318 selected_child=self._scrollwidget, 319 visible_child=self._scrollwidget, 320 ) 321 322 if prev_type_button is not None: 323 bui.widget(edit=prev_type_button, down_widget=btn) 324 prev_type_button = btn 325 bui.textwidget(parent=row, size=(10, 32), text='') # spacing 326 assert bui.app.classic is not None 327 btn = bui.buttonwidget( 328 parent=row, 329 size=(50, 32), 330 label=bui.Lstr(resource=f'{self._r}.testText'), 331 text_scale=0.6, 332 on_activate_call=bui.Call(self._test, bs.MusicType(song_type)), 333 up_widget=( 334 prev_test_button 335 if prev_test_button is not None 336 else self._text_field 337 ), 338 ) 339 if prev_test_button is not None: 340 bui.widget(edit=prev_test_button, down_widget=btn) 341 bui.widget(edit=btn, down_widget=btn, right_widget=btn) 342 prev_test_button = btn 343 344 @classmethod 345 def _restore_editor( 346 cls, state: dict[str, Any], musictype: str, entry: Any 347 ) -> None: 348 assert bui.app.classic is not None 349 music = bui.app.classic.music 350 351 print('GoT RESTORE', state, musictype, entry) 352 353 # Apply the change and recreate the window. 354 soundtrack = state['soundtrack'] 355 existing_entry = ( 356 None if musictype not in soundtrack else soundtrack[musictype] 357 ) 358 if existing_entry != entry: 359 bui.getsound('gunCocking').play() 360 361 # Make sure this doesn't get mucked with after we get it. 362 if entry is not None: 363 entry = copy.deepcopy(entry) 364 365 entry_type = music.get_soundtrack_entry_type(entry) 366 if entry_type == 'default': 367 # For 'default' entries simply exclude them from the list. 368 if musictype in soundtrack: 369 del soundtrack[musictype] 370 else: 371 soundtrack[musictype] = entry 372 373 def _get_entry( 374 self, song_type: str, entry: Any, selection_target_name: str 375 ) -> None: 376 assert bui.app.classic is not None 377 music = bui.app.classic.music 378 379 # no-op if our underlying widget is dead or on its way out. 380 if not self._root_widget or self._root_widget.transitioning_out: 381 return 382 383 if selection_target_name != '': 384 selection_target_name = "'" + selection_target_name + "'" 385 state = { 386 'name': self._soundtrack_name, 387 'existing_name': self._existing_soundtrack_name, 388 'soundtrack': self._soundtrack, 389 'last_edited_song_type': song_type, 390 } 391 self.main_window_replace( 392 music.get_music_player().select_entry( 393 bui.Call(self._restore_editor, state, song_type), 394 entry, 395 selection_target_name, 396 ), 397 group_id='soundtrackentryselect', 398 ) 399 # bui.containerwidget(edit=self._root_widget, transition='out_left') 400 # bui.app.ui_v1.set_main_window( 401 # music.get_music_player().select_entry( 402 # bui.Call(self._restore_editor, state, song_type), 403 # entry, 404 # selection_target_name, 405 # ), 406 # from_window=self, 407 # ) 408 409 def _test(self, song_type: bs.MusicType) -> None: 410 assert bui.app.classic is not None 411 music = bui.app.classic.music 412 413 # Warn if volume is zero. 414 if bui.app.config.resolve('Music Volume') < 0.01: 415 bui.getsound('error').play() 416 bui.screenmessage( 417 bui.Lstr(resource=f'{self._r}.musicVolumeZeroWarning'), 418 color=(1, 0.5, 0), 419 ) 420 music.set_music_play_mode(bui.app.classic.MusicPlayMode.TEST) 421 music.do_play_music( 422 song_type, 423 mode=bui.app.classic.MusicPlayMode.TEST, 424 testsoundtrack=self._soundtrack, 425 ) 426 427 def _get_entry_button_display_name(self, entry: Any) -> str | bui.Lstr: 428 assert bui.app.classic is not None 429 music = bui.app.classic.music 430 etype = music.get_soundtrack_entry_type(entry) 431 ename: str | bui.Lstr 432 if etype == 'default': 433 ename = bui.Lstr(resource=f'{self._r}.defaultGameMusicText') 434 elif etype in ('musicFile', 'musicFolder'): 435 ename = os.path.basename(music.get_soundtrack_entry_name(entry)) 436 else: 437 ename = music.get_soundtrack_entry_name(entry) 438 return ename 439 440 def _get_entry_button_display_icon_type(self, entry: Any) -> str | None: 441 assert bui.app.classic is not None 442 music = bui.app.classic.music 443 etype = music.get_soundtrack_entry_type(entry) 444 if etype == 'musicFile': 445 return 'file' 446 if etype == 'musicFolder': 447 return 'folder' 448 return None 449 450 def _cancel(self) -> None: 451 # from bauiv1lib.soundtrack.browser import SoundtrackBrowserWindow 452 453 # no-op if our underlying widget is dead or on its way out. 454 if not self._root_widget or self._root_widget.transitioning_out: 455 return 456 457 assert bui.app.classic is not None 458 music = bui.app.classic.music 459 460 # Resets music back to normal. 461 music.set_music_play_mode(bui.app.classic.MusicPlayMode.REGULAR) 462 463 self.main_window_back() 464 # bui.containerwidget(edit=self._root_widget, transition='out_right') 465 # bui.app.ui_v1.set_main_window( 466 # SoundtrackBrowserWindow(transition='in_left'), 467 # from_window=self, 468 # is_back=True, 469 # ) 470 471 def _do_it(self) -> None: 472 # from bauiv1lib.soundtrack.browser import SoundtrackBrowserWindow 473 474 # no-op if our underlying widget is dead or on its way out. 475 if not self._root_widget or self._root_widget.transitioning_out: 476 return 477 478 assert bui.app.classic is not None 479 music = bui.app.classic.music 480 cfg = bui.app.config 481 new_name = cast(str, bui.textwidget(query=self._text_field)) 482 if new_name != self._soundtrack_name and new_name in cfg['Soundtracks']: 483 bui.screenmessage( 484 bui.Lstr(resource=f'{self._r}.cantSaveAlreadyExistsText') 485 ) 486 bui.getsound('error').play() 487 return 488 if not new_name: 489 bui.getsound('error').play() 490 return 491 if ( 492 new_name 493 == bui.Lstr( 494 resource=f'{self._r}.defaultSoundtrackNameText' 495 ).evaluate() 496 ): 497 bui.screenmessage( 498 bui.Lstr(resource=f'{self._r}.cantOverwriteDefaultText') 499 ) 500 bui.getsound('error').play() 501 return 502 503 # Make sure config exists. 504 if 'Soundtracks' not in cfg: 505 cfg['Soundtracks'] = {} 506 507 # If we had an old one, delete it. 508 if ( 509 self._existing_soundtrack_name is not None 510 and self._existing_soundtrack_name in cfg['Soundtracks'] 511 ): 512 del cfg['Soundtracks'][self._existing_soundtrack_name] 513 cfg['Soundtracks'][new_name] = self._soundtrack 514 cfg['Soundtrack'] = new_name 515 516 cfg.commit() 517 bui.getsound('gunCocking').play() 518 # bui.containerwidget(edit=self._root_widget, transition='out_right') 519 520 # Resets music back to normal. 521 music.set_music_play_mode( 522 bui.app.classic.MusicPlayMode.REGULAR, force_restart=True 523 ) 524 525 self.main_window_back() 526 # bui.app.ui_v1.set_main_window( 527 # SoundtrackBrowserWindow(transition='in_left'), 528 # from_window=self, 529 # is_back=True, 530 # ) 531 532 def _do_it_with_sound(self) -> None: 533 bui.getsound('swish').play() 534 self._do_it()
class
SoundtrackEditWindow(bauiv1._uitypes.MainWindow):
19class SoundtrackEditWindow(bui.MainWindow): 20 """Window for editing a soundtrack.""" 21 22 def __init__( 23 self, 24 existing_soundtrack: str | dict[str, Any] | None, 25 transition: str | None = 'in_right', 26 origin_widget: bui.Widget | None = None, 27 ): 28 # pylint: disable=too-many-statements 29 30 print('SPAWNING EDIT') 31 32 appconfig = bui.app.config 33 self._r = 'editSoundtrackWindow' 34 self._folder_tex = bui.gettexture('folder') 35 self._file_tex = bui.gettexture('file') 36 assert bui.app.classic is not None 37 uiscale = bui.app.ui_v1.uiscale 38 self._width = 900 if uiscale is bui.UIScale.SMALL else 648 39 x_inset = 100 if uiscale is bui.UIScale.SMALL else 0 40 self._height = ( 41 395 42 if uiscale is bui.UIScale.SMALL 43 else 450 if uiscale is bui.UIScale.MEDIUM else 560 44 ) 45 super().__init__( 46 root_widget=bui.containerwidget( 47 size=(self._width, self._height), 48 scale=( 49 1.8 50 if uiscale is bui.UIScale.SMALL 51 else 1.5 if uiscale is bui.UIScale.MEDIUM else 1.0 52 ), 53 stack_offset=( 54 (0, -37) 55 if uiscale is bui.UIScale.SMALL 56 else (0, 15) if uiscale is bui.UIScale.MEDIUM else (0, 0) 57 ), 58 ), 59 transition=transition, 60 origin_widget=origin_widget, 61 ) 62 cancel_button = bui.buttonwidget( 63 parent=self._root_widget, 64 position=(38 + x_inset, self._height - 60), 65 size=(160, 60), 66 autoselect=True, 67 label=bui.Lstr(resource='cancelText'), 68 scale=0.8, 69 ) 70 save_button = bui.buttonwidget( 71 parent=self._root_widget, 72 position=(self._width - (168 + x_inset), self._height - 60), 73 autoselect=True, 74 size=(160, 60), 75 label=bui.Lstr(resource='saveText'), 76 scale=0.8, 77 ) 78 bui.widget(edit=save_button, left_widget=cancel_button) 79 bui.widget(edit=cancel_button, right_widget=save_button) 80 bui.textwidget( 81 parent=self._root_widget, 82 position=(0, self._height - 50), 83 size=(self._width, 25), 84 text=bui.Lstr( 85 resource=self._r 86 + ( 87 '.editSoundtrackText' 88 if existing_soundtrack is not None 89 else '.newSoundtrackText' 90 ) 91 ), 92 color=bui.app.ui_v1.title_color, 93 h_align='center', 94 v_align='center', 95 maxwidth=280, 96 ) 97 v = self._height - 110 98 if 'Soundtracks' not in appconfig: 99 appconfig['Soundtracks'] = {} 100 101 self._soundtrack_name: str | None 102 self._existing_soundtrack = existing_soundtrack 103 self._existing_soundtrack_name: str | None 104 if existing_soundtrack is not None: 105 # if they passed just a name, pull info from that soundtrack 106 if isinstance(existing_soundtrack, str): 107 self._soundtrack = copy.deepcopy( 108 appconfig['Soundtracks'][existing_soundtrack] 109 ) 110 self._soundtrack_name = existing_soundtrack 111 self._existing_soundtrack_name = existing_soundtrack 112 self._last_edited_song_type = None 113 else: 114 # otherwise they can pass info on an in-progress edit 115 self._soundtrack = existing_soundtrack['soundtrack'] 116 self._soundtrack_name = existing_soundtrack['name'] 117 self._existing_soundtrack_name = existing_soundtrack[ 118 'existing_name' 119 ] 120 self._last_edited_song_type = existing_soundtrack[ 121 'last_edited_song_type' 122 ] 123 else: 124 self._soundtrack_name = None 125 self._existing_soundtrack_name = None 126 self._soundtrack = {} 127 self._last_edited_song_type = None 128 129 bui.textwidget( 130 parent=self._root_widget, 131 text=bui.Lstr(resource=f'{self._r}.nameText'), 132 maxwidth=80, 133 scale=0.8, 134 position=(105 + x_inset, v + 19), 135 color=(0.8, 0.8, 0.8, 0.5), 136 size=(0, 0), 137 h_align='right', 138 v_align='center', 139 ) 140 141 # if there's no initial value, find a good initial unused name 142 if existing_soundtrack is None: 143 i = 1 144 st_name_text = bui.Lstr( 145 resource=f'{self._r}.newSoundtrackNameText' 146 ).evaluate() 147 if '${COUNT}' not in st_name_text: 148 # make sure we insert number *somewhere* 149 st_name_text = st_name_text + ' ${COUNT}' 150 while True: 151 self._soundtrack_name = st_name_text.replace('${COUNT}', str(i)) 152 if self._soundtrack_name not in appconfig['Soundtracks']: 153 break 154 i += 1 155 156 self._text_field = bui.textwidget( 157 parent=self._root_widget, 158 position=(120 + x_inset, v - 5), 159 size=(self._width - (160 + 2 * x_inset), 43), 160 text=self._soundtrack_name, 161 h_align='left', 162 v_align='center', 163 max_chars=32, 164 autoselect=True, 165 description=bui.Lstr(resource=f'{self._r}.nameText'), 166 editable=True, 167 padding=4, 168 on_return_press_call=self._do_it_with_sound, 169 ) 170 171 scroll_height = self._height - 180 172 self._scrollwidget = scrollwidget = bui.scrollwidget( 173 parent=self._root_widget, 174 highlight=False, 175 position=(40 + x_inset, v - (scroll_height + 10)), 176 size=(self._width - (80 + 2 * x_inset), scroll_height), 177 simple_culling_v=10, 178 claims_left_right=True, 179 claims_tab=True, 180 selection_loops_to_parent=True, 181 ) 182 bui.widget(edit=self._text_field, down_widget=self._scrollwidget) 183 self._col = bui.columnwidget( 184 parent=scrollwidget, 185 claims_left_right=True, 186 claims_tab=True, 187 selection_loops_to_parent=True, 188 ) 189 190 self._song_type_buttons: dict[str, bui.Widget] = {} 191 self._refresh() 192 bui.buttonwidget(edit=cancel_button, on_activate_call=self._cancel) 193 bui.containerwidget(edit=self._root_widget, cancel_button=cancel_button) 194 bui.buttonwidget(edit=save_button, on_activate_call=self._do_it) 195 bui.containerwidget(edit=self._root_widget, start_button=save_button) 196 bui.widget(edit=self._text_field, up_widget=cancel_button) 197 bui.widget(edit=cancel_button, down_widget=self._text_field) 198 199 @override 200 def get_main_window_state(self) -> bui.MainWindowState: 201 # Support recreating our window for back/refresh purposes. 202 cls = type(self) 203 204 # Pull this out of self here; if we do it in the lambda we'll 205 # keep our window alive due to the 'self' reference. 206 existing_soundtrack = self._existing_soundtrack 207 return bui.BasicMainWindowState( 208 create_call=lambda transition, origin_widget: cls( 209 transition=transition, 210 origin_widget=origin_widget, 211 existing_soundtrack=existing_soundtrack, 212 ) 213 ) 214 215 def _refresh(self) -> None: 216 for widget in self._col.get_children(): 217 widget.delete() 218 219 types = [ 220 'Menu', 221 'CharSelect', 222 'ToTheDeath', 223 'Onslaught', 224 'Keep Away', 225 'Race', 226 'Epic Race', 227 'ForwardMarch', 228 'FlagCatcher', 229 'Survival', 230 'Epic', 231 'Hockey', 232 'Football', 233 'Flying', 234 'Scary', 235 'Marching', 236 'GrandRomp', 237 'Chosen One', 238 'Scores', 239 'Victory', 240 ] 241 242 # FIXME: We should probably convert this to use translations. 243 type_names_translated = bui.app.lang.get_resource('soundtrackTypeNames') 244 prev_type_button: bui.Widget | None = None 245 prev_test_button: bui.Widget | None = None 246 247 for index, song_type in enumerate(types): 248 row = bui.rowwidget( 249 parent=self._col, 250 size=(self._width - 40, 40), 251 claims_left_right=True, 252 claims_tab=True, 253 selection_loops_to_parent=True, 254 ) 255 type_name = type_names_translated.get(song_type, song_type) 256 bui.textwidget( 257 parent=row, 258 size=(230, 25), 259 always_highlight=True, 260 text=type_name, 261 scale=0.7, 262 h_align='left', 263 v_align='center', 264 maxwidth=190, 265 ) 266 267 if song_type in self._soundtrack: 268 entry = self._soundtrack[song_type] 269 else: 270 entry = None 271 272 if entry is not None: 273 # Make sure they don't muck with this after it gets to us. 274 entry = copy.deepcopy(entry) 275 276 icon_type = self._get_entry_button_display_icon_type(entry) 277 self._song_type_buttons[song_type] = btn = bui.buttonwidget( 278 parent=row, 279 size=(230, 32), 280 label=self._get_entry_button_display_name(entry), 281 text_scale=0.6, 282 on_activate_call=bui.Call( 283 self._get_entry, song_type, entry, type_name 284 ), 285 icon=( 286 self._file_tex 287 if icon_type == 'file' 288 else self._folder_tex if icon_type == 'folder' else None 289 ), 290 icon_color=( 291 (1.1, 0.8, 0.2) if icon_type == 'folder' else (1, 1, 1) 292 ), 293 left_widget=self._text_field, 294 iconscale=0.7, 295 autoselect=True, 296 up_widget=prev_type_button, 297 ) 298 if index == 0: 299 bui.widget(edit=btn, up_widget=self._text_field) 300 bui.widget(edit=btn, down_widget=btn) 301 302 if ( 303 self._last_edited_song_type is not None 304 and song_type == self._last_edited_song_type 305 ): 306 bui.containerwidget( 307 edit=row, selected_child=btn, visible_child=btn 308 ) 309 bui.containerwidget( 310 edit=self._col, selected_child=row, visible_child=row 311 ) 312 bui.containerwidget( 313 edit=self._scrollwidget, 314 selected_child=self._col, 315 visible_child=self._col, 316 ) 317 bui.containerwidget( 318 edit=self._root_widget, 319 selected_child=self._scrollwidget, 320 visible_child=self._scrollwidget, 321 ) 322 323 if prev_type_button is not None: 324 bui.widget(edit=prev_type_button, down_widget=btn) 325 prev_type_button = btn 326 bui.textwidget(parent=row, size=(10, 32), text='') # spacing 327 assert bui.app.classic is not None 328 btn = bui.buttonwidget( 329 parent=row, 330 size=(50, 32), 331 label=bui.Lstr(resource=f'{self._r}.testText'), 332 text_scale=0.6, 333 on_activate_call=bui.Call(self._test, bs.MusicType(song_type)), 334 up_widget=( 335 prev_test_button 336 if prev_test_button is not None 337 else self._text_field 338 ), 339 ) 340 if prev_test_button is not None: 341 bui.widget(edit=prev_test_button, down_widget=btn) 342 bui.widget(edit=btn, down_widget=btn, right_widget=btn) 343 prev_test_button = btn 344 345 @classmethod 346 def _restore_editor( 347 cls, state: dict[str, Any], musictype: str, entry: Any 348 ) -> None: 349 assert bui.app.classic is not None 350 music = bui.app.classic.music 351 352 print('GoT RESTORE', state, musictype, entry) 353 354 # Apply the change and recreate the window. 355 soundtrack = state['soundtrack'] 356 existing_entry = ( 357 None if musictype not in soundtrack else soundtrack[musictype] 358 ) 359 if existing_entry != entry: 360 bui.getsound('gunCocking').play() 361 362 # Make sure this doesn't get mucked with after we get it. 363 if entry is not None: 364 entry = copy.deepcopy(entry) 365 366 entry_type = music.get_soundtrack_entry_type(entry) 367 if entry_type == 'default': 368 # For 'default' entries simply exclude them from the list. 369 if musictype in soundtrack: 370 del soundtrack[musictype] 371 else: 372 soundtrack[musictype] = entry 373 374 def _get_entry( 375 self, song_type: str, entry: Any, selection_target_name: str 376 ) -> None: 377 assert bui.app.classic is not None 378 music = bui.app.classic.music 379 380 # no-op if our underlying widget is dead or on its way out. 381 if not self._root_widget or self._root_widget.transitioning_out: 382 return 383 384 if selection_target_name != '': 385 selection_target_name = "'" + selection_target_name + "'" 386 state = { 387 'name': self._soundtrack_name, 388 'existing_name': self._existing_soundtrack_name, 389 'soundtrack': self._soundtrack, 390 'last_edited_song_type': song_type, 391 } 392 self.main_window_replace( 393 music.get_music_player().select_entry( 394 bui.Call(self._restore_editor, state, song_type), 395 entry, 396 selection_target_name, 397 ), 398 group_id='soundtrackentryselect', 399 ) 400 # bui.containerwidget(edit=self._root_widget, transition='out_left') 401 # bui.app.ui_v1.set_main_window( 402 # music.get_music_player().select_entry( 403 # bui.Call(self._restore_editor, state, song_type), 404 # entry, 405 # selection_target_name, 406 # ), 407 # from_window=self, 408 # ) 409 410 def _test(self, song_type: bs.MusicType) -> None: 411 assert bui.app.classic is not None 412 music = bui.app.classic.music 413 414 # Warn if volume is zero. 415 if bui.app.config.resolve('Music Volume') < 0.01: 416 bui.getsound('error').play() 417 bui.screenmessage( 418 bui.Lstr(resource=f'{self._r}.musicVolumeZeroWarning'), 419 color=(1, 0.5, 0), 420 ) 421 music.set_music_play_mode(bui.app.classic.MusicPlayMode.TEST) 422 music.do_play_music( 423 song_type, 424 mode=bui.app.classic.MusicPlayMode.TEST, 425 testsoundtrack=self._soundtrack, 426 ) 427 428 def _get_entry_button_display_name(self, entry: Any) -> str | bui.Lstr: 429 assert bui.app.classic is not None 430 music = bui.app.classic.music 431 etype = music.get_soundtrack_entry_type(entry) 432 ename: str | bui.Lstr 433 if etype == 'default': 434 ename = bui.Lstr(resource=f'{self._r}.defaultGameMusicText') 435 elif etype in ('musicFile', 'musicFolder'): 436 ename = os.path.basename(music.get_soundtrack_entry_name(entry)) 437 else: 438 ename = music.get_soundtrack_entry_name(entry) 439 return ename 440 441 def _get_entry_button_display_icon_type(self, entry: Any) -> str | None: 442 assert bui.app.classic is not None 443 music = bui.app.classic.music 444 etype = music.get_soundtrack_entry_type(entry) 445 if etype == 'musicFile': 446 return 'file' 447 if etype == 'musicFolder': 448 return 'folder' 449 return None 450 451 def _cancel(self) -> None: 452 # from bauiv1lib.soundtrack.browser import SoundtrackBrowserWindow 453 454 # no-op if our underlying widget is dead or on its way out. 455 if not self._root_widget or self._root_widget.transitioning_out: 456 return 457 458 assert bui.app.classic is not None 459 music = bui.app.classic.music 460 461 # Resets music back to normal. 462 music.set_music_play_mode(bui.app.classic.MusicPlayMode.REGULAR) 463 464 self.main_window_back() 465 # bui.containerwidget(edit=self._root_widget, transition='out_right') 466 # bui.app.ui_v1.set_main_window( 467 # SoundtrackBrowserWindow(transition='in_left'), 468 # from_window=self, 469 # is_back=True, 470 # ) 471 472 def _do_it(self) -> None: 473 # from bauiv1lib.soundtrack.browser import SoundtrackBrowserWindow 474 475 # no-op if our underlying widget is dead or on its way out. 476 if not self._root_widget or self._root_widget.transitioning_out: 477 return 478 479 assert bui.app.classic is not None 480 music = bui.app.classic.music 481 cfg = bui.app.config 482 new_name = cast(str, bui.textwidget(query=self._text_field)) 483 if new_name != self._soundtrack_name and new_name in cfg['Soundtracks']: 484 bui.screenmessage( 485 bui.Lstr(resource=f'{self._r}.cantSaveAlreadyExistsText') 486 ) 487 bui.getsound('error').play() 488 return 489 if not new_name: 490 bui.getsound('error').play() 491 return 492 if ( 493 new_name 494 == bui.Lstr( 495 resource=f'{self._r}.defaultSoundtrackNameText' 496 ).evaluate() 497 ): 498 bui.screenmessage( 499 bui.Lstr(resource=f'{self._r}.cantOverwriteDefaultText') 500 ) 501 bui.getsound('error').play() 502 return 503 504 # Make sure config exists. 505 if 'Soundtracks' not in cfg: 506 cfg['Soundtracks'] = {} 507 508 # If we had an old one, delete it. 509 if ( 510 self._existing_soundtrack_name is not None 511 and self._existing_soundtrack_name in cfg['Soundtracks'] 512 ): 513 del cfg['Soundtracks'][self._existing_soundtrack_name] 514 cfg['Soundtracks'][new_name] = self._soundtrack 515 cfg['Soundtrack'] = new_name 516 517 cfg.commit() 518 bui.getsound('gunCocking').play() 519 # bui.containerwidget(edit=self._root_widget, transition='out_right') 520 521 # Resets music back to normal. 522 music.set_music_play_mode( 523 bui.app.classic.MusicPlayMode.REGULAR, force_restart=True 524 ) 525 526 self.main_window_back() 527 # bui.app.ui_v1.set_main_window( 528 # SoundtrackBrowserWindow(transition='in_left'), 529 # from_window=self, 530 # is_back=True, 531 # ) 532 533 def _do_it_with_sound(self) -> None: 534 bui.getsound('swish').play() 535 self._do_it()
Window for editing a soundtrack.
SoundtrackEditWindow( existing_soundtrack: str | dict[str, typing.Any] | None, transition: str | None = 'in_right', origin_widget: _bauiv1.Widget | None = None)
22 def __init__( 23 self, 24 existing_soundtrack: str | dict[str, Any] | None, 25 transition: str | None = 'in_right', 26 origin_widget: bui.Widget | None = None, 27 ): 28 # pylint: disable=too-many-statements 29 30 print('SPAWNING EDIT') 31 32 appconfig = bui.app.config 33 self._r = 'editSoundtrackWindow' 34 self._folder_tex = bui.gettexture('folder') 35 self._file_tex = bui.gettexture('file') 36 assert bui.app.classic is not None 37 uiscale = bui.app.ui_v1.uiscale 38 self._width = 900 if uiscale is bui.UIScale.SMALL else 648 39 x_inset = 100 if uiscale is bui.UIScale.SMALL else 0 40 self._height = ( 41 395 42 if uiscale is bui.UIScale.SMALL 43 else 450 if uiscale is bui.UIScale.MEDIUM else 560 44 ) 45 super().__init__( 46 root_widget=bui.containerwidget( 47 size=(self._width, self._height), 48 scale=( 49 1.8 50 if uiscale is bui.UIScale.SMALL 51 else 1.5 if uiscale is bui.UIScale.MEDIUM else 1.0 52 ), 53 stack_offset=( 54 (0, -37) 55 if uiscale is bui.UIScale.SMALL 56 else (0, 15) if uiscale is bui.UIScale.MEDIUM else (0, 0) 57 ), 58 ), 59 transition=transition, 60 origin_widget=origin_widget, 61 ) 62 cancel_button = bui.buttonwidget( 63 parent=self._root_widget, 64 position=(38 + x_inset, self._height - 60), 65 size=(160, 60), 66 autoselect=True, 67 label=bui.Lstr(resource='cancelText'), 68 scale=0.8, 69 ) 70 save_button = bui.buttonwidget( 71 parent=self._root_widget, 72 position=(self._width - (168 + x_inset), self._height - 60), 73 autoselect=True, 74 size=(160, 60), 75 label=bui.Lstr(resource='saveText'), 76 scale=0.8, 77 ) 78 bui.widget(edit=save_button, left_widget=cancel_button) 79 bui.widget(edit=cancel_button, right_widget=save_button) 80 bui.textwidget( 81 parent=self._root_widget, 82 position=(0, self._height - 50), 83 size=(self._width, 25), 84 text=bui.Lstr( 85 resource=self._r 86 + ( 87 '.editSoundtrackText' 88 if existing_soundtrack is not None 89 else '.newSoundtrackText' 90 ) 91 ), 92 color=bui.app.ui_v1.title_color, 93 h_align='center', 94 v_align='center', 95 maxwidth=280, 96 ) 97 v = self._height - 110 98 if 'Soundtracks' not in appconfig: 99 appconfig['Soundtracks'] = {} 100 101 self._soundtrack_name: str | None 102 self._existing_soundtrack = existing_soundtrack 103 self._existing_soundtrack_name: str | None 104 if existing_soundtrack is not None: 105 # if they passed just a name, pull info from that soundtrack 106 if isinstance(existing_soundtrack, str): 107 self._soundtrack = copy.deepcopy( 108 appconfig['Soundtracks'][existing_soundtrack] 109 ) 110 self._soundtrack_name = existing_soundtrack 111 self._existing_soundtrack_name = existing_soundtrack 112 self._last_edited_song_type = None 113 else: 114 # otherwise they can pass info on an in-progress edit 115 self._soundtrack = existing_soundtrack['soundtrack'] 116 self._soundtrack_name = existing_soundtrack['name'] 117 self._existing_soundtrack_name = existing_soundtrack[ 118 'existing_name' 119 ] 120 self._last_edited_song_type = existing_soundtrack[ 121 'last_edited_song_type' 122 ] 123 else: 124 self._soundtrack_name = None 125 self._existing_soundtrack_name = None 126 self._soundtrack = {} 127 self._last_edited_song_type = None 128 129 bui.textwidget( 130 parent=self._root_widget, 131 text=bui.Lstr(resource=f'{self._r}.nameText'), 132 maxwidth=80, 133 scale=0.8, 134 position=(105 + x_inset, v + 19), 135 color=(0.8, 0.8, 0.8, 0.5), 136 size=(0, 0), 137 h_align='right', 138 v_align='center', 139 ) 140 141 # if there's no initial value, find a good initial unused name 142 if existing_soundtrack is None: 143 i = 1 144 st_name_text = bui.Lstr( 145 resource=f'{self._r}.newSoundtrackNameText' 146 ).evaluate() 147 if '${COUNT}' not in st_name_text: 148 # make sure we insert number *somewhere* 149 st_name_text = st_name_text + ' ${COUNT}' 150 while True: 151 self._soundtrack_name = st_name_text.replace('${COUNT}', str(i)) 152 if self._soundtrack_name not in appconfig['Soundtracks']: 153 break 154 i += 1 155 156 self._text_field = bui.textwidget( 157 parent=self._root_widget, 158 position=(120 + x_inset, v - 5), 159 size=(self._width - (160 + 2 * x_inset), 43), 160 text=self._soundtrack_name, 161 h_align='left', 162 v_align='center', 163 max_chars=32, 164 autoselect=True, 165 description=bui.Lstr(resource=f'{self._r}.nameText'), 166 editable=True, 167 padding=4, 168 on_return_press_call=self._do_it_with_sound, 169 ) 170 171 scroll_height = self._height - 180 172 self._scrollwidget = scrollwidget = bui.scrollwidget( 173 parent=self._root_widget, 174 highlight=False, 175 position=(40 + x_inset, v - (scroll_height + 10)), 176 size=(self._width - (80 + 2 * x_inset), scroll_height), 177 simple_culling_v=10, 178 claims_left_right=True, 179 claims_tab=True, 180 selection_loops_to_parent=True, 181 ) 182 bui.widget(edit=self._text_field, down_widget=self._scrollwidget) 183 self._col = bui.columnwidget( 184 parent=scrollwidget, 185 claims_left_right=True, 186 claims_tab=True, 187 selection_loops_to_parent=True, 188 ) 189 190 self._song_type_buttons: dict[str, bui.Widget] = {} 191 self._refresh() 192 bui.buttonwidget(edit=cancel_button, on_activate_call=self._cancel) 193 bui.containerwidget(edit=self._root_widget, cancel_button=cancel_button) 194 bui.buttonwidget(edit=save_button, on_activate_call=self._do_it) 195 bui.containerwidget(edit=self._root_widget, start_button=save_button) 196 bui.widget(edit=self._text_field, up_widget=cancel_button) 197 bui.widget(edit=cancel_button, down_widget=self._text_field)
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.
199 @override 200 def get_main_window_state(self) -> bui.MainWindowState: 201 # Support recreating our window for back/refresh purposes. 202 cls = type(self) 203 204 # Pull this out of self here; if we do it in the lambda we'll 205 # keep our window alive due to the 'self' reference. 206 existing_soundtrack = self._existing_soundtrack 207 return bui.BasicMainWindowState( 208 create_call=lambda transition, origin_widget: cls( 209 transition=transition, 210 origin_widget=origin_widget, 211 existing_soundtrack=existing_soundtrack, 212 ) 213 )
Return a WindowState to recreate this window, if supported.
Inherited Members
- bauiv1._uitypes.MainWindow
- main_window_back_state
- main_window_close
- can_change_main_window
- main_window_back
- main_window_replace
- on_main_window_close
- bauiv1._uitypes.Window
- get_root_widget