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