bastd.ui.playlist.editgame
Provides UI for editing a game config.
1# Released under the MIT License. See LICENSE for details. 2# 3"""Provides UI for editing a game config.""" 4 5from __future__ import annotations 6 7import copy 8import random 9from typing import TYPE_CHECKING, cast 10 11import ba 12import ba.internal 13 14if TYPE_CHECKING: 15 from typing import Any, Callable 16 17 18class PlaylistEditGameWindow(ba.Window): 19 """Window for editing a game config.""" 20 21 def __init__( 22 self, 23 gametype: type[ba.GameActivity], 24 sessiontype: type[ba.Session], 25 config: dict[str, Any] | None, 26 completion_call: Callable[[dict[str, Any] | None], Any], 27 default_selection: str | None = None, 28 transition: str = 'in_right', 29 edit_info: dict[str, Any] | None = None, 30 ): 31 # pylint: disable=too-many-branches 32 # pylint: disable=too-many-statements 33 # pylint: disable=too-many-locals 34 from ba.internal import ( 35 get_unowned_maps, 36 get_filtered_map_name, 37 get_map_class, 38 get_map_display_string, 39 ) 40 41 self._gametype = gametype 42 self._sessiontype = sessiontype 43 44 # If we're within an editing session we get passed edit_info 45 # (returning from map selection window, etc). 46 if edit_info is not None: 47 self._edit_info = edit_info 48 49 # ..otherwise determine whether we're adding or editing a game based 50 # on whether an existing config was passed to us. 51 else: 52 if config is None: 53 self._edit_info = {'editType': 'add'} 54 else: 55 self._edit_info = {'editType': 'edit'} 56 57 self._r = 'gameSettingsWindow' 58 59 valid_maps = gametype.get_supported_maps(sessiontype) 60 if not valid_maps: 61 ba.screenmessage(ba.Lstr(resource='noValidMapsErrorText')) 62 raise Exception('No valid maps') 63 64 self._settings_defs = gametype.get_available_settings(sessiontype) 65 self._completion_call = completion_call 66 67 # To start with, pick a random map out of the ones we own. 68 unowned_maps = get_unowned_maps() 69 valid_maps_owned = [m for m in valid_maps if m not in unowned_maps] 70 if valid_maps_owned: 71 self._map = valid_maps[random.randrange(len(valid_maps_owned))] 72 73 # Hmmm.. we own none of these maps.. just pick a random un-owned one 74 # I guess.. should this ever happen? 75 else: 76 self._map = valid_maps[random.randrange(len(valid_maps))] 77 78 is_add = self._edit_info['editType'] == 'add' 79 80 # If there's a valid map name in the existing config, use that. 81 try: 82 if ( 83 config is not None 84 and 'settings' in config 85 and 'map' in config['settings'] 86 ): 87 filtered_map_name = get_filtered_map_name( 88 config['settings']['map'] 89 ) 90 if filtered_map_name in valid_maps: 91 self._map = filtered_map_name 92 except Exception: 93 ba.print_exception('Error getting map for editor.') 94 95 if config is not None and 'settings' in config: 96 self._settings = config['settings'] 97 else: 98 self._settings = {} 99 100 self._choice_selections: dict[str, int] = {} 101 102 uiscale = ba.app.ui.uiscale 103 width = 720 if uiscale is ba.UIScale.SMALL else 620 104 x_inset = 50 if uiscale is ba.UIScale.SMALL else 0 105 height = ( 106 365 107 if uiscale is ba.UIScale.SMALL 108 else 460 109 if uiscale is ba.UIScale.MEDIUM 110 else 550 111 ) 112 spacing = 52 113 y_extra = 15 114 y_extra2 = 21 115 116 map_tex_name = get_map_class(self._map).get_preview_texture_name() 117 if map_tex_name is None: 118 raise Exception('no map preview tex found for' + self._map) 119 map_tex = ba.gettexture(map_tex_name) 120 121 top_extra = 20 if uiscale is ba.UIScale.SMALL else 0 122 super().__init__( 123 root_widget=ba.containerwidget( 124 size=(width, height + top_extra), 125 transition=transition, 126 scale=( 127 2.19 128 if uiscale is ba.UIScale.SMALL 129 else 1.35 130 if uiscale is ba.UIScale.MEDIUM 131 else 1.0 132 ), 133 stack_offset=(0, -17) 134 if uiscale is ba.UIScale.SMALL 135 else (0, 0), 136 ) 137 ) 138 139 btn = ba.buttonwidget( 140 parent=self._root_widget, 141 position=(45 + x_inset, height - 82 + y_extra2), 142 size=(180, 70) if is_add else (180, 65), 143 label=ba.Lstr(resource='backText') 144 if is_add 145 else ba.Lstr(resource='cancelText'), 146 button_type='back' if is_add else None, 147 autoselect=True, 148 scale=0.75, 149 text_scale=1.3, 150 on_activate_call=ba.Call(self._cancel), 151 ) 152 ba.containerwidget(edit=self._root_widget, cancel_button=btn) 153 154 add_button = ba.buttonwidget( 155 parent=self._root_widget, 156 position=(width - (193 + x_inset), height - 82 + y_extra2), 157 size=(200, 65), 158 scale=0.75, 159 text_scale=1.3, 160 label=ba.Lstr(resource=self._r + '.addGameText') 161 if is_add 162 else ba.Lstr(resource='doneText'), 163 ) 164 165 if ba.app.ui.use_toolbars: 166 pbtn = ba.internal.get_special_widget('party_button') 167 ba.widget(edit=add_button, right_widget=pbtn, up_widget=pbtn) 168 169 ba.textwidget( 170 parent=self._root_widget, 171 position=(-8, height - 70 + y_extra2), 172 size=(width, 25), 173 text=gametype.get_display_string(), 174 color=ba.app.ui.title_color, 175 maxwidth=235, 176 scale=1.1, 177 h_align='center', 178 v_align='center', 179 ) 180 181 map_height = 100 182 183 scroll_height = map_height + 10 # map select and margin 184 185 # Calc our total height we'll need 186 scroll_height += spacing * len(self._settings_defs) 187 188 scroll_width = width - (86 + 2 * x_inset) 189 self._scrollwidget = ba.scrollwidget( 190 parent=self._root_widget, 191 position=(44 + x_inset, 35 + y_extra), 192 size=(scroll_width, height - 116), 193 highlight=False, 194 claims_left_right=True, 195 claims_tab=True, 196 selection_loops_to_parent=True, 197 ) 198 self._subcontainer = ba.containerwidget( 199 parent=self._scrollwidget, 200 size=(scroll_width, scroll_height), 201 background=False, 202 claims_left_right=True, 203 claims_tab=True, 204 selection_loops_to_parent=True, 205 ) 206 207 v = scroll_height - 5 208 h = -40 209 210 # Keep track of all the selectable widgets we make so we can wire 211 # them up conveniently. 212 widget_column: list[list[ba.Widget]] = [] 213 214 # Map select button. 215 ba.textwidget( 216 parent=self._subcontainer, 217 position=(h + 49, v - 63), 218 size=(100, 30), 219 maxwidth=110, 220 text=ba.Lstr(resource='mapText'), 221 h_align='left', 222 color=(0.8, 0.8, 0.8, 1.0), 223 v_align='center', 224 ) 225 226 ba.imagewidget( 227 parent=self._subcontainer, 228 size=(256 * 0.7, 125 * 0.7), 229 position=(h + 261 - 128 + 128.0 * 0.56, v - 90), 230 texture=map_tex, 231 model_opaque=ba.getmodel('level_select_button_opaque'), 232 model_transparent=ba.getmodel('level_select_button_transparent'), 233 mask_texture=ba.gettexture('mapPreviewMask'), 234 ) 235 map_button = btn = ba.buttonwidget( 236 parent=self._subcontainer, 237 size=(140, 60), 238 position=(h + 448, v - 72), 239 on_activate_call=ba.Call(self._select_map), 240 scale=0.7, 241 label=ba.Lstr(resource='mapSelectText'), 242 ) 243 widget_column.append([btn]) 244 245 ba.textwidget( 246 parent=self._subcontainer, 247 position=(h + 363 - 123, v - 114), 248 size=(100, 30), 249 flatness=1.0, 250 shadow=1.0, 251 scale=0.55, 252 maxwidth=256 * 0.7 * 0.8, 253 text=get_map_display_string(self._map), 254 h_align='center', 255 color=(0.6, 1.0, 0.6, 1.0), 256 v_align='center', 257 ) 258 v -= map_height 259 260 for setting in self._settings_defs: 261 value = setting.default 262 value_type = type(value) 263 264 # Now, if there's an existing value for it in the config, 265 # override with that. 266 try: 267 if ( 268 config is not None 269 and 'settings' in config 270 and setting.name in config['settings'] 271 ): 272 value = value_type(config['settings'][setting.name]) 273 except Exception: 274 ba.print_exception() 275 276 # Shove the starting value in there to start. 277 self._settings[setting.name] = value 278 279 name_translated = self._get_localized_setting_name(setting.name) 280 281 mw1 = 280 282 mw2 = 70 283 284 # Handle types with choices specially: 285 if isinstance(setting, ba.ChoiceSetting): 286 for choice in setting.choices: 287 if len(choice) != 2: 288 raise ValueError( 289 "Expected 2-member tuples for 'choices'; got: " 290 + repr(choice) 291 ) 292 if not isinstance(choice[0], str): 293 raise TypeError( 294 'First value for choice tuple must be a str; got: ' 295 + repr(choice) 296 ) 297 if not isinstance(choice[1], value_type): 298 raise TypeError( 299 'Choice type does not match default value; choice:' 300 + repr(choice) 301 + '; setting:' 302 + repr(setting) 303 ) 304 if value_type not in (int, float): 305 raise TypeError( 306 'Choice type setting must have int or float default; ' 307 'got: ' + repr(setting) 308 ) 309 310 # Start at the choice corresponding to the default if possible. 311 self._choice_selections[setting.name] = 0 312 for index, choice in enumerate(setting.choices): 313 if choice[1] == value: 314 self._choice_selections[setting.name] = index 315 break 316 317 v -= spacing 318 ba.textwidget( 319 parent=self._subcontainer, 320 position=(h + 50, v), 321 size=(100, 30), 322 maxwidth=mw1, 323 text=name_translated, 324 h_align='left', 325 color=(0.8, 0.8, 0.8, 1.0), 326 v_align='center', 327 ) 328 txt = ba.textwidget( 329 parent=self._subcontainer, 330 position=(h + 509 - 95, v), 331 size=(0, 28), 332 text=self._get_localized_setting_name( 333 setting.choices[self._choice_selections[setting.name]][ 334 0 335 ] 336 ), 337 editable=False, 338 color=(0.6, 1.0, 0.6, 1.0), 339 maxwidth=mw2, 340 h_align='right', 341 v_align='center', 342 padding=2, 343 ) 344 btn1 = ba.buttonwidget( 345 parent=self._subcontainer, 346 position=(h + 509 - 50 - 1, v), 347 size=(28, 28), 348 label='<', 349 autoselect=True, 350 on_activate_call=ba.Call( 351 self._choice_inc, setting.name, txt, setting, -1 352 ), 353 repeat=True, 354 ) 355 btn2 = ba.buttonwidget( 356 parent=self._subcontainer, 357 position=(h + 509 + 5, v), 358 size=(28, 28), 359 label='>', 360 autoselect=True, 361 on_activate_call=ba.Call( 362 self._choice_inc, setting.name, txt, setting, 1 363 ), 364 repeat=True, 365 ) 366 widget_column.append([btn1, btn2]) 367 368 elif isinstance(setting, (ba.IntSetting, ba.FloatSetting)): 369 v -= spacing 370 min_value = setting.min_value 371 max_value = setting.max_value 372 increment = setting.increment 373 ba.textwidget( 374 parent=self._subcontainer, 375 position=(h + 50, v), 376 size=(100, 30), 377 text=name_translated, 378 h_align='left', 379 color=(0.8, 0.8, 0.8, 1.0), 380 v_align='center', 381 maxwidth=mw1, 382 ) 383 txt = ba.textwidget( 384 parent=self._subcontainer, 385 position=(h + 509 - 95, v), 386 size=(0, 28), 387 text=str(value), 388 editable=False, 389 color=(0.6, 1.0, 0.6, 1.0), 390 maxwidth=mw2, 391 h_align='right', 392 v_align='center', 393 padding=2, 394 ) 395 btn1 = ba.buttonwidget( 396 parent=self._subcontainer, 397 position=(h + 509 - 50 - 1, v), 398 size=(28, 28), 399 label='-', 400 autoselect=True, 401 on_activate_call=ba.Call( 402 self._inc, 403 txt, 404 min_value, 405 max_value, 406 -increment, 407 value_type, 408 setting.name, 409 ), 410 repeat=True, 411 ) 412 btn2 = ba.buttonwidget( 413 parent=self._subcontainer, 414 position=(h + 509 + 5, v), 415 size=(28, 28), 416 label='+', 417 autoselect=True, 418 on_activate_call=ba.Call( 419 self._inc, 420 txt, 421 min_value, 422 max_value, 423 increment, 424 value_type, 425 setting.name, 426 ), 427 repeat=True, 428 ) 429 widget_column.append([btn1, btn2]) 430 431 elif value_type == bool: 432 v -= spacing 433 ba.textwidget( 434 parent=self._subcontainer, 435 position=(h + 50, v), 436 size=(100, 30), 437 text=name_translated, 438 h_align='left', 439 color=(0.8, 0.8, 0.8, 1.0), 440 v_align='center', 441 maxwidth=mw1, 442 ) 443 txt = ba.textwidget( 444 parent=self._subcontainer, 445 position=(h + 509 - 95, v), 446 size=(0, 28), 447 text=ba.Lstr(resource='onText') 448 if value 449 else ba.Lstr(resource='offText'), 450 editable=False, 451 color=(0.6, 1.0, 0.6, 1.0), 452 maxwidth=mw2, 453 h_align='right', 454 v_align='center', 455 padding=2, 456 ) 457 cbw = ba.checkboxwidget( 458 parent=self._subcontainer, 459 text='', 460 position=(h + 505 - 50 - 5, v - 2), 461 size=(200, 30), 462 autoselect=True, 463 textcolor=(0.8, 0.8, 0.8), 464 value=value, 465 on_value_change_call=ba.Call( 466 self._check_value_change, setting.name, txt 467 ), 468 ) 469 widget_column.append([cbw]) 470 471 else: 472 raise Exception() 473 474 # Ok now wire up the column. 475 try: 476 prev_widgets: list[ba.Widget] | None = None 477 for cwdg in widget_column: 478 if prev_widgets is not None: 479 # Wire our rightmost to their rightmost. 480 ba.widget(edit=prev_widgets[-1], down_widget=cwdg[-1]) 481 ba.widget(cwdg[-1], up_widget=prev_widgets[-1]) 482 483 # Wire our leftmost to their leftmost. 484 ba.widget(edit=prev_widgets[0], down_widget=cwdg[0]) 485 ba.widget(cwdg[0], up_widget=prev_widgets[0]) 486 prev_widgets = cwdg 487 except Exception: 488 ba.print_exception( 489 'Error wiring up game-settings-select widget column.' 490 ) 491 492 ba.buttonwidget(edit=add_button, on_activate_call=ba.Call(self._add)) 493 ba.containerwidget( 494 edit=self._root_widget, 495 selected_child=add_button, 496 start_button=add_button, 497 ) 498 499 if default_selection == 'map': 500 ba.containerwidget( 501 edit=self._root_widget, selected_child=self._scrollwidget 502 ) 503 ba.containerwidget( 504 edit=self._subcontainer, selected_child=map_button 505 ) 506 507 def _get_localized_setting_name(self, name: str) -> ba.Lstr: 508 return ba.Lstr(translate=('settingNames', name)) 509 510 def _select_map(self) -> None: 511 # pylint: disable=cyclic-import 512 from bastd.ui.playlist.mapselect import PlaylistMapSelectWindow 513 514 # Replace ourself with the map-select UI. 515 ba.containerwidget(edit=self._root_widget, transition='out_left') 516 ba.app.ui.set_main_menu_window( 517 PlaylistMapSelectWindow( 518 self._gametype, 519 self._sessiontype, 520 copy.deepcopy(self._getconfig()), 521 self._edit_info, 522 self._completion_call, 523 ).get_root_widget() 524 ) 525 526 def _choice_inc( 527 self, 528 setting_name: str, 529 widget: ba.Widget, 530 setting: ba.ChoiceSetting, 531 increment: int, 532 ) -> None: 533 choices = setting.choices 534 if increment > 0: 535 self._choice_selections[setting_name] = min( 536 len(choices) - 1, self._choice_selections[setting_name] + 1 537 ) 538 else: 539 self._choice_selections[setting_name] = max( 540 0, self._choice_selections[setting_name] - 1 541 ) 542 ba.textwidget( 543 edit=widget, 544 text=self._get_localized_setting_name( 545 choices[self._choice_selections[setting_name]][0] 546 ), 547 ) 548 self._settings[setting_name] = choices[ 549 self._choice_selections[setting_name] 550 ][1] 551 552 def _cancel(self) -> None: 553 self._completion_call(None) 554 555 def _check_value_change( 556 self, setting_name: str, widget: ba.Widget, value: int 557 ) -> None: 558 ba.textwidget( 559 edit=widget, 560 text=ba.Lstr(resource='onText') 561 if value 562 else ba.Lstr(resource='offText'), 563 ) 564 self._settings[setting_name] = value 565 566 def _getconfig(self) -> dict[str, Any]: 567 settings = copy.deepcopy(self._settings) 568 settings['map'] = self._map 569 return {'settings': settings} 570 571 def _add(self) -> None: 572 self._completion_call(copy.deepcopy(self._getconfig())) 573 574 def _inc( 575 self, 576 ctrl: ba.Widget, 577 min_val: int | float, 578 max_val: int | float, 579 increment: int | float, 580 setting_type: type, 581 setting_name: str, 582 ) -> None: 583 if setting_type == float: 584 val = float(cast(str, ba.textwidget(query=ctrl))) 585 else: 586 val = int(cast(str, ba.textwidget(query=ctrl))) 587 val += increment 588 val = max(min_val, min(val, max_val)) 589 if setting_type == float: 590 ba.textwidget(edit=ctrl, text=str(round(val, 2))) 591 elif setting_type == int: 592 ba.textwidget(edit=ctrl, text=str(int(val))) 593 else: 594 raise TypeError('invalid vartype: ' + str(setting_type)) 595 self._settings[setting_name] = val
class
PlaylistEditGameWindow(ba.ui.Window):
19class PlaylistEditGameWindow(ba.Window): 20 """Window for editing a game config.""" 21 22 def __init__( 23 self, 24 gametype: type[ba.GameActivity], 25 sessiontype: type[ba.Session], 26 config: dict[str, Any] | None, 27 completion_call: Callable[[dict[str, Any] | None], Any], 28 default_selection: str | None = None, 29 transition: str = 'in_right', 30 edit_info: dict[str, Any] | None = None, 31 ): 32 # pylint: disable=too-many-branches 33 # pylint: disable=too-many-statements 34 # pylint: disable=too-many-locals 35 from ba.internal import ( 36 get_unowned_maps, 37 get_filtered_map_name, 38 get_map_class, 39 get_map_display_string, 40 ) 41 42 self._gametype = gametype 43 self._sessiontype = sessiontype 44 45 # If we're within an editing session we get passed edit_info 46 # (returning from map selection window, etc). 47 if edit_info is not None: 48 self._edit_info = edit_info 49 50 # ..otherwise determine whether we're adding or editing a game based 51 # on whether an existing config was passed to us. 52 else: 53 if config is None: 54 self._edit_info = {'editType': 'add'} 55 else: 56 self._edit_info = {'editType': 'edit'} 57 58 self._r = 'gameSettingsWindow' 59 60 valid_maps = gametype.get_supported_maps(sessiontype) 61 if not valid_maps: 62 ba.screenmessage(ba.Lstr(resource='noValidMapsErrorText')) 63 raise Exception('No valid maps') 64 65 self._settings_defs = gametype.get_available_settings(sessiontype) 66 self._completion_call = completion_call 67 68 # To start with, pick a random map out of the ones we own. 69 unowned_maps = get_unowned_maps() 70 valid_maps_owned = [m for m in valid_maps if m not in unowned_maps] 71 if valid_maps_owned: 72 self._map = valid_maps[random.randrange(len(valid_maps_owned))] 73 74 # Hmmm.. we own none of these maps.. just pick a random un-owned one 75 # I guess.. should this ever happen? 76 else: 77 self._map = valid_maps[random.randrange(len(valid_maps))] 78 79 is_add = self._edit_info['editType'] == 'add' 80 81 # If there's a valid map name in the existing config, use that. 82 try: 83 if ( 84 config is not None 85 and 'settings' in config 86 and 'map' in config['settings'] 87 ): 88 filtered_map_name = get_filtered_map_name( 89 config['settings']['map'] 90 ) 91 if filtered_map_name in valid_maps: 92 self._map = filtered_map_name 93 except Exception: 94 ba.print_exception('Error getting map for editor.') 95 96 if config is not None and 'settings' in config: 97 self._settings = config['settings'] 98 else: 99 self._settings = {} 100 101 self._choice_selections: dict[str, int] = {} 102 103 uiscale = ba.app.ui.uiscale 104 width = 720 if uiscale is ba.UIScale.SMALL else 620 105 x_inset = 50 if uiscale is ba.UIScale.SMALL else 0 106 height = ( 107 365 108 if uiscale is ba.UIScale.SMALL 109 else 460 110 if uiscale is ba.UIScale.MEDIUM 111 else 550 112 ) 113 spacing = 52 114 y_extra = 15 115 y_extra2 = 21 116 117 map_tex_name = get_map_class(self._map).get_preview_texture_name() 118 if map_tex_name is None: 119 raise Exception('no map preview tex found for' + self._map) 120 map_tex = ba.gettexture(map_tex_name) 121 122 top_extra = 20 if uiscale is ba.UIScale.SMALL else 0 123 super().__init__( 124 root_widget=ba.containerwidget( 125 size=(width, height + top_extra), 126 transition=transition, 127 scale=( 128 2.19 129 if uiscale is ba.UIScale.SMALL 130 else 1.35 131 if uiscale is ba.UIScale.MEDIUM 132 else 1.0 133 ), 134 stack_offset=(0, -17) 135 if uiscale is ba.UIScale.SMALL 136 else (0, 0), 137 ) 138 ) 139 140 btn = ba.buttonwidget( 141 parent=self._root_widget, 142 position=(45 + x_inset, height - 82 + y_extra2), 143 size=(180, 70) if is_add else (180, 65), 144 label=ba.Lstr(resource='backText') 145 if is_add 146 else ba.Lstr(resource='cancelText'), 147 button_type='back' if is_add else None, 148 autoselect=True, 149 scale=0.75, 150 text_scale=1.3, 151 on_activate_call=ba.Call(self._cancel), 152 ) 153 ba.containerwidget(edit=self._root_widget, cancel_button=btn) 154 155 add_button = ba.buttonwidget( 156 parent=self._root_widget, 157 position=(width - (193 + x_inset), height - 82 + y_extra2), 158 size=(200, 65), 159 scale=0.75, 160 text_scale=1.3, 161 label=ba.Lstr(resource=self._r + '.addGameText') 162 if is_add 163 else ba.Lstr(resource='doneText'), 164 ) 165 166 if ba.app.ui.use_toolbars: 167 pbtn = ba.internal.get_special_widget('party_button') 168 ba.widget(edit=add_button, right_widget=pbtn, up_widget=pbtn) 169 170 ba.textwidget( 171 parent=self._root_widget, 172 position=(-8, height - 70 + y_extra2), 173 size=(width, 25), 174 text=gametype.get_display_string(), 175 color=ba.app.ui.title_color, 176 maxwidth=235, 177 scale=1.1, 178 h_align='center', 179 v_align='center', 180 ) 181 182 map_height = 100 183 184 scroll_height = map_height + 10 # map select and margin 185 186 # Calc our total height we'll need 187 scroll_height += spacing * len(self._settings_defs) 188 189 scroll_width = width - (86 + 2 * x_inset) 190 self._scrollwidget = ba.scrollwidget( 191 parent=self._root_widget, 192 position=(44 + x_inset, 35 + y_extra), 193 size=(scroll_width, height - 116), 194 highlight=False, 195 claims_left_right=True, 196 claims_tab=True, 197 selection_loops_to_parent=True, 198 ) 199 self._subcontainer = ba.containerwidget( 200 parent=self._scrollwidget, 201 size=(scroll_width, scroll_height), 202 background=False, 203 claims_left_right=True, 204 claims_tab=True, 205 selection_loops_to_parent=True, 206 ) 207 208 v = scroll_height - 5 209 h = -40 210 211 # Keep track of all the selectable widgets we make so we can wire 212 # them up conveniently. 213 widget_column: list[list[ba.Widget]] = [] 214 215 # Map select button. 216 ba.textwidget( 217 parent=self._subcontainer, 218 position=(h + 49, v - 63), 219 size=(100, 30), 220 maxwidth=110, 221 text=ba.Lstr(resource='mapText'), 222 h_align='left', 223 color=(0.8, 0.8, 0.8, 1.0), 224 v_align='center', 225 ) 226 227 ba.imagewidget( 228 parent=self._subcontainer, 229 size=(256 * 0.7, 125 * 0.7), 230 position=(h + 261 - 128 + 128.0 * 0.56, v - 90), 231 texture=map_tex, 232 model_opaque=ba.getmodel('level_select_button_opaque'), 233 model_transparent=ba.getmodel('level_select_button_transparent'), 234 mask_texture=ba.gettexture('mapPreviewMask'), 235 ) 236 map_button = btn = ba.buttonwidget( 237 parent=self._subcontainer, 238 size=(140, 60), 239 position=(h + 448, v - 72), 240 on_activate_call=ba.Call(self._select_map), 241 scale=0.7, 242 label=ba.Lstr(resource='mapSelectText'), 243 ) 244 widget_column.append([btn]) 245 246 ba.textwidget( 247 parent=self._subcontainer, 248 position=(h + 363 - 123, v - 114), 249 size=(100, 30), 250 flatness=1.0, 251 shadow=1.0, 252 scale=0.55, 253 maxwidth=256 * 0.7 * 0.8, 254 text=get_map_display_string(self._map), 255 h_align='center', 256 color=(0.6, 1.0, 0.6, 1.0), 257 v_align='center', 258 ) 259 v -= map_height 260 261 for setting in self._settings_defs: 262 value = setting.default 263 value_type = type(value) 264 265 # Now, if there's an existing value for it in the config, 266 # override with that. 267 try: 268 if ( 269 config is not None 270 and 'settings' in config 271 and setting.name in config['settings'] 272 ): 273 value = value_type(config['settings'][setting.name]) 274 except Exception: 275 ba.print_exception() 276 277 # Shove the starting value in there to start. 278 self._settings[setting.name] = value 279 280 name_translated = self._get_localized_setting_name(setting.name) 281 282 mw1 = 280 283 mw2 = 70 284 285 # Handle types with choices specially: 286 if isinstance(setting, ba.ChoiceSetting): 287 for choice in setting.choices: 288 if len(choice) != 2: 289 raise ValueError( 290 "Expected 2-member tuples for 'choices'; got: " 291 + repr(choice) 292 ) 293 if not isinstance(choice[0], str): 294 raise TypeError( 295 'First value for choice tuple must be a str; got: ' 296 + repr(choice) 297 ) 298 if not isinstance(choice[1], value_type): 299 raise TypeError( 300 'Choice type does not match default value; choice:' 301 + repr(choice) 302 + '; setting:' 303 + repr(setting) 304 ) 305 if value_type not in (int, float): 306 raise TypeError( 307 'Choice type setting must have int or float default; ' 308 'got: ' + repr(setting) 309 ) 310 311 # Start at the choice corresponding to the default if possible. 312 self._choice_selections[setting.name] = 0 313 for index, choice in enumerate(setting.choices): 314 if choice[1] == value: 315 self._choice_selections[setting.name] = index 316 break 317 318 v -= spacing 319 ba.textwidget( 320 parent=self._subcontainer, 321 position=(h + 50, v), 322 size=(100, 30), 323 maxwidth=mw1, 324 text=name_translated, 325 h_align='left', 326 color=(0.8, 0.8, 0.8, 1.0), 327 v_align='center', 328 ) 329 txt = ba.textwidget( 330 parent=self._subcontainer, 331 position=(h + 509 - 95, v), 332 size=(0, 28), 333 text=self._get_localized_setting_name( 334 setting.choices[self._choice_selections[setting.name]][ 335 0 336 ] 337 ), 338 editable=False, 339 color=(0.6, 1.0, 0.6, 1.0), 340 maxwidth=mw2, 341 h_align='right', 342 v_align='center', 343 padding=2, 344 ) 345 btn1 = ba.buttonwidget( 346 parent=self._subcontainer, 347 position=(h + 509 - 50 - 1, v), 348 size=(28, 28), 349 label='<', 350 autoselect=True, 351 on_activate_call=ba.Call( 352 self._choice_inc, setting.name, txt, setting, -1 353 ), 354 repeat=True, 355 ) 356 btn2 = ba.buttonwidget( 357 parent=self._subcontainer, 358 position=(h + 509 + 5, v), 359 size=(28, 28), 360 label='>', 361 autoselect=True, 362 on_activate_call=ba.Call( 363 self._choice_inc, setting.name, txt, setting, 1 364 ), 365 repeat=True, 366 ) 367 widget_column.append([btn1, btn2]) 368 369 elif isinstance(setting, (ba.IntSetting, ba.FloatSetting)): 370 v -= spacing 371 min_value = setting.min_value 372 max_value = setting.max_value 373 increment = setting.increment 374 ba.textwidget( 375 parent=self._subcontainer, 376 position=(h + 50, v), 377 size=(100, 30), 378 text=name_translated, 379 h_align='left', 380 color=(0.8, 0.8, 0.8, 1.0), 381 v_align='center', 382 maxwidth=mw1, 383 ) 384 txt = ba.textwidget( 385 parent=self._subcontainer, 386 position=(h + 509 - 95, v), 387 size=(0, 28), 388 text=str(value), 389 editable=False, 390 color=(0.6, 1.0, 0.6, 1.0), 391 maxwidth=mw2, 392 h_align='right', 393 v_align='center', 394 padding=2, 395 ) 396 btn1 = ba.buttonwidget( 397 parent=self._subcontainer, 398 position=(h + 509 - 50 - 1, v), 399 size=(28, 28), 400 label='-', 401 autoselect=True, 402 on_activate_call=ba.Call( 403 self._inc, 404 txt, 405 min_value, 406 max_value, 407 -increment, 408 value_type, 409 setting.name, 410 ), 411 repeat=True, 412 ) 413 btn2 = ba.buttonwidget( 414 parent=self._subcontainer, 415 position=(h + 509 + 5, v), 416 size=(28, 28), 417 label='+', 418 autoselect=True, 419 on_activate_call=ba.Call( 420 self._inc, 421 txt, 422 min_value, 423 max_value, 424 increment, 425 value_type, 426 setting.name, 427 ), 428 repeat=True, 429 ) 430 widget_column.append([btn1, btn2]) 431 432 elif value_type == bool: 433 v -= spacing 434 ba.textwidget( 435 parent=self._subcontainer, 436 position=(h + 50, v), 437 size=(100, 30), 438 text=name_translated, 439 h_align='left', 440 color=(0.8, 0.8, 0.8, 1.0), 441 v_align='center', 442 maxwidth=mw1, 443 ) 444 txt = ba.textwidget( 445 parent=self._subcontainer, 446 position=(h + 509 - 95, v), 447 size=(0, 28), 448 text=ba.Lstr(resource='onText') 449 if value 450 else ba.Lstr(resource='offText'), 451 editable=False, 452 color=(0.6, 1.0, 0.6, 1.0), 453 maxwidth=mw2, 454 h_align='right', 455 v_align='center', 456 padding=2, 457 ) 458 cbw = ba.checkboxwidget( 459 parent=self._subcontainer, 460 text='', 461 position=(h + 505 - 50 - 5, v - 2), 462 size=(200, 30), 463 autoselect=True, 464 textcolor=(0.8, 0.8, 0.8), 465 value=value, 466 on_value_change_call=ba.Call( 467 self._check_value_change, setting.name, txt 468 ), 469 ) 470 widget_column.append([cbw]) 471 472 else: 473 raise Exception() 474 475 # Ok now wire up the column. 476 try: 477 prev_widgets: list[ba.Widget] | None = None 478 for cwdg in widget_column: 479 if prev_widgets is not None: 480 # Wire our rightmost to their rightmost. 481 ba.widget(edit=prev_widgets[-1], down_widget=cwdg[-1]) 482 ba.widget(cwdg[-1], up_widget=prev_widgets[-1]) 483 484 # Wire our leftmost to their leftmost. 485 ba.widget(edit=prev_widgets[0], down_widget=cwdg[0]) 486 ba.widget(cwdg[0], up_widget=prev_widgets[0]) 487 prev_widgets = cwdg 488 except Exception: 489 ba.print_exception( 490 'Error wiring up game-settings-select widget column.' 491 ) 492 493 ba.buttonwidget(edit=add_button, on_activate_call=ba.Call(self._add)) 494 ba.containerwidget( 495 edit=self._root_widget, 496 selected_child=add_button, 497 start_button=add_button, 498 ) 499 500 if default_selection == 'map': 501 ba.containerwidget( 502 edit=self._root_widget, selected_child=self._scrollwidget 503 ) 504 ba.containerwidget( 505 edit=self._subcontainer, selected_child=map_button 506 ) 507 508 def _get_localized_setting_name(self, name: str) -> ba.Lstr: 509 return ba.Lstr(translate=('settingNames', name)) 510 511 def _select_map(self) -> None: 512 # pylint: disable=cyclic-import 513 from bastd.ui.playlist.mapselect import PlaylistMapSelectWindow 514 515 # Replace ourself with the map-select UI. 516 ba.containerwidget(edit=self._root_widget, transition='out_left') 517 ba.app.ui.set_main_menu_window( 518 PlaylistMapSelectWindow( 519 self._gametype, 520 self._sessiontype, 521 copy.deepcopy(self._getconfig()), 522 self._edit_info, 523 self._completion_call, 524 ).get_root_widget() 525 ) 526 527 def _choice_inc( 528 self, 529 setting_name: str, 530 widget: ba.Widget, 531 setting: ba.ChoiceSetting, 532 increment: int, 533 ) -> None: 534 choices = setting.choices 535 if increment > 0: 536 self._choice_selections[setting_name] = min( 537 len(choices) - 1, self._choice_selections[setting_name] + 1 538 ) 539 else: 540 self._choice_selections[setting_name] = max( 541 0, self._choice_selections[setting_name] - 1 542 ) 543 ba.textwidget( 544 edit=widget, 545 text=self._get_localized_setting_name( 546 choices[self._choice_selections[setting_name]][0] 547 ), 548 ) 549 self._settings[setting_name] = choices[ 550 self._choice_selections[setting_name] 551 ][1] 552 553 def _cancel(self) -> None: 554 self._completion_call(None) 555 556 def _check_value_change( 557 self, setting_name: str, widget: ba.Widget, value: int 558 ) -> None: 559 ba.textwidget( 560 edit=widget, 561 text=ba.Lstr(resource='onText') 562 if value 563 else ba.Lstr(resource='offText'), 564 ) 565 self._settings[setting_name] = value 566 567 def _getconfig(self) -> dict[str, Any]: 568 settings = copy.deepcopy(self._settings) 569 settings['map'] = self._map 570 return {'settings': settings} 571 572 def _add(self) -> None: 573 self._completion_call(copy.deepcopy(self._getconfig())) 574 575 def _inc( 576 self, 577 ctrl: ba.Widget, 578 min_val: int | float, 579 max_val: int | float, 580 increment: int | float, 581 setting_type: type, 582 setting_name: str, 583 ) -> None: 584 if setting_type == float: 585 val = float(cast(str, ba.textwidget(query=ctrl))) 586 else: 587 val = int(cast(str, ba.textwidget(query=ctrl))) 588 val += increment 589 val = max(min_val, min(val, max_val)) 590 if setting_type == float: 591 ba.textwidget(edit=ctrl, text=str(round(val, 2))) 592 elif setting_type == int: 593 ba.textwidget(edit=ctrl, text=str(int(val))) 594 else: 595 raise TypeError('invalid vartype: ' + str(setting_type)) 596 self._settings[setting_name] = val
Window for editing a game config.
PlaylistEditGameWindow( gametype: type[ba._gameactivity.GameActivity], sessiontype: type[ba._session.Session], config: dict[str, typing.Any] | None, completion_call: Callable[[dict[str, Any] | None], Any], default_selection: str | None = None, transition: str = 'in_right', edit_info: dict[str, typing.Any] | None = None)
22 def __init__( 23 self, 24 gametype: type[ba.GameActivity], 25 sessiontype: type[ba.Session], 26 config: dict[str, Any] | None, 27 completion_call: Callable[[dict[str, Any] | None], Any], 28 default_selection: str | None = None, 29 transition: str = 'in_right', 30 edit_info: dict[str, Any] | None = None, 31 ): 32 # pylint: disable=too-many-branches 33 # pylint: disable=too-many-statements 34 # pylint: disable=too-many-locals 35 from ba.internal import ( 36 get_unowned_maps, 37 get_filtered_map_name, 38 get_map_class, 39 get_map_display_string, 40 ) 41 42 self._gametype = gametype 43 self._sessiontype = sessiontype 44 45 # If we're within an editing session we get passed edit_info 46 # (returning from map selection window, etc). 47 if edit_info is not None: 48 self._edit_info = edit_info 49 50 # ..otherwise determine whether we're adding or editing a game based 51 # on whether an existing config was passed to us. 52 else: 53 if config is None: 54 self._edit_info = {'editType': 'add'} 55 else: 56 self._edit_info = {'editType': 'edit'} 57 58 self._r = 'gameSettingsWindow' 59 60 valid_maps = gametype.get_supported_maps(sessiontype) 61 if not valid_maps: 62 ba.screenmessage(ba.Lstr(resource='noValidMapsErrorText')) 63 raise Exception('No valid maps') 64 65 self._settings_defs = gametype.get_available_settings(sessiontype) 66 self._completion_call = completion_call 67 68 # To start with, pick a random map out of the ones we own. 69 unowned_maps = get_unowned_maps() 70 valid_maps_owned = [m for m in valid_maps if m not in unowned_maps] 71 if valid_maps_owned: 72 self._map = valid_maps[random.randrange(len(valid_maps_owned))] 73 74 # Hmmm.. we own none of these maps.. just pick a random un-owned one 75 # I guess.. should this ever happen? 76 else: 77 self._map = valid_maps[random.randrange(len(valid_maps))] 78 79 is_add = self._edit_info['editType'] == 'add' 80 81 # If there's a valid map name in the existing config, use that. 82 try: 83 if ( 84 config is not None 85 and 'settings' in config 86 and 'map' in config['settings'] 87 ): 88 filtered_map_name = get_filtered_map_name( 89 config['settings']['map'] 90 ) 91 if filtered_map_name in valid_maps: 92 self._map = filtered_map_name 93 except Exception: 94 ba.print_exception('Error getting map for editor.') 95 96 if config is not None and 'settings' in config: 97 self._settings = config['settings'] 98 else: 99 self._settings = {} 100 101 self._choice_selections: dict[str, int] = {} 102 103 uiscale = ba.app.ui.uiscale 104 width = 720 if uiscale is ba.UIScale.SMALL else 620 105 x_inset = 50 if uiscale is ba.UIScale.SMALL else 0 106 height = ( 107 365 108 if uiscale is ba.UIScale.SMALL 109 else 460 110 if uiscale is ba.UIScale.MEDIUM 111 else 550 112 ) 113 spacing = 52 114 y_extra = 15 115 y_extra2 = 21 116 117 map_tex_name = get_map_class(self._map).get_preview_texture_name() 118 if map_tex_name is None: 119 raise Exception('no map preview tex found for' + self._map) 120 map_tex = ba.gettexture(map_tex_name) 121 122 top_extra = 20 if uiscale is ba.UIScale.SMALL else 0 123 super().__init__( 124 root_widget=ba.containerwidget( 125 size=(width, height + top_extra), 126 transition=transition, 127 scale=( 128 2.19 129 if uiscale is ba.UIScale.SMALL 130 else 1.35 131 if uiscale is ba.UIScale.MEDIUM 132 else 1.0 133 ), 134 stack_offset=(0, -17) 135 if uiscale is ba.UIScale.SMALL 136 else (0, 0), 137 ) 138 ) 139 140 btn = ba.buttonwidget( 141 parent=self._root_widget, 142 position=(45 + x_inset, height - 82 + y_extra2), 143 size=(180, 70) if is_add else (180, 65), 144 label=ba.Lstr(resource='backText') 145 if is_add 146 else ba.Lstr(resource='cancelText'), 147 button_type='back' if is_add else None, 148 autoselect=True, 149 scale=0.75, 150 text_scale=1.3, 151 on_activate_call=ba.Call(self._cancel), 152 ) 153 ba.containerwidget(edit=self._root_widget, cancel_button=btn) 154 155 add_button = ba.buttonwidget( 156 parent=self._root_widget, 157 position=(width - (193 + x_inset), height - 82 + y_extra2), 158 size=(200, 65), 159 scale=0.75, 160 text_scale=1.3, 161 label=ba.Lstr(resource=self._r + '.addGameText') 162 if is_add 163 else ba.Lstr(resource='doneText'), 164 ) 165 166 if ba.app.ui.use_toolbars: 167 pbtn = ba.internal.get_special_widget('party_button') 168 ba.widget(edit=add_button, right_widget=pbtn, up_widget=pbtn) 169 170 ba.textwidget( 171 parent=self._root_widget, 172 position=(-8, height - 70 + y_extra2), 173 size=(width, 25), 174 text=gametype.get_display_string(), 175 color=ba.app.ui.title_color, 176 maxwidth=235, 177 scale=1.1, 178 h_align='center', 179 v_align='center', 180 ) 181 182 map_height = 100 183 184 scroll_height = map_height + 10 # map select and margin 185 186 # Calc our total height we'll need 187 scroll_height += spacing * len(self._settings_defs) 188 189 scroll_width = width - (86 + 2 * x_inset) 190 self._scrollwidget = ba.scrollwidget( 191 parent=self._root_widget, 192 position=(44 + x_inset, 35 + y_extra), 193 size=(scroll_width, height - 116), 194 highlight=False, 195 claims_left_right=True, 196 claims_tab=True, 197 selection_loops_to_parent=True, 198 ) 199 self._subcontainer = ba.containerwidget( 200 parent=self._scrollwidget, 201 size=(scroll_width, scroll_height), 202 background=False, 203 claims_left_right=True, 204 claims_tab=True, 205 selection_loops_to_parent=True, 206 ) 207 208 v = scroll_height - 5 209 h = -40 210 211 # Keep track of all the selectable widgets we make so we can wire 212 # them up conveniently. 213 widget_column: list[list[ba.Widget]] = [] 214 215 # Map select button. 216 ba.textwidget( 217 parent=self._subcontainer, 218 position=(h + 49, v - 63), 219 size=(100, 30), 220 maxwidth=110, 221 text=ba.Lstr(resource='mapText'), 222 h_align='left', 223 color=(0.8, 0.8, 0.8, 1.0), 224 v_align='center', 225 ) 226 227 ba.imagewidget( 228 parent=self._subcontainer, 229 size=(256 * 0.7, 125 * 0.7), 230 position=(h + 261 - 128 + 128.0 * 0.56, v - 90), 231 texture=map_tex, 232 model_opaque=ba.getmodel('level_select_button_opaque'), 233 model_transparent=ba.getmodel('level_select_button_transparent'), 234 mask_texture=ba.gettexture('mapPreviewMask'), 235 ) 236 map_button = btn = ba.buttonwidget( 237 parent=self._subcontainer, 238 size=(140, 60), 239 position=(h + 448, v - 72), 240 on_activate_call=ba.Call(self._select_map), 241 scale=0.7, 242 label=ba.Lstr(resource='mapSelectText'), 243 ) 244 widget_column.append([btn]) 245 246 ba.textwidget( 247 parent=self._subcontainer, 248 position=(h + 363 - 123, v - 114), 249 size=(100, 30), 250 flatness=1.0, 251 shadow=1.0, 252 scale=0.55, 253 maxwidth=256 * 0.7 * 0.8, 254 text=get_map_display_string(self._map), 255 h_align='center', 256 color=(0.6, 1.0, 0.6, 1.0), 257 v_align='center', 258 ) 259 v -= map_height 260 261 for setting in self._settings_defs: 262 value = setting.default 263 value_type = type(value) 264 265 # Now, if there's an existing value for it in the config, 266 # override with that. 267 try: 268 if ( 269 config is not None 270 and 'settings' in config 271 and setting.name in config['settings'] 272 ): 273 value = value_type(config['settings'][setting.name]) 274 except Exception: 275 ba.print_exception() 276 277 # Shove the starting value in there to start. 278 self._settings[setting.name] = value 279 280 name_translated = self._get_localized_setting_name(setting.name) 281 282 mw1 = 280 283 mw2 = 70 284 285 # Handle types with choices specially: 286 if isinstance(setting, ba.ChoiceSetting): 287 for choice in setting.choices: 288 if len(choice) != 2: 289 raise ValueError( 290 "Expected 2-member tuples for 'choices'; got: " 291 + repr(choice) 292 ) 293 if not isinstance(choice[0], str): 294 raise TypeError( 295 'First value for choice tuple must be a str; got: ' 296 + repr(choice) 297 ) 298 if not isinstance(choice[1], value_type): 299 raise TypeError( 300 'Choice type does not match default value; choice:' 301 + repr(choice) 302 + '; setting:' 303 + repr(setting) 304 ) 305 if value_type not in (int, float): 306 raise TypeError( 307 'Choice type setting must have int or float default; ' 308 'got: ' + repr(setting) 309 ) 310 311 # Start at the choice corresponding to the default if possible. 312 self._choice_selections[setting.name] = 0 313 for index, choice in enumerate(setting.choices): 314 if choice[1] == value: 315 self._choice_selections[setting.name] = index 316 break 317 318 v -= spacing 319 ba.textwidget( 320 parent=self._subcontainer, 321 position=(h + 50, v), 322 size=(100, 30), 323 maxwidth=mw1, 324 text=name_translated, 325 h_align='left', 326 color=(0.8, 0.8, 0.8, 1.0), 327 v_align='center', 328 ) 329 txt = ba.textwidget( 330 parent=self._subcontainer, 331 position=(h + 509 - 95, v), 332 size=(0, 28), 333 text=self._get_localized_setting_name( 334 setting.choices[self._choice_selections[setting.name]][ 335 0 336 ] 337 ), 338 editable=False, 339 color=(0.6, 1.0, 0.6, 1.0), 340 maxwidth=mw2, 341 h_align='right', 342 v_align='center', 343 padding=2, 344 ) 345 btn1 = ba.buttonwidget( 346 parent=self._subcontainer, 347 position=(h + 509 - 50 - 1, v), 348 size=(28, 28), 349 label='<', 350 autoselect=True, 351 on_activate_call=ba.Call( 352 self._choice_inc, setting.name, txt, setting, -1 353 ), 354 repeat=True, 355 ) 356 btn2 = ba.buttonwidget( 357 parent=self._subcontainer, 358 position=(h + 509 + 5, v), 359 size=(28, 28), 360 label='>', 361 autoselect=True, 362 on_activate_call=ba.Call( 363 self._choice_inc, setting.name, txt, setting, 1 364 ), 365 repeat=True, 366 ) 367 widget_column.append([btn1, btn2]) 368 369 elif isinstance(setting, (ba.IntSetting, ba.FloatSetting)): 370 v -= spacing 371 min_value = setting.min_value 372 max_value = setting.max_value 373 increment = setting.increment 374 ba.textwidget( 375 parent=self._subcontainer, 376 position=(h + 50, v), 377 size=(100, 30), 378 text=name_translated, 379 h_align='left', 380 color=(0.8, 0.8, 0.8, 1.0), 381 v_align='center', 382 maxwidth=mw1, 383 ) 384 txt = ba.textwidget( 385 parent=self._subcontainer, 386 position=(h + 509 - 95, v), 387 size=(0, 28), 388 text=str(value), 389 editable=False, 390 color=(0.6, 1.0, 0.6, 1.0), 391 maxwidth=mw2, 392 h_align='right', 393 v_align='center', 394 padding=2, 395 ) 396 btn1 = ba.buttonwidget( 397 parent=self._subcontainer, 398 position=(h + 509 - 50 - 1, v), 399 size=(28, 28), 400 label='-', 401 autoselect=True, 402 on_activate_call=ba.Call( 403 self._inc, 404 txt, 405 min_value, 406 max_value, 407 -increment, 408 value_type, 409 setting.name, 410 ), 411 repeat=True, 412 ) 413 btn2 = ba.buttonwidget( 414 parent=self._subcontainer, 415 position=(h + 509 + 5, v), 416 size=(28, 28), 417 label='+', 418 autoselect=True, 419 on_activate_call=ba.Call( 420 self._inc, 421 txt, 422 min_value, 423 max_value, 424 increment, 425 value_type, 426 setting.name, 427 ), 428 repeat=True, 429 ) 430 widget_column.append([btn1, btn2]) 431 432 elif value_type == bool: 433 v -= spacing 434 ba.textwidget( 435 parent=self._subcontainer, 436 position=(h + 50, v), 437 size=(100, 30), 438 text=name_translated, 439 h_align='left', 440 color=(0.8, 0.8, 0.8, 1.0), 441 v_align='center', 442 maxwidth=mw1, 443 ) 444 txt = ba.textwidget( 445 parent=self._subcontainer, 446 position=(h + 509 - 95, v), 447 size=(0, 28), 448 text=ba.Lstr(resource='onText') 449 if value 450 else ba.Lstr(resource='offText'), 451 editable=False, 452 color=(0.6, 1.0, 0.6, 1.0), 453 maxwidth=mw2, 454 h_align='right', 455 v_align='center', 456 padding=2, 457 ) 458 cbw = ba.checkboxwidget( 459 parent=self._subcontainer, 460 text='', 461 position=(h + 505 - 50 - 5, v - 2), 462 size=(200, 30), 463 autoselect=True, 464 textcolor=(0.8, 0.8, 0.8), 465 value=value, 466 on_value_change_call=ba.Call( 467 self._check_value_change, setting.name, txt 468 ), 469 ) 470 widget_column.append([cbw]) 471 472 else: 473 raise Exception() 474 475 # Ok now wire up the column. 476 try: 477 prev_widgets: list[ba.Widget] | None = None 478 for cwdg in widget_column: 479 if prev_widgets is not None: 480 # Wire our rightmost to their rightmost. 481 ba.widget(edit=prev_widgets[-1], down_widget=cwdg[-1]) 482 ba.widget(cwdg[-1], up_widget=prev_widgets[-1]) 483 484 # Wire our leftmost to their leftmost. 485 ba.widget(edit=prev_widgets[0], down_widget=cwdg[0]) 486 ba.widget(cwdg[0], up_widget=prev_widgets[0]) 487 prev_widgets = cwdg 488 except Exception: 489 ba.print_exception( 490 'Error wiring up game-settings-select widget column.' 491 ) 492 493 ba.buttonwidget(edit=add_button, on_activate_call=ba.Call(self._add)) 494 ba.containerwidget( 495 edit=self._root_widget, 496 selected_child=add_button, 497 start_button=add_button, 498 ) 499 500 if default_selection == 'map': 501 ba.containerwidget( 502 edit=self._root_widget, selected_child=self._scrollwidget 503 ) 504 ba.containerwidget( 505 edit=self._subcontainer, selected_child=map_button 506 )
Inherited Members
- ba.ui.Window
- get_root_widget