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