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