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