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