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 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.Window): 18 """Window for graphics settings.""" 19 20 def __init__( 21 self, 22 transition: str = '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 # if they provided an origin-widget, scale up from that 30 scale_origin: tuple[float, float] | None 31 if origin_widget is not None: 32 self._transition_out = 'out_scale' 33 scale_origin = origin_widget.get_screen_space_center() 34 transition = 'in_scale' 35 else: 36 self._transition_out = 'out_right' 37 scale_origin = None 38 39 self._r = 'graphicsSettingsWindow' 40 app = bui.app 41 assert app.classic is not None 42 43 spacing = 32 44 self._have_selected_child = False 45 uiscale = app.ui_v1.uiscale 46 width = 450.0 47 height = 302.0 48 self._max_fps_dirty = False 49 self._last_max_fps_set_time = bui.apptime() 50 self._last_max_fps_str = '' 51 52 self._show_fullscreen = False 53 fullscreen_spacing_top = spacing * 0.2 54 fullscreen_spacing = spacing * 1.2 55 if bui.can_toggle_fullscreen(): 56 self._show_fullscreen = True 57 height += fullscreen_spacing + fullscreen_spacing_top 58 59 show_vsync = bui.supports_vsync() 60 show_tv_mode = not bui.app.env.vr 61 62 show_max_fps = bui.supports_max_fps() 63 if show_max_fps: 64 height += 50 65 66 show_resolution = True 67 if app.env.vr: 68 show_resolution = ( 69 app.classic.platform == 'android' 70 and app.classic.subplatform == 'cardboard' 71 ) 72 73 assert bui.app.classic is not None 74 uiscale = bui.app.ui_v1.uiscale 75 base_scale = ( 76 2.0 77 if uiscale is bui.UIScale.SMALL 78 else 1.5 79 if uiscale is bui.UIScale.MEDIUM 80 else 1.0 81 ) 82 popup_menu_scale = base_scale * 1.2 83 v = height - 50 84 v -= spacing * 1.15 85 super().__init__( 86 root_widget=bui.containerwidget( 87 size=(width, height), 88 transition=transition, 89 scale_origin_stack_offset=scale_origin, 90 scale=base_scale, 91 stack_offset=(0, -30) 92 if uiscale is bui.UIScale.SMALL 93 else (0, 0), 94 ) 95 ) 96 97 back_button = bui.buttonwidget( 98 parent=self._root_widget, 99 position=(35, height - 50), 100 # size=(120, 60), 101 size=(60, 60), 102 scale=0.8, 103 text_scale=1.2, 104 autoselect=True, 105 label=bui.charstr(bui.SpecialChar.BACK), 106 button_type='backSmall', 107 on_activate_call=self._back, 108 ) 109 110 bui.containerwidget(edit=self._root_widget, cancel_button=back_button) 111 112 bui.textwidget( 113 parent=self._root_widget, 114 position=(0, height - 44), 115 size=(width, 25), 116 text=bui.Lstr(resource=self._r + '.titleText'), 117 color=bui.app.ui_v1.title_color, 118 h_align='center', 119 v_align='top', 120 ) 121 122 self._fullscreen_checkbox: bui.Widget | None = None 123 if self._show_fullscreen: 124 v -= fullscreen_spacing_top 125 self._fullscreen_checkbox = ConfigCheckBox( 126 parent=self._root_widget, 127 position=(100, v), 128 maxwidth=200, 129 size=(300, 30), 130 configkey='Fullscreen', 131 displayname=bui.Lstr( 132 resource=self._r 133 + ( 134 '.fullScreenCmdText' 135 if app.classic.platform == 'mac' 136 else '.fullScreenCtrlText' 137 ) 138 ), 139 ).widget 140 if not self._have_selected_child: 141 bui.containerwidget( 142 edit=self._root_widget, 143 selected_child=self._fullscreen_checkbox, 144 ) 145 self._have_selected_child = True 146 v -= fullscreen_spacing 147 148 self._selected_color = (0.5, 1, 0.5, 1) 149 self._unselected_color = (0.7, 0.7, 0.7, 1) 150 151 # Quality 152 bui.textwidget( 153 parent=self._root_widget, 154 position=(60, v), 155 size=(160, 25), 156 text=bui.Lstr(resource=self._r + '.visualsText'), 157 color=bui.app.ui_v1.heading_color, 158 scale=0.65, 159 maxwidth=150, 160 h_align='center', 161 v_align='center', 162 ) 163 PopupMenu( 164 parent=self._root_widget, 165 position=(60, v - 50), 166 width=150, 167 scale=popup_menu_scale, 168 choices=['Auto', 'Higher', 'High', 'Medium', 'Low'], 169 choices_disabled=['Higher', 'High'] 170 if bui.get_max_graphics_quality() == 'Medium' 171 else [], 172 choices_display=[ 173 bui.Lstr(resource='autoText'), 174 bui.Lstr(resource=self._r + '.higherText'), 175 bui.Lstr(resource=self._r + '.highText'), 176 bui.Lstr(resource=self._r + '.mediumText'), 177 bui.Lstr(resource=self._r + '.lowText'), 178 ], 179 current_choice=bui.app.config.resolve('Graphics Quality'), 180 on_value_change_call=self._set_quality, 181 ) 182 183 # Texture controls 184 bui.textwidget( 185 parent=self._root_widget, 186 position=(230, v), 187 size=(160, 25), 188 text=bui.Lstr(resource=self._r + '.texturesText'), 189 color=bui.app.ui_v1.heading_color, 190 scale=0.65, 191 maxwidth=150, 192 h_align='center', 193 v_align='center', 194 ) 195 textures_popup = PopupMenu( 196 parent=self._root_widget, 197 position=(230, v - 50), 198 width=150, 199 scale=popup_menu_scale, 200 choices=['Auto', 'High', 'Medium', 'Low'], 201 choices_display=[ 202 bui.Lstr(resource='autoText'), 203 bui.Lstr(resource=self._r + '.highText'), 204 bui.Lstr(resource=self._r + '.mediumText'), 205 bui.Lstr(resource=self._r + '.lowText'), 206 ], 207 current_choice=bui.app.config.resolve('Texture Quality'), 208 on_value_change_call=self._set_textures, 209 ) 210 if bui.app.ui_v1.use_toolbars: 211 bui.widget( 212 edit=textures_popup.get_button(), 213 right_widget=bui.get_special_widget('party_button'), 214 ) 215 v -= 80 216 217 h_offs = 0 218 219 resolution_popup: PopupMenu | None = None 220 221 if show_resolution: 222 bui.textwidget( 223 parent=self._root_widget, 224 position=(h_offs + 60, v), 225 size=(160, 25), 226 text=bui.Lstr(resource=self._r + '.resolutionText'), 227 color=bui.app.ui_v1.heading_color, 228 scale=0.65, 229 maxwidth=150, 230 h_align='center', 231 v_align='center', 232 ) 233 234 # On standard android we have 'Auto', 'Native', and a few 235 # HD standards. 236 if app.classic.platform == 'android': 237 # on cardboard/daydream android we have a few 238 # render-target-scale options 239 if app.classic.subplatform == 'cardboard': 240 rawval = bui.app.config.resolve('GVR Render Target Scale') 241 current_res_cardboard = ( 242 str(min(100, max(10, int(round(rawval * 100.0))))) + '%' 243 ) 244 resolution_popup = PopupMenu( 245 parent=self._root_widget, 246 position=(h_offs + 60, v - 50), 247 width=120, 248 scale=popup_menu_scale, 249 choices=['100%', '75%', '50%', '35%'], 250 current_choice=current_res_cardboard, 251 on_value_change_call=self._set_gvr_render_target_scale, 252 ) 253 else: 254 native_res = bui.get_display_resolution() 255 assert native_res is not None 256 choices = ['Auto', 'Native'] 257 choices_display = [ 258 bui.Lstr(resource='autoText'), 259 bui.Lstr(resource='nativeText'), 260 ] 261 for res in [1440, 1080, 960, 720, 480]: 262 # Nav bar is 72px so lets allow for that in what 263 # choices we show. 264 if native_res[1] >= res - 72: 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=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=self._r + '.alwaysText'), 332 bui.Lstr(resource=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=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=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=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 def _back(self) -> None: 431 from bauiv1lib.settings import allsettings 432 433 # Applying max-fps takes a few moments. Apply if it hasn't been 434 # yet. 435 self._apply_max_fps() 436 437 bui.containerwidget( 438 edit=self._root_widget, transition=self._transition_out 439 ) 440 assert bui.app.classic is not None 441 bui.app.ui_v1.set_main_menu_window( 442 allsettings.AllSettingsWindow( 443 transition='in_left' 444 ).get_root_widget() 445 ) 446 447 def _set_quality(self, quality: str) -> None: 448 cfg = bui.app.config 449 cfg['Graphics Quality'] = quality 450 cfg.apply_and_commit() 451 452 def _set_textures(self, val: str) -> None: 453 cfg = bui.app.config 454 cfg['Texture Quality'] = val 455 cfg.apply_and_commit() 456 457 def _set_android_res(self, val: str) -> None: 458 cfg = bui.app.config 459 cfg['Resolution (Android)'] = val 460 cfg.apply_and_commit() 461 462 def _set_pixel_scale(self, res: str) -> None: 463 cfg = bui.app.config 464 cfg['Screen Pixel Scale'] = float(res[:-1]) / 100.0 465 cfg.apply_and_commit() 466 467 def _set_gvr_render_target_scale(self, res: str) -> None: 468 cfg = bui.app.config 469 cfg['GVR Render Target Scale'] = float(res[:-1]) / 100.0 470 cfg.apply_and_commit() 471 472 def _set_vsync(self, val: str) -> None: 473 cfg = bui.app.config 474 cfg['Vertical Sync'] = val 475 cfg.apply_and_commit() 476 477 def _on_max_fps_return_press(self) -> None: 478 self._apply_max_fps() 479 bui.containerwidget( 480 edit=self._root_widget, selected_child=cast(bui.Widget, 0) 481 ) 482 483 def _apply_max_fps(self) -> None: 484 if not self._max_fps_dirty or not self._max_fps_text: 485 return 486 487 val: Any = bui.textwidget(query=self._max_fps_text) 488 assert isinstance(val, str) 489 # If there's a broken value, replace it with the default. 490 try: 491 ival = int(val) 492 except ValueError: 493 ival = bui.app.config.default_value('Max FPS') 494 assert isinstance(ival, int) 495 496 # Clamp to reasonable limits (allow -1 to mean no max). 497 if ival != -1: 498 ival = max(10, ival) 499 ival = min(99999, ival) 500 501 # Store it to the config. 502 cfg = bui.app.config 503 cfg['Max FPS'] = ival 504 cfg.apply_and_commit() 505 506 # Update the display if we changed the value. 507 if str(ival) != val: 508 bui.textwidget(edit=self._max_fps_text, text=str(ival)) 509 510 self._max_fps_dirty = False 511 512 def _update_controls(self) -> None: 513 if self._max_fps_text is not None: 514 # Keep track of when the max-fps value changes. Once it 515 # remains stable for a few moments, apply it. 516 val: Any = bui.textwidget(query=self._max_fps_text) 517 assert isinstance(val, str) 518 if val != self._last_max_fps_str: 519 # Oop; it changed. Note the time and the fact that we'll 520 # need to apply it at some point. 521 self._max_fps_dirty = True 522 self._last_max_fps_str = val 523 self._last_max_fps_set_time = bui.apptime() 524 else: 525 # If its been stable long enough, apply it. 526 if ( 527 self._max_fps_dirty 528 and bui.apptime() - self._last_max_fps_set_time > 1.0 529 ): 530 self._apply_max_fps() 531 if self._show_fullscreen: 532 bui.checkboxwidget( 533 edit=self._fullscreen_checkbox, 534 value=bui.app.config.resolve('Fullscreen'), 535 )
class
GraphicsSettingsWindow(bauiv1._uitypes.Window):
18class GraphicsSettingsWindow(bui.Window): 19 """Window for graphics settings.""" 20 21 def __init__( 22 self, 23 transition: str = '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 # if they provided an origin-widget, scale up from that 31 scale_origin: tuple[float, float] | None 32 if origin_widget is not None: 33 self._transition_out = 'out_scale' 34 scale_origin = origin_widget.get_screen_space_center() 35 transition = 'in_scale' 36 else: 37 self._transition_out = 'out_right' 38 scale_origin = None 39 40 self._r = 'graphicsSettingsWindow' 41 app = bui.app 42 assert app.classic is not None 43 44 spacing = 32 45 self._have_selected_child = False 46 uiscale = app.ui_v1.uiscale 47 width = 450.0 48 height = 302.0 49 self._max_fps_dirty = False 50 self._last_max_fps_set_time = bui.apptime() 51 self._last_max_fps_str = '' 52 53 self._show_fullscreen = False 54 fullscreen_spacing_top = spacing * 0.2 55 fullscreen_spacing = spacing * 1.2 56 if bui.can_toggle_fullscreen(): 57 self._show_fullscreen = True 58 height += fullscreen_spacing + fullscreen_spacing_top 59 60 show_vsync = bui.supports_vsync() 61 show_tv_mode = not bui.app.env.vr 62 63 show_max_fps = bui.supports_max_fps() 64 if show_max_fps: 65 height += 50 66 67 show_resolution = True 68 if app.env.vr: 69 show_resolution = ( 70 app.classic.platform == 'android' 71 and app.classic.subplatform == 'cardboard' 72 ) 73 74 assert bui.app.classic is not None 75 uiscale = bui.app.ui_v1.uiscale 76 base_scale = ( 77 2.0 78 if uiscale is bui.UIScale.SMALL 79 else 1.5 80 if uiscale is bui.UIScale.MEDIUM 81 else 1.0 82 ) 83 popup_menu_scale = base_scale * 1.2 84 v = height - 50 85 v -= spacing * 1.15 86 super().__init__( 87 root_widget=bui.containerwidget( 88 size=(width, height), 89 transition=transition, 90 scale_origin_stack_offset=scale_origin, 91 scale=base_scale, 92 stack_offset=(0, -30) 93 if uiscale is bui.UIScale.SMALL 94 else (0, 0), 95 ) 96 ) 97 98 back_button = bui.buttonwidget( 99 parent=self._root_widget, 100 position=(35, height - 50), 101 # size=(120, 60), 102 size=(60, 60), 103 scale=0.8, 104 text_scale=1.2, 105 autoselect=True, 106 label=bui.charstr(bui.SpecialChar.BACK), 107 button_type='backSmall', 108 on_activate_call=self._back, 109 ) 110 111 bui.containerwidget(edit=self._root_widget, cancel_button=back_button) 112 113 bui.textwidget( 114 parent=self._root_widget, 115 position=(0, height - 44), 116 size=(width, 25), 117 text=bui.Lstr(resource=self._r + '.titleText'), 118 color=bui.app.ui_v1.title_color, 119 h_align='center', 120 v_align='top', 121 ) 122 123 self._fullscreen_checkbox: bui.Widget | None = None 124 if self._show_fullscreen: 125 v -= fullscreen_spacing_top 126 self._fullscreen_checkbox = ConfigCheckBox( 127 parent=self._root_widget, 128 position=(100, v), 129 maxwidth=200, 130 size=(300, 30), 131 configkey='Fullscreen', 132 displayname=bui.Lstr( 133 resource=self._r 134 + ( 135 '.fullScreenCmdText' 136 if app.classic.platform == 'mac' 137 else '.fullScreenCtrlText' 138 ) 139 ), 140 ).widget 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=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=['Higher', 'High'] 171 if bui.get_max_graphics_quality() == 'Medium' 172 else [], 173 choices_display=[ 174 bui.Lstr(resource='autoText'), 175 bui.Lstr(resource=self._r + '.higherText'), 176 bui.Lstr(resource=self._r + '.highText'), 177 bui.Lstr(resource=self._r + '.mediumText'), 178 bui.Lstr(resource=self._r + '.lowText'), 179 ], 180 current_choice=bui.app.config.resolve('Graphics Quality'), 181 on_value_change_call=self._set_quality, 182 ) 183 184 # Texture controls 185 bui.textwidget( 186 parent=self._root_widget, 187 position=(230, v), 188 size=(160, 25), 189 text=bui.Lstr(resource=self._r + '.texturesText'), 190 color=bui.app.ui_v1.heading_color, 191 scale=0.65, 192 maxwidth=150, 193 h_align='center', 194 v_align='center', 195 ) 196 textures_popup = PopupMenu( 197 parent=self._root_widget, 198 position=(230, v - 50), 199 width=150, 200 scale=popup_menu_scale, 201 choices=['Auto', 'High', 'Medium', 'Low'], 202 choices_display=[ 203 bui.Lstr(resource='autoText'), 204 bui.Lstr(resource=self._r + '.highText'), 205 bui.Lstr(resource=self._r + '.mediumText'), 206 bui.Lstr(resource=self._r + '.lowText'), 207 ], 208 current_choice=bui.app.config.resolve('Texture Quality'), 209 on_value_change_call=self._set_textures, 210 ) 211 if bui.app.ui_v1.use_toolbars: 212 bui.widget( 213 edit=textures_popup.get_button(), 214 right_widget=bui.get_special_widget('party_button'), 215 ) 216 v -= 80 217 218 h_offs = 0 219 220 resolution_popup: PopupMenu | None = None 221 222 if show_resolution: 223 bui.textwidget( 224 parent=self._root_widget, 225 position=(h_offs + 60, v), 226 size=(160, 25), 227 text=bui.Lstr(resource=self._r + '.resolutionText'), 228 color=bui.app.ui_v1.heading_color, 229 scale=0.65, 230 maxwidth=150, 231 h_align='center', 232 v_align='center', 233 ) 234 235 # On standard android we have 'Auto', 'Native', and a few 236 # HD standards. 237 if app.classic.platform == 'android': 238 # on cardboard/daydream android we have a few 239 # render-target-scale options 240 if app.classic.subplatform == 'cardboard': 241 rawval = bui.app.config.resolve('GVR Render Target Scale') 242 current_res_cardboard = ( 243 str(min(100, max(10, int(round(rawval * 100.0))))) + '%' 244 ) 245 resolution_popup = PopupMenu( 246 parent=self._root_widget, 247 position=(h_offs + 60, v - 50), 248 width=120, 249 scale=popup_menu_scale, 250 choices=['100%', '75%', '50%', '35%'], 251 current_choice=current_res_cardboard, 252 on_value_change_call=self._set_gvr_render_target_scale, 253 ) 254 else: 255 native_res = bui.get_display_resolution() 256 assert native_res is not None 257 choices = ['Auto', 'Native'] 258 choices_display = [ 259 bui.Lstr(resource='autoText'), 260 bui.Lstr(resource='nativeText'), 261 ] 262 for res in [1440, 1080, 960, 720, 480]: 263 # Nav bar is 72px so lets allow for that in what 264 # choices we show. 265 if native_res[1] >= res - 72: 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=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=self._r + '.alwaysText'), 333 bui.Lstr(resource=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=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=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=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 def _back(self) -> None: 432 from bauiv1lib.settings import allsettings 433 434 # Applying max-fps takes a few moments. Apply if it hasn't been 435 # yet. 436 self._apply_max_fps() 437 438 bui.containerwidget( 439 edit=self._root_widget, transition=self._transition_out 440 ) 441 assert bui.app.classic is not None 442 bui.app.ui_v1.set_main_menu_window( 443 allsettings.AllSettingsWindow( 444 transition='in_left' 445 ).get_root_widget() 446 ) 447 448 def _set_quality(self, quality: str) -> None: 449 cfg = bui.app.config 450 cfg['Graphics Quality'] = quality 451 cfg.apply_and_commit() 452 453 def _set_textures(self, val: str) -> None: 454 cfg = bui.app.config 455 cfg['Texture Quality'] = val 456 cfg.apply_and_commit() 457 458 def _set_android_res(self, val: str) -> None: 459 cfg = bui.app.config 460 cfg['Resolution (Android)'] = val 461 cfg.apply_and_commit() 462 463 def _set_pixel_scale(self, res: str) -> None: 464 cfg = bui.app.config 465 cfg['Screen Pixel Scale'] = float(res[:-1]) / 100.0 466 cfg.apply_and_commit() 467 468 def _set_gvr_render_target_scale(self, res: str) -> None: 469 cfg = bui.app.config 470 cfg['GVR Render Target Scale'] = float(res[:-1]) / 100.0 471 cfg.apply_and_commit() 472 473 def _set_vsync(self, val: str) -> None: 474 cfg = bui.app.config 475 cfg['Vertical Sync'] = val 476 cfg.apply_and_commit() 477 478 def _on_max_fps_return_press(self) -> None: 479 self._apply_max_fps() 480 bui.containerwidget( 481 edit=self._root_widget, selected_child=cast(bui.Widget, 0) 482 ) 483 484 def _apply_max_fps(self) -> None: 485 if not self._max_fps_dirty or not self._max_fps_text: 486 return 487 488 val: Any = bui.textwidget(query=self._max_fps_text) 489 assert isinstance(val, str) 490 # If there's a broken value, replace it with the default. 491 try: 492 ival = int(val) 493 except ValueError: 494 ival = bui.app.config.default_value('Max FPS') 495 assert isinstance(ival, int) 496 497 # Clamp to reasonable limits (allow -1 to mean no max). 498 if ival != -1: 499 ival = max(10, ival) 500 ival = min(99999, ival) 501 502 # Store it to the config. 503 cfg = bui.app.config 504 cfg['Max FPS'] = ival 505 cfg.apply_and_commit() 506 507 # Update the display if we changed the value. 508 if str(ival) != val: 509 bui.textwidget(edit=self._max_fps_text, text=str(ival)) 510 511 self._max_fps_dirty = False 512 513 def _update_controls(self) -> None: 514 if self._max_fps_text is not None: 515 # Keep track of when the max-fps value changes. Once it 516 # remains stable for a few moments, apply it. 517 val: Any = bui.textwidget(query=self._max_fps_text) 518 assert isinstance(val, str) 519 if val != self._last_max_fps_str: 520 # Oop; it changed. Note the time and the fact that we'll 521 # need to apply it at some point. 522 self._max_fps_dirty = True 523 self._last_max_fps_str = val 524 self._last_max_fps_set_time = bui.apptime() 525 else: 526 # If its been stable long enough, apply it. 527 if ( 528 self._max_fps_dirty 529 and bui.apptime() - self._last_max_fps_set_time > 1.0 530 ): 531 self._apply_max_fps() 532 if self._show_fullscreen: 533 bui.checkboxwidget( 534 edit=self._fullscreen_checkbox, 535 value=bui.app.config.resolve('Fullscreen'), 536 )
Window for graphics settings.
GraphicsSettingsWindow( transition: str = 'in_right', origin_widget: _bauiv1.Widget | None = None)
21 def __init__( 22 self, 23 transition: str = '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 # if they provided an origin-widget, scale up from that 31 scale_origin: tuple[float, float] | None 32 if origin_widget is not None: 33 self._transition_out = 'out_scale' 34 scale_origin = origin_widget.get_screen_space_center() 35 transition = 'in_scale' 36 else: 37 self._transition_out = 'out_right' 38 scale_origin = None 39 40 self._r = 'graphicsSettingsWindow' 41 app = bui.app 42 assert app.classic is not None 43 44 spacing = 32 45 self._have_selected_child = False 46 uiscale = app.ui_v1.uiscale 47 width = 450.0 48 height = 302.0 49 self._max_fps_dirty = False 50 self._last_max_fps_set_time = bui.apptime() 51 self._last_max_fps_str = '' 52 53 self._show_fullscreen = False 54 fullscreen_spacing_top = spacing * 0.2 55 fullscreen_spacing = spacing * 1.2 56 if bui.can_toggle_fullscreen(): 57 self._show_fullscreen = True 58 height += fullscreen_spacing + fullscreen_spacing_top 59 60 show_vsync = bui.supports_vsync() 61 show_tv_mode = not bui.app.env.vr 62 63 show_max_fps = bui.supports_max_fps() 64 if show_max_fps: 65 height += 50 66 67 show_resolution = True 68 if app.env.vr: 69 show_resolution = ( 70 app.classic.platform == 'android' 71 and app.classic.subplatform == 'cardboard' 72 ) 73 74 assert bui.app.classic is not None 75 uiscale = bui.app.ui_v1.uiscale 76 base_scale = ( 77 2.0 78 if uiscale is bui.UIScale.SMALL 79 else 1.5 80 if uiscale is bui.UIScale.MEDIUM 81 else 1.0 82 ) 83 popup_menu_scale = base_scale * 1.2 84 v = height - 50 85 v -= spacing * 1.15 86 super().__init__( 87 root_widget=bui.containerwidget( 88 size=(width, height), 89 transition=transition, 90 scale_origin_stack_offset=scale_origin, 91 scale=base_scale, 92 stack_offset=(0, -30) 93 if uiscale is bui.UIScale.SMALL 94 else (0, 0), 95 ) 96 ) 97 98 back_button = bui.buttonwidget( 99 parent=self._root_widget, 100 position=(35, height - 50), 101 # size=(120, 60), 102 size=(60, 60), 103 scale=0.8, 104 text_scale=1.2, 105 autoselect=True, 106 label=bui.charstr(bui.SpecialChar.BACK), 107 button_type='backSmall', 108 on_activate_call=self._back, 109 ) 110 111 bui.containerwidget(edit=self._root_widget, cancel_button=back_button) 112 113 bui.textwidget( 114 parent=self._root_widget, 115 position=(0, height - 44), 116 size=(width, 25), 117 text=bui.Lstr(resource=self._r + '.titleText'), 118 color=bui.app.ui_v1.title_color, 119 h_align='center', 120 v_align='top', 121 ) 122 123 self._fullscreen_checkbox: bui.Widget | None = None 124 if self._show_fullscreen: 125 v -= fullscreen_spacing_top 126 self._fullscreen_checkbox = ConfigCheckBox( 127 parent=self._root_widget, 128 position=(100, v), 129 maxwidth=200, 130 size=(300, 30), 131 configkey='Fullscreen', 132 displayname=bui.Lstr( 133 resource=self._r 134 + ( 135 '.fullScreenCmdText' 136 if app.classic.platform == 'mac' 137 else '.fullScreenCtrlText' 138 ) 139 ), 140 ).widget 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=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=['Higher', 'High'] 171 if bui.get_max_graphics_quality() == 'Medium' 172 else [], 173 choices_display=[ 174 bui.Lstr(resource='autoText'), 175 bui.Lstr(resource=self._r + '.higherText'), 176 bui.Lstr(resource=self._r + '.highText'), 177 bui.Lstr(resource=self._r + '.mediumText'), 178 bui.Lstr(resource=self._r + '.lowText'), 179 ], 180 current_choice=bui.app.config.resolve('Graphics Quality'), 181 on_value_change_call=self._set_quality, 182 ) 183 184 # Texture controls 185 bui.textwidget( 186 parent=self._root_widget, 187 position=(230, v), 188 size=(160, 25), 189 text=bui.Lstr(resource=self._r + '.texturesText'), 190 color=bui.app.ui_v1.heading_color, 191 scale=0.65, 192 maxwidth=150, 193 h_align='center', 194 v_align='center', 195 ) 196 textures_popup = PopupMenu( 197 parent=self._root_widget, 198 position=(230, v - 50), 199 width=150, 200 scale=popup_menu_scale, 201 choices=['Auto', 'High', 'Medium', 'Low'], 202 choices_display=[ 203 bui.Lstr(resource='autoText'), 204 bui.Lstr(resource=self._r + '.highText'), 205 bui.Lstr(resource=self._r + '.mediumText'), 206 bui.Lstr(resource=self._r + '.lowText'), 207 ], 208 current_choice=bui.app.config.resolve('Texture Quality'), 209 on_value_change_call=self._set_textures, 210 ) 211 if bui.app.ui_v1.use_toolbars: 212 bui.widget( 213 edit=textures_popup.get_button(), 214 right_widget=bui.get_special_widget('party_button'), 215 ) 216 v -= 80 217 218 h_offs = 0 219 220 resolution_popup: PopupMenu | None = None 221 222 if show_resolution: 223 bui.textwidget( 224 parent=self._root_widget, 225 position=(h_offs + 60, v), 226 size=(160, 25), 227 text=bui.Lstr(resource=self._r + '.resolutionText'), 228 color=bui.app.ui_v1.heading_color, 229 scale=0.65, 230 maxwidth=150, 231 h_align='center', 232 v_align='center', 233 ) 234 235 # On standard android we have 'Auto', 'Native', and a few 236 # HD standards. 237 if app.classic.platform == 'android': 238 # on cardboard/daydream android we have a few 239 # render-target-scale options 240 if app.classic.subplatform == 'cardboard': 241 rawval = bui.app.config.resolve('GVR Render Target Scale') 242 current_res_cardboard = ( 243 str(min(100, max(10, int(round(rawval * 100.0))))) + '%' 244 ) 245 resolution_popup = PopupMenu( 246 parent=self._root_widget, 247 position=(h_offs + 60, v - 50), 248 width=120, 249 scale=popup_menu_scale, 250 choices=['100%', '75%', '50%', '35%'], 251 current_choice=current_res_cardboard, 252 on_value_change_call=self._set_gvr_render_target_scale, 253 ) 254 else: 255 native_res = bui.get_display_resolution() 256 assert native_res is not None 257 choices = ['Auto', 'Native'] 258 choices_display = [ 259 bui.Lstr(resource='autoText'), 260 bui.Lstr(resource='nativeText'), 261 ] 262 for res in [1440, 1080, 960, 720, 480]: 263 # Nav bar is 72px so lets allow for that in what 264 # choices we show. 265 if native_res[1] >= res - 72: 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=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=self._r + '.alwaysText'), 333 bui.Lstr(resource=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=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=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=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 )
Inherited Members
- bauiv1._uitypes.Window
- get_root_widget