bauiv1lib.settings.graphics

Provides UI for graphics settings.

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

Window for graphics settings.

GraphicsSettingsWindow( transition: str | None = 'in_right', origin_widget: _bauiv1.Widget | None = None)
 21    def __init__(
 22        self,
 23        transition: str | None = 'in_right',
 24        origin_widget: bui.Widget | None = None,
 25    ):
 26        # pylint: disable=too-many-locals
 27        # pylint: disable=too-many-branches
 28        # pylint: disable=too-many-statements
 29
 30        self._r = 'graphicsSettingsWindow'
 31        app = bui.app
 32        assert app.classic is not None
 33
 34        spacing = 32
 35        self._have_selected_child = False
 36        uiscale = app.ui_v1.uiscale
 37        width = 450.0
 38        height = 302.0
 39        self._max_fps_dirty = False
 40        self._last_max_fps_set_time = bui.apptime()
 41        self._last_max_fps_str = ''
 42
 43        self._show_fullscreen = False
 44        fullscreen_spacing_top = spacing * 0.2
 45        fullscreen_spacing = spacing * 1.2
 46        if bui.fullscreen_control_available():
 47            self._show_fullscreen = True
 48            height += fullscreen_spacing + fullscreen_spacing_top
 49
 50        show_vsync = bui.supports_vsync()
 51        show_tv_mode = not bui.app.env.vr
 52
 53        show_max_fps = bui.supports_max_fps()
 54        if show_max_fps:
 55            height += 50
 56
 57        show_resolution = True
 58        if app.env.vr:
 59            show_resolution = (
 60                app.classic.platform == 'android'
 61                and app.classic.subplatform == 'cardboard'
 62            )
 63
 64        assert bui.app.classic is not None
 65        uiscale = bui.app.ui_v1.uiscale
 66        base_scale = (
 67            1.5
 68            if uiscale is bui.UIScale.SMALL
 69            else 1.3 if uiscale is bui.UIScale.MEDIUM else 1.0
 70        )
 71        popup_menu_scale = base_scale * 1.2
 72        v = height - 50
 73        v -= spacing * 1.15
 74        super().__init__(
 75            root_widget=bui.containerwidget(
 76                size=(width, height),
 77                scale=base_scale,
 78                stack_offset=(
 79                    (0, -10) if uiscale is bui.UIScale.SMALL else (0, 0)
 80                ),
 81                toolbar_visibility=(
 82                    'menu_minimal'
 83                    if uiscale is bui.UIScale.SMALL
 84                    else 'menu_full'
 85                ),
 86            ),
 87            transition=transition,
 88            origin_widget=origin_widget,
 89        )
 90
 91        back_button = bui.buttonwidget(
 92            parent=self._root_widget,
 93            position=(35, height - 50),
 94            # size=(120, 60),
 95            size=(60, 60),
 96            scale=0.8,
 97            text_scale=1.2,
 98            autoselect=True,
 99            label=bui.charstr(bui.SpecialChar.BACK),
100            button_type='backSmall',
101            on_activate_call=self.main_window_back,
102        )
103
104        bui.containerwidget(edit=self._root_widget, cancel_button=back_button)
105
106        bui.textwidget(
107            parent=self._root_widget,
108            position=(0, height - 44),
109            size=(width, 25),
110            text=bui.Lstr(resource=f'{self._r}.titleText'),
111            color=bui.app.ui_v1.title_color,
112            h_align='center',
113            v_align='top',
114        )
115
116        self._fullscreen_checkbox: bui.Widget | None = None
117        if self._show_fullscreen:
118            v -= fullscreen_spacing_top
119            # Fullscreen control does not necessarily talk to the
120            # app config so we have to wrangle it manually instead of
121            # using a config-checkbox.
122            label = bui.Lstr(resource=f'{self._r}.fullScreenText')
123
124            # Show keyboard shortcut alongside the control if they
125            # provide one.
126            shortcut = bui.fullscreen_control_key_shortcut()
127            if shortcut is not None:
128                label = bui.Lstr(
129                    value='$(NAME) [$(SHORTCUT)]',
130                    subs=[('$(NAME)', label), ('$(SHORTCUT)', shortcut)],
131                )
132            self._fullscreen_checkbox = bui.checkboxwidget(
133                parent=self._root_widget,
134                position=(100, v),
135                value=bui.fullscreen_control_get(),
136                on_value_change_call=bui.fullscreen_control_set,
137                maxwidth=250,
138                size=(300, 30),
139                text=label,
140            )
141
142            if not self._have_selected_child:
143                bui.containerwidget(
144                    edit=self._root_widget,
145                    selected_child=self._fullscreen_checkbox,
146                )
147                self._have_selected_child = True
148            v -= fullscreen_spacing
149
150        self._selected_color = (0.5, 1, 0.5, 1)
151        self._unselected_color = (0.7, 0.7, 0.7, 1)
152
153        # Quality
154        bui.textwidget(
155            parent=self._root_widget,
156            position=(60, v),
157            size=(160, 25),
158            text=bui.Lstr(resource=f'{self._r}.visualsText'),
159            color=bui.app.ui_v1.heading_color,
160            scale=0.65,
161            maxwidth=150,
162            h_align='center',
163            v_align='center',
164        )
165        PopupMenu(
166            parent=self._root_widget,
167            position=(60, v - 50),
168            width=150,
169            scale=popup_menu_scale,
170            choices=['Auto', 'Higher', 'High', 'Medium', 'Low'],
171            choices_disabled=(
172                ['Higher', 'High']
173                if bui.get_max_graphics_quality() == 'Medium'
174                else []
175            ),
176            choices_display=[
177                bui.Lstr(resource='autoText'),
178                bui.Lstr(resource=f'{self._r}.higherText'),
179                bui.Lstr(resource=f'{self._r}.highText'),
180                bui.Lstr(resource=f'{self._r}.mediumText'),
181                bui.Lstr(resource=f'{self._r}.lowText'),
182            ],
183            current_choice=bui.app.config.resolve('Graphics Quality'),
184            on_value_change_call=self._set_quality,
185        )
186
187        # Texture controls
188        bui.textwidget(
189            parent=self._root_widget,
190            position=(230, v),
191            size=(160, 25),
192            text=bui.Lstr(resource=f'{self._r}.texturesText'),
193            color=bui.app.ui_v1.heading_color,
194            scale=0.65,
195            maxwidth=150,
196            h_align='center',
197            v_align='center',
198        )
199        textures_popup = PopupMenu(
200            parent=self._root_widget,
201            position=(230, v - 50),
202            width=150,
203            scale=popup_menu_scale,
204            choices=['Auto', 'High', 'Medium', 'Low'],
205            choices_display=[
206                bui.Lstr(resource='autoText'),
207                bui.Lstr(resource=f'{self._r}.highText'),
208                bui.Lstr(resource=f'{self._r}.mediumText'),
209                bui.Lstr(resource=f'{self._r}.lowText'),
210            ],
211            current_choice=bui.app.config.resolve('Texture Quality'),
212            on_value_change_call=self._set_textures,
213        )
214        bui.widget(
215            edit=textures_popup.get_button(),
216            right_widget=bui.get_special_widget('squad_button'),
217        )
218        v -= 80
219
220        h_offs = 0
221
222        resolution_popup: PopupMenu | None = None
223
224        if show_resolution:
225            bui.textwidget(
226                parent=self._root_widget,
227                position=(h_offs + 60, v),
228                size=(160, 25),
229                text=bui.Lstr(resource=f'{self._r}.resolutionText'),
230                color=bui.app.ui_v1.heading_color,
231                scale=0.65,
232                maxwidth=150,
233                h_align='center',
234                v_align='center',
235            )
236
237            # On standard android we have 'Auto', 'Native', and a few
238            # HD standards.
239            if app.classic.platform == 'android':
240                # on cardboard/daydream android we have a few
241                # render-target-scale options
242                if app.classic.subplatform == 'cardboard':
243                    rawval = bui.app.config.resolve('GVR Render Target Scale')
244                    current_res_cardboard = (
245                        str(min(100, max(10, int(round(rawval * 100.0))))) + '%'
246                    )
247                    resolution_popup = PopupMenu(
248                        parent=self._root_widget,
249                        position=(h_offs + 60, v - 50),
250                        width=120,
251                        scale=popup_menu_scale,
252                        choices=['100%', '75%', '50%', '35%'],
253                        current_choice=current_res_cardboard,
254                        on_value_change_call=self._set_gvr_render_target_scale,
255                    )
256                else:
257                    native_res = bui.get_display_resolution()
258                    assert native_res is not None
259                    choices = ['Auto', 'Native']
260                    choices_display = [
261                        bui.Lstr(resource='autoText'),
262                        bui.Lstr(resource='nativeText'),
263                    ]
264                    for res in [1440, 1080, 960, 720, 480]:
265                        if native_res[1] >= res:
266                            res_str = f'{res}p'
267                            choices.append(res_str)
268                            choices_display.append(bui.Lstr(value=res_str))
269                    current_res_android = bui.app.config.resolve(
270                        'Resolution (Android)'
271                    )
272                    resolution_popup = PopupMenu(
273                        parent=self._root_widget,
274                        position=(h_offs + 60, v - 50),
275                        width=120,
276                        scale=popup_menu_scale,
277                        choices=choices,
278                        choices_display=choices_display,
279                        current_choice=current_res_android,
280                        on_value_change_call=self._set_android_res,
281                    )
282            else:
283                # If we're on a system that doesn't allow setting resolution,
284                # set pixel-scale instead.
285                current_res = bui.get_display_resolution()
286                if current_res is None:
287                    rawval = bui.app.config.resolve('Screen Pixel Scale')
288                    current_res2 = (
289                        str(min(100, max(10, int(round(rawval * 100.0))))) + '%'
290                    )
291                    resolution_popup = PopupMenu(
292                        parent=self._root_widget,
293                        position=(h_offs + 60, v - 50),
294                        width=120,
295                        scale=popup_menu_scale,
296                        choices=['100%', '88%', '75%', '63%', '50%'],
297                        current_choice=current_res2,
298                        on_value_change_call=self._set_pixel_scale,
299                    )
300                else:
301                    raise RuntimeError(
302                        'obsolete code path; discrete resolutions'
303                        ' no longer supported'
304                    )
305        if resolution_popup is not None:
306            bui.widget(
307                edit=resolution_popup.get_button(),
308                left_widget=back_button,
309            )
310
311        vsync_popup: PopupMenu | None = None
312        if show_vsync:
313            bui.textwidget(
314                parent=self._root_widget,
315                position=(230, v),
316                size=(160, 25),
317                text=bui.Lstr(resource=f'{self._r}.verticalSyncText'),
318                color=bui.app.ui_v1.heading_color,
319                scale=0.65,
320                maxwidth=150,
321                h_align='center',
322                v_align='center',
323            )
324            vsync_popup = PopupMenu(
325                parent=self._root_widget,
326                position=(230, v - 50),
327                width=150,
328                scale=popup_menu_scale,
329                choices=['Auto', 'Always', 'Never'],
330                choices_display=[
331                    bui.Lstr(resource='autoText'),
332                    bui.Lstr(resource=f'{self._r}.alwaysText'),
333                    bui.Lstr(resource=f'{self._r}.neverText'),
334                ],
335                current_choice=bui.app.config.resolve('Vertical Sync'),
336                on_value_change_call=self._set_vsync,
337            )
338            if resolution_popup is not None:
339                bui.widget(
340                    edit=vsync_popup.get_button(),
341                    left_widget=resolution_popup.get_button(),
342                )
343
344        if resolution_popup is not None and vsync_popup is not None:
345            bui.widget(
346                edit=resolution_popup.get_button(),
347                right_widget=vsync_popup.get_button(),
348            )
349
350        v -= 90
351        self._max_fps_text: bui.Widget | None = None
352        if show_max_fps:
353            v -= 5
354            bui.textwidget(
355                parent=self._root_widget,
356                position=(155, v + 10),
357                size=(0, 0),
358                text=bui.Lstr(resource=f'{self._r}.maxFPSText'),
359                color=bui.app.ui_v1.heading_color,
360                scale=0.9,
361                maxwidth=90,
362                h_align='right',
363                v_align='center',
364            )
365
366            max_fps_str = str(bui.app.config.resolve('Max FPS'))
367            self._last_max_fps_str = max_fps_str
368            self._max_fps_text = bui.textwidget(
369                parent=self._root_widget,
370                position=(170, v - 5),
371                size=(105, 30),
372                text=max_fps_str,
373                max_chars=5,
374                editable=True,
375                h_align='left',
376                v_align='center',
377                on_return_press_call=self._on_max_fps_return_press,
378            )
379            v -= 45
380
381        if self._max_fps_text is not None and resolution_popup is not None:
382            bui.widget(
383                edit=resolution_popup.get_button(),
384                down_widget=self._max_fps_text,
385            )
386            bui.widget(
387                edit=self._max_fps_text,
388                up_widget=resolution_popup.get_button(),
389            )
390
391        fpsc = ConfigCheckBox(
392            parent=self._root_widget,
393            position=(69, v - 6),
394            size=(210, 30),
395            scale=0.86,
396            configkey='Show FPS',
397            displayname=bui.Lstr(resource=f'{self._r}.showFPSText'),
398            maxwidth=130,
399        )
400        if self._max_fps_text is not None:
401            bui.widget(
402                edit=self._max_fps_text,
403                down_widget=fpsc.widget,
404            )
405            bui.widget(
406                edit=fpsc.widget,
407                up_widget=self._max_fps_text,
408            )
409
410        if show_tv_mode:
411            tvc = ConfigCheckBox(
412                parent=self._root_widget,
413                position=(240, v - 6),
414                size=(210, 30),
415                scale=0.86,
416                configkey='TV Border',
417                displayname=bui.Lstr(resource=f'{self._r}.tvBorderText'),
418                maxwidth=130,
419            )
420            bui.widget(edit=fpsc.widget, right_widget=tvc.widget)
421            bui.widget(edit=tvc.widget, left_widget=fpsc.widget)
422
423        v -= spacing
424
425        # Make a timer to update our controls in case the config changes
426        # under us.
427        self._update_timer = bui.AppTimer(
428            0.25, bui.WeakCall(self._update_controls), repeat=True
429        )

Create a MainWindow given a root widget and transition info.

Automatically handles in and out transitions on the provided widget, so there is no need to set transitions when creating it.

@override
def get_main_window_state(self) -> bauiv1.MainWindowState:
431    @override
432    def get_main_window_state(self) -> bui.MainWindowState:
433        # Support recreating our window for back/refresh purposes.
434        cls = type(self)
435        return bui.BasicMainWindowState(
436            create_call=lambda transition, origin_widget: cls(
437                transition=transition, origin_widget=origin_widget
438            )
439        )

Return a WindowState to recreate this window, if supported.

@override
def on_main_window_close(self) -> None:
441    @override
442    def on_main_window_close(self) -> None:
443        self._apply_max_fps()

Called before transitioning out a main window.

A good opportunity to save window state/etc.

Inherited Members
bauiv1._uitypes.MainWindow
main_window_back_state
main_window_close
can_change_main_window
main_window_back
main_window_replace
bauiv1._uitypes.Window
get_root_widget