bauiv1lib.settings.graphics
Provides UI for graphics settings.
1# Released under the MIT License. See LICENSE for details. 2# 3"""Provides UI for graphics settings.""" 4 5from __future__ import annotations 6 7from typing import TYPE_CHECKING, cast, override 8 9from bauiv1lib.popup import PopupMenu 10from bauiv1lib.config import ConfigCheckBox 11import bauiv1 as bui 12 13if TYPE_CHECKING: 14 from typing import Any 15 16 17class GraphicsSettingsWindow(bui.MainWindow): 18 """Window for graphics settings.""" 19 20 def __init__( 21 self, 22 transition: str | None = 'in_right', 23 origin_widget: bui.Widget | None = None, 24 ): 25 # pylint: disable=too-many-locals 26 # pylint: disable=too-many-branches 27 # pylint: disable=too-many-statements 28 29 self._r = 'graphicsSettingsWindow' 30 app = bui.app 31 assert app.classic is not None 32 33 spacing = 32 34 self._have_selected_child = False 35 uiscale = app.ui_v1.uiscale 36 width = 450.0 37 height = 302.0 38 self._max_fps_dirty = False 39 self._last_max_fps_set_time = bui.apptime() 40 self._last_max_fps_str = '' 41 42 self._show_fullscreen = False 43 fullscreen_spacing_top = spacing * 0.2 44 fullscreen_spacing = spacing * 1.2 45 if bui.fullscreen_control_available(): 46 self._show_fullscreen = True 47 height += fullscreen_spacing + fullscreen_spacing_top 48 49 show_vsync = bui.supports_vsync() 50 show_tv_mode = not bui.app.env.vr 51 52 show_max_fps = bui.supports_max_fps() 53 if show_max_fps: 54 height += 50 55 56 show_resolution = True 57 if app.env.vr: 58 show_resolution = ( 59 app.classic.platform == 'android' 60 and app.classic.subplatform == 'cardboard' 61 ) 62 63 assert bui.app.classic is not None 64 uiscale = bui.app.ui_v1.uiscale 65 base_scale = ( 66 1.5 67 if uiscale is bui.UIScale.SMALL 68 else 1.3 if uiscale is bui.UIScale.MEDIUM else 1.0 69 ) 70 popup_menu_scale = base_scale * 1.2 71 v = height - 50 72 v -= spacing * 1.15 73 super().__init__( 74 root_widget=bui.containerwidget( 75 size=(width, height), 76 scale=base_scale, 77 stack_offset=( 78 (0, -10) if uiscale is bui.UIScale.SMALL else (0, 0) 79 ), 80 toolbar_visibility=( 81 'menu_minimal' 82 if uiscale is bui.UIScale.SMALL 83 else 'menu_full' 84 ), 85 ), 86 transition=transition, 87 origin_widget=origin_widget, 88 ) 89 90 back_button = bui.buttonwidget( 91 parent=self._root_widget, 92 position=(35, height - 50), 93 # size=(120, 60), 94 size=(60, 60), 95 scale=0.8, 96 text_scale=1.2, 97 autoselect=True, 98 label=bui.charstr(bui.SpecialChar.BACK), 99 button_type='backSmall', 100 on_activate_call=self.main_window_back, 101 ) 102 103 bui.containerwidget(edit=self._root_widget, cancel_button=back_button) 104 105 bui.textwidget( 106 parent=self._root_widget, 107 position=(0, height - 44), 108 size=(width, 25), 109 text=bui.Lstr(resource=f'{self._r}.titleText'), 110 color=bui.app.ui_v1.title_color, 111 h_align='center', 112 v_align='top', 113 ) 114 115 self._fullscreen_checkbox: bui.Widget | None = None 116 if self._show_fullscreen: 117 v -= fullscreen_spacing_top 118 # Fullscreen control does not necessarily talk to the 119 # app config so we have to wrangle it manually instead of 120 # using a config-checkbox. 121 label = bui.Lstr(resource=f'{self._r}.fullScreenText') 122 123 # Show keyboard shortcut alongside the control if they 124 # provide one. 125 shortcut = bui.fullscreen_control_key_shortcut() 126 if shortcut is not None: 127 label = bui.Lstr( 128 value='$(NAME) [$(SHORTCUT)]', 129 subs=[('$(NAME)', label), ('$(SHORTCUT)', shortcut)], 130 ) 131 self._fullscreen_checkbox = bui.checkboxwidget( 132 parent=self._root_widget, 133 position=(100, v), 134 value=bui.fullscreen_control_get(), 135 on_value_change_call=bui.fullscreen_control_set, 136 maxwidth=250, 137 size=(300, 30), 138 text=label, 139 ) 140 141 if not self._have_selected_child: 142 bui.containerwidget( 143 edit=self._root_widget, 144 selected_child=self._fullscreen_checkbox, 145 ) 146 self._have_selected_child = True 147 v -= fullscreen_spacing 148 149 self._selected_color = (0.5, 1, 0.5, 1) 150 self._unselected_color = (0.7, 0.7, 0.7, 1) 151 152 # Quality 153 bui.textwidget( 154 parent=self._root_widget, 155 position=(60, v), 156 size=(160, 25), 157 text=bui.Lstr(resource=f'{self._r}.visualsText'), 158 color=bui.app.ui_v1.heading_color, 159 scale=0.65, 160 maxwidth=150, 161 h_align='center', 162 v_align='center', 163 ) 164 PopupMenu( 165 parent=self._root_widget, 166 position=(60, v - 50), 167 width=150, 168 scale=popup_menu_scale, 169 choices=['Auto', 'Higher', 'High', 'Medium', 'Low'], 170 choices_disabled=( 171 ['Higher', 'High'] 172 if bui.get_max_graphics_quality() == 'Medium' 173 else [] 174 ), 175 choices_display=[ 176 bui.Lstr(resource='autoText'), 177 bui.Lstr(resource=f'{self._r}.higherText'), 178 bui.Lstr(resource=f'{self._r}.highText'), 179 bui.Lstr(resource=f'{self._r}.mediumText'), 180 bui.Lstr(resource=f'{self._r}.lowText'), 181 ], 182 current_choice=bui.app.config.resolve('Graphics Quality'), 183 on_value_change_call=self._set_quality, 184 ) 185 186 # Texture controls 187 bui.textwidget( 188 parent=self._root_widget, 189 position=(230, v), 190 size=(160, 25), 191 text=bui.Lstr(resource=f'{self._r}.texturesText'), 192 color=bui.app.ui_v1.heading_color, 193 scale=0.65, 194 maxwidth=150, 195 h_align='center', 196 v_align='center', 197 ) 198 textures_popup = PopupMenu( 199 parent=self._root_widget, 200 position=(230, v - 50), 201 width=150, 202 scale=popup_menu_scale, 203 choices=['Auto', 'High', 'Medium', 'Low'], 204 choices_display=[ 205 bui.Lstr(resource='autoText'), 206 bui.Lstr(resource=f'{self._r}.highText'), 207 bui.Lstr(resource=f'{self._r}.mediumText'), 208 bui.Lstr(resource=f'{self._r}.lowText'), 209 ], 210 current_choice=bui.app.config.resolve('Texture Quality'), 211 on_value_change_call=self._set_textures, 212 ) 213 bui.widget( 214 edit=textures_popup.get_button(), 215 right_widget=bui.get_special_widget('squad_button'), 216 ) 217 v -= 80 218 219 h_offs = 0 220 221 resolution_popup: PopupMenu | None = None 222 223 if show_resolution: 224 bui.textwidget( 225 parent=self._root_widget, 226 position=(h_offs + 60, v), 227 size=(160, 25), 228 text=bui.Lstr(resource=f'{self._r}.resolutionText'), 229 color=bui.app.ui_v1.heading_color, 230 scale=0.65, 231 maxwidth=150, 232 h_align='center', 233 v_align='center', 234 ) 235 236 # On standard android we have 'Auto', 'Native', and a few 237 # HD standards. 238 if app.classic.platform == 'android': 239 # on cardboard/daydream android we have a few 240 # render-target-scale options 241 if app.classic.subplatform == 'cardboard': 242 rawval = bui.app.config.resolve('GVR Render Target Scale') 243 current_res_cardboard = ( 244 str(min(100, max(10, int(round(rawval * 100.0))))) + '%' 245 ) 246 resolution_popup = PopupMenu( 247 parent=self._root_widget, 248 position=(h_offs + 60, v - 50), 249 width=120, 250 scale=popup_menu_scale, 251 choices=['100%', '75%', '50%', '35%'], 252 current_choice=current_res_cardboard, 253 on_value_change_call=self._set_gvr_render_target_scale, 254 ) 255 else: 256 native_res = bui.get_display_resolution() 257 assert native_res is not None 258 choices = ['Auto', 'Native'] 259 choices_display = [ 260 bui.Lstr(resource='autoText'), 261 bui.Lstr(resource='nativeText'), 262 ] 263 for res in [1440, 1080, 960, 720, 480]: 264 if native_res[1] >= res: 265 res_str = f'{res}p' 266 choices.append(res_str) 267 choices_display.append(bui.Lstr(value=res_str)) 268 current_res_android = bui.app.config.resolve( 269 'Resolution (Android)' 270 ) 271 resolution_popup = PopupMenu( 272 parent=self._root_widget, 273 position=(h_offs + 60, v - 50), 274 width=120, 275 scale=popup_menu_scale, 276 choices=choices, 277 choices_display=choices_display, 278 current_choice=current_res_android, 279 on_value_change_call=self._set_android_res, 280 ) 281 else: 282 # If we're on a system that doesn't allow setting resolution, 283 # set pixel-scale instead. 284 current_res = bui.get_display_resolution() 285 if current_res is None: 286 rawval = bui.app.config.resolve('Screen Pixel Scale') 287 current_res2 = ( 288 str(min(100, max(10, int(round(rawval * 100.0))))) + '%' 289 ) 290 resolution_popup = PopupMenu( 291 parent=self._root_widget, 292 position=(h_offs + 60, v - 50), 293 width=120, 294 scale=popup_menu_scale, 295 choices=['100%', '88%', '75%', '63%', '50%'], 296 current_choice=current_res2, 297 on_value_change_call=self._set_pixel_scale, 298 ) 299 else: 300 raise RuntimeError( 301 'obsolete code path; discrete resolutions' 302 ' no longer supported' 303 ) 304 if resolution_popup is not None: 305 bui.widget( 306 edit=resolution_popup.get_button(), 307 left_widget=back_button, 308 ) 309 310 vsync_popup: PopupMenu | None = None 311 if show_vsync: 312 bui.textwidget( 313 parent=self._root_widget, 314 position=(230, v), 315 size=(160, 25), 316 text=bui.Lstr(resource=f'{self._r}.verticalSyncText'), 317 color=bui.app.ui_v1.heading_color, 318 scale=0.65, 319 maxwidth=150, 320 h_align='center', 321 v_align='center', 322 ) 323 vsync_popup = PopupMenu( 324 parent=self._root_widget, 325 position=(230, v - 50), 326 width=150, 327 scale=popup_menu_scale, 328 choices=['Auto', 'Always', 'Never'], 329 choices_display=[ 330 bui.Lstr(resource='autoText'), 331 bui.Lstr(resource=f'{self._r}.alwaysText'), 332 bui.Lstr(resource=f'{self._r}.neverText'), 333 ], 334 current_choice=bui.app.config.resolve('Vertical Sync'), 335 on_value_change_call=self._set_vsync, 336 ) 337 if resolution_popup is not None: 338 bui.widget( 339 edit=vsync_popup.get_button(), 340 left_widget=resolution_popup.get_button(), 341 ) 342 343 if resolution_popup is not None and vsync_popup is not None: 344 bui.widget( 345 edit=resolution_popup.get_button(), 346 right_widget=vsync_popup.get_button(), 347 ) 348 349 v -= 90 350 self._max_fps_text: bui.Widget | None = None 351 if show_max_fps: 352 v -= 5 353 bui.textwidget( 354 parent=self._root_widget, 355 position=(155, v + 10), 356 size=(0, 0), 357 text=bui.Lstr(resource=f'{self._r}.maxFPSText'), 358 color=bui.app.ui_v1.heading_color, 359 scale=0.9, 360 maxwidth=90, 361 h_align='right', 362 v_align='center', 363 ) 364 365 max_fps_str = str(bui.app.config.resolve('Max FPS')) 366 self._last_max_fps_str = max_fps_str 367 self._max_fps_text = bui.textwidget( 368 parent=self._root_widget, 369 position=(170, v - 5), 370 size=(105, 30), 371 text=max_fps_str, 372 max_chars=5, 373 editable=True, 374 h_align='left', 375 v_align='center', 376 on_return_press_call=self._on_max_fps_return_press, 377 ) 378 v -= 45 379 380 if self._max_fps_text is not None and resolution_popup is not None: 381 bui.widget( 382 edit=resolution_popup.get_button(), 383 down_widget=self._max_fps_text, 384 ) 385 bui.widget( 386 edit=self._max_fps_text, 387 up_widget=resolution_popup.get_button(), 388 ) 389 390 fpsc = ConfigCheckBox( 391 parent=self._root_widget, 392 position=(69, v - 6), 393 size=(210, 30), 394 scale=0.86, 395 configkey='Show FPS', 396 displayname=bui.Lstr(resource=f'{self._r}.showFPSText'), 397 maxwidth=130, 398 ) 399 if self._max_fps_text is not None: 400 bui.widget( 401 edit=self._max_fps_text, 402 down_widget=fpsc.widget, 403 ) 404 bui.widget( 405 edit=fpsc.widget, 406 up_widget=self._max_fps_text, 407 ) 408 409 if show_tv_mode: 410 tvc = ConfigCheckBox( 411 parent=self._root_widget, 412 position=(240, v - 6), 413 size=(210, 30), 414 scale=0.86, 415 configkey='TV Border', 416 displayname=bui.Lstr(resource=f'{self._r}.tvBorderText'), 417 maxwidth=130, 418 ) 419 bui.widget(edit=fpsc.widget, right_widget=tvc.widget) 420 bui.widget(edit=tvc.widget, left_widget=fpsc.widget) 421 422 v -= spacing 423 424 # Make a timer to update our controls in case the config changes 425 # under us. 426 self._update_timer = bui.AppTimer( 427 0.25, bui.WeakCall(self._update_controls), repeat=True 428 ) 429 430 @override 431 def get_main_window_state(self) -> bui.MainWindowState: 432 # Support recreating our window for back/refresh purposes. 433 cls = type(self) 434 return bui.BasicMainWindowState( 435 create_call=lambda transition, origin_widget: cls( 436 transition=transition, origin_widget=origin_widget 437 ) 438 ) 439 440 @override 441 def on_main_window_close(self) -> None: 442 self._apply_max_fps() 443 444 def _set_quality(self, quality: str) -> None: 445 cfg = bui.app.config 446 cfg['Graphics Quality'] = quality 447 cfg.apply_and_commit() 448 449 def _set_textures(self, val: str) -> None: 450 cfg = bui.app.config 451 cfg['Texture Quality'] = val 452 cfg.apply_and_commit() 453 454 def _set_android_res(self, val: str) -> None: 455 cfg = bui.app.config 456 cfg['Resolution (Android)'] = val 457 cfg.apply_and_commit() 458 459 def _set_pixel_scale(self, res: str) -> None: 460 cfg = bui.app.config 461 cfg['Screen Pixel Scale'] = float(res[:-1]) / 100.0 462 cfg.apply_and_commit() 463 464 def _set_gvr_render_target_scale(self, res: str) -> None: 465 cfg = bui.app.config 466 cfg['GVR Render Target Scale'] = float(res[:-1]) / 100.0 467 cfg.apply_and_commit() 468 469 def _set_vsync(self, val: str) -> None: 470 cfg = bui.app.config 471 cfg['Vertical Sync'] = val 472 cfg.apply_and_commit() 473 474 def _on_max_fps_return_press(self) -> None: 475 self._apply_max_fps() 476 bui.containerwidget( 477 edit=self._root_widget, selected_child=cast(bui.Widget, 0) 478 ) 479 480 def _apply_max_fps(self) -> None: 481 if not self._max_fps_dirty or not self._max_fps_text: 482 return 483 484 val: Any = bui.textwidget(query=self._max_fps_text) 485 assert isinstance(val, str) 486 # If there's a broken value, replace it with the default. 487 try: 488 ival = int(val) 489 except ValueError: 490 ival = bui.app.config.default_value('Max FPS') 491 assert isinstance(ival, int) 492 493 # Clamp to reasonable limits (allow -1 to mean no max). 494 if ival != -1: 495 ival = max(10, ival) 496 ival = min(99999, ival) 497 498 # Store it to the config. 499 cfg = bui.app.config 500 cfg['Max FPS'] = ival 501 cfg.apply_and_commit() 502 503 # Update the display if we changed the value. 504 if str(ival) != val: 505 bui.textwidget(edit=self._max_fps_text, text=str(ival)) 506 507 self._max_fps_dirty = False 508 509 def _update_controls(self) -> None: 510 if self._max_fps_text is not None: 511 # Keep track of when the max-fps value changes. Once it 512 # remains stable for a few moments, apply it. 513 val: Any = bui.textwidget(query=self._max_fps_text) 514 assert isinstance(val, str) 515 if val != self._last_max_fps_str: 516 # Oop; it changed. Note the time and the fact that we'll 517 # need to apply it at some point. 518 self._max_fps_dirty = True 519 self._last_max_fps_str = val 520 self._last_max_fps_set_time = bui.apptime() 521 else: 522 # If its been stable long enough, apply it. 523 if ( 524 self._max_fps_dirty 525 and bui.apptime() - self._last_max_fps_set_time > 1.0 526 ): 527 self._apply_max_fps() 528 529 if self._show_fullscreen: 530 # Keep the fullscreen checkbox up to date with the current value. 531 bui.checkboxwidget( 532 edit=self._fullscreen_checkbox, 533 value=bui.fullscreen_control_get(), 534 )
class
GraphicsSettingsWindow(bauiv1._uitypes.MainWindow):
18class GraphicsSettingsWindow(bui.MainWindow): 19 """Window for graphics settings.""" 20 21 def __init__( 22 self, 23 transition: str | None = 'in_right', 24 origin_widget: bui.Widget | None = None, 25 ): 26 # pylint: disable=too-many-locals 27 # pylint: disable=too-many-branches 28 # pylint: disable=too-many-statements 29 30 self._r = 'graphicsSettingsWindow' 31 app = bui.app 32 assert app.classic is not None 33 34 spacing = 32 35 self._have_selected_child = False 36 uiscale = app.ui_v1.uiscale 37 width = 450.0 38 height = 302.0 39 self._max_fps_dirty = False 40 self._last_max_fps_set_time = bui.apptime() 41 self._last_max_fps_str = '' 42 43 self._show_fullscreen = False 44 fullscreen_spacing_top = spacing * 0.2 45 fullscreen_spacing = spacing * 1.2 46 if bui.fullscreen_control_available(): 47 self._show_fullscreen = True 48 height += fullscreen_spacing + fullscreen_spacing_top 49 50 show_vsync = bui.supports_vsync() 51 show_tv_mode = not bui.app.env.vr 52 53 show_max_fps = bui.supports_max_fps() 54 if show_max_fps: 55 height += 50 56 57 show_resolution = True 58 if app.env.vr: 59 show_resolution = ( 60 app.classic.platform == 'android' 61 and app.classic.subplatform == 'cardboard' 62 ) 63 64 assert bui.app.classic is not None 65 uiscale = bui.app.ui_v1.uiscale 66 base_scale = ( 67 1.5 68 if uiscale is bui.UIScale.SMALL 69 else 1.3 if uiscale is bui.UIScale.MEDIUM else 1.0 70 ) 71 popup_menu_scale = base_scale * 1.2 72 v = height - 50 73 v -= spacing * 1.15 74 super().__init__( 75 root_widget=bui.containerwidget( 76 size=(width, height), 77 scale=base_scale, 78 stack_offset=( 79 (0, -10) if uiscale is bui.UIScale.SMALL else (0, 0) 80 ), 81 toolbar_visibility=( 82 'menu_minimal' 83 if uiscale is bui.UIScale.SMALL 84 else 'menu_full' 85 ), 86 ), 87 transition=transition, 88 origin_widget=origin_widget, 89 ) 90 91 back_button = bui.buttonwidget( 92 parent=self._root_widget, 93 position=(35, height - 50), 94 # size=(120, 60), 95 size=(60, 60), 96 scale=0.8, 97 text_scale=1.2, 98 autoselect=True, 99 label=bui.charstr(bui.SpecialChar.BACK), 100 button_type='backSmall', 101 on_activate_call=self.main_window_back, 102 ) 103 104 bui.containerwidget(edit=self._root_widget, cancel_button=back_button) 105 106 bui.textwidget( 107 parent=self._root_widget, 108 position=(0, height - 44), 109 size=(width, 25), 110 text=bui.Lstr(resource=f'{self._r}.titleText'), 111 color=bui.app.ui_v1.title_color, 112 h_align='center', 113 v_align='top', 114 ) 115 116 self._fullscreen_checkbox: bui.Widget | None = None 117 if self._show_fullscreen: 118 v -= fullscreen_spacing_top 119 # Fullscreen control does not necessarily talk to the 120 # app config so we have to wrangle it manually instead of 121 # using a config-checkbox. 122 label = bui.Lstr(resource=f'{self._r}.fullScreenText') 123 124 # Show keyboard shortcut alongside the control if they 125 # provide one. 126 shortcut = bui.fullscreen_control_key_shortcut() 127 if shortcut is not None: 128 label = bui.Lstr( 129 value='$(NAME) [$(SHORTCUT)]', 130 subs=[('$(NAME)', label), ('$(SHORTCUT)', shortcut)], 131 ) 132 self._fullscreen_checkbox = bui.checkboxwidget( 133 parent=self._root_widget, 134 position=(100, v), 135 value=bui.fullscreen_control_get(), 136 on_value_change_call=bui.fullscreen_control_set, 137 maxwidth=250, 138 size=(300, 30), 139 text=label, 140 ) 141 142 if not self._have_selected_child: 143 bui.containerwidget( 144 edit=self._root_widget, 145 selected_child=self._fullscreen_checkbox, 146 ) 147 self._have_selected_child = True 148 v -= fullscreen_spacing 149 150 self._selected_color = (0.5, 1, 0.5, 1) 151 self._unselected_color = (0.7, 0.7, 0.7, 1) 152 153 # Quality 154 bui.textwidget( 155 parent=self._root_widget, 156 position=(60, v), 157 size=(160, 25), 158 text=bui.Lstr(resource=f'{self._r}.visualsText'), 159 color=bui.app.ui_v1.heading_color, 160 scale=0.65, 161 maxwidth=150, 162 h_align='center', 163 v_align='center', 164 ) 165 PopupMenu( 166 parent=self._root_widget, 167 position=(60, v - 50), 168 width=150, 169 scale=popup_menu_scale, 170 choices=['Auto', 'Higher', 'High', 'Medium', 'Low'], 171 choices_disabled=( 172 ['Higher', 'High'] 173 if bui.get_max_graphics_quality() == 'Medium' 174 else [] 175 ), 176 choices_display=[ 177 bui.Lstr(resource='autoText'), 178 bui.Lstr(resource=f'{self._r}.higherText'), 179 bui.Lstr(resource=f'{self._r}.highText'), 180 bui.Lstr(resource=f'{self._r}.mediumText'), 181 bui.Lstr(resource=f'{self._r}.lowText'), 182 ], 183 current_choice=bui.app.config.resolve('Graphics Quality'), 184 on_value_change_call=self._set_quality, 185 ) 186 187 # Texture controls 188 bui.textwidget( 189 parent=self._root_widget, 190 position=(230, v), 191 size=(160, 25), 192 text=bui.Lstr(resource=f'{self._r}.texturesText'), 193 color=bui.app.ui_v1.heading_color, 194 scale=0.65, 195 maxwidth=150, 196 h_align='center', 197 v_align='center', 198 ) 199 textures_popup = PopupMenu( 200 parent=self._root_widget, 201 position=(230, v - 50), 202 width=150, 203 scale=popup_menu_scale, 204 choices=['Auto', 'High', 'Medium', 'Low'], 205 choices_display=[ 206 bui.Lstr(resource='autoText'), 207 bui.Lstr(resource=f'{self._r}.highText'), 208 bui.Lstr(resource=f'{self._r}.mediumText'), 209 bui.Lstr(resource=f'{self._r}.lowText'), 210 ], 211 current_choice=bui.app.config.resolve('Texture Quality'), 212 on_value_change_call=self._set_textures, 213 ) 214 bui.widget( 215 edit=textures_popup.get_button(), 216 right_widget=bui.get_special_widget('squad_button'), 217 ) 218 v -= 80 219 220 h_offs = 0 221 222 resolution_popup: PopupMenu | None = None 223 224 if show_resolution: 225 bui.textwidget( 226 parent=self._root_widget, 227 position=(h_offs + 60, v), 228 size=(160, 25), 229 text=bui.Lstr(resource=f'{self._r}.resolutionText'), 230 color=bui.app.ui_v1.heading_color, 231 scale=0.65, 232 maxwidth=150, 233 h_align='center', 234 v_align='center', 235 ) 236 237 # On standard android we have 'Auto', 'Native', and a few 238 # HD standards. 239 if app.classic.platform == 'android': 240 # on cardboard/daydream android we have a few 241 # render-target-scale options 242 if app.classic.subplatform == 'cardboard': 243 rawval = bui.app.config.resolve('GVR Render Target Scale') 244 current_res_cardboard = ( 245 str(min(100, max(10, int(round(rawval * 100.0))))) + '%' 246 ) 247 resolution_popup = PopupMenu( 248 parent=self._root_widget, 249 position=(h_offs + 60, v - 50), 250 width=120, 251 scale=popup_menu_scale, 252 choices=['100%', '75%', '50%', '35%'], 253 current_choice=current_res_cardboard, 254 on_value_change_call=self._set_gvr_render_target_scale, 255 ) 256 else: 257 native_res = bui.get_display_resolution() 258 assert native_res is not None 259 choices = ['Auto', 'Native'] 260 choices_display = [ 261 bui.Lstr(resource='autoText'), 262 bui.Lstr(resource='nativeText'), 263 ] 264 for res in [1440, 1080, 960, 720, 480]: 265 if native_res[1] >= res: 266 res_str = f'{res}p' 267 choices.append(res_str) 268 choices_display.append(bui.Lstr(value=res_str)) 269 current_res_android = bui.app.config.resolve( 270 'Resolution (Android)' 271 ) 272 resolution_popup = PopupMenu( 273 parent=self._root_widget, 274 position=(h_offs + 60, v - 50), 275 width=120, 276 scale=popup_menu_scale, 277 choices=choices, 278 choices_display=choices_display, 279 current_choice=current_res_android, 280 on_value_change_call=self._set_android_res, 281 ) 282 else: 283 # If we're on a system that doesn't allow setting resolution, 284 # set pixel-scale instead. 285 current_res = bui.get_display_resolution() 286 if current_res is None: 287 rawval = bui.app.config.resolve('Screen Pixel Scale') 288 current_res2 = ( 289 str(min(100, max(10, int(round(rawval * 100.0))))) + '%' 290 ) 291 resolution_popup = PopupMenu( 292 parent=self._root_widget, 293 position=(h_offs + 60, v - 50), 294 width=120, 295 scale=popup_menu_scale, 296 choices=['100%', '88%', '75%', '63%', '50%'], 297 current_choice=current_res2, 298 on_value_change_call=self._set_pixel_scale, 299 ) 300 else: 301 raise RuntimeError( 302 'obsolete code path; discrete resolutions' 303 ' no longer supported' 304 ) 305 if resolution_popup is not None: 306 bui.widget( 307 edit=resolution_popup.get_button(), 308 left_widget=back_button, 309 ) 310 311 vsync_popup: PopupMenu | None = None 312 if show_vsync: 313 bui.textwidget( 314 parent=self._root_widget, 315 position=(230, v), 316 size=(160, 25), 317 text=bui.Lstr(resource=f'{self._r}.verticalSyncText'), 318 color=bui.app.ui_v1.heading_color, 319 scale=0.65, 320 maxwidth=150, 321 h_align='center', 322 v_align='center', 323 ) 324 vsync_popup = PopupMenu( 325 parent=self._root_widget, 326 position=(230, v - 50), 327 width=150, 328 scale=popup_menu_scale, 329 choices=['Auto', 'Always', 'Never'], 330 choices_display=[ 331 bui.Lstr(resource='autoText'), 332 bui.Lstr(resource=f'{self._r}.alwaysText'), 333 bui.Lstr(resource=f'{self._r}.neverText'), 334 ], 335 current_choice=bui.app.config.resolve('Vertical Sync'), 336 on_value_change_call=self._set_vsync, 337 ) 338 if resolution_popup is not None: 339 bui.widget( 340 edit=vsync_popup.get_button(), 341 left_widget=resolution_popup.get_button(), 342 ) 343 344 if resolution_popup is not None and vsync_popup is not None: 345 bui.widget( 346 edit=resolution_popup.get_button(), 347 right_widget=vsync_popup.get_button(), 348 ) 349 350 v -= 90 351 self._max_fps_text: bui.Widget | None = None 352 if show_max_fps: 353 v -= 5 354 bui.textwidget( 355 parent=self._root_widget, 356 position=(155, v + 10), 357 size=(0, 0), 358 text=bui.Lstr(resource=f'{self._r}.maxFPSText'), 359 color=bui.app.ui_v1.heading_color, 360 scale=0.9, 361 maxwidth=90, 362 h_align='right', 363 v_align='center', 364 ) 365 366 max_fps_str = str(bui.app.config.resolve('Max FPS')) 367 self._last_max_fps_str = max_fps_str 368 self._max_fps_text = bui.textwidget( 369 parent=self._root_widget, 370 position=(170, v - 5), 371 size=(105, 30), 372 text=max_fps_str, 373 max_chars=5, 374 editable=True, 375 h_align='left', 376 v_align='center', 377 on_return_press_call=self._on_max_fps_return_press, 378 ) 379 v -= 45 380 381 if self._max_fps_text is not None and resolution_popup is not None: 382 bui.widget( 383 edit=resolution_popup.get_button(), 384 down_widget=self._max_fps_text, 385 ) 386 bui.widget( 387 edit=self._max_fps_text, 388 up_widget=resolution_popup.get_button(), 389 ) 390 391 fpsc = ConfigCheckBox( 392 parent=self._root_widget, 393 position=(69, v - 6), 394 size=(210, 30), 395 scale=0.86, 396 configkey='Show FPS', 397 displayname=bui.Lstr(resource=f'{self._r}.showFPSText'), 398 maxwidth=130, 399 ) 400 if self._max_fps_text is not None: 401 bui.widget( 402 edit=self._max_fps_text, 403 down_widget=fpsc.widget, 404 ) 405 bui.widget( 406 edit=fpsc.widget, 407 up_widget=self._max_fps_text, 408 ) 409 410 if show_tv_mode: 411 tvc = ConfigCheckBox( 412 parent=self._root_widget, 413 position=(240, v - 6), 414 size=(210, 30), 415 scale=0.86, 416 configkey='TV Border', 417 displayname=bui.Lstr(resource=f'{self._r}.tvBorderText'), 418 maxwidth=130, 419 ) 420 bui.widget(edit=fpsc.widget, right_widget=tvc.widget) 421 bui.widget(edit=tvc.widget, left_widget=fpsc.widget) 422 423 v -= spacing 424 425 # Make a timer to update our controls in case the config changes 426 # under us. 427 self._update_timer = bui.AppTimer( 428 0.25, bui.WeakCall(self._update_controls), repeat=True 429 ) 430 431 @override 432 def get_main_window_state(self) -> bui.MainWindowState: 433 # Support recreating our window for back/refresh purposes. 434 cls = type(self) 435 return bui.BasicMainWindowState( 436 create_call=lambda transition, origin_widget: cls( 437 transition=transition, origin_widget=origin_widget 438 ) 439 ) 440 441 @override 442 def on_main_window_close(self) -> None: 443 self._apply_max_fps() 444 445 def _set_quality(self, quality: str) -> None: 446 cfg = bui.app.config 447 cfg['Graphics Quality'] = quality 448 cfg.apply_and_commit() 449 450 def _set_textures(self, val: str) -> None: 451 cfg = bui.app.config 452 cfg['Texture Quality'] = val 453 cfg.apply_and_commit() 454 455 def _set_android_res(self, val: str) -> None: 456 cfg = bui.app.config 457 cfg['Resolution (Android)'] = val 458 cfg.apply_and_commit() 459 460 def _set_pixel_scale(self, res: str) -> None: 461 cfg = bui.app.config 462 cfg['Screen Pixel Scale'] = float(res[:-1]) / 100.0 463 cfg.apply_and_commit() 464 465 def _set_gvr_render_target_scale(self, res: str) -> None: 466 cfg = bui.app.config 467 cfg['GVR Render Target Scale'] = float(res[:-1]) / 100.0 468 cfg.apply_and_commit() 469 470 def _set_vsync(self, val: str) -> None: 471 cfg = bui.app.config 472 cfg['Vertical Sync'] = val 473 cfg.apply_and_commit() 474 475 def _on_max_fps_return_press(self) -> None: 476 self._apply_max_fps() 477 bui.containerwidget( 478 edit=self._root_widget, selected_child=cast(bui.Widget, 0) 479 ) 480 481 def _apply_max_fps(self) -> None: 482 if not self._max_fps_dirty or not self._max_fps_text: 483 return 484 485 val: Any = bui.textwidget(query=self._max_fps_text) 486 assert isinstance(val, str) 487 # If there's a broken value, replace it with the default. 488 try: 489 ival = int(val) 490 except ValueError: 491 ival = bui.app.config.default_value('Max FPS') 492 assert isinstance(ival, int) 493 494 # Clamp to reasonable limits (allow -1 to mean no max). 495 if ival != -1: 496 ival = max(10, ival) 497 ival = min(99999, ival) 498 499 # Store it to the config. 500 cfg = bui.app.config 501 cfg['Max FPS'] = ival 502 cfg.apply_and_commit() 503 504 # Update the display if we changed the value. 505 if str(ival) != val: 506 bui.textwidget(edit=self._max_fps_text, text=str(ival)) 507 508 self._max_fps_dirty = False 509 510 def _update_controls(self) -> None: 511 if self._max_fps_text is not None: 512 # Keep track of when the max-fps value changes. Once it 513 # remains stable for a few moments, apply it. 514 val: Any = bui.textwidget(query=self._max_fps_text) 515 assert isinstance(val, str) 516 if val != self._last_max_fps_str: 517 # Oop; it changed. Note the time and the fact that we'll 518 # need to apply it at some point. 519 self._max_fps_dirty = True 520 self._last_max_fps_str = val 521 self._last_max_fps_set_time = bui.apptime() 522 else: 523 # If its been stable long enough, apply it. 524 if ( 525 self._max_fps_dirty 526 and bui.apptime() - self._last_max_fps_set_time > 1.0 527 ): 528 self._apply_max_fps() 529 530 if self._show_fullscreen: 531 # Keep the fullscreen checkbox up to date with the current value. 532 bui.checkboxwidget( 533 edit=self._fullscreen_checkbox, 534 value=bui.fullscreen_control_get(), 535 )
Window for graphics settings.
GraphicsSettingsWindow( transition: str | None = 'in_right', origin_widget: _bauiv1.Widget | None = None)
21 def __init__( 22 self, 23 transition: str | None = 'in_right', 24 origin_widget: bui.Widget | None = None, 25 ): 26 # pylint: disable=too-many-locals 27 # pylint: disable=too-many-branches 28 # pylint: disable=too-many-statements 29 30 self._r = 'graphicsSettingsWindow' 31 app = bui.app 32 assert app.classic is not None 33 34 spacing = 32 35 self._have_selected_child = False 36 uiscale = app.ui_v1.uiscale 37 width = 450.0 38 height = 302.0 39 self._max_fps_dirty = False 40 self._last_max_fps_set_time = bui.apptime() 41 self._last_max_fps_str = '' 42 43 self._show_fullscreen = False 44 fullscreen_spacing_top = spacing * 0.2 45 fullscreen_spacing = spacing * 1.2 46 if bui.fullscreen_control_available(): 47 self._show_fullscreen = True 48 height += fullscreen_spacing + fullscreen_spacing_top 49 50 show_vsync = bui.supports_vsync() 51 show_tv_mode = not bui.app.env.vr 52 53 show_max_fps = bui.supports_max_fps() 54 if show_max_fps: 55 height += 50 56 57 show_resolution = True 58 if app.env.vr: 59 show_resolution = ( 60 app.classic.platform == 'android' 61 and app.classic.subplatform == 'cardboard' 62 ) 63 64 assert bui.app.classic is not None 65 uiscale = bui.app.ui_v1.uiscale 66 base_scale = ( 67 1.5 68 if uiscale is bui.UIScale.SMALL 69 else 1.3 if uiscale is bui.UIScale.MEDIUM else 1.0 70 ) 71 popup_menu_scale = base_scale * 1.2 72 v = height - 50 73 v -= spacing * 1.15 74 super().__init__( 75 root_widget=bui.containerwidget( 76 size=(width, height), 77 scale=base_scale, 78 stack_offset=( 79 (0, -10) if uiscale is bui.UIScale.SMALL else (0, 0) 80 ), 81 toolbar_visibility=( 82 'menu_minimal' 83 if uiscale is bui.UIScale.SMALL 84 else 'menu_full' 85 ), 86 ), 87 transition=transition, 88 origin_widget=origin_widget, 89 ) 90 91 back_button = bui.buttonwidget( 92 parent=self._root_widget, 93 position=(35, height - 50), 94 # size=(120, 60), 95 size=(60, 60), 96 scale=0.8, 97 text_scale=1.2, 98 autoselect=True, 99 label=bui.charstr(bui.SpecialChar.BACK), 100 button_type='backSmall', 101 on_activate_call=self.main_window_back, 102 ) 103 104 bui.containerwidget(edit=self._root_widget, cancel_button=back_button) 105 106 bui.textwidget( 107 parent=self._root_widget, 108 position=(0, height - 44), 109 size=(width, 25), 110 text=bui.Lstr(resource=f'{self._r}.titleText'), 111 color=bui.app.ui_v1.title_color, 112 h_align='center', 113 v_align='top', 114 ) 115 116 self._fullscreen_checkbox: bui.Widget | None = None 117 if self._show_fullscreen: 118 v -= fullscreen_spacing_top 119 # Fullscreen control does not necessarily talk to the 120 # app config so we have to wrangle it manually instead of 121 # using a config-checkbox. 122 label = bui.Lstr(resource=f'{self._r}.fullScreenText') 123 124 # Show keyboard shortcut alongside the control if they 125 # provide one. 126 shortcut = bui.fullscreen_control_key_shortcut() 127 if shortcut is not None: 128 label = bui.Lstr( 129 value='$(NAME) [$(SHORTCUT)]', 130 subs=[('$(NAME)', label), ('$(SHORTCUT)', shortcut)], 131 ) 132 self._fullscreen_checkbox = bui.checkboxwidget( 133 parent=self._root_widget, 134 position=(100, v), 135 value=bui.fullscreen_control_get(), 136 on_value_change_call=bui.fullscreen_control_set, 137 maxwidth=250, 138 size=(300, 30), 139 text=label, 140 ) 141 142 if not self._have_selected_child: 143 bui.containerwidget( 144 edit=self._root_widget, 145 selected_child=self._fullscreen_checkbox, 146 ) 147 self._have_selected_child = True 148 v -= fullscreen_spacing 149 150 self._selected_color = (0.5, 1, 0.5, 1) 151 self._unselected_color = (0.7, 0.7, 0.7, 1) 152 153 # Quality 154 bui.textwidget( 155 parent=self._root_widget, 156 position=(60, v), 157 size=(160, 25), 158 text=bui.Lstr(resource=f'{self._r}.visualsText'), 159 color=bui.app.ui_v1.heading_color, 160 scale=0.65, 161 maxwidth=150, 162 h_align='center', 163 v_align='center', 164 ) 165 PopupMenu( 166 parent=self._root_widget, 167 position=(60, v - 50), 168 width=150, 169 scale=popup_menu_scale, 170 choices=['Auto', 'Higher', 'High', 'Medium', 'Low'], 171 choices_disabled=( 172 ['Higher', 'High'] 173 if bui.get_max_graphics_quality() == 'Medium' 174 else [] 175 ), 176 choices_display=[ 177 bui.Lstr(resource='autoText'), 178 bui.Lstr(resource=f'{self._r}.higherText'), 179 bui.Lstr(resource=f'{self._r}.highText'), 180 bui.Lstr(resource=f'{self._r}.mediumText'), 181 bui.Lstr(resource=f'{self._r}.lowText'), 182 ], 183 current_choice=bui.app.config.resolve('Graphics Quality'), 184 on_value_change_call=self._set_quality, 185 ) 186 187 # Texture controls 188 bui.textwidget( 189 parent=self._root_widget, 190 position=(230, v), 191 size=(160, 25), 192 text=bui.Lstr(resource=f'{self._r}.texturesText'), 193 color=bui.app.ui_v1.heading_color, 194 scale=0.65, 195 maxwidth=150, 196 h_align='center', 197 v_align='center', 198 ) 199 textures_popup = PopupMenu( 200 parent=self._root_widget, 201 position=(230, v - 50), 202 width=150, 203 scale=popup_menu_scale, 204 choices=['Auto', 'High', 'Medium', 'Low'], 205 choices_display=[ 206 bui.Lstr(resource='autoText'), 207 bui.Lstr(resource=f'{self._r}.highText'), 208 bui.Lstr(resource=f'{self._r}.mediumText'), 209 bui.Lstr(resource=f'{self._r}.lowText'), 210 ], 211 current_choice=bui.app.config.resolve('Texture Quality'), 212 on_value_change_call=self._set_textures, 213 ) 214 bui.widget( 215 edit=textures_popup.get_button(), 216 right_widget=bui.get_special_widget('squad_button'), 217 ) 218 v -= 80 219 220 h_offs = 0 221 222 resolution_popup: PopupMenu | None = None 223 224 if show_resolution: 225 bui.textwidget( 226 parent=self._root_widget, 227 position=(h_offs + 60, v), 228 size=(160, 25), 229 text=bui.Lstr(resource=f'{self._r}.resolutionText'), 230 color=bui.app.ui_v1.heading_color, 231 scale=0.65, 232 maxwidth=150, 233 h_align='center', 234 v_align='center', 235 ) 236 237 # On standard android we have 'Auto', 'Native', and a few 238 # HD standards. 239 if app.classic.platform == 'android': 240 # on cardboard/daydream android we have a few 241 # render-target-scale options 242 if app.classic.subplatform == 'cardboard': 243 rawval = bui.app.config.resolve('GVR Render Target Scale') 244 current_res_cardboard = ( 245 str(min(100, max(10, int(round(rawval * 100.0))))) + '%' 246 ) 247 resolution_popup = PopupMenu( 248 parent=self._root_widget, 249 position=(h_offs + 60, v - 50), 250 width=120, 251 scale=popup_menu_scale, 252 choices=['100%', '75%', '50%', '35%'], 253 current_choice=current_res_cardboard, 254 on_value_change_call=self._set_gvr_render_target_scale, 255 ) 256 else: 257 native_res = bui.get_display_resolution() 258 assert native_res is not None 259 choices = ['Auto', 'Native'] 260 choices_display = [ 261 bui.Lstr(resource='autoText'), 262 bui.Lstr(resource='nativeText'), 263 ] 264 for res in [1440, 1080, 960, 720, 480]: 265 if native_res[1] >= res: 266 res_str = f'{res}p' 267 choices.append(res_str) 268 choices_display.append(bui.Lstr(value=res_str)) 269 current_res_android = bui.app.config.resolve( 270 'Resolution (Android)' 271 ) 272 resolution_popup = PopupMenu( 273 parent=self._root_widget, 274 position=(h_offs + 60, v - 50), 275 width=120, 276 scale=popup_menu_scale, 277 choices=choices, 278 choices_display=choices_display, 279 current_choice=current_res_android, 280 on_value_change_call=self._set_android_res, 281 ) 282 else: 283 # If we're on a system that doesn't allow setting resolution, 284 # set pixel-scale instead. 285 current_res = bui.get_display_resolution() 286 if current_res is None: 287 rawval = bui.app.config.resolve('Screen Pixel Scale') 288 current_res2 = ( 289 str(min(100, max(10, int(round(rawval * 100.0))))) + '%' 290 ) 291 resolution_popup = PopupMenu( 292 parent=self._root_widget, 293 position=(h_offs + 60, v - 50), 294 width=120, 295 scale=popup_menu_scale, 296 choices=['100%', '88%', '75%', '63%', '50%'], 297 current_choice=current_res2, 298 on_value_change_call=self._set_pixel_scale, 299 ) 300 else: 301 raise RuntimeError( 302 'obsolete code path; discrete resolutions' 303 ' no longer supported' 304 ) 305 if resolution_popup is not None: 306 bui.widget( 307 edit=resolution_popup.get_button(), 308 left_widget=back_button, 309 ) 310 311 vsync_popup: PopupMenu | None = None 312 if show_vsync: 313 bui.textwidget( 314 parent=self._root_widget, 315 position=(230, v), 316 size=(160, 25), 317 text=bui.Lstr(resource=f'{self._r}.verticalSyncText'), 318 color=bui.app.ui_v1.heading_color, 319 scale=0.65, 320 maxwidth=150, 321 h_align='center', 322 v_align='center', 323 ) 324 vsync_popup = PopupMenu( 325 parent=self._root_widget, 326 position=(230, v - 50), 327 width=150, 328 scale=popup_menu_scale, 329 choices=['Auto', 'Always', 'Never'], 330 choices_display=[ 331 bui.Lstr(resource='autoText'), 332 bui.Lstr(resource=f'{self._r}.alwaysText'), 333 bui.Lstr(resource=f'{self._r}.neverText'), 334 ], 335 current_choice=bui.app.config.resolve('Vertical Sync'), 336 on_value_change_call=self._set_vsync, 337 ) 338 if resolution_popup is not None: 339 bui.widget( 340 edit=vsync_popup.get_button(), 341 left_widget=resolution_popup.get_button(), 342 ) 343 344 if resolution_popup is not None and vsync_popup is not None: 345 bui.widget( 346 edit=resolution_popup.get_button(), 347 right_widget=vsync_popup.get_button(), 348 ) 349 350 v -= 90 351 self._max_fps_text: bui.Widget | None = None 352 if show_max_fps: 353 v -= 5 354 bui.textwidget( 355 parent=self._root_widget, 356 position=(155, v + 10), 357 size=(0, 0), 358 text=bui.Lstr(resource=f'{self._r}.maxFPSText'), 359 color=bui.app.ui_v1.heading_color, 360 scale=0.9, 361 maxwidth=90, 362 h_align='right', 363 v_align='center', 364 ) 365 366 max_fps_str = str(bui.app.config.resolve('Max FPS')) 367 self._last_max_fps_str = max_fps_str 368 self._max_fps_text = bui.textwidget( 369 parent=self._root_widget, 370 position=(170, v - 5), 371 size=(105, 30), 372 text=max_fps_str, 373 max_chars=5, 374 editable=True, 375 h_align='left', 376 v_align='center', 377 on_return_press_call=self._on_max_fps_return_press, 378 ) 379 v -= 45 380 381 if self._max_fps_text is not None and resolution_popup is not None: 382 bui.widget( 383 edit=resolution_popup.get_button(), 384 down_widget=self._max_fps_text, 385 ) 386 bui.widget( 387 edit=self._max_fps_text, 388 up_widget=resolution_popup.get_button(), 389 ) 390 391 fpsc = ConfigCheckBox( 392 parent=self._root_widget, 393 position=(69, v - 6), 394 size=(210, 30), 395 scale=0.86, 396 configkey='Show FPS', 397 displayname=bui.Lstr(resource=f'{self._r}.showFPSText'), 398 maxwidth=130, 399 ) 400 if self._max_fps_text is not None: 401 bui.widget( 402 edit=self._max_fps_text, 403 down_widget=fpsc.widget, 404 ) 405 bui.widget( 406 edit=fpsc.widget, 407 up_widget=self._max_fps_text, 408 ) 409 410 if show_tv_mode: 411 tvc = ConfigCheckBox( 412 parent=self._root_widget, 413 position=(240, v - 6), 414 size=(210, 30), 415 scale=0.86, 416 configkey='TV Border', 417 displayname=bui.Lstr(resource=f'{self._r}.tvBorderText'), 418 maxwidth=130, 419 ) 420 bui.widget(edit=fpsc.widget, right_widget=tvc.widget) 421 bui.widget(edit=tvc.widget, left_widget=fpsc.widget) 422 423 v -= spacing 424 425 # Make a timer to update our controls in case the config changes 426 # under us. 427 self._update_timer = bui.AppTimer( 428 0.25, bui.WeakCall(self._update_controls), repeat=True 429 )
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.
431 @override 432 def get_main_window_state(self) -> bui.MainWindowState: 433 # Support recreating our window for back/refresh purposes. 434 cls = type(self) 435 return bui.BasicMainWindowState( 436 create_call=lambda transition, origin_widget: cls( 437 transition=transition, origin_widget=origin_widget 438 ) 439 )
Return a WindowState to recreate this window, if supported.
@override
def
on_main_window_close(self) -> None:
Called before transitioning out a main window.
A good opportunity to save window state/etc.
Inherited Members
- bauiv1._uitypes.MainWindow
- main_window_back_state
- main_window_close
- can_change_main_window
- main_window_back
- main_window_replace
- bauiv1._uitypes.Window
- get_root_widget