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